mirror of
https://github.com/Ed94/Odin.git
synced 2026-06-26 07:25:00 -07:00
Merge remote-tracking branch 'upstream/master' into sys-windows-2
# Conflicts: # core/sys/windows/kernel32.odin # core/sys/windows/types.odin # core/sys/windows/user32.odin # core/sys/windows/winerror.odin
This commit is contained in:
@@ -32,8 +32,8 @@ jobs:
|
||||
gmake -C vendor/stb/src
|
||||
gmake -C vendor/cgltf/src
|
||||
gmake -C vendor/miniaudio/src
|
||||
./odin check examples/all -vet -strict-style -target:netbsd_amd64
|
||||
./odin check examples/all -vet -strict-style -target:netbsd_arm64
|
||||
./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 test tests/core/normal.odin -file -all-packages -define:ODIN_TEST_FANCY=false
|
||||
./odin test tests/core/speed.odin -file -all-packages -o:speed -define:ODIN_TEST_FANCY=false
|
||||
./odin test tests/vendor -all-packages -define:ODIN_TEST_FANCY=false
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
gmake -C vendor/stb/src
|
||||
gmake -C vendor/cgltf/src
|
||||
gmake -C vendor/miniaudio/src
|
||||
./odin check examples/all -vet -strict-style -target:freebsd_amd64
|
||||
./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64
|
||||
./odin test tests/core/normal.odin -file -all-packages -define:ODIN_TEST_FANCY=false
|
||||
./odin test tests/core/speed.odin -file -all-packages -o:speed -define:ODIN_TEST_FANCY=false
|
||||
./odin test tests/vendor -all-packages -define:ODIN_TEST_FANCY=false
|
||||
@@ -97,7 +97,7 @@ jobs:
|
||||
- name: Download LLVM (MacOS ARM)
|
||||
if: matrix.os == 'macos-14'
|
||||
run: |
|
||||
brew install llvm@17
|
||||
brew install llvm@17 wasmtime
|
||||
echo "/opt/homebrew/opt/llvm@17/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Build Odin
|
||||
@@ -135,18 +135,24 @@ jobs:
|
||||
./run.sh
|
||||
|
||||
- name: Odin check examples/all for Linux i386
|
||||
run: ./odin check examples/all -vet -strict-style -target:linux_i386
|
||||
run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_i386
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
- name: Odin check examples/all for Linux arm64
|
||||
run: ./odin check examples/all -vet -strict-style -target:linux_arm64
|
||||
run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_arm64
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
- name: Odin check examples/all for FreeBSD amd64
|
||||
run: ./odin check examples/all -vet -strict-style -target:freebsd_amd64
|
||||
run: ./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
- name: Odin check examples/all for OpenBSD amd64
|
||||
run: ./odin check examples/all -vet -strict-style -target:openbsd_amd64
|
||||
run: ./odin check examples/all -vet -strict-style -disallow-do -target:openbsd_amd64
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
|
||||
- name: Run demo on WASI WASM32
|
||||
run: |
|
||||
./odin build examples/demo -target:wasi_wasm32 -vet -strict-style -disallow-do -out:demo.wasm
|
||||
wasmtime ./demo.wasm
|
||||
if: matrix.os == 'macos-14'
|
||||
|
||||
build_windows:
|
||||
name: Windows Build, Check, and Test
|
||||
runs-on: windows-2022
|
||||
|
||||
@@ -50,8 +50,8 @@ jobs:
|
||||
run: |
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh 17
|
||||
echo "/usr/lib/llvm-17/bin" >> $GITHUB_PATH
|
||||
sudo ./llvm.sh 18
|
||||
echo "/usr/lib/llvm-18/bin" >> $GITHUB_PATH
|
||||
- name: build odin
|
||||
run: make nightly
|
||||
- name: Odin run
|
||||
@@ -82,8 +82,8 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Download LLVM and setup PATH
|
||||
run: |
|
||||
brew install llvm@17 dylibbundler
|
||||
echo "/usr/local/opt/llvm@17/bin" >> $GITHUB_PATH
|
||||
brew install llvm@18 dylibbundler
|
||||
echo "/usr/local/opt/llvm@18/bin" >> $GITHUB_PATH
|
||||
- name: build odin
|
||||
# These -L makes the linker prioritize system libraries over LLVM libraries, this is mainly to
|
||||
# not link with libunwind bundled with LLVM but link with libunwind on the system.
|
||||
@@ -116,8 +116,8 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Download LLVM and setup PATH
|
||||
run: |
|
||||
brew install llvm@17 dylibbundler
|
||||
echo "/opt/homebrew/opt/llvm@17/bin" >> $GITHUB_PATH
|
||||
brew install llvm@18 dylibbundler
|
||||
echo "/opt/homebrew/opt/llvm@18/bin" >> $GITHUB_PATH
|
||||
- name: build odin
|
||||
# These -L makes the linker prioritize system libraries over LLVM libraries, this is mainly to
|
||||
# not link with libunwind bundled with LLVM but link with libunwind on the system.
|
||||
|
||||
+1
-1
@@ -303,7 +303,7 @@ bin/
|
||||
# - Linux/MacOS
|
||||
odin
|
||||
!odin/
|
||||
odin.dSYM
|
||||
**/*.dSYM
|
||||
*.bin
|
||||
demo.bin
|
||||
libLLVM*.so*
|
||||
|
||||
BIN
Binary file not shown.
@@ -73,6 +73,8 @@ expect :: proc(val, expected_val: T) -> T ---
|
||||
|
||||
// Linux and Darwin Only
|
||||
syscall :: proc(id: uintptr, args: ..uintptr) -> uintptr ---
|
||||
// FreeBSD, NetBSD, et cetera
|
||||
syscall_bsd :: proc(id: uintptr, args: ..uintptr) -> (uintptr, bool) ---
|
||||
|
||||
|
||||
// Atomics
|
||||
@@ -192,7 +194,8 @@ type_proc_return_count :: proc($T: typeid) -> int where type_is_proc(T) ---
|
||||
type_proc_parameter_type :: proc($T: typeid, index: int) -> typeid where type_is_proc(T) ---
|
||||
type_proc_return_type :: proc($T: typeid, index: int) -> typeid where type_is_proc(T) ---
|
||||
|
||||
type_struct_field_count :: proc($T: typeid) -> int where type_is_struct(T) ---
|
||||
type_struct_field_count :: proc($T: typeid) -> int where type_is_struct(T) ---
|
||||
type_struct_has_implicit_padding :: proc($T: typeid) -> bool where type_is_struct(T) ---
|
||||
|
||||
type_polymorphic_record_parameter_count :: proc($T: typeid) -> typeid ---
|
||||
type_polymorphic_record_parameter_value :: proc($T: typeid, index: int) -> $V ---
|
||||
|
||||
@@ -299,6 +299,8 @@ when ODIN_OS == .Windows {
|
||||
Thread_Detach = 3,
|
||||
}
|
||||
dll_forward_reason: DLL_Forward_Reason
|
||||
|
||||
dll_instance: rawptr
|
||||
}
|
||||
|
||||
// IMPORTANT NOTE(bill): Must be in this order (as the compiler relies upon it)
|
||||
@@ -397,11 +399,34 @@ Logger :: struct {
|
||||
options: Logger_Options,
|
||||
}
|
||||
|
||||
|
||||
Random_Generator_Mode :: enum {
|
||||
Read,
|
||||
Reset,
|
||||
Query_Info,
|
||||
}
|
||||
|
||||
Random_Generator_Query_Info_Flag :: enum u32 {
|
||||
Cryptographic,
|
||||
Uniform,
|
||||
External_Entropy,
|
||||
Resettable,
|
||||
}
|
||||
Random_Generator_Query_Info :: distinct bit_set[Random_Generator_Query_Info_Flag; u32]
|
||||
|
||||
Random_Generator_Proc :: #type proc(data: rawptr, mode: Random_Generator_Mode, p: []byte)
|
||||
|
||||
Random_Generator :: struct {
|
||||
procedure: Random_Generator_Proc,
|
||||
data: rawptr,
|
||||
}
|
||||
|
||||
Context :: struct {
|
||||
allocator: Allocator,
|
||||
temp_allocator: Allocator,
|
||||
assertion_failure_proc: Assertion_Failure_Proc,
|
||||
logger: Logger,
|
||||
random_generator: Random_Generator,
|
||||
|
||||
user_ptr: rawptr,
|
||||
user_index: int,
|
||||
@@ -708,6 +733,9 @@ __init_context :: proc "contextless" (c: ^Context) {
|
||||
|
||||
c.logger.procedure = default_logger_proc
|
||||
c.logger.data = nil
|
||||
|
||||
c.random_generator.procedure = default_random_generator_proc
|
||||
c.random_generator.data = nil
|
||||
}
|
||||
|
||||
default_assertion_failure_proc :: proc(prefix, message: string, loc: Source_Code_Location) -> ! {
|
||||
|
||||
@@ -65,7 +65,7 @@ copy :: proc{copy_slice, copy_from_string}
|
||||
// with the old value, and reducing the length of the dynamic array by 1.
|
||||
//
|
||||
// Note: This is an O(1) operation.
|
||||
// Note: If you the elements to remain in their order, use `ordered_remove`.
|
||||
// Note: If you want the elements to remain in their order, use `ordered_remove`.
|
||||
// Note: If the index is out of bounds, this procedure will panic.
|
||||
@builtin
|
||||
unordered_remove :: proc(array: ^$D/[dynamic]$T, index: int, loc := #caller_location) #no_bounds_check {
|
||||
@@ -79,7 +79,7 @@ unordered_remove :: proc(array: ^$D/[dynamic]$T, index: int, loc := #caller_loca
|
||||
// `ordered_remove` removed the element at the specified `index` whilst keeping the order of the other elements.
|
||||
//
|
||||
// Note: This is an O(N) operation.
|
||||
// Note: If you the elements do not have to remain in their order, prefer `unordered_remove`.
|
||||
// Note: If the elements do not have to remain in their order, prefer `unordered_remove`.
|
||||
// Note: If the index is out of bounds, this procedure will panic.
|
||||
@builtin
|
||||
ordered_remove :: proc(array: ^$D/[dynamic]$T, index: int, loc := #caller_location) #no_bounds_check {
|
||||
@@ -163,21 +163,43 @@ pop_front_safe :: proc "contextless" (array: ^$T/[dynamic]$E) -> (res: E, ok: bo
|
||||
|
||||
// `clear` will set the length of a passed dynamic array or map to `0`
|
||||
@builtin
|
||||
clear :: proc{clear_dynamic_array, clear_map}
|
||||
clear :: proc{
|
||||
clear_dynamic_array,
|
||||
clear_map,
|
||||
|
||||
clear_soa_dynamic_array,
|
||||
}
|
||||
|
||||
// `reserve` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`).
|
||||
@builtin
|
||||
reserve :: proc{reserve_dynamic_array, reserve_map}
|
||||
reserve :: proc{
|
||||
reserve_dynamic_array,
|
||||
reserve_map,
|
||||
|
||||
reserve_soa,
|
||||
}
|
||||
|
||||
@builtin
|
||||
non_zero_reserve :: proc{non_zero_reserve_dynamic_array}
|
||||
non_zero_reserve :: proc{
|
||||
non_zero_reserve_dynamic_array,
|
||||
|
||||
non_zero_reserve_soa,
|
||||
}
|
||||
|
||||
// `resize` will try to resize memory of a passed dynamic array to the requested element count (setting the `len`, and possibly `cap`).
|
||||
@builtin
|
||||
resize :: proc{resize_dynamic_array}
|
||||
resize :: proc{
|
||||
resize_dynamic_array,
|
||||
|
||||
resize_soa,
|
||||
}
|
||||
|
||||
@builtin
|
||||
non_zero_resize :: proc{non_zero_resize_dynamic_array}
|
||||
non_zero_resize :: proc{
|
||||
non_zero_resize_dynamic_array,
|
||||
|
||||
non_zero_resize_soa,
|
||||
}
|
||||
|
||||
// Shrinks the capacity of a dynamic array or map down to the current length, or the given capacity.
|
||||
@builtin
|
||||
@@ -268,7 +290,7 @@ new_clone :: proc(data: $T, allocator := context.allocator, loc := #caller_locat
|
||||
return
|
||||
}
|
||||
|
||||
DEFAULT_RESERVE_CAPACITY :: 16
|
||||
DEFAULT_DYNAMIC_ARRAY_CAPACITY :: 8
|
||||
|
||||
@(require_results)
|
||||
make_aligned :: proc($T: typeid/[]$E, #any_int len: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error {
|
||||
@@ -295,7 +317,7 @@ make_slice :: proc($T: typeid/[]$E, #any_int len: int, allocator := context.allo
|
||||
// Note: Prefer using the procedure group `make`.
|
||||
@(builtin, require_results)
|
||||
make_dynamic_array :: proc($T: typeid/[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error {
|
||||
return make_dynamic_array_len_cap(T, 0, DEFAULT_RESERVE_CAPACITY, allocator, loc)
|
||||
return make_dynamic_array_len_cap(T, 0, 0, allocator, loc)
|
||||
}
|
||||
// `make_dynamic_array_len` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value.
|
||||
// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it.
|
||||
@@ -364,6 +386,11 @@ make :: proc{
|
||||
make_dynamic_array_len_cap,
|
||||
make_map,
|
||||
make_multi_pointer,
|
||||
|
||||
make_soa_slice,
|
||||
make_soa_dynamic_array,
|
||||
make_soa_dynamic_array_len,
|
||||
make_soa_dynamic_array_len_cap,
|
||||
}
|
||||
|
||||
|
||||
@@ -423,7 +450,8 @@ _append_elem :: #force_inline proc(array: ^$T/[dynamic]$E, arg: E, should_zero:
|
||||
return 1, nil
|
||||
} else {
|
||||
if cap(array) < len(array)+1 {
|
||||
cap := 2 * cap(array) + max(8, 1)
|
||||
// Same behavior as _append_elems but there's only one arg, so we always just add DEFAULT_DYNAMIC_ARRAY_CAPACITY.
|
||||
cap := 2 * cap(array) + DEFAULT_DYNAMIC_ARRAY_CAPACITY
|
||||
|
||||
// do not 'or_return' here as it could be a partial success
|
||||
if should_zero {
|
||||
@@ -472,7 +500,7 @@ _append_elems :: #force_inline proc(array: ^$T/[dynamic]$E, should_zero: bool, l
|
||||
return arg_len, nil
|
||||
} else {
|
||||
if cap(array) < len(array)+arg_len {
|
||||
cap := 2 * cap(array) + max(8, arg_len)
|
||||
cap := 2 * cap(array) + max(DEFAULT_DYNAMIC_ARRAY_CAPACITY, arg_len)
|
||||
|
||||
// do not 'or_return' here as it could be a partial success
|
||||
if should_zero {
|
||||
@@ -540,8 +568,23 @@ append_string :: proc(array: ^$T/[dynamic]$E/u8, args: ..string, loc := #caller_
|
||||
}
|
||||
|
||||
// The append built-in procedure appends elements to the end of a dynamic array
|
||||
@builtin append :: proc{append_elem, append_elems, append_elem_string}
|
||||
@builtin non_zero_append :: proc{non_zero_append_elem, non_zero_append_elems, non_zero_append_elem_string}
|
||||
@builtin append :: proc{
|
||||
append_elem,
|
||||
append_elems,
|
||||
append_elem_string,
|
||||
|
||||
append_soa_elem,
|
||||
append_soa_elems,
|
||||
}
|
||||
|
||||
@builtin non_zero_append :: proc{
|
||||
non_zero_append_elem,
|
||||
non_zero_append_elems,
|
||||
non_zero_append_elem_string,
|
||||
|
||||
non_zero_append_soa_elem,
|
||||
non_zero_append_soa_elems,
|
||||
}
|
||||
|
||||
|
||||
@builtin
|
||||
|
||||
@@ -55,7 +55,7 @@ raw_soa_footer_slice :: proc(array: ^$T/#soa[]$E) -> (footer: ^Raw_SOA_Footer_Sl
|
||||
if array == nil {
|
||||
return nil
|
||||
}
|
||||
field_count := uintptr(intrinsics.type_struct_field_count(E))
|
||||
field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
|
||||
footer = (^Raw_SOA_Footer_Slice)(uintptr(array) + field_count*size_of(rawptr))
|
||||
return
|
||||
}
|
||||
@@ -64,12 +64,7 @@ raw_soa_footer_dynamic_array :: proc(array: ^$T/#soa[dynamic]$E) -> (footer: ^Ra
|
||||
if array == nil {
|
||||
return nil
|
||||
}
|
||||
field_count: uintptr
|
||||
when intrinsics.type_is_array(E) {
|
||||
field_count = len(E)
|
||||
} else {
|
||||
field_count = uintptr(intrinsics.type_struct_field_count(E))
|
||||
}
|
||||
field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
|
||||
footer = (^Raw_SOA_Footer_Dynamic_Array)(uintptr(array) + field_count*size_of(rawptr))
|
||||
return
|
||||
}
|
||||
@@ -98,7 +93,7 @@ make_soa_aligned :: proc($T: typeid/#soa[]$E, length: int, alignment: int, alloc
|
||||
ti = type_info_base(ti)
|
||||
si := &ti.variant.(Type_Info_Struct)
|
||||
|
||||
field_count := uintptr(intrinsics.type_struct_field_count(E))
|
||||
field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
|
||||
|
||||
total_size := 0
|
||||
for i in 0..<field_count {
|
||||
@@ -147,7 +142,7 @@ make_soa_slice :: proc($T: typeid/#soa[]$E, length: int, allocator := context.al
|
||||
@(builtin, require_results)
|
||||
make_soa_dynamic_array :: proc($T: typeid/#soa[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error {
|
||||
context.allocator = allocator
|
||||
reserve_soa(&array, DEFAULT_RESERVE_CAPACITY, loc) or_return
|
||||
reserve_soa(&array, 0, loc) or_return
|
||||
return array, nil
|
||||
}
|
||||
|
||||
@@ -187,8 +182,28 @@ resize_soa :: proc(array: ^$T/#soa[dynamic]$E, length: int, loc := #caller_locat
|
||||
return nil
|
||||
}
|
||||
|
||||
@builtin
|
||||
non_zero_resize_soa :: proc(array: ^$T/#soa[dynamic]$E, length: int, loc := #caller_location) -> Allocator_Error {
|
||||
if array == nil {
|
||||
return nil
|
||||
}
|
||||
non_zero_reserve_soa(array, length, loc) or_return
|
||||
footer := raw_soa_footer(array)
|
||||
footer.len = length
|
||||
return nil
|
||||
}
|
||||
|
||||
@builtin
|
||||
reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error {
|
||||
return _reserve_soa(array, capacity, true, loc)
|
||||
}
|
||||
|
||||
@builtin
|
||||
non_zero_reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error {
|
||||
return _reserve_soa(array, capacity, false, loc)
|
||||
}
|
||||
|
||||
_reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, zero_memory: bool, loc := #caller_location) -> Allocator_Error {
|
||||
if array == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -213,12 +228,7 @@ reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_lo
|
||||
ti = type_info_base(ti)
|
||||
si := &ti.variant.(Type_Info_Struct)
|
||||
|
||||
field_count: uintptr
|
||||
when intrinsics.type_is_array(E) {
|
||||
field_count = len(E)
|
||||
} else {
|
||||
field_count = uintptr(intrinsics.type_struct_field_count(E))
|
||||
}
|
||||
field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
|
||||
assert(footer.cap == old_cap)
|
||||
|
||||
old_size := 0
|
||||
@@ -238,7 +248,7 @@ reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_lo
|
||||
old_data := (^rawptr)(array)^
|
||||
|
||||
new_bytes := array.allocator.procedure(
|
||||
array.allocator.data, .Alloc, new_size, max_align,
|
||||
array.allocator.data, .Alloc if zero_memory else .Alloc_Non_Zeroed, new_size, max_align,
|
||||
nil, old_size, loc,
|
||||
) or_return
|
||||
new_data := raw_data(new_bytes)
|
||||
@@ -273,15 +283,26 @@ reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_lo
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@builtin
|
||||
append_soa_elem :: proc(array: ^$T/#soa[dynamic]$E, arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||
append_soa_elem :: proc(array: ^$T/#soa[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||
return _append_soa_elem(array, true, arg, loc)
|
||||
}
|
||||
|
||||
@builtin
|
||||
non_zero_append_soa_elem :: proc(array: ^$T/#soa[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||
return _append_soa_elem(array, false, arg, loc)
|
||||
}
|
||||
|
||||
_append_soa_elem :: proc(array: ^$T/#soa[dynamic]$E, zero_memory: bool, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||
if array == nil {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if cap(array) <= len(array) + 1 {
|
||||
cap := 2 * cap(array) + 8
|
||||
err = reserve_soa(array, cap, loc) // do not 'or_return' here as it could be a partial success
|
||||
// Same behavior as append_soa_elems but there's only one arg, so we always just add DEFAULT_DYNAMIC_ARRAY_CAPACITY.
|
||||
cap := 2 * cap(array) + DEFAULT_DYNAMIC_ARRAY_CAPACITY
|
||||
err = _reserve_soa(array, cap, zero_memory, loc) // do not 'or_return' here as it could be a partial success
|
||||
}
|
||||
|
||||
footer := raw_soa_footer(array)
|
||||
@@ -290,12 +311,7 @@ append_soa_elem :: proc(array: ^$T/#soa[dynamic]$E, arg: E, loc := #caller_locat
|
||||
ti := type_info_of(T)
|
||||
ti = type_info_base(ti)
|
||||
si := &ti.variant.(Type_Info_Struct)
|
||||
field_count: uintptr
|
||||
when intrinsics.type_is_array(E) {
|
||||
field_count = len(E)
|
||||
} else {
|
||||
field_count = uintptr(intrinsics.type_struct_field_count(E))
|
||||
}
|
||||
field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
|
||||
|
||||
data := (^rawptr)(array)^
|
||||
|
||||
@@ -326,7 +342,17 @@ append_soa_elem :: proc(array: ^$T/#soa[dynamic]$E, arg: E, loc := #caller_locat
|
||||
}
|
||||
|
||||
@builtin
|
||||
append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||
append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||
return _append_soa_elems(array, true, args=args, loc=loc)
|
||||
}
|
||||
|
||||
@builtin
|
||||
non_zero_append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||
return _append_soa_elems(array, false, args=args, loc=loc)
|
||||
}
|
||||
|
||||
|
||||
_append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, zero_memory: bool, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
|
||||
if array == nil {
|
||||
return
|
||||
}
|
||||
@@ -337,8 +363,8 @@ append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, args: ..E, loc := #caller_l
|
||||
}
|
||||
|
||||
if cap(array) <= len(array)+arg_len {
|
||||
cap := 2 * cap(array) + max(8, arg_len)
|
||||
err = reserve_soa(array, cap, loc) // do not 'or_return' here as it could be a partial success
|
||||
cap := 2 * cap(array) + max(DEFAULT_DYNAMIC_ARRAY_CAPACITY, arg_len)
|
||||
err = _reserve_soa(array, cap, zero_memory, loc) // do not 'or_return' here as it could be a partial success
|
||||
}
|
||||
arg_len = min(cap(array)-len(array), arg_len)
|
||||
|
||||
@@ -347,7 +373,7 @@ append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, args: ..E, loc := #caller_l
|
||||
ti := type_info_of(typeid_of(T))
|
||||
ti = type_info_base(ti)
|
||||
si := &ti.variant.(Type_Info_Struct)
|
||||
field_count := uintptr(intrinsics.type_struct_field_count(E))
|
||||
field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
|
||||
|
||||
data := (^rawptr)(array)^
|
||||
|
||||
@@ -389,7 +415,8 @@ append_soa :: proc{
|
||||
|
||||
|
||||
delete_soa_slice :: proc(array: $T/#soa[]$E, allocator := context.allocator, loc := #caller_location) -> Allocator_Error {
|
||||
when intrinsics.type_struct_field_count(E) != 0 {
|
||||
field_count :: len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E)
|
||||
when field_count != 0 {
|
||||
array := array
|
||||
ptr := (^rawptr)(&array)^
|
||||
free(ptr, allocator, loc) or_return
|
||||
@@ -398,7 +425,8 @@ delete_soa_slice :: proc(array: $T/#soa[]$E, allocator := context.allocator, loc
|
||||
}
|
||||
|
||||
delete_soa_dynamic_array :: proc(array: $T/#soa[dynamic]$E, loc := #caller_location) -> Allocator_Error {
|
||||
when intrinsics.type_struct_field_count(E) != 0 {
|
||||
field_count :: len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E)
|
||||
when field_count != 0 {
|
||||
array := array
|
||||
ptr := (^rawptr)(&array)^
|
||||
footer := raw_soa_footer(&array)
|
||||
@@ -416,7 +444,8 @@ delete_soa :: proc{
|
||||
|
||||
|
||||
clear_soa_dynamic_array :: proc(array: ^$T/#soa[dynamic]$E) {
|
||||
when intrinsics.type_struct_field_count(E) != 0 {
|
||||
field_count :: len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E)
|
||||
when field_count != 0 {
|
||||
footer := raw_soa_footer(array)
|
||||
footer.len = 0
|
||||
}
|
||||
@@ -438,12 +467,7 @@ into_dynamic_soa :: proc(array: $T/#soa[]$E) -> #soa[dynamic]E {
|
||||
allocator = nil_allocator(),
|
||||
}
|
||||
|
||||
field_count: uintptr
|
||||
when intrinsics.type_is_array(E) {
|
||||
field_count = len(E)
|
||||
} else {
|
||||
field_count = uintptr(intrinsics.type_struct_field_count(E))
|
||||
}
|
||||
field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
|
||||
|
||||
array := array
|
||||
dynamic_data := ([^]rawptr)(&d)[:field_count]
|
||||
@@ -467,12 +491,7 @@ unordered_remove_soa :: proc(array: ^$T/#soa[dynamic]$E, index: int, loc := #cal
|
||||
ti = type_info_base(ti)
|
||||
si := &ti.variant.(Type_Info_Struct)
|
||||
|
||||
field_count: uintptr
|
||||
when intrinsics.type_is_array(E) {
|
||||
field_count = len(E)
|
||||
} else {
|
||||
field_count = uintptr(intrinsics.type_struct_field_count(E))
|
||||
}
|
||||
field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
|
||||
|
||||
data := uintptr(array)
|
||||
for i in 0..<field_count {
|
||||
@@ -500,12 +519,7 @@ ordered_remove_soa :: proc(array: ^$T/#soa[dynamic]$E, index: int, loc := #calle
|
||||
ti = type_info_base(ti)
|
||||
si := &ti.variant.(Type_Info_Struct)
|
||||
|
||||
field_count: uintptr
|
||||
when intrinsics.type_is_array(E) {
|
||||
field_count = len(E)
|
||||
} else {
|
||||
field_count = uintptr(intrinsics.type_struct_field_count(E))
|
||||
}
|
||||
field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
|
||||
|
||||
data := uintptr(array)
|
||||
for i in 0..<field_count {
|
||||
|
||||
+2
-1
@@ -12,7 +12,8 @@ Memory_Block :: struct {
|
||||
capacity: uint,
|
||||
}
|
||||
|
||||
// NOTE: This is for internal use, prefer `Arena` from `core:mem/virtual` if necessary
|
||||
// NOTE: This is a growing arena that is only used for the default temp allocator.
|
||||
// For your own growing arena needs, prefer `Arena` from `core:mem/virtual`.
|
||||
Arena :: struct {
|
||||
backing_allocator: Allocator,
|
||||
curr_block: ^Memory_Block,
|
||||
@@ -22,6 +22,11 @@ when !ODIN_TEST && !ODIN_NO_ENTRY_POINT {
|
||||
@(link_name="_start", linkage="strong", require, export)
|
||||
_start :: proc "c" () {
|
||||
context = default_context()
|
||||
|
||||
when ODIN_OS == .WASI {
|
||||
_wasi_setup_args()
|
||||
}
|
||||
|
||||
#force_no_inline _startup_runtime()
|
||||
intrinsics.__entry_point()
|
||||
}
|
||||
|
||||
@@ -10,8 +10,9 @@ when ODIN_BUILD_MODE == .Dynamic {
|
||||
DllMain :: proc "system" (hinstDLL: rawptr, fdwReason: u32, lpReserved: rawptr) -> b32 {
|
||||
context = default_context()
|
||||
|
||||
// Populate Windows DLL-specific global
|
||||
// Populate Windows DLL-specific globals
|
||||
dll_forward_reason = DLL_Forward_Reason(fdwReason)
|
||||
dll_instance = hinstDLL
|
||||
|
||||
switch dll_forward_reason {
|
||||
case .Process_Attach:
|
||||
|
||||
@@ -97,14 +97,14 @@ heap_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
}
|
||||
|
||||
|
||||
heap_alloc :: proc(size: int, zero_memory := true) -> rawptr {
|
||||
heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr {
|
||||
return _heap_alloc(size, zero_memory)
|
||||
}
|
||||
|
||||
heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr {
|
||||
heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
|
||||
return _heap_resize(ptr, new_size)
|
||||
}
|
||||
|
||||
heap_free :: proc(ptr: rawptr) {
|
||||
heap_free :: proc "contextless" (ptr: rawptr) {
|
||||
_heap_free(ptr)
|
||||
}
|
||||
@@ -9,7 +9,7 @@ foreign {
|
||||
@(link_name="realloc") _orca_realloc :: proc "c" (ptr: rawptr, size: int) -> rawptr ---
|
||||
}
|
||||
|
||||
_heap_alloc :: proc(size: int, zero_memory := true) -> rawptr {
|
||||
_heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr {
|
||||
if size <= 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -20,10 +20,10 @@ _heap_alloc :: proc(size: int, zero_memory := true) -> rawptr {
|
||||
}
|
||||
}
|
||||
|
||||
_heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr {
|
||||
_heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
|
||||
return _orca_realloc(ptr, new_size)
|
||||
}
|
||||
|
||||
_heap_free :: proc(ptr: rawptr) {
|
||||
_heap_free :: proc "contextless" (ptr: rawptr) {
|
||||
_orca_free(ptr)
|
||||
}
|
||||
|
||||
@@ -2,14 +2,17 @@
|
||||
//+private
|
||||
package runtime
|
||||
|
||||
_heap_alloc :: proc(size: int, zero_memory := true) -> rawptr {
|
||||
_heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr {
|
||||
context = default_context()
|
||||
unimplemented("base:runtime 'heap_alloc' procedure is not supported on this platform")
|
||||
}
|
||||
|
||||
_heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr {
|
||||
_heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
|
||||
context = default_context()
|
||||
unimplemented("base:runtime 'heap_resize' procedure is not supported on this platform")
|
||||
}
|
||||
|
||||
_heap_free :: proc(ptr: rawptr) {
|
||||
_heap_free :: proc "contextless" (ptr: rawptr) {
|
||||
context = default_context()
|
||||
unimplemented("base:runtime 'heap_free' procedure is not supported on this platform")
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ foreign libc {
|
||||
@(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: int) -> rawptr ---
|
||||
}
|
||||
|
||||
_heap_alloc :: proc(size: int, zero_memory := true) -> rawptr {
|
||||
_heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr {
|
||||
if size <= 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -27,12 +27,12 @@ _heap_alloc :: proc(size: int, zero_memory := true) -> rawptr {
|
||||
}
|
||||
}
|
||||
|
||||
_heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr {
|
||||
_heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
|
||||
// NOTE: _unix_realloc doesn't guarantee new memory will be zeroed on
|
||||
// POSIX platforms. Ensure your caller takes this into account.
|
||||
return _unix_realloc(ptr, new_size)
|
||||
}
|
||||
|
||||
_heap_free :: proc(ptr: rawptr) {
|
||||
_heap_free :: proc "contextless" (ptr: rawptr) {
|
||||
_unix_free(ptr)
|
||||
}
|
||||
|
||||
@@ -14,11 +14,11 @@ foreign kernel32 {
|
||||
HeapFree :: proc(hHeap: rawptr, dwFlags: u32, lpMem: rawptr) -> b32 ---
|
||||
}
|
||||
|
||||
_heap_alloc :: proc(size: int, zero_memory := true) -> rawptr {
|
||||
_heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr {
|
||||
HEAP_ZERO_MEMORY :: 0x00000008
|
||||
return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY if zero_memory else 0, uint(size))
|
||||
}
|
||||
_heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr {
|
||||
_heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
|
||||
if new_size == 0 {
|
||||
_heap_free(ptr)
|
||||
return nil
|
||||
@@ -30,7 +30,7 @@ _heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr {
|
||||
HEAP_ZERO_MEMORY :: 0x00000008
|
||||
return HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ptr, uint(new_size))
|
||||
}
|
||||
_heap_free :: proc(ptr: rawptr) {
|
||||
_heap_free :: proc "contextless" (ptr: rawptr) {
|
||||
if ptr == nil {
|
||||
return
|
||||
}
|
||||
|
||||
+31
-27
@@ -1,3 +1,4 @@
|
||||
//+vet !cast
|
||||
package runtime
|
||||
|
||||
import "base:intrinsics"
|
||||
@@ -29,7 +30,7 @@ is_power_of_two_int :: #force_inline proc "contextless" (x: int) -> bool {
|
||||
return (x & (x-1)) == 0
|
||||
}
|
||||
|
||||
align_forward_int :: #force_inline proc(ptr, align: int) -> int {
|
||||
align_forward_int :: #force_inline proc "odin" (ptr, align: int) -> int {
|
||||
assert(is_power_of_two_int(align))
|
||||
|
||||
p := ptr
|
||||
@@ -47,7 +48,7 @@ is_power_of_two_uint :: #force_inline proc "contextless" (x: uint) -> bool {
|
||||
return (x & (x-1)) == 0
|
||||
}
|
||||
|
||||
align_forward_uint :: #force_inline proc(ptr, align: uint) -> uint {
|
||||
align_forward_uint :: #force_inline proc "odin" (ptr, align: uint) -> uint {
|
||||
assert(is_power_of_two_uint(align))
|
||||
|
||||
p := ptr
|
||||
@@ -65,7 +66,7 @@ is_power_of_two_uintptr :: #force_inline proc "contextless" (x: uintptr) -> bool
|
||||
return (x & (x-1)) == 0
|
||||
}
|
||||
|
||||
align_forward_uintptr :: #force_inline proc(ptr, align: uintptr) -> uintptr {
|
||||
align_forward_uintptr :: #force_inline proc "odin" (ptr, align: uintptr) -> uintptr {
|
||||
assert(is_power_of_two_uintptr(align))
|
||||
|
||||
p := ptr
|
||||
@@ -642,21 +643,24 @@ abs_quaternion256 :: #force_inline proc "contextless" (x: quaternion256) -> f64
|
||||
|
||||
|
||||
quo_complex32 :: proc "contextless" (n, m: complex32) -> complex32 {
|
||||
e, f: f16
|
||||
nr, ni := f32(real(n)), f32(imag(n))
|
||||
mr, mi := f32(real(m)), f32(imag(m))
|
||||
|
||||
if abs(real(m)) >= abs(imag(m)) {
|
||||
ratio := imag(m) / real(m)
|
||||
denom := real(m) + ratio*imag(m)
|
||||
e = (real(n) + imag(n)*ratio) / denom
|
||||
f = (imag(n) - real(n)*ratio) / denom
|
||||
e, f: f32
|
||||
|
||||
if abs(mr) >= abs(mi) {
|
||||
ratio := mi / mr
|
||||
denom := mr + ratio*mi
|
||||
e = (nr + ni*ratio) / denom
|
||||
f = (ni - nr*ratio) / denom
|
||||
} else {
|
||||
ratio := real(m) / imag(m)
|
||||
denom := imag(m) + ratio*real(m)
|
||||
e = (real(n)*ratio + imag(n)) / denom
|
||||
f = (imag(n)*ratio - real(n)) / denom
|
||||
ratio := mr / mi
|
||||
denom := mi + ratio*mr
|
||||
e = (nr*ratio + ni) / denom
|
||||
f = (ni*ratio - nr) / denom
|
||||
}
|
||||
|
||||
return complex(e, f)
|
||||
return complex(f16(e), f16(f))
|
||||
}
|
||||
|
||||
|
||||
@@ -697,15 +701,15 @@ quo_complex128 :: proc "contextless" (n, m: complex128) -> complex128 {
|
||||
}
|
||||
|
||||
mul_quaternion64 :: proc "contextless" (q, r: quaternion64) -> quaternion64 {
|
||||
q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q)
|
||||
r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r)
|
||||
q0, q1, q2, q3 := f32(real(q)), f32(imag(q)), f32(jmag(q)), f32(kmag(q))
|
||||
r0, r1, r2, r3 := f32(real(r)), f32(imag(r)), f32(jmag(r)), f32(kmag(r))
|
||||
|
||||
t0 := r0*q0 - r1*q1 - r2*q2 - r3*q3
|
||||
t1 := r0*q1 + r1*q0 - r2*q3 + r3*q2
|
||||
t2 := r0*q2 + r1*q3 + r2*q0 - r3*q1
|
||||
t3 := r0*q3 - r1*q2 + r2*q1 + r3*q0
|
||||
|
||||
return quaternion(w=t0, x=t1, y=t2, z=t3)
|
||||
return quaternion(w=f16(t0), x=f16(t1), y=f16(t2), z=f16(t3))
|
||||
}
|
||||
|
||||
mul_quaternion128 :: proc "contextless" (q, r: quaternion128) -> quaternion128 {
|
||||
@@ -733,8 +737,8 @@ mul_quaternion256 :: proc "contextless" (q, r: quaternion256) -> quaternion256 {
|
||||
}
|
||||
|
||||
quo_quaternion64 :: proc "contextless" (q, r: quaternion64) -> quaternion64 {
|
||||
q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q)
|
||||
r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r)
|
||||
q0, q1, q2, q3 := f32(real(q)), f32(imag(q)), f32(jmag(q)), f32(kmag(q))
|
||||
r0, r1, r2, r3 := f32(real(r)), f32(imag(r)), f32(jmag(r)), f32(kmag(r))
|
||||
|
||||
invmag2 := 1.0 / (r0*r0 + r1*r1 + r2*r2 + r3*r3)
|
||||
|
||||
@@ -743,7 +747,7 @@ quo_quaternion64 :: proc "contextless" (q, r: quaternion64) -> quaternion64 {
|
||||
t2 := (r0*q2 - r1*q3 - r2*q0 + r3*q1) * invmag2
|
||||
t3 := (r0*q3 + r1*q2 + r2*q1 - r3*q0) * invmag2
|
||||
|
||||
return quaternion(w=t0, x=t1, y=t2, z=t3)
|
||||
return quaternion(w=f16(t0), x=f16(t1), y=f16(t2), z=f16(t3))
|
||||
}
|
||||
|
||||
quo_quaternion128 :: proc "contextless" (q, r: quaternion128) -> quaternion128 {
|
||||
@@ -1012,26 +1016,26 @@ modti3 :: proc "c" (a, b: i128) -> i128 {
|
||||
bn := (b ~ s_b) - s_b
|
||||
|
||||
r: u128 = ---
|
||||
_ = udivmod128(transmute(u128)an, transmute(u128)bn, &r)
|
||||
return (transmute(i128)r ~ s_a) - s_a
|
||||
_ = udivmod128(u128(an), u128(bn), &r)
|
||||
return (i128(r) ~ s_a) - s_a
|
||||
}
|
||||
|
||||
|
||||
@(link_name="__divmodti4", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
divmodti4 :: proc "c" (a, b: i128, rem: ^i128) -> i128 {
|
||||
u := udivmod128(transmute(u128)a, transmute(u128)b, cast(^u128)rem)
|
||||
return transmute(i128)u
|
||||
u := udivmod128(u128(a), u128(b), (^u128)(rem))
|
||||
return i128(u)
|
||||
}
|
||||
|
||||
@(link_name="__divti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
divti3 :: proc "c" (a, b: i128) -> i128 {
|
||||
u := udivmodti4(transmute(u128)a, transmute(u128)b, nil)
|
||||
return transmute(i128)u
|
||||
u := udivmodti4(u128(a), u128(b), nil)
|
||||
return i128(u)
|
||||
}
|
||||
|
||||
|
||||
@(link_name="__fixdfti", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||
fixdfti :: proc(a: u64) -> i128 {
|
||||
fixdfti :: proc "c" (a: u64) -> i128 {
|
||||
significandBits :: 52
|
||||
typeWidth :: (size_of(u64)*8)
|
||||
exponentBits :: (typeWidth - significandBits - 1)
|
||||
|
||||
@@ -5,11 +5,24 @@ package runtime
|
||||
import "base:intrinsics"
|
||||
|
||||
_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
|
||||
WRITE :: 0x2000004
|
||||
STDERR :: 2
|
||||
ret := intrinsics.syscall(WRITE, STDERR, uintptr(raw_data(data)), uintptr(len(data)))
|
||||
if ret < 0 {
|
||||
return 0, _OS_Errno(-ret)
|
||||
when ODIN_NO_CRT {
|
||||
WRITE :: 0x2000004
|
||||
ret := intrinsics.syscall(WRITE, STDERR, uintptr(raw_data(data)), uintptr(len(data)))
|
||||
if ret < 0 {
|
||||
return 0, _OS_Errno(-ret)
|
||||
}
|
||||
return int(ret), 0
|
||||
} else {
|
||||
foreign {
|
||||
write :: proc(handle: i32, buffer: [^]byte, count: uint) -> int ---
|
||||
__error :: proc() -> ^i32 ---
|
||||
}
|
||||
|
||||
if ret := write(STDERR, raw_data(data), len(data)); ret >= 0 {
|
||||
return int(ret), 0
|
||||
}
|
||||
|
||||
return 0, _OS_Errno(__error()^)
|
||||
}
|
||||
return int(ret), 0
|
||||
}
|
||||
|
||||
@@ -2,10 +2,54 @@
|
||||
//+private
|
||||
package runtime
|
||||
|
||||
import "core:sys/wasm/wasi"
|
||||
foreign import wasi "wasi_snapshot_preview1"
|
||||
|
||||
@(default_calling_convention="contextless")
|
||||
foreign wasi {
|
||||
fd_write :: proc(
|
||||
fd: i32,
|
||||
iovs: [][]byte,
|
||||
n: ^uint,
|
||||
) -> u16 ---
|
||||
|
||||
@(private="file")
|
||||
args_sizes_get :: proc(
|
||||
num_of_args: ^uint,
|
||||
size_of_args: ^uint,
|
||||
) -> u16 ---
|
||||
|
||||
@(private="file")
|
||||
args_get :: proc(
|
||||
argv: [^]cstring,
|
||||
argv_buf: [^]byte,
|
||||
) -> u16 ---
|
||||
}
|
||||
|
||||
_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
|
||||
data_iovec := (wasi.ciovec_t)(data)
|
||||
n, err := wasi.fd_write(1, {data_iovec})
|
||||
n: uint
|
||||
err := fd_write(1, {data}, &n)
|
||||
return int(n), _OS_Errno(err)
|
||||
}
|
||||
|
||||
_wasi_setup_args :: proc() {
|
||||
num_of_args, size_of_args: uint
|
||||
if errno := args_sizes_get(&num_of_args, &size_of_args); errno != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
err: Allocator_Error
|
||||
if args__, err = make([]cstring, num_of_args); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
args_buf: []byte
|
||||
if args_buf, err = make([]byte, size_of_args); err != nil {
|
||||
delete(args__)
|
||||
return
|
||||
}
|
||||
|
||||
if errno := args_get(raw_data(args__), raw_data(args_buf)); errno != 0 {
|
||||
delete(args__)
|
||||
delete(args_buf)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,7 +262,7 @@ print_typeid :: #force_no_inline proc "contextless" (id: typeid) {
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="size")
|
||||
@(optimization_mode="favor_size")
|
||||
print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) {
|
||||
if ti == nil {
|
||||
print_string("nil")
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
package runtime
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
@(require_results)
|
||||
random_generator_read_bytes :: proc(rg: Random_Generator, p: []byte) -> bool {
|
||||
if rg.procedure != nil {
|
||||
rg.procedure(rg.data, .Read, p)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
random_generator_read_ptr :: proc(rg: Random_Generator, p: rawptr, len: uint) -> bool {
|
||||
if rg.procedure != nil {
|
||||
rg.procedure(rg.data, .Read, ([^]byte)(p)[:len])
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
random_generator_query_info :: proc(rg: Random_Generator) -> (info: Random_Generator_Query_Info) {
|
||||
if rg.procedure != nil {
|
||||
rg.procedure(rg.data, .Query_Info, ([^]byte)(&info)[:size_of(info)])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
random_generator_reset_bytes :: proc(rg: Random_Generator, p: []byte) {
|
||||
if rg.procedure != nil {
|
||||
rg.procedure(rg.data, .Reset, p)
|
||||
}
|
||||
}
|
||||
|
||||
random_generator_reset_u64 :: proc(rg: Random_Generator, p: u64) {
|
||||
if rg.procedure != nil {
|
||||
p := p
|
||||
rg.procedure(rg.data, .Reset, ([^]byte)(&p)[:size_of(p)])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Default_Random_State :: struct {
|
||||
state: u64,
|
||||
inc: u64,
|
||||
}
|
||||
|
||||
default_random_generator_proc :: proc(data: rawptr, mode: Random_Generator_Mode, p: []byte) {
|
||||
@(require_results)
|
||||
read_u64 :: proc "contextless" (r: ^Default_Random_State) -> u64 {
|
||||
old_state := r.state
|
||||
r.state = old_state * 6364136223846793005 + (r.inc|1)
|
||||
xor_shifted := (((old_state >> 59) + 5) ~ old_state) * 12605985483714917081
|
||||
rot := (old_state >> 59)
|
||||
return (xor_shifted >> rot) | (xor_shifted << ((-rot) & 63))
|
||||
}
|
||||
|
||||
@(thread_local)
|
||||
global_rand_seed: Default_Random_State
|
||||
|
||||
init :: proc "contextless" (r: ^Default_Random_State, seed: u64) {
|
||||
seed := seed
|
||||
if seed == 0 {
|
||||
seed = u64(intrinsics.read_cycle_counter())
|
||||
}
|
||||
r.state = 0
|
||||
r.inc = (seed << 1) | 1
|
||||
_ = read_u64(r)
|
||||
r.state += seed
|
||||
_ = read_u64(r)
|
||||
}
|
||||
|
||||
r: ^Default_Random_State = ---
|
||||
if data == nil {
|
||||
r = &global_rand_seed
|
||||
} else {
|
||||
r = cast(^Default_Random_State)data
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case .Read:
|
||||
if r.state == 0 && r.inc == 0 {
|
||||
init(r, 0)
|
||||
}
|
||||
|
||||
switch len(p) {
|
||||
case size_of(u64):
|
||||
// Fast path for a 64-bit destination.
|
||||
intrinsics.unaligned_store((^u64)(raw_data(p)), read_u64(r))
|
||||
case:
|
||||
// All other cases.
|
||||
pos := i8(0)
|
||||
val := u64(0)
|
||||
for &v in p {
|
||||
if pos == 0 {
|
||||
val = read_u64(r)
|
||||
pos = 7
|
||||
}
|
||||
v = byte(val)
|
||||
val >>= 8
|
||||
pos -= 1
|
||||
}
|
||||
}
|
||||
|
||||
case .Reset:
|
||||
seed: u64
|
||||
mem_copy_non_overlapping(&seed, raw_data(p), min(size_of(seed), len(p)))
|
||||
init(r, seed)
|
||||
|
||||
case .Query_Info:
|
||||
if len(p) != size_of(Random_Generator_Query_Info) {
|
||||
return
|
||||
}
|
||||
info := (^Random_Generator_Query_Info)(raw_data(p))
|
||||
info^ += {.Uniform, .Resettable}
|
||||
}
|
||||
}
|
||||
|
||||
default_random_generator :: proc "contextless" (state: ^Default_Random_State = nil) -> Random_Generator {
|
||||
return {
|
||||
procedure = default_random_generator_proc,
|
||||
data = state,
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,7 @@ udivmod128 :: proc "c" (a, b: u128, rem: ^u128) -> u128 {
|
||||
return u128(n[high] >> _ctz(d[high]))
|
||||
}
|
||||
|
||||
sr = transmute(u32)(i32(_clz(d[high])) - i32(_clz(n[high])))
|
||||
sr = u32(i32(_clz(d[high])) - i32(_clz(n[high])))
|
||||
if sr > U64_BITS - 2 {
|
||||
if rem != nil {
|
||||
rem^ = a
|
||||
@@ -107,7 +107,7 @@ udivmod128 :: proc "c" (a, b: u128, rem: ^u128) -> u128 {
|
||||
r[low] = n[high] >> (sr - U64_BITS)
|
||||
}
|
||||
} else {
|
||||
sr = transmute(u32)(i32(_clz(d[high])) - i32(_clz(n[high])))
|
||||
sr = u32(i32(_clz(d[high])) - i32(_clz(n[high])))
|
||||
|
||||
if sr > U64_BITS - 1 {
|
||||
if rem != nil {
|
||||
@@ -143,7 +143,7 @@ udivmod128 :: proc "c" (a, b: u128, rem: ^u128) -> u128 {
|
||||
r_all = transmute(u128)r
|
||||
s := i128(b - r_all - 1) >> (U128_BITS - 1)
|
||||
carry = u32(s & 1)
|
||||
r_all -= b & transmute(u128)s
|
||||
r_all -= b & u128(s)
|
||||
r = transmute([2]u64)r_all
|
||||
}
|
||||
|
||||
|
||||
@@ -495,7 +495,7 @@ claim_more_memory :: proc(a: ^WASM_Allocator, num_bytes: uint) -> bool {
|
||||
// we can just extend the spill.
|
||||
spill_end := uintptr(raw_data(a.spill)) + uintptr(len(a.spill))
|
||||
if spill_end == uintptr(raw_data(allocated)) {
|
||||
raw_spill := transmute(^Raw_Slice)(&a.spill)
|
||||
raw_spill := (^Raw_Slice)(&a.spill)
|
||||
raw_spill.len += len(allocated)
|
||||
} else {
|
||||
// Otherwise, we have to "waste" the previous spill.
|
||||
@@ -679,7 +679,7 @@ allocate_memory :: proc(a: ^WASM_Allocator, alignment: uint, size: uint, loc :=
|
||||
// but we just had a stale bit set to mark a populated bucket.
|
||||
// Reset the bit to update latest status so that we do not
|
||||
// redundantly look at this bucket again.
|
||||
a.free_region_buckets_used &= ~(BUCKET_BITMASK_T(1) << bucket_index)
|
||||
a.free_region_buckets_used &~= BUCKET_BITMASK_T(1) << bucket_index
|
||||
bucket_mask ~= 1
|
||||
}
|
||||
|
||||
@@ -760,7 +760,7 @@ free :: proc(a: ^WASM_Allocator, ptr: rawptr, loc := #caller_location) {
|
||||
defer unlock(a)
|
||||
|
||||
size := region.size
|
||||
assert(region_is_in_use(region), "double free", loc=loc)
|
||||
assert(region_is_in_use(region), "double free or corrupt region", loc=loc)
|
||||
|
||||
prev_region_size_field := ([^]uint)(region)[-1]
|
||||
prev_region_size := prev_region_size_field & ~uint(FREE_REGION_FLAG)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -48,6 +48,9 @@ if "%2" == "1" (
|
||||
set odin_version_raw="dev-%curr_year%-%curr_month%"
|
||||
|
||||
set compiler_flags= -nologo -Oi -TP -fp:precise -Gm- -MP -FC -EHsc- -GR- -GF
|
||||
rem Parse source code as utf-8 even on shift-jis and other codepages
|
||||
rem See https://learn.microsoft.com/en-us/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170
|
||||
set compiler_flags= %compiler_flags% /utf-8
|
||||
set compiler_defines= -DODIN_VERSION_RAW=\"%odin_version_raw%\"
|
||||
|
||||
if not exist .git\ goto skip_git_hash
|
||||
@@ -111,7 +114,7 @@ call build_vendor.bat
|
||||
if %errorlevel% neq 0 goto end_of_build
|
||||
|
||||
rem If the demo doesn't run for you and your CPU is more than a decade old, try -microarch:native
|
||||
if %release_mode% EQU 0 odin run examples/demo -- Hellope World
|
||||
if %release_mode% EQU 0 odin run examples/demo -vet -strict-style -- Hellope World
|
||||
|
||||
del *.obj > NUL 2> NUL
|
||||
|
||||
|
||||
+1
-1
@@ -144,7 +144,7 @@ build_odin() {
|
||||
}
|
||||
|
||||
run_demo() {
|
||||
./odin run examples/demo/demo.odin -file -- Hellope World
|
||||
./odin run examples/demo -vet -strict-style -- Hellope World
|
||||
}
|
||||
|
||||
if [ $# -eq 0 ]; then
|
||||
|
||||
+1
-14
@@ -34,20 +34,7 @@ when ODIN_OS == .Windows {
|
||||
SIGTERM :: 15
|
||||
}
|
||||
|
||||
when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Haiku || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
|
||||
SIG_ERR :: rawptr(~uintptr(0))
|
||||
SIG_DFL :: rawptr(uintptr(0))
|
||||
SIG_IGN :: rawptr(uintptr(1))
|
||||
|
||||
SIGABRT :: 6
|
||||
SIGFPE :: 8
|
||||
SIGILL :: 4
|
||||
SIGINT :: 2
|
||||
SIGSEGV :: 11
|
||||
SIGTERM :: 15
|
||||
}
|
||||
|
||||
when ODIN_OS == .Darwin {
|
||||
when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Haiku || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Darwin {
|
||||
SIG_ERR :: rawptr(~uintptr(0))
|
||||
SIG_DFL :: rawptr(uintptr(0))
|
||||
SIG_IGN :: rawptr(uintptr(1))
|
||||
|
||||
+28
-28
@@ -34,13 +34,13 @@ COMPRESS_OUTPUT_ALLOCATE_MIN :: int(#config(COMPRESS_OUTPUT_ALLOCATE_MIN, 1 << 2
|
||||
*/
|
||||
when size_of(uintptr) == 8 {
|
||||
|
||||
// For 64-bit platforms, we set the default max buffer size to 4 GiB,
|
||||
// which is GZIP and PKZIP's max payload size.
|
||||
// For 64-bit platforms, we set the default max buffer size to 4 GiB,
|
||||
// which is GZIP and PKZIP's max payload size.
|
||||
COMPRESS_OUTPUT_ALLOCATE_MAX :: int(#config(COMPRESS_OUTPUT_ALLOCATE_MAX, 1 << 32))
|
||||
} else {
|
||||
|
||||
// For 32-bit platforms, we set the default max buffer size to 512 MiB.
|
||||
COMPRESS_OUTPUT_ALLOCATE_MAX :: int(#config(COMPRESS_OUTPUT_ALLOCATE_MAX, 1 << 29))
|
||||
COMPRESS_OUTPUT_ALLOCATE_MAX :: int(#config(COMPRESS_OUTPUT_ALLOCATE_MAX, 1 << 29))
|
||||
}
|
||||
|
||||
|
||||
@@ -186,7 +186,7 @@ input_size_from_stream :: proc(z: ^Context_Stream_Input) -> (res: i64, err: Erro
|
||||
|
||||
input_size :: proc{input_size_from_memory, input_size_from_stream}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
read_slice_from_memory :: #force_inline proc(z: ^Context_Memory_Input, size: int) -> (res: []u8, err: io.Error) {
|
||||
#no_bounds_check {
|
||||
if len(z.input_data) >= size {
|
||||
@@ -203,7 +203,7 @@ read_slice_from_memory :: #force_inline proc(z: ^Context_Memory_Input, size: int
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
read_slice_from_stream :: #force_inline proc(z: ^Context_Stream_Input, size: int) -> (res: []u8, err: io.Error) {
|
||||
// TODO: REMOVE ALL USE OF context.temp_allocator here
|
||||
// there is literally no need for it
|
||||
@@ -214,13 +214,13 @@ read_slice_from_stream :: #force_inline proc(z: ^Context_Stream_Input, size: int
|
||||
|
||||
read_slice :: proc{read_slice_from_memory, read_slice_from_stream}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
read_data :: #force_inline proc(z: ^$C, $T: typeid) -> (res: T, err: io.Error) {
|
||||
b := read_slice(z, size_of(T)) or_return
|
||||
return (^T)(&b[0])^, nil
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
read_u8_from_memory :: #force_inline proc(z: ^Context_Memory_Input) -> (res: u8, err: io.Error) {
|
||||
#no_bounds_check {
|
||||
if len(z.input_data) >= 1 {
|
||||
@@ -232,7 +232,7 @@ read_u8_from_memory :: #force_inline proc(z: ^Context_Memory_Input) -> (res: u8,
|
||||
return 0, .EOF
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
read_u8_from_stream :: #force_inline proc(z: ^Context_Stream_Input) -> (res: u8, err: io.Error) {
|
||||
b := read_slice_from_stream(z, 1) or_return
|
||||
return b[0], nil
|
||||
@@ -242,7 +242,7 @@ read_u8 :: proc{read_u8_from_memory, read_u8_from_stream}
|
||||
|
||||
// You would typically only use this at the end of Inflate, to drain bits from the code buffer
|
||||
// preferentially.
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
read_u8_prefer_code_buffer_lsb :: #force_inline proc(z: ^$C) -> (res: u8, err: io.Error) {
|
||||
if z.num_bits >= 8 {
|
||||
res = u8(read_bits_no_refill_lsb(z, 8))
|
||||
@@ -257,7 +257,7 @@ read_u8_prefer_code_buffer_lsb :: #force_inline proc(z: ^$C) -> (res: u8, err: i
|
||||
return
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
peek_data_from_memory :: #force_inline proc(z: ^Context_Memory_Input, $T: typeid) -> (res: T, err: io.Error) {
|
||||
size :: size_of(T)
|
||||
|
||||
@@ -275,7 +275,7 @@ peek_data_from_memory :: #force_inline proc(z: ^Context_Memory_Input, $T: typeid
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
peek_data_at_offset_from_memory :: #force_inline proc(z: ^Context_Memory_Input, $T: typeid, #any_int offset: int) -> (res: T, err: io.Error) {
|
||||
size :: size_of(T)
|
||||
|
||||
@@ -293,7 +293,7 @@ peek_data_at_offset_from_memory :: #force_inline proc(z: ^Context_Memory_Input,
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
peek_data_from_stream :: #force_inline proc(z: ^Context_Stream_Input, $T: typeid) -> (res: T, err: io.Error) {
|
||||
size :: size_of(T)
|
||||
|
||||
@@ -317,7 +317,7 @@ peek_data_from_stream :: #force_inline proc(z: ^Context_Stream_Input, $T: typeid
|
||||
return res, .None
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
peek_data_at_offset_from_stream :: #force_inline proc(z: ^Context_Stream_Input, $T: typeid, #any_int offset: int) -> (res: T, err: io.Error) {
|
||||
size :: size_of(T)
|
||||
|
||||
@@ -352,14 +352,14 @@ peek_data :: proc{peek_data_from_memory, peek_data_from_stream, peek_data_at_off
|
||||
|
||||
|
||||
// Sliding window read back
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
peek_back_byte :: #force_inline proc(z: ^$C, offset: i64) -> (res: u8, err: io.Error) {
|
||||
// Look back into the sliding window.
|
||||
return z.output.buf[z.bytes_written - offset], .None
|
||||
}
|
||||
|
||||
// Generalized bit reader LSB
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
refill_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width := i8(48)) {
|
||||
refill := u64(width)
|
||||
b := u64(0)
|
||||
@@ -385,7 +385,7 @@ refill_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width :=
|
||||
}
|
||||
|
||||
// Generalized bit reader LSB
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
refill_lsb_from_stream :: proc(z: ^Context_Stream_Input, width := i8(24)) {
|
||||
refill := u64(width)
|
||||
|
||||
@@ -414,13 +414,13 @@ refill_lsb_from_stream :: proc(z: ^Context_Stream_Input, width := i8(24)) {
|
||||
refill_lsb :: proc{refill_lsb_from_memory, refill_lsb_from_stream}
|
||||
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
consume_bits_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) {
|
||||
z.code_buffer >>= width
|
||||
z.num_bits -= u64(width)
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
consume_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) {
|
||||
z.code_buffer >>= width
|
||||
z.num_bits -= u64(width)
|
||||
@@ -428,7 +428,7 @@ consume_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, wid
|
||||
|
||||
consume_bits_lsb :: proc{consume_bits_lsb_from_memory, consume_bits_lsb_from_stream}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
peek_bits_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) -> u32 {
|
||||
if z.num_bits < u64(width) {
|
||||
refill_lsb(z)
|
||||
@@ -436,7 +436,7 @@ peek_bits_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width:
|
||||
return u32(z.code_buffer &~ (~u64(0) << width))
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
peek_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) -> u32 {
|
||||
if z.num_bits < u64(width) {
|
||||
refill_lsb(z)
|
||||
@@ -446,13 +446,13 @@ peek_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width:
|
||||
|
||||
peek_bits_lsb :: proc{peek_bits_lsb_from_memory, peek_bits_lsb_from_stream}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
peek_bits_no_refill_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) -> u32 {
|
||||
assert(z.num_bits >= u64(width))
|
||||
return u32(z.code_buffer &~ (~u64(0) << width))
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
peek_bits_no_refill_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) -> u32 {
|
||||
assert(z.num_bits >= u64(width))
|
||||
return u32(z.code_buffer &~ (~u64(0) << width))
|
||||
@@ -460,14 +460,14 @@ peek_bits_no_refill_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Inp
|
||||
|
||||
peek_bits_no_refill_lsb :: proc{peek_bits_no_refill_lsb_from_memory, peek_bits_no_refill_lsb_from_stream}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
read_bits_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) -> u32 {
|
||||
k := #force_inline peek_bits_lsb(z, width)
|
||||
#force_inline consume_bits_lsb(z, width)
|
||||
return k
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
read_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) -> u32 {
|
||||
k := peek_bits_lsb(z, width)
|
||||
consume_bits_lsb(z, width)
|
||||
@@ -476,14 +476,14 @@ read_bits_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width:
|
||||
|
||||
read_bits_lsb :: proc{read_bits_lsb_from_memory, read_bits_lsb_from_stream}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
read_bits_no_refill_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width: u8) -> u32 {
|
||||
k := #force_inline peek_bits_no_refill_lsb(z, width)
|
||||
#force_inline consume_bits_lsb(z, width)
|
||||
return k
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
read_bits_no_refill_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Input, width: u8) -> u32 {
|
||||
k := peek_bits_no_refill_lsb(z, width)
|
||||
consume_bits_lsb(z, width)
|
||||
@@ -493,14 +493,14 @@ read_bits_no_refill_lsb_from_stream :: #force_inline proc(z: ^Context_Stream_Inp
|
||||
read_bits_no_refill_lsb :: proc{read_bits_no_refill_lsb_from_memory, read_bits_no_refill_lsb_from_stream}
|
||||
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
discard_to_next_byte_lsb_from_memory :: proc(z: ^Context_Memory_Input) {
|
||||
discard := u8(z.num_bits & 7)
|
||||
#force_inline consume_bits_lsb(z, discard)
|
||||
}
|
||||
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
discard_to_next_byte_lsb_from_stream :: proc(z: ^Context_Stream_Input) {
|
||||
discard := u8(z.num_bits & 7)
|
||||
consume_bits_lsb(z, discard)
|
||||
|
||||
@@ -98,7 +98,7 @@ decompress_slice_to_output_buffer :: proc(input: []u8, output: []u8, model := DE
|
||||
validate_model(model) or_return
|
||||
|
||||
for inp < inp_end {
|
||||
val := transmute(i8)input[inp]
|
||||
val := i8(input[inp])
|
||||
mark := int(-1)
|
||||
|
||||
for val < 0 {
|
||||
@@ -274,12 +274,9 @@ compress_string_to_buffer :: proc(input: string, output: []u8, model := DEFAULT_
|
||||
out_ptr := raw_data(output[out:])
|
||||
|
||||
switch pack.bytes_packed {
|
||||
case 4:
|
||||
intrinsics.unaligned_store(transmute(^u32)out_ptr, code)
|
||||
case 2:
|
||||
intrinsics.unaligned_store(transmute(^u16)out_ptr, u16(code))
|
||||
case 1:
|
||||
intrinsics.unaligned_store(transmute(^u8)out_ptr, u8(code))
|
||||
case 4: intrinsics.unaligned_store((^u32)(out_ptr), code)
|
||||
case 2: intrinsics.unaligned_store((^u16)(out_ptr), u16(code))
|
||||
case 1: intrinsics.unaligned_store( (^u8)(out_ptr), u8(code))
|
||||
case:
|
||||
return out, .Unknown_Compression_Method
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ Huffman_Table :: struct {
|
||||
}
|
||||
|
||||
// Implementation starts here
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
z_bit_reverse :: #force_inline proc(n: u16, bits: u8) -> (r: u16) {
|
||||
assert(bits <= 16)
|
||||
// NOTE: Can optimize with llvm.bitreverse.i64 or some bit twiddling
|
||||
@@ -136,7 +136,7 @@ z_bit_reverse :: #force_inline proc(n: u16, bits: u8) -> (r: u16) {
|
||||
}
|
||||
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
grow_buffer :: proc(buf: ^[dynamic]u8) -> (err: compress.Error) {
|
||||
/*
|
||||
That we get here at all means that we didn't pass an expected output size,
|
||||
@@ -154,7 +154,7 @@ grow_buffer :: proc(buf: ^[dynamic]u8) -> (err: compress.Error) {
|
||||
TODO: Make these return compress.Error.
|
||||
*/
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
write_byte :: #force_inline proc(z: ^$C, c: u8) -> (err: io.Error) #no_bounds_check {
|
||||
/*
|
||||
Resize if needed.
|
||||
@@ -173,7 +173,7 @@ write_byte :: #force_inline proc(z: ^$C, c: u8) -> (err: io.Error) #no_bounds_ch
|
||||
return .None
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
repl_byte :: proc(z: ^$C, count: u16, c: u8) -> (err: io.Error) #no_bounds_check {
|
||||
/*
|
||||
TODO(Jeroen): Once we have a magic ring buffer, we can just peek/write into it
|
||||
@@ -201,7 +201,7 @@ repl_byte :: proc(z: ^$C, count: u16, c: u8) -> (err: io.Error) #no_bounds_check
|
||||
return .None
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
repl_bytes :: proc(z: ^$C, count: u16, distance: u16) -> (err: io.Error) {
|
||||
/*
|
||||
TODO(Jeroen): Once we have a magic ring buffer, we can just peek/write into it
|
||||
@@ -234,7 +234,7 @@ allocate_huffman_table :: proc(allocator := context.allocator) -> (z: ^Huffman_T
|
||||
return new(Huffman_Table, allocator), nil
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
build_huffman :: proc(z: ^Huffman_Table, code_lengths: []u8) -> (err: Error) {
|
||||
sizes: [HUFFMAN_MAX_BITS+1]int
|
||||
next_code: [HUFFMAN_MAX_BITS+1]int
|
||||
@@ -293,7 +293,7 @@ build_huffman :: proc(z: ^Huffman_Table, code_lengths: []u8) -> (err: Error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
decode_huffman_slowpath :: proc(z: ^$C, t: ^Huffman_Table) -> (r: u16, err: Error) #no_bounds_check {
|
||||
code := u16(compress.peek_bits_lsb(z,16))
|
||||
|
||||
@@ -324,7 +324,7 @@ decode_huffman_slowpath :: proc(z: ^$C, t: ^Huffman_Table) -> (r: u16, err: Erro
|
||||
return r, nil
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
decode_huffman :: proc(z: ^$C, t: ^Huffman_Table) -> (r: u16, err: Error) #no_bounds_check {
|
||||
if z.num_bits < 16 {
|
||||
if z.num_bits > 63 {
|
||||
@@ -344,7 +344,7 @@ decode_huffman :: proc(z: ^$C, t: ^Huffman_Table) -> (r: u16, err: Error) #no_bo
|
||||
return decode_huffman_slowpath(z, t)
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
parse_huffman_block :: proc(z: ^$C, z_repeat, z_offset: ^Huffman_Table) -> (err: Error) #no_bounds_check {
|
||||
#no_bounds_check for {
|
||||
value, e := decode_huffman(z, z_repeat)
|
||||
@@ -413,7 +413,7 @@ parse_huffman_block :: proc(z: ^$C, z_repeat, z_offset: ^Huffman_Table) -> (err:
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
inflate_from_context :: proc(using ctx: ^compress.Context_Memory_Input, raw := false, expected_output_size := -1, allocator := context.allocator) -> (err: Error) #no_bounds_check {
|
||||
/*
|
||||
ctx.output must be a bytes.Buffer for now. We'll add a separate implementation that writes to a stream.
|
||||
@@ -486,7 +486,7 @@ inflate_from_context :: proc(using ctx: ^compress.Context_Memory_Input, raw := f
|
||||
|
||||
// TODO: Check alignment of reserve/resize.
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
inflate_raw :: proc(z: ^$C, expected_output_size := -1, allocator := context.allocator) -> (err: Error) #no_bounds_check {
|
||||
context.allocator = allocator
|
||||
expected_output_size := expected_output_size
|
||||
|
||||
@@ -87,7 +87,7 @@ init_cmp :: proc(
|
||||
init_ordered :: proc(
|
||||
t: ^$T/Tree($Value),
|
||||
node_allocator := context.allocator,
|
||||
) where intrinsics.type_is_ordered_numeric(Value) {
|
||||
) where intrinsics.type_is_ordered(Value) {
|
||||
init_cmp(t, slice.cmp_proc(Value), node_allocator)
|
||||
}
|
||||
|
||||
|
||||
@@ -210,8 +210,11 @@ set :: proc(ba: ^Bit_Array, #any_int index: uint, set_to: bool = true, allocator
|
||||
|
||||
ba.max_index = max(idx, ba.max_index)
|
||||
|
||||
if set_to{ ba.bits[leg_index] |= 1 << uint(bit_index) }
|
||||
else { ba.bits[leg_index] &= ~(1 << uint(bit_index)) }
|
||||
if set_to {
|
||||
ba.bits[leg_index] |= 1 << uint(bit_index)
|
||||
} else {
|
||||
ba.bits[leg_index] &~= 1 << uint(bit_index)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -253,7 +256,7 @@ Inputs:
|
||||
- index: Which bit in the array
|
||||
*/
|
||||
unsafe_unset :: proc(b: ^Bit_Array, bit: int) #no_bounds_check {
|
||||
b.bits[bit >> INDEX_SHIFT] &= ~(1 << uint(bit & INDEX_MASK))
|
||||
b.bits[bit >> INDEX_SHIFT] &~= 1 << uint(bit & INDEX_MASK)
|
||||
}
|
||||
/*
|
||||
A helper function to create a Bit Array with optional bias, in case your smallest index is non-zero (including negative).
|
||||
|
||||
@@ -70,8 +70,7 @@ set :: proc(c: ^$C/Cache($Key, $Value), key: Key, value: Value) -> runtime.Alloc
|
||||
if c.count == c.capacity {
|
||||
e = c.tail
|
||||
_remove_node(c, e)
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
c.count += 1
|
||||
e = new(Node(Key, Value), c.node_allocator) or_return
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ pop_front_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) {
|
||||
return
|
||||
}
|
||||
|
||||
// Push multiple elements to the front of the queue
|
||||
// Push multiple elements to the back of the queue
|
||||
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) {
|
||||
@@ -241,7 +241,7 @@ clear :: proc(q: ^$Q/Queue($T)) {
|
||||
}
|
||||
|
||||
|
||||
// Internal growinh procedure
|
||||
// Internal growing procedure
|
||||
_grow :: proc(q: ^$Q/Queue($T), min_capacity: uint = 0) -> runtime.Allocator_Error {
|
||||
new_capacity := max(min_capacity, uint(8), uint(builtin.len(q.data))*2)
|
||||
n := uint(builtin.len(q.data))
|
||||
|
||||
@@ -29,7 +29,7 @@ Tree :: struct($Key: typeid, $Value: typeid) {
|
||||
|
||||
_root: ^Node(Key, Value),
|
||||
_node_allocator: runtime.Allocator,
|
||||
_cmp_fn: proc(Key, Key) -> Ordering,
|
||||
_cmp_fn: proc(Key, Key) -> Ordering,
|
||||
_size: int,
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ init_cmp :: proc(t: ^$T/Tree($Key, $Value), cmp_fn: proc(a, b: Key) -> Ordering,
|
||||
|
||||
// init_ordered initializes a tree containing ordered keys, with
|
||||
// a comparison function that results in an ascending order sort.
|
||||
init_ordered :: proc(t: ^$T/Tree($Key, $Value), node_allocator := context.allocator) where intrinsics.type_is_ordered_numeric(Key) {
|
||||
init_ordered :: proc(t: ^$T/Tree($Key, $Value), node_allocator := context.allocator) where intrinsics.type_is_ordered(Key) {
|
||||
init_cmp(t, slice.cmp_proc(Key), node_allocator)
|
||||
}
|
||||
|
||||
|
||||
@@ -119,20 +119,20 @@ consume :: proc "odin" (a: ^$A/Small_Array($N, $T), count: int, loc := #caller_l
|
||||
}
|
||||
|
||||
ordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, loc := #caller_location) #no_bounds_check {
|
||||
runtime.bounds_check_error_loc(loc, index, a.len)
|
||||
if index+1 < a.len {
|
||||
runtime.bounds_check_error_loc(loc, index, a.len)
|
||||
if index+1 < a.len {
|
||||
copy(a.data[index:], a.data[index+1:])
|
||||
}
|
||||
a.len -= 1
|
||||
}
|
||||
|
||||
unordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, loc := #caller_location) #no_bounds_check {
|
||||
runtime.bounds_check_error_loc(loc, index, a.len)
|
||||
runtime.bounds_check_error_loc(loc, index, a.len)
|
||||
n := a.len-1
|
||||
if index != n {
|
||||
a.data[index] = a.data[n]
|
||||
}
|
||||
a.len -= 1
|
||||
a.len -= 1
|
||||
}
|
||||
|
||||
clear :: proc "contextless" (a: ^$A/Small_Array($N, $T)) {
|
||||
|
||||
@@ -61,7 +61,7 @@ add_dependency :: proc(sorter: ^$S/Sorter($K), key, dependency: K) -> bool {
|
||||
}
|
||||
find.dependents[key] = true
|
||||
|
||||
find = &sorter.relations[key]
|
||||
find = &sorter.relations[key]
|
||||
if find == nil {
|
||||
find = map_insert(&sorter.relations, key, make_relations(sorter))
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ ge_set_bytes :: proc "contextless" (ge: ^Group_Element, b: []byte) -> bool {
|
||||
if len(b) != 32 {
|
||||
intrinsics.trap()
|
||||
}
|
||||
b_ := transmute(^[32]byte)(raw_data(b))
|
||||
b_ := (^[32]byte)(raw_data(b))
|
||||
|
||||
// Do the work in a scratch element, so that ge is unchanged on
|
||||
// failure.
|
||||
@@ -169,7 +169,7 @@ ge_bytes :: proc "contextless" (ge: ^Group_Element, dst: []byte) {
|
||||
if len(dst) != 32 {
|
||||
intrinsics.trap()
|
||||
}
|
||||
dst_ := transmute(^[32]byte)(raw_data(dst))
|
||||
dst_ := (^[32]byte)(raw_data(dst))
|
||||
|
||||
// Convert the element to affine (x, y) representation.
|
||||
x, y, z_inv: field.Tight_Field_Element = ---, ---, ---
|
||||
|
||||
@@ -28,7 +28,7 @@ sc_set_bytes :: proc "contextless" (sc: ^Scalar, b: []byte) -> bool {
|
||||
if len(b) != 32 {
|
||||
intrinsics.trap()
|
||||
}
|
||||
b_ := transmute(^[32]byte)(raw_data(b))
|
||||
b_ := (^[32]byte)(raw_data(b))
|
||||
return field.fe_from_bytes(sc, b_)
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ sc_set_bytes_rfc8032 :: proc "contextless" (sc: ^Scalar, b: []byte) {
|
||||
if len(b) != 32 {
|
||||
intrinsics.trap()
|
||||
}
|
||||
b_ := transmute(^[32]byte)(raw_data(b))
|
||||
b_ := (^[32]byte)(raw_data(b))
|
||||
field.fe_from_bytes_rfc8032(sc, b_)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,13 +6,13 @@ import "core:mem"
|
||||
fe_relax_cast :: #force_inline proc "contextless" (
|
||||
arg1: ^Tight_Field_Element,
|
||||
) -> ^Loose_Field_Element {
|
||||
return transmute(^Loose_Field_Element)(arg1)
|
||||
return (^Loose_Field_Element)(arg1)
|
||||
}
|
||||
|
||||
fe_tighten_cast :: #force_inline proc "contextless" (
|
||||
arg1: ^Loose_Field_Element,
|
||||
) -> ^Tight_Field_Element {
|
||||
return transmute(^Tight_Field_Element)(arg1)
|
||||
return (^Tight_Field_Element)(arg1)
|
||||
}
|
||||
|
||||
fe_clear :: proc "contextless" (
|
||||
|
||||
@@ -7,13 +7,13 @@ import "core:mem"
|
||||
fe_relax_cast :: #force_inline proc "contextless" (
|
||||
arg1: ^Tight_Field_Element,
|
||||
) -> ^Loose_Field_Element {
|
||||
return transmute(^Loose_Field_Element)(arg1)
|
||||
return (^Loose_Field_Element)(arg1)
|
||||
}
|
||||
|
||||
fe_tighten_cast :: #force_inline proc "contextless" (
|
||||
arg1: ^Loose_Field_Element,
|
||||
) -> ^Tight_Field_Element {
|
||||
return transmute(^Tight_Field_Element)(arg1)
|
||||
return (^Tight_Field_Element)(arg1)
|
||||
}
|
||||
|
||||
fe_from_bytes :: #force_inline proc "contextless" (
|
||||
|
||||
@@ -4,6 +4,7 @@ helper routines.
|
||||
*/
|
||||
package crypto
|
||||
|
||||
import "base:runtime"
|
||||
import "core:mem"
|
||||
|
||||
// compare_constant_time returns 1 iff a and b are equal, 0 otherwise.
|
||||
@@ -58,3 +59,24 @@ rand_bytes :: proc (dst: []byte) {
|
||||
|
||||
_rand_bytes(dst)
|
||||
}
|
||||
|
||||
|
||||
random_generator :: proc() -> runtime.Random_Generator {
|
||||
return {
|
||||
procedure = proc(data: rawptr, mode: runtime.Random_Generator_Mode, p: []byte) {
|
||||
switch mode {
|
||||
case .Read:
|
||||
rand_bytes(p)
|
||||
case .Reset:
|
||||
// do nothing
|
||||
case .Query_Info:
|
||||
if len(p) != size_of(runtime.Random_Generator_Query_Info) {
|
||||
return
|
||||
}
|
||||
info := (^runtime.Random_Generator_Query_Info)(raw_data(p))
|
||||
info^ += {.Uniform, .Cryptographic, .External_Entropy}
|
||||
}
|
||||
},
|
||||
data = nil,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ init_256 :: proc(ctx: ^Context, key, domain_sep: []byte) {
|
||||
update :: proc(ctx: ^Context, data: []byte) {
|
||||
assert(ctx.is_initialized)
|
||||
|
||||
shake.write(transmute(^shake.Context)(ctx), data)
|
||||
shake.write((^shake.Context)(ctx), data)
|
||||
}
|
||||
|
||||
// final finalizes the Context, writes the tag to dst, and calls reset
|
||||
@@ -75,7 +75,7 @@ final :: proc(ctx: ^Context, dst: []byte) {
|
||||
panic("crypto/kmac: invalid KMAC tag_size, too short")
|
||||
}
|
||||
|
||||
_sha3.final_cshake(transmute(^_sha3.Context)(ctx), dst)
|
||||
_sha3.final_cshake((^_sha3.Context)(ctx), dst)
|
||||
}
|
||||
|
||||
// clone clones the Context other into ctx.
|
||||
@@ -84,7 +84,7 @@ clone :: proc(ctx, other: ^Context) {
|
||||
return
|
||||
}
|
||||
|
||||
shake.clone(transmute(^shake.Context)(ctx), transmute(^shake.Context)(other))
|
||||
shake.clone((^shake.Context)(ctx), (^shake.Context)(other))
|
||||
}
|
||||
|
||||
// reset sanitizes the Context. The Context must be re-initialized to
|
||||
@@ -94,7 +94,7 @@ reset :: proc(ctx: ^Context) {
|
||||
return
|
||||
}
|
||||
|
||||
shake.reset(transmute(^shake.Context)(ctx))
|
||||
shake.reset((^shake.Context)(ctx))
|
||||
}
|
||||
|
||||
@(private)
|
||||
@@ -107,7 +107,7 @@ _init_kmac :: proc(ctx: ^Context, key, s: []byte, sec_strength: int) {
|
||||
panic("crypto/kmac: invalid KMAC key, too short")
|
||||
}
|
||||
|
||||
ctx_ := transmute(^_sha3.Context)(ctx)
|
||||
ctx_ := (^_sha3.Context)(ctx)
|
||||
_sha3.init_cshake(ctx_, N_KMAC, s, sec_strength)
|
||||
_sha3.bytepad(ctx_, [][]byte{key}, _sha3.rate_cshake(sec_strength))
|
||||
}
|
||||
|
||||
@@ -66,12 +66,12 @@ init_512 :: proc(ctx: ^Context) {
|
||||
@(private)
|
||||
_init :: proc(ctx: ^Context) {
|
||||
ctx.dsbyte = _sha3.DS_KECCAK
|
||||
_sha3.init(transmute(^_sha3.Context)(ctx))
|
||||
_sha3.init((^_sha3.Context)(ctx))
|
||||
}
|
||||
|
||||
// update adds more data to the Context.
|
||||
update :: proc(ctx: ^Context, data: []byte) {
|
||||
_sha3.update(transmute(^_sha3.Context)(ctx), data)
|
||||
_sha3.update((^_sha3.Context)(ctx), data)
|
||||
}
|
||||
|
||||
// final finalizes the Context, writes the digest to hash, and calls
|
||||
@@ -80,16 +80,16 @@ update :: proc(ctx: ^Context, data: []byte) {
|
||||
// Iff finalize_clone is set, final will work on a copy of the Context,
|
||||
// which is useful for for calculating rolling digests.
|
||||
final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) {
|
||||
_sha3.final(transmute(^_sha3.Context)(ctx), hash, finalize_clone)
|
||||
_sha3.final((^_sha3.Context)(ctx), hash, finalize_clone)
|
||||
}
|
||||
|
||||
// clone clones the Context other into ctx.
|
||||
clone :: proc(ctx, other: ^Context) {
|
||||
_sha3.clone(transmute(^_sha3.Context)(ctx), transmute(^_sha3.Context)(other))
|
||||
_sha3.clone((^_sha3.Context)(ctx), (^_sha3.Context)(other))
|
||||
}
|
||||
|
||||
// reset sanitizes the Context. The Context must be re-initialized to
|
||||
// be used again.
|
||||
reset :: proc(ctx: ^Context) {
|
||||
_sha3.reset(transmute(^_sha3.Context)(ctx))
|
||||
_sha3.reset((^_sha3.Context)(ctx))
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ HAS_RAND_BYTES :: true
|
||||
_rand_bytes :: proc(dst: []byte) {
|
||||
err := Sec.RandomCopyBytes(count=len(dst), bytes=raw_data(dst))
|
||||
if err != .Success {
|
||||
msg := CF.StringCopyToOdinString(Sec.CopyErrorMessageString(err))
|
||||
fmt.panicf("crypto/rand_bytes: SecRandomCopyBytes returned non-zero result: %v %s", err, msg)
|
||||
msg := CF.StringCopyToOdinString(Sec.CopyErrorMessageString(err))
|
||||
fmt.panicf("crypto/rand_bytes: SecRandomCopyBytes returned non-zero result: %v %s", err, msg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ ge_set_bytes :: proc "contextless" (ge: ^Group_Element, b: []byte) -> bool {
|
||||
return false
|
||||
}
|
||||
|
||||
b_ := transmute(^[32]byte)(raw_data(b))
|
||||
b_ := (^[32]byte)(raw_data(b))
|
||||
|
||||
s: field.Tight_Field_Element = ---
|
||||
defer field.fe_clear(&s)
|
||||
@@ -297,7 +297,7 @@ ge_bytes :: proc(ge: ^Group_Element, dst: []byte) {
|
||||
// 2. Return the 32-byte little-endian encoding of s. More
|
||||
// specifically, this is the encoding of the canonical
|
||||
// representation of s as an integer between 0 and p-1, inclusive.
|
||||
dst_ := transmute(^[32]byte)(raw_data(dst))
|
||||
dst_ := (^[32]byte)(raw_data(dst))
|
||||
field.fe_to_bytes(dst_, &tmp)
|
||||
|
||||
field.fe_clear_vec([]^field.Tight_Field_Element{&u1, &u2, &tmp, &z_inv, &ix0, &iy0, &x, &y})
|
||||
@@ -417,7 +417,7 @@ ge_is_identity :: proc(ge: ^Group_Element) -> int {
|
||||
|
||||
@(private)
|
||||
ge_map :: proc "contextless" (ge: ^Group_Element, b: []byte) {
|
||||
b_ := transmute(^[32]byte)(raw_data(b))
|
||||
b_ := (^[32]byte)(raw_data(b))
|
||||
|
||||
// The MAP function is defined on 32-byte strings as:
|
||||
//
|
||||
|
||||
@@ -46,7 +46,7 @@ sc_set_bytes_wide :: proc(sc: ^Scalar, b: []byte) {
|
||||
panic("crypto/ristretto255: invalid wide input size")
|
||||
}
|
||||
|
||||
b_ := transmute(^[WIDE_SCALAR_SIZE]byte)(raw_data(b))
|
||||
b_ := (^[WIDE_SCALAR_SIZE]byte)(raw_data(b))
|
||||
grp.sc_set_bytes_wide(sc, b_)
|
||||
}
|
||||
|
||||
|
||||
@@ -68,12 +68,12 @@ init_512 :: proc(ctx: ^Context) {
|
||||
@(private)
|
||||
_init :: proc(ctx: ^Context) {
|
||||
ctx.dsbyte = _sha3.DS_SHA3
|
||||
_sha3.init(transmute(^_sha3.Context)(ctx))
|
||||
_sha3.init((^_sha3.Context)(ctx))
|
||||
}
|
||||
|
||||
// update adds more data to the Context.
|
||||
update :: proc(ctx: ^Context, data: []byte) {
|
||||
_sha3.update(transmute(^_sha3.Context)(ctx), data)
|
||||
_sha3.update((^_sha3.Context)(ctx), data)
|
||||
}
|
||||
|
||||
// final finalizes the Context, writes the digest to hash, and calls
|
||||
@@ -82,16 +82,16 @@ update :: proc(ctx: ^Context, data: []byte) {
|
||||
// Iff finalize_clone is set, final will work on a copy of the Context,
|
||||
// which is useful for for calculating rolling digests.
|
||||
final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) {
|
||||
_sha3.final(transmute(^_sha3.Context)(ctx), hash, finalize_clone)
|
||||
_sha3.final((^_sha3.Context)(ctx), hash, finalize_clone)
|
||||
}
|
||||
|
||||
// clone clones the Context other into ctx.
|
||||
clone :: proc(ctx, other: ^Context) {
|
||||
_sha3.clone(transmute(^_sha3.Context)(ctx), transmute(^_sha3.Context)(other))
|
||||
_sha3.clone((^_sha3.Context)(ctx), (^_sha3.Context)(other))
|
||||
}
|
||||
|
||||
// reset sanitizes the Context. The Context must be re-initialized to
|
||||
// be used again.
|
||||
reset :: proc(ctx: ^Context) {
|
||||
_sha3.reset(transmute(^_sha3.Context)(ctx))
|
||||
_sha3.reset((^_sha3.Context)(ctx))
|
||||
}
|
||||
|
||||
@@ -24,35 +24,35 @@ Context :: distinct _sha3.Context
|
||||
|
||||
// init_128 initializes a Context for SHAKE128.
|
||||
init_128 :: proc(ctx: ^Context) {
|
||||
_sha3.init_cshake(transmute(^_sha3.Context)(ctx), nil, nil, 128)
|
||||
_sha3.init_cshake((^_sha3.Context)(ctx), nil, nil, 128)
|
||||
}
|
||||
|
||||
// init_256 initializes a Context for SHAKE256.
|
||||
init_256 :: proc(ctx: ^Context) {
|
||||
_sha3.init_cshake(transmute(^_sha3.Context)(ctx), nil, nil, 256)
|
||||
_sha3.init_cshake((^_sha3.Context)(ctx), nil, nil, 256)
|
||||
}
|
||||
|
||||
// init_cshake_128 initializes a Context for cSHAKE128.
|
||||
init_cshake_128 :: proc(ctx: ^Context, domain_sep: []byte) {
|
||||
_sha3.init_cshake(transmute(^_sha3.Context)(ctx), nil, domain_sep, 128)
|
||||
_sha3.init_cshake((^_sha3.Context)(ctx), nil, domain_sep, 128)
|
||||
}
|
||||
|
||||
// init_cshake_256 initializes a Context for cSHAKE256.
|
||||
init_cshake_256 :: proc(ctx: ^Context, domain_sep: []byte) {
|
||||
_sha3.init_cshake(transmute(^_sha3.Context)(ctx), nil, domain_sep, 256)
|
||||
_sha3.init_cshake((^_sha3.Context)(ctx), nil, domain_sep, 256)
|
||||
}
|
||||
|
||||
// write writes more data into the SHAKE instance. This MUST not be called
|
||||
// after any reads have been done, and attempts to do so will panic.
|
||||
write :: proc(ctx: ^Context, data: []byte) {
|
||||
_sha3.update(transmute(^_sha3.Context)(ctx), data)
|
||||
_sha3.update((^_sha3.Context)(ctx), data)
|
||||
}
|
||||
|
||||
// read reads output from the SHAKE instance. There is no practical upper
|
||||
// limit to the amount of data that can be read from SHAKE. After read has
|
||||
// been called one or more times, further calls to write will panic.
|
||||
read :: proc(ctx: ^Context, dst: []byte) {
|
||||
ctx_ := transmute(^_sha3.Context)(ctx)
|
||||
ctx_ := (^_sha3.Context)(ctx)
|
||||
if !ctx.is_finalized {
|
||||
_sha3.shake_xof(ctx_)
|
||||
}
|
||||
@@ -62,11 +62,11 @@ read :: proc(ctx: ^Context, dst: []byte) {
|
||||
|
||||
// clone clones the Context other into ctx.
|
||||
clone :: proc(ctx, other: ^Context) {
|
||||
_sha3.clone(transmute(^_sha3.Context)(ctx), transmute(^_sha3.Context)(other))
|
||||
_sha3.clone((^_sha3.Context)(ctx), (^_sha3.Context)(other))
|
||||
}
|
||||
|
||||
// reset sanitizes the Context. The Context must be re-initialized to
|
||||
// be used again.
|
||||
reset :: proc(ctx: ^Context) {
|
||||
_sha3.reset(transmute(^_sha3.Context)(ctx))
|
||||
_sha3.reset((^_sha3.Context)(ctx))
|
||||
}
|
||||
|
||||
@@ -13,19 +13,19 @@ Context :: distinct _sha3.Context
|
||||
|
||||
// init_128 initializes a Context for TupleHash128 or TupleHashXOF128.
|
||||
init_128 :: proc(ctx: ^Context, domain_sep: []byte) {
|
||||
_sha3.init_cshake(transmute(^_sha3.Context)(ctx), N_TUPLEHASH, domain_sep, 128)
|
||||
_sha3.init_cshake((^_sha3.Context)(ctx), N_TUPLEHASH, domain_sep, 128)
|
||||
}
|
||||
|
||||
// init_256 initializes a Context for TupleHash256 or TupleHashXOF256.
|
||||
init_256 :: proc(ctx: ^Context, domain_sep: []byte) {
|
||||
_sha3.init_cshake(transmute(^_sha3.Context)(ctx), N_TUPLEHASH, domain_sep, 256)
|
||||
_sha3.init_cshake((^_sha3.Context)(ctx), N_TUPLEHASH, domain_sep, 256)
|
||||
}
|
||||
|
||||
// write_element writes a tuple element into the TupleHash or TupleHashXOF
|
||||
// instance. This MUST not be called after any reads have been done, and
|
||||
// any attempts to do so will panic.
|
||||
write_element :: proc(ctx: ^Context, data: []byte) {
|
||||
_, _ = _sha3.encode_string(transmute(^_sha3.Context)(ctx), data)
|
||||
_, _ = _sha3.encode_string((^_sha3.Context)(ctx), data)
|
||||
}
|
||||
|
||||
// final finalizes the Context, writes the digest to hash, and calls
|
||||
@@ -34,7 +34,7 @@ write_element :: proc(ctx: ^Context, data: []byte) {
|
||||
// Iff finalize_clone is set, final will work on a copy of the Context,
|
||||
// which is useful for for calculating rolling digests.
|
||||
final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) {
|
||||
_sha3.final_cshake(transmute(^_sha3.Context)(ctx), hash, finalize_clone)
|
||||
_sha3.final_cshake((^_sha3.Context)(ctx), hash, finalize_clone)
|
||||
}
|
||||
|
||||
// read reads output from the TupleHashXOF instance. There is no practical
|
||||
@@ -42,7 +42,7 @@ final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) {
|
||||
// After read has been called one or more times, further calls to
|
||||
// write_element will panic.
|
||||
read :: proc(ctx: ^Context, dst: []byte) {
|
||||
ctx_ := transmute(^_sha3.Context)(ctx)
|
||||
ctx_ := (^_sha3.Context)(ctx)
|
||||
if !ctx.is_finalized {
|
||||
_sha3.encode_byte_len(ctx_, 0, false) // right_encode
|
||||
_sha3.shake_xof(ctx_)
|
||||
@@ -53,13 +53,13 @@ read :: proc(ctx: ^Context, dst: []byte) {
|
||||
|
||||
// clone clones the Context other into ctx.
|
||||
clone :: proc(ctx, other: ^Context) {
|
||||
_sha3.clone(transmute(^_sha3.Context)(ctx), transmute(^_sha3.Context)(other))
|
||||
_sha3.clone((^_sha3.Context)(ctx), (^_sha3.Context)(other))
|
||||
}
|
||||
|
||||
// reset sanitizes the Context. The Context must be re-initialized to
|
||||
// be used again.
|
||||
reset :: proc(ctx: ^Context) {
|
||||
_sha3.reset(transmute(^_sha3.Context)(ctx))
|
||||
_sha3.reset((^_sha3.Context)(ctx))
|
||||
}
|
||||
|
||||
@(private)
|
||||
|
||||
@@ -78,7 +78,7 @@ _Context :: struct {
|
||||
|
||||
@(private="package")
|
||||
_init :: proc(ctx: ^Context) -> (ok: bool) {
|
||||
defer if !ok do destroy(ctx)
|
||||
defer if !ok { destroy(ctx) }
|
||||
|
||||
ctx.impl.state = backtrace_create_state("odin-debug-trace", 1, nil, ctx)
|
||||
return ctx.impl.state != nil
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
//+build !windows !linux !darwin
|
||||
package debug_trace
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
_Context :: struct {
|
||||
}
|
||||
|
||||
@@ -10,9 +12,9 @@ _init :: proc(ctx: ^Context) -> (ok: bool) {
|
||||
_destroy :: proc(ctx: ^Context) -> bool {
|
||||
return true
|
||||
}
|
||||
_frames :: proc(ctx: ^Context, skip: uint, allocator: runtime.Allocator) -> []Frame {
|
||||
_frames :: proc(ctx: ^Context, skip: uint, frames_buffer: []Frame) -> []Frame {
|
||||
return nil
|
||||
}
|
||||
_resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> (result: runtime.Source_Code_Location) {
|
||||
_resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> (result: Frame_Location) {
|
||||
return
|
||||
}
|
||||
|
||||
+5
-13
@@ -16,15 +16,12 @@ Library :: distinct rawptr
|
||||
Loads a dynamic library from the filesystem. The paramater `global_symbols` makes the symbols in the loaded
|
||||
library available to resolve references in subsequently loaded libraries.
|
||||
|
||||
The paramater `global_symbols` is only used for the platforms `linux`, `darwin`, `freebsd` and `openbsd`.
|
||||
The parameter `global_symbols` is only used for the platforms `linux`, `darwin`, `freebsd` and `openbsd`.
|
||||
On `windows` this paramater is ignored.
|
||||
|
||||
The underlying behaviour is platform specific.
|
||||
On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlopen`.
|
||||
On `windows` refer to `LoadLibraryW`.
|
||||
|
||||
**Implicit Allocators**
|
||||
`context.temp_allocator`
|
||||
On `windows` refer to `LoadLibraryW`. Also temporarily needs an allocator to convert a string.
|
||||
|
||||
Example:
|
||||
import "core:dynlib"
|
||||
@@ -79,10 +76,7 @@ Loads the address of a procedure/variable from a dynamic library.
|
||||
|
||||
The underlying behaviour is platform specific.
|
||||
On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlsym`.
|
||||
On `windows` refer to `GetProcAddress`.
|
||||
|
||||
**Implicit Allocators**
|
||||
`context.temp_allocator`
|
||||
On `windows` refer to `GetProcAddress`. Also temporarily needs an allocator to convert a string.
|
||||
|
||||
Example:
|
||||
import "core:dynlib"
|
||||
@@ -177,9 +171,7 @@ initialize_symbols :: proc(
|
||||
return count, count > 0
|
||||
}
|
||||
|
||||
/*
|
||||
Returns an error message for the last failed procedure call.
|
||||
*/
|
||||
// Returns an error message for the last failed procedure call.
|
||||
last_error :: proc() -> string {
|
||||
return _last_error()
|
||||
}
|
||||
}
|
||||
@@ -16,4 +16,4 @@ _symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found
|
||||
|
||||
_last_error :: proc() -> string {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
@@ -26,4 +26,4 @@ _symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found
|
||||
_last_error :: proc() -> string {
|
||||
err := os.dlerror()
|
||||
return "unknown" if err == "" else err
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,12 @@ package dynlib
|
||||
|
||||
import win32 "core:sys/windows"
|
||||
import "core:strings"
|
||||
import "base:runtime"
|
||||
import "core:reflect"
|
||||
|
||||
_load_library :: proc(path: string, global_symbols := false) -> (Library, bool) {
|
||||
_load_library :: proc(path: string, global_symbols := false, allocator := context.temp_allocator) -> (Library, bool) {
|
||||
// NOTE(bill): 'global_symbols' is here only for consistency with POSIX which has RTLD_GLOBAL
|
||||
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
wide_path := win32.utf8_to_wstring(path, context.temp_allocator)
|
||||
wide_path := win32.utf8_to_wstring(path, allocator)
|
||||
defer free(wide_path, allocator)
|
||||
handle := cast(Library)win32.LoadLibraryW(wide_path)
|
||||
return handle, handle != nil
|
||||
}
|
||||
@@ -21,9 +19,9 @@ _unload_library :: proc(library: Library) -> bool {
|
||||
return bool(ok)
|
||||
}
|
||||
|
||||
_symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
c_str := strings.clone_to_cstring(symbol, context.temp_allocator)
|
||||
_symbol_address :: proc(library: Library, symbol: string, allocator := context.temp_allocator) -> (ptr: rawptr, found: bool) {
|
||||
c_str := strings.clone_to_cstring(symbol, allocator)
|
||||
defer delete(c_str, allocator)
|
||||
ptr = win32.GetProcAddress(cast(win32.HMODULE)library, c_str)
|
||||
found = ptr != nil
|
||||
return
|
||||
@@ -33,4 +31,4 @@ _last_error :: proc() -> string {
|
||||
err := win32.System_Error(win32.GetLastError())
|
||||
err_msg := reflect.enum_string(err)
|
||||
return "unknown" if err_msg == "" else err_msg
|
||||
}
|
||||
}
|
||||
+116
-116
@@ -8,141 +8,141 @@ package encoding_base32
|
||||
// truncate it from the encoded output.
|
||||
|
||||
ENC_TABLE := [32]byte {
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
||||
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
||||
'Y', 'Z', '2', '3', '4', '5', '6', '7',
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
||||
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
||||
'Y', 'Z', '2', '3', '4', '5', '6', '7',
|
||||
}
|
||||
|
||||
PADDING :: '='
|
||||
|
||||
DEC_TABLE := [?]u8 {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 26, 27, 28, 29, 30, 31, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0,
|
||||
0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 26, 27, 28, 29, 30, 31, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0,
|
||||
0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
}
|
||||
|
||||
encode :: proc(data: []byte, ENC_TBL := ENC_TABLE, allocator := context.allocator) -> string {
|
||||
out_length := (len(data) + 4) / 5 * 8
|
||||
out := make([]byte, out_length)
|
||||
_encode(out, data)
|
||||
return string(out)
|
||||
out_length := (len(data) + 4) / 5 * 8
|
||||
out := make([]byte, out_length)
|
||||
_encode(out, data)
|
||||
return string(out)
|
||||
}
|
||||
|
||||
@private
|
||||
_encode :: proc(out, data: []byte, ENC_TBL := ENC_TABLE, allocator := context.allocator) {
|
||||
out := out
|
||||
data := data
|
||||
out := out
|
||||
data := data
|
||||
|
||||
for len(data) > 0 {
|
||||
carry: byte
|
||||
switch len(data) {
|
||||
case:
|
||||
out[7] = ENC_TABLE[data[4] & 0x1f]
|
||||
carry = data[4] >> 5
|
||||
fallthrough
|
||||
case 4:
|
||||
out[6] = ENC_TABLE[carry | (data[3] << 3) & 0x1f]
|
||||
out[5] = ENC_TABLE[(data[3] >> 2) & 0x1f]
|
||||
carry = data[3] >> 7
|
||||
fallthrough
|
||||
case 3:
|
||||
out[4] = ENC_TABLE[carry | (data[2] << 1) & 0x1f]
|
||||
carry = (data[2] >> 4) & 0x1f
|
||||
fallthrough
|
||||
case 2:
|
||||
out[3] = ENC_TABLE[carry | (data[1] << 4) & 0x1f]
|
||||
out[2] = ENC_TABLE[(data[1] >> 1) & 0x1f]
|
||||
carry = (data[1] >> 6) & 0x1f
|
||||
fallthrough
|
||||
case 1:
|
||||
out[1] = ENC_TABLE[carry | (data[0] << 2) & 0x1f]
|
||||
out[0] = ENC_TABLE[data[0] >> 3]
|
||||
}
|
||||
for len(data) > 0 {
|
||||
carry: byte
|
||||
switch len(data) {
|
||||
case:
|
||||
out[7] = ENC_TABLE[data[4] & 0x1f]
|
||||
carry = data[4] >> 5
|
||||
fallthrough
|
||||
case 4:
|
||||
out[6] = ENC_TABLE[carry | (data[3] << 3) & 0x1f]
|
||||
out[5] = ENC_TABLE[(data[3] >> 2) & 0x1f]
|
||||
carry = data[3] >> 7
|
||||
fallthrough
|
||||
case 3:
|
||||
out[4] = ENC_TABLE[carry | (data[2] << 1) & 0x1f]
|
||||
carry = (data[2] >> 4) & 0x1f
|
||||
fallthrough
|
||||
case 2:
|
||||
out[3] = ENC_TABLE[carry | (data[1] << 4) & 0x1f]
|
||||
out[2] = ENC_TABLE[(data[1] >> 1) & 0x1f]
|
||||
carry = (data[1] >> 6) & 0x1f
|
||||
fallthrough
|
||||
case 1:
|
||||
out[1] = ENC_TABLE[carry | (data[0] << 2) & 0x1f]
|
||||
out[0] = ENC_TABLE[data[0] >> 3]
|
||||
}
|
||||
|
||||
if len(data) < 5 {
|
||||
out[7] = byte(PADDING)
|
||||
if len(data) < 4 {
|
||||
out[6] = byte(PADDING)
|
||||
out[5] = byte(PADDING)
|
||||
if len(data) < 3 {
|
||||
out[4] = byte(PADDING)
|
||||
if len(data) < 2 {
|
||||
out[3] = byte(PADDING)
|
||||
out[2] = byte(PADDING)
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
data = data[5:]
|
||||
out = out[8:]
|
||||
}
|
||||
if len(data) < 5 {
|
||||
out[7] = byte(PADDING)
|
||||
if len(data) < 4 {
|
||||
out[6] = byte(PADDING)
|
||||
out[5] = byte(PADDING)
|
||||
if len(data) < 3 {
|
||||
out[4] = byte(PADDING)
|
||||
if len(data) < 2 {
|
||||
out[3] = byte(PADDING)
|
||||
out[2] = byte(PADDING)
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
data = data[5:]
|
||||
out = out[8:]
|
||||
}
|
||||
}
|
||||
|
||||
decode :: proc(data: string, DEC_TBL := DEC_TABLE, allocator := context.allocator) -> []byte #no_bounds_check{
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
outi := 0
|
||||
data := data
|
||||
outi := 0
|
||||
data := data
|
||||
|
||||
out := make([]byte, len(data) / 8 * 5, allocator)
|
||||
end := false
|
||||
for len(data) > 0 && !end {
|
||||
dbuf : [8]byte
|
||||
dlen := 8
|
||||
out := make([]byte, len(data) / 8 * 5, allocator)
|
||||
end := false
|
||||
for len(data) > 0 && !end {
|
||||
dbuf : [8]byte
|
||||
dlen := 8
|
||||
|
||||
for j := 0; j < 8; {
|
||||
if len(data) == 0 {
|
||||
dlen, end = j, true
|
||||
break
|
||||
}
|
||||
input := data[0]
|
||||
data = data[1:]
|
||||
if input == byte(PADDING) && j >= 2 && len(data) < 8 {
|
||||
assert(!(len(data) + j < 8 - 1), "Corrupted input")
|
||||
for k := 0; k < 8-1-j; k +=1 {
|
||||
assert(len(data) < k || data[k] == byte(PADDING), "Corrupted input")
|
||||
}
|
||||
dlen, end = j, true
|
||||
assert(dlen != 1 && dlen != 3 && dlen != 6, "Corrupted input")
|
||||
break
|
||||
}
|
||||
dbuf[j] = DEC_TABLE[input]
|
||||
assert(dbuf[j] != 0xff, "Corrupted input")
|
||||
j += 1
|
||||
}
|
||||
for j := 0; j < 8; {
|
||||
if len(data) == 0 {
|
||||
dlen, end = j, true
|
||||
break
|
||||
}
|
||||
input := data[0]
|
||||
data = data[1:]
|
||||
if input == byte(PADDING) && j >= 2 && len(data) < 8 {
|
||||
assert(!(len(data) + j < 8 - 1), "Corrupted input")
|
||||
for k := 0; k < 8-1-j; k +=1 {
|
||||
assert(len(data) < k || data[k] == byte(PADDING), "Corrupted input")
|
||||
}
|
||||
dlen, end = j, true
|
||||
assert(dlen != 1 && dlen != 3 && dlen != 6, "Corrupted input")
|
||||
break
|
||||
}
|
||||
dbuf[j] = DEC_TABLE[input]
|
||||
assert(dbuf[j] != 0xff, "Corrupted input")
|
||||
j += 1
|
||||
}
|
||||
|
||||
switch dlen {
|
||||
case 8:
|
||||
out[outi + 4] = dbuf[6] << 5 | dbuf[7]
|
||||
fallthrough
|
||||
case 7:
|
||||
out[outi + 3] = dbuf[4] << 7 | dbuf[5] << 2 | dbuf[6] >> 3
|
||||
fallthrough
|
||||
case 5:
|
||||
out[outi + 2] = dbuf[3] << 4 | dbuf[4] >> 1
|
||||
fallthrough
|
||||
case 4:
|
||||
out[outi + 1] = dbuf[1] << 6 | dbuf[2] << 1 | dbuf[3] >> 4
|
||||
fallthrough
|
||||
case 2:
|
||||
out[outi + 0] = dbuf[0] << 3 | dbuf[1] >> 2
|
||||
}
|
||||
outi += 5
|
||||
}
|
||||
return out
|
||||
switch dlen {
|
||||
case 8:
|
||||
out[outi + 4] = dbuf[6] << 5 | dbuf[7]
|
||||
fallthrough
|
||||
case 7:
|
||||
out[outi + 3] = dbuf[4] << 7 | dbuf[5] << 2 | dbuf[6] >> 3
|
||||
fallthrough
|
||||
case 5:
|
||||
out[outi + 2] = dbuf[3] << 4 | dbuf[4] >> 1
|
||||
fallthrough
|
||||
case 4:
|
||||
out[outi + 1] = dbuf[1] << 6 | dbuf[2] << 1 | dbuf[3] >> 4
|
||||
fallthrough
|
||||
case 2:
|
||||
out[outi + 0] = dbuf[0] << 3 | dbuf[1] >> 2
|
||||
}
|
||||
outi += 5
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@ encode_into_encoder :: proc(e: Encoder, v: Value, loc := #caller_location) -> En
|
||||
|
||||
if .Self_Described_CBOR in e.flags {
|
||||
_encode_u64(e, TAG_SELF_DESCRIBED_CBOR, .Tag) or_return
|
||||
e.flags &~= { .Self_Described_CBOR }
|
||||
e.flags -= { .Self_Described_CBOR }
|
||||
}
|
||||
|
||||
switch v_spec in v {
|
||||
@@ -423,7 +423,7 @@ _decode_bytes :: proc(d: Decoder, add: Add, type: Major = .Bytes, allocator := c
|
||||
_encode_bytes :: proc(e: Encoder, val: Bytes, major: Major = .Bytes) -> (err: Encode_Error) {
|
||||
assert(len(val) >= 0)
|
||||
_encode_u64(e, u64(len(val)), major) or_return
|
||||
_, err = io.write_full(e.writer, val[:])
|
||||
_, err = io.write_full(e.writer, val[:])
|
||||
return
|
||||
}
|
||||
|
||||
@@ -440,7 +440,7 @@ _decode_text :: proc(d: Decoder, add: Add, allocator := context.allocator, loc :
|
||||
}
|
||||
|
||||
_encode_text :: proc(e: Encoder, val: Text) -> Encode_Error {
|
||||
return _encode_bytes(e, transmute([]byte)val, .Text)
|
||||
return _encode_bytes(e, transmute([]byte)val, .Text)
|
||||
}
|
||||
|
||||
_decode_array_ptr :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: ^Array, err: Decode_Error) {
|
||||
@@ -480,10 +480,10 @@ _decode_array :: proc(d: Decoder, add: Add, allocator := context.allocator, loc
|
||||
_encode_array :: proc(e: Encoder, arr: Array) -> Encode_Error {
|
||||
assert(len(arr) >= 0)
|
||||
_encode_u64(e, u64(len(arr)), .Array)
|
||||
for val in arr {
|
||||
encode(e, val) or_return
|
||||
}
|
||||
return nil
|
||||
for val in arr {
|
||||
encode(e, val) or_return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
_decode_map_ptr :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: ^Map, err: Decode_Error) {
|
||||
@@ -576,7 +576,7 @@ _encode_map :: proc(e: Encoder, m: Map) -> (err: Encode_Error) {
|
||||
encode(e, entry.entry.value) or_return
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil
|
||||
}
|
||||
|
||||
_decode_tag_ptr :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) {
|
||||
@@ -626,7 +626,7 @@ _decode_uint_as_u64 :: proc(r: io.Reader, add: Add) -> (nr: u64, err: Decode_Err
|
||||
|
||||
_encode_tag :: proc(e: Encoder, val: Tag) -> Encode_Error {
|
||||
_encode_u64(e, val.number, .Tag) or_return
|
||||
return encode(e, val.value)
|
||||
return encode(e, val.value)
|
||||
}
|
||||
|
||||
_decode_simple :: proc(r: io.Reader) -> (v: Simple, err: io.Error) {
|
||||
@@ -739,16 +739,16 @@ _encode_nil :: proc(w: io.Writer) -> io.Error {
|
||||
// Streaming
|
||||
|
||||
encode_stream_begin :: proc(w: io.Writer, major: Major) -> (err: io.Error) {
|
||||
assert(major >= Major(.Bytes) && major <= Major(.Map), "illegal stream type")
|
||||
assert(major >= Major(.Bytes) && major <= Major(.Map), "illegal stream type")
|
||||
|
||||
header := (u8(major) << 5) | u8(Add.Length_Unknown)
|
||||
_, err = io.write_full(w, {header})
|
||||
header := (u8(major) << 5) | u8(Add.Length_Unknown)
|
||||
_, err = io.write_full(w, {header})
|
||||
return
|
||||
}
|
||||
|
||||
encode_stream_end :: proc(w: io.Writer) -> io.Error {
|
||||
header := (u8(Major.Other) << 5) | u8(Add.Break)
|
||||
_, err := io.write_full(w, {header})
|
||||
header := (u8(Major.Other) << 5) | u8(Add.Break)
|
||||
_, err := io.write_full(w, {header})
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -757,8 +757,8 @@ encode_stream_text :: _encode_text
|
||||
encode_stream_array_item :: encode
|
||||
|
||||
encode_stream_map_entry :: proc(e: Encoder, key: Value, val: Value) -> Encode_Error {
|
||||
encode(e, key) or_return
|
||||
return encode(e, val)
|
||||
encode(e, key) or_return
|
||||
return encode(e, val)
|
||||
}
|
||||
|
||||
// For `Bytes` and `Text` strings: Decodes the number of items the header says follows.
|
||||
|
||||
@@ -77,8 +77,11 @@ You can look at the default tags provided for pointers on how these implementati
|
||||
Example:
|
||||
package main
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
import "core:encoding/cbor"
|
||||
import "core:fmt"
|
||||
import "core:reflect"
|
||||
import "core:time"
|
||||
|
||||
Possibilities :: union {
|
||||
@@ -93,9 +96,32 @@ Example:
|
||||
ignore_this: ^Data `cbor:"-"`, // Ignored by implementation.
|
||||
renamed: f32 `cbor:"renamed :)"`, // Renamed when encoded.
|
||||
my_union: Possibilities, // Union support.
|
||||
|
||||
my_raw: [8]u32 `cbor_tag:"raw"`, // Custom tag that just writes the value as bytes.
|
||||
}
|
||||
|
||||
main :: proc() {
|
||||
// Example custom tag implementation that instead of breaking down all parts,
|
||||
// just writes the value as a big byte blob. This is an advanced feature but very powerful.
|
||||
RAW_TAG_NR :: 200
|
||||
cbor.tag_register_number({
|
||||
marshal = proc(_: ^cbor.Tag_Implementation, e: cbor.Encoder, v: any) -> cbor.Marshal_Error {
|
||||
cbor._encode_u8(e.writer, RAW_TAG_NR, .Tag) or_return
|
||||
return cbor.err_conv(cbor._encode_bytes(e, reflect.as_bytes(v)))
|
||||
},
|
||||
unmarshal = proc(_: ^cbor.Tag_Implementation, d: cbor.Decoder, _: cbor.Tag_Number, v: any) -> (cbor.Unmarshal_Error) {
|
||||
hdr := cbor._decode_header(d.reader) or_return
|
||||
maj, add := cbor._header_split(hdr)
|
||||
if maj != .Bytes {
|
||||
return .Bad_Tag_Value
|
||||
}
|
||||
|
||||
bytes := cbor.err_conv(cbor._decode_bytes(d, add, maj)) or_return
|
||||
intrinsics.mem_copy_non_overlapping(v.data, raw_data(bytes), len(bytes))
|
||||
return nil
|
||||
},
|
||||
}, RAW_TAG_NR, "raw")
|
||||
|
||||
now := time.Time{_nsec = 1701117968 * 1e9}
|
||||
|
||||
data := Data{
|
||||
@@ -105,21 +131,22 @@ Example:
|
||||
ignore_this = &Data{},
|
||||
renamed = 123123.125,
|
||||
my_union = 3,
|
||||
my_raw = {1=1, 2=2, 3=3},
|
||||
}
|
||||
|
||||
|
||||
// Marshal the struct into binary CBOR.
|
||||
binary, err := cbor.marshal(data, cbor.ENCODE_FULLY_DETERMINISTIC)
|
||||
assert(err == nil)
|
||||
fmt.assertf(err == nil, "marshal error: %v", err)
|
||||
defer delete(binary)
|
||||
|
||||
|
||||
// Decode the binary data into a `cbor.Value`.
|
||||
decoded, derr := cbor.decode(string(binary))
|
||||
assert(derr == nil)
|
||||
fmt.assertf(derr == nil, "decode error: %v", derr)
|
||||
defer cbor.destroy(decoded)
|
||||
|
||||
// Turn the CBOR into a human readable representation defined as the diagnostic format in [[RFC 8949 Section 8;https://www.rfc-editor.org/rfc/rfc8949.html#name-diagnostic-notation]].
|
||||
diagnosis, eerr := cbor.to_diagnostic_format(decoded)
|
||||
assert(eerr == nil)
|
||||
fmt.assertf(eerr == nil, "to diagnostic error: %v", eerr)
|
||||
defer delete(diagnosis)
|
||||
|
||||
fmt.println(diagnosis)
|
||||
@@ -127,6 +154,7 @@ Example:
|
||||
|
||||
Output:
|
||||
{
|
||||
"my_raw": 200(h'00001000200030000000000000000000'),
|
||||
"my_union": 1010([
|
||||
"int",
|
||||
3
|
||||
|
||||
@@ -54,7 +54,7 @@ marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.a
|
||||
|
||||
defer if err != nil { strings.builder_destroy(&b) }
|
||||
|
||||
if err = marshal_into_builder(&b, v, flags, temp_allocator, loc=loc); err != nil {
|
||||
if err = marshal_into_builder(&b, v, flags, temp_allocator); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -63,20 +63,20 @@ marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.a
|
||||
|
||||
// Marshals the given value into a CBOR byte stream written to the given builder.
|
||||
// See docs on the `marshal_into` proc group for more info.
|
||||
marshal_into_builder :: proc(b: ^strings.Builder, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator, loc := #caller_location) -> Marshal_Error {
|
||||
return marshal_into_writer(strings.to_writer(b), v, flags, temp_allocator, loc=loc)
|
||||
marshal_into_builder :: proc(b: ^strings.Builder, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Marshal_Error {
|
||||
return marshal_into_writer(strings.to_writer(b), v, flags, temp_allocator)
|
||||
}
|
||||
|
||||
// Marshals the given value into a CBOR byte stream written to the given writer.
|
||||
// See docs on the `marshal_into` proc group for more info.
|
||||
marshal_into_writer :: proc(w: io.Writer, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator, loc := #caller_location) -> Marshal_Error {
|
||||
marshal_into_writer :: proc(w: io.Writer, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Marshal_Error {
|
||||
encoder := Encoder{flags, w, temp_allocator}
|
||||
return marshal_into_encoder(encoder, v, loc=loc)
|
||||
return marshal_into_encoder(encoder, v)
|
||||
}
|
||||
|
||||
// Marshals the given value into a CBOR byte stream written to the given encoder.
|
||||
// See docs on the `marshal_into` proc group for more info.
|
||||
marshal_into_encoder :: proc(e: Encoder, v: any, loc := #caller_location) -> (err: Marshal_Error) {
|
||||
marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) {
|
||||
e := e
|
||||
|
||||
if e.temp_allocator.procedure == nil {
|
||||
@@ -85,7 +85,7 @@ marshal_into_encoder :: proc(e: Encoder, v: any, loc := #caller_location) -> (e
|
||||
|
||||
if .Self_Described_CBOR in e.flags {
|
||||
err_conv(_encode_u64(e, TAG_SELF_DESCRIBED_CBOR, .Tag)) or_return
|
||||
e.flags &~= { .Self_Described_CBOR }
|
||||
e.flags -= { .Self_Described_CBOR }
|
||||
}
|
||||
|
||||
if v == nil {
|
||||
@@ -97,11 +97,14 @@ marshal_into_encoder :: proc(e: Encoder, v: any, loc := #caller_location) -> (e
|
||||
return impl->marshal(e, v)
|
||||
}
|
||||
|
||||
ti := runtime.type_info_base(type_info_of(v.id))
|
||||
a := any{v.data, ti.id}
|
||||
ti := runtime.type_info_core(type_info_of(v.id))
|
||||
return _marshal_into_encoder(e, v, ti)
|
||||
}
|
||||
|
||||
_marshal_into_encoder :: proc(e: Encoder, v: any, ti: ^runtime.Type_Info) -> (err: Marshal_Error) {
|
||||
a := any{v.data, ti.id}
|
||||
#partial switch info in ti.variant {
|
||||
case runtime.Type_Info_Named:
|
||||
case runtime.Type_Info_Named, runtime.Type_Info_Enum, runtime.Type_Info_Bit_Field:
|
||||
unreachable()
|
||||
|
||||
case runtime.Type_Info_Pointer:
|
||||
@@ -223,18 +226,38 @@ marshal_into_encoder :: proc(e: Encoder, v: any, loc := #caller_location) -> (e
|
||||
}
|
||||
|
||||
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(e, any{rawptr(data), info.elem.id}) or_return
|
||||
_marshal_into_encoder(e, any{rawptr(data), info.elem.id}, elem_ti) or_return
|
||||
}
|
||||
return
|
||||
|
||||
case runtime.Type_Info_Enumerated_Array:
|
||||
// index := runtime.type_info_base(info.index).variant.(runtime.Type_Info_Enum)
|
||||
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(e, any{rawptr(data), info.elem.id}) or_return
|
||||
_marshal_into_encoder(e, any{rawptr(data), info.elem.id}, elem_ti) or_return
|
||||
}
|
||||
return
|
||||
|
||||
@@ -246,9 +269,19 @@ marshal_into_encoder :: proc(e: Encoder, v: any, loc := #caller_location) -> (e
|
||||
|
||||
array := (^mem.Raw_Dynamic_Array)(v.data)
|
||||
err_conv(_encode_u64(e, u64(array.len), .Array)) or_return
|
||||
|
||||
if impl, ok := _tag_implementations_type[info.elem.id]; ok {
|
||||
for i in 0..<array.len {
|
||||
data := uintptr(array.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..<array.len {
|
||||
data := uintptr(array.data) + uintptr(i*info.elem_size)
|
||||
marshal_into(e, any{rawptr(data), info.elem.id}) or_return
|
||||
_marshal_into_encoder(e, any{rawptr(data), info.elem.id}, elem_ti) or_return
|
||||
}
|
||||
return
|
||||
|
||||
@@ -260,9 +293,19 @@ marshal_into_encoder :: proc(e: Encoder, v: any, loc := #caller_location) -> (e
|
||||
|
||||
array := (^mem.Raw_Slice)(v.data)
|
||||
err_conv(_encode_u64(e, u64(array.len), .Array)) or_return
|
||||
|
||||
if impl, ok := _tag_implementations_type[info.elem.id]; ok {
|
||||
for i in 0..<array.len {
|
||||
data := uintptr(array.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..<array.len {
|
||||
data := uintptr(array.data) + uintptr(i*info.elem_size)
|
||||
marshal_into(e, any{rawptr(data), info.elem.id}) or_return
|
||||
_marshal_into_encoder(e, any{rawptr(data), info.elem.id}, elem_ti) or_return
|
||||
}
|
||||
return
|
||||
|
||||
@@ -542,9 +585,6 @@ marshal_into_encoder :: proc(e: Encoder, v: any, loc := #caller_location) -> (e
|
||||
|
||||
return marshal_into(e, any{v.data, vti.id})
|
||||
|
||||
case runtime.Type_Info_Enum:
|
||||
return marshal_into(e, any{v.data, info.base.id})
|
||||
|
||||
case runtime.Type_Info_Bit_Set:
|
||||
// Store bit_set as big endian just like the protocol.
|
||||
do_byte_swap := !reflect.bit_set_is_big_endian(v)
|
||||
|
||||
@@ -95,7 +95,6 @@ tag_register_number :: proc(impl: Tag_Implementation, nr: Tag_Number, id: string
|
||||
}
|
||||
|
||||
// Controls initialization of default tag implementations.
|
||||
// JS and WASI default to a panic allocator so we don't want to do it on those.
|
||||
INITIALIZE_DEFAULT_TAGS :: #config(CBOR_INITIALIZE_DEFAULT_TAGS, !ODIN_DEFAULT_TO_PANIC_ALLOCATOR && !ODIN_DEFAULT_TO_NIL_ALLOCATOR)
|
||||
|
||||
@(private, init, disabled=!INITIALIZE_DEFAULT_TAGS)
|
||||
|
||||
@@ -273,13 +273,13 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header, allocator := context.a
|
||||
|
||||
// NOTE: Because this is a special type and not to be treated as a general integer,
|
||||
// We only put the value of it in fields that are explicitly of type `Simple`.
|
||||
switch &dst in v {
|
||||
case Simple:
|
||||
dst = decoded
|
||||
return
|
||||
case:
|
||||
return _unsupported(v, hdr, add)
|
||||
}
|
||||
switch &dst in v {
|
||||
case Simple:
|
||||
dst = decoded
|
||||
return
|
||||
case:
|
||||
return _unsupported(v, hdr, add)
|
||||
}
|
||||
|
||||
case .Tag:
|
||||
switch &dst in v {
|
||||
@@ -520,9 +520,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
||||
return
|
||||
|
||||
case reflect.Type_Info_Array:
|
||||
_, scap := err_conv(_decode_len_container(d, add)) or_return
|
||||
length := min(scap, t.count)
|
||||
|
||||
length, _ := err_conv(_decode_len_container(d, add)) or_return
|
||||
if length > t.count {
|
||||
return _unsupported(v, hdr)
|
||||
}
|
||||
@@ -534,9 +532,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
||||
return
|
||||
|
||||
case reflect.Type_Info_Enumerated_Array:
|
||||
_, scap := err_conv(_decode_len_container(d, add)) or_return
|
||||
length := min(scap, t.count)
|
||||
|
||||
length, _ := err_conv(_decode_len_container(d, add)) or_return
|
||||
if length > t.count {
|
||||
return _unsupported(v, hdr)
|
||||
}
|
||||
@@ -548,9 +544,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
||||
return
|
||||
|
||||
case reflect.Type_Info_Complex:
|
||||
_, scap := err_conv(_decode_len_container(d, add)) or_return
|
||||
length := min(scap, 2)
|
||||
|
||||
length, _ := err_conv(_decode_len_container(d, add)) or_return
|
||||
if length > 2 {
|
||||
return _unsupported(v, hdr)
|
||||
}
|
||||
@@ -570,9 +564,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
|
||||
return
|
||||
|
||||
case reflect.Type_Info_Quaternion:
|
||||
_, scap := err_conv(_decode_len_container(d, add)) or_return
|
||||
length := min(scap, 4)
|
||||
|
||||
length, _ := err_conv(_decode_len_container(d, add)) or_return
|
||||
if length > 4 {
|
||||
return _unsupported(v, hdr)
|
||||
}
|
||||
@@ -633,7 +625,7 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header,
|
||||
length, _ := err_conv(_decode_len_container(d, add)) or_return
|
||||
unknown := length == -1
|
||||
fields := reflect.struct_fields_zipped(ti.id)
|
||||
|
||||
|
||||
for idx := 0; idx < len(fields) && (unknown || idx < length); idx += 1 {
|
||||
// Decode key, keys can only be strings.
|
||||
key: string
|
||||
@@ -646,7 +638,7 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header,
|
||||
key = keyv
|
||||
}
|
||||
defer delete(key, context.temp_allocator)
|
||||
|
||||
|
||||
// Find matching field.
|
||||
use_field_idx := -1
|
||||
{
|
||||
|
||||
@@ -138,7 +138,9 @@ iterator_next :: proc(r: ^Reader) -> (record: []string, idx: int, err: Error, mo
|
||||
return record, r.line_count - 1, r.last_iterator_error, r.last_iterator_error == nil
|
||||
}
|
||||
|
||||
// Get last error if we the iterator
|
||||
// Get last CSV parse error if we ignored it in the iterator loop
|
||||
//
|
||||
// for record, row_idx in csv.iterator_next(&r) { ... }
|
||||
iterator_last_error :: proc(r: Reader) -> (err: Error) {
|
||||
return r.last_iterator_error
|
||||
}
|
||||
@@ -169,7 +171,7 @@ is_io_error :: proc(err: Error, io_err: io.Error) -> bool {
|
||||
|
||||
// read_all reads all the remaining records from r.
|
||||
// Each record is a slice of fields.
|
||||
// read_all is defined to read until an EOF, and does not treat, and does not treat EOF as an error
|
||||
// read_all is defined to read until an EOF, and does not treat EOF as an error
|
||||
@(require_results)
|
||||
read_all :: proc(r: ^Reader, allocator := context.allocator) -> ([][]string, Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
+4816
-4816
File diff suppressed because it is too large
Load Diff
@@ -25,7 +25,7 @@ Marshal_Options :: struct {
|
||||
spec: Specification,
|
||||
|
||||
// Use line breaks & tabs/spaces
|
||||
pretty: bool,
|
||||
pretty: bool,
|
||||
|
||||
// Use spaces for indentation instead of tabs
|
||||
use_spaces: bool,
|
||||
@@ -34,7 +34,7 @@ Marshal_Options :: struct {
|
||||
spaces: int,
|
||||
|
||||
// Output uint as hex in JSON5 & MJSON
|
||||
write_uint_as_hex: bool,
|
||||
write_uint_as_hex: bool,
|
||||
|
||||
// If spec is MJSON and this is true, then keys will be quoted.
|
||||
//
|
||||
@@ -138,7 +138,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
||||
// allow uints to be printed as hex
|
||||
if opt.write_uint_as_hex && (opt.spec == .JSON5 || opt.spec == .MJSON) {
|
||||
switch i in a {
|
||||
case u8, u16, u32, u64, u128:
|
||||
case u8, u16, u32, u64, u128:
|
||||
s = strconv.append_bits_128(buf[:], u, 16, info.signed, 8*ti.size, "0123456789abcdef", { .Prefix })
|
||||
|
||||
case:
|
||||
@@ -239,7 +239,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
||||
case runtime.Type_Info_Array:
|
||||
opt_write_start(w, opt, '[') or_return
|
||||
for i in 0..<info.count {
|
||||
opt_write_iteration(w, opt, i) or_return
|
||||
opt_write_iteration(w, opt, i == 0) or_return
|
||||
data := uintptr(v.data) + uintptr(i*info.elem_size)
|
||||
marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt) or_return
|
||||
}
|
||||
@@ -248,7 +248,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
||||
case runtime.Type_Info_Enumerated_Array:
|
||||
opt_write_start(w, opt, '[') or_return
|
||||
for i in 0..<info.count {
|
||||
opt_write_iteration(w, opt, i) or_return
|
||||
opt_write_iteration(w, opt, i == 0) or_return
|
||||
data := uintptr(v.data) + uintptr(i*info.elem_size)
|
||||
marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt) or_return
|
||||
}
|
||||
@@ -258,7 +258,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
||||
opt_write_start(w, opt, '[') or_return
|
||||
array := cast(^mem.Raw_Dynamic_Array)v.data
|
||||
for i in 0..<array.len {
|
||||
opt_write_iteration(w, opt, i) or_return
|
||||
opt_write_iteration(w, opt, i == 0) or_return
|
||||
data := uintptr(array.data) + uintptr(i*info.elem_size)
|
||||
marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt) or_return
|
||||
}
|
||||
@@ -268,7 +268,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
||||
opt_write_start(w, opt, '[') or_return
|
||||
slice := cast(^mem.Raw_Slice)v.data
|
||||
for i in 0..<slice.len {
|
||||
opt_write_iteration(w, opt, i) or_return
|
||||
opt_write_iteration(w, opt, i == 0) or_return
|
||||
data := uintptr(slice.data) + uintptr(i*info.elem_size)
|
||||
marshal_to_writer(w, any{rawptr(data), info.elem.id}, opt) or_return
|
||||
}
|
||||
@@ -290,7 +290,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
||||
for bucket_index in 0..<map_cap {
|
||||
runtime.map_hash_is_valid(hs[bucket_index]) or_continue
|
||||
|
||||
opt_write_iteration(w, opt, i) or_return
|
||||
opt_write_iteration(w, opt, i == 0) or_return
|
||||
i += 1
|
||||
|
||||
key := rawptr(runtime.map_cell_index_dynamic(ks, info.map_info.ks, bucket_index))
|
||||
@@ -356,7 +356,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
||||
slice.sort_by(sorted[:], proc(i, j: Entry) -> bool { return i.key < j.key })
|
||||
|
||||
for s, i in sorted {
|
||||
opt_write_iteration(w, opt, i) or_return
|
||||
opt_write_iteration(w, opt, i == 0) or_return
|
||||
opt_write_key(w, opt, s.key) or_return
|
||||
marshal_to_writer(w, s.value, opt) or_return
|
||||
}
|
||||
@@ -387,17 +387,17 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
||||
case runtime.Type_Info_Pointer,
|
||||
runtime.Type_Info_Multi_Pointer,
|
||||
runtime.Type_Info_Procedure:
|
||||
return (^rawptr)(v.data)^ == nil
|
||||
return (^rawptr)(v.data)^ == nil
|
||||
case runtime.Type_Info_Dynamic_Array:
|
||||
return (^runtime.Raw_Dynamic_Array)(v.data).len == 0
|
||||
return (^runtime.Raw_Dynamic_Array)(v.data).len == 0
|
||||
case runtime.Type_Info_Slice:
|
||||
return (^runtime.Raw_Slice)(v.data).len == 0
|
||||
return (^runtime.Raw_Slice)(v.data).len == 0
|
||||
case runtime.Type_Info_Union,
|
||||
runtime.Type_Info_Bit_Set,
|
||||
runtime.Type_Info_Soa_Pointer:
|
||||
return reflect.is_nil(v)
|
||||
case runtime.Type_Info_Map:
|
||||
return (^runtime.Raw_Map)(v.data).len == 0
|
||||
return (^runtime.Raw_Map)(v.data).len == 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -405,6 +405,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
||||
marshal_struct_fields :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: Marshal_Error) {
|
||||
ti := runtime.type_info_base(type_info_of(v.id))
|
||||
info := ti.variant.(runtime.Type_Info_Struct)
|
||||
first_iteration := true
|
||||
for name, i in info.names {
|
||||
omitempty := false
|
||||
|
||||
@@ -424,7 +425,8 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
||||
continue
|
||||
}
|
||||
|
||||
opt_write_iteration(w, opt, i) or_return
|
||||
opt_write_iteration(w, opt, first_iteration) or_return
|
||||
first_iteration = false
|
||||
if json_name != "" {
|
||||
opt_write_key(w, opt, json_name) or_return
|
||||
} else {
|
||||
@@ -588,10 +590,10 @@ opt_write_start :: proc(w: io.Writer, opt: ^Marshal_Options, c: byte) -> (err: i
|
||||
}
|
||||
|
||||
// insert comma separation and write indentations
|
||||
opt_write_iteration :: proc(w: io.Writer, opt: ^Marshal_Options, iteration: int) -> (err: io.Error) {
|
||||
opt_write_iteration :: proc(w: io.Writer, opt: ^Marshal_Options, first_iteration: bool) -> (err: io.Error) {
|
||||
switch opt.spec {
|
||||
case .JSON, .JSON5:
|
||||
if iteration > 0 {
|
||||
case .JSON, .JSON5:
|
||||
if !first_iteration {
|
||||
io.write_byte(w, ',') or_return
|
||||
|
||||
if opt.pretty {
|
||||
@@ -601,8 +603,8 @@ opt_write_iteration :: proc(w: io.Writer, opt: ^Marshal_Options, iteration: int)
|
||||
|
||||
opt_write_indentation(w, opt) or_return
|
||||
|
||||
case .MJSON:
|
||||
if iteration > 0 {
|
||||
case .MJSON:
|
||||
if !first_iteration {
|
||||
// on pretty no commas necessary
|
||||
if opt.pretty {
|
||||
io.write_byte(w, '\n') or_return
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2024, Feoramund
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,67 @@
|
||||
package uuid
|
||||
|
||||
// A RFC 4122 Universally Unique Identifier
|
||||
Identifier :: distinct [16]u8
|
||||
|
||||
EXPECTED_LENGTH :: 8 + 4 + 4 + 4 + 12 + 4
|
||||
|
||||
VERSION_BYTE_INDEX :: 6
|
||||
VARIANT_BYTE_INDEX :: 8
|
||||
|
||||
// The number of 100-nanosecond intervals between 1582-10-15 and 1970-01-01.
|
||||
HNS_INTERVALS_BETWEEN_GREG_AND_UNIX :: 141427 * 24 * 60 * 60 * 1000 * 1000 * 10
|
||||
|
||||
VERSION_7_TIME_MASK :: 0xffffffff_ffff0000_00000000_00000000
|
||||
VERSION_7_TIME_SHIFT :: 80
|
||||
VERSION_7_COUNTER_MASK :: 0x00000000_00000fff_00000000_00000000
|
||||
VERSION_7_COUNTER_SHIFT :: 64
|
||||
|
||||
@(private)
|
||||
NO_CSPRNG_ERROR :: "The context random generator is not cryptographic. See the documentation for an example of how to set one up."
|
||||
@(private)
|
||||
BIG_CLOCK_ERROR :: "The clock sequence can only hold 14 bits of data, therefore no number greater than 16,383 (0x3FFF)."
|
||||
@(private)
|
||||
VERSION_7_BIG_COUNTER_ERROR :: "This implementation of the version 7 UUID counter can only hold 12 bits of data, therefore no number greater than 4,095 (0xFFF)."
|
||||
|
||||
Read_Error :: enum {
|
||||
None,
|
||||
Invalid_Length,
|
||||
Invalid_Hexadecimal,
|
||||
Invalid_Separator,
|
||||
}
|
||||
|
||||
Variant_Type :: enum {
|
||||
Unknown,
|
||||
Reserved_Apollo_NCS, // 0b0xx
|
||||
RFC_4122, // 0b10x
|
||||
Reserved_Microsoft_COM, // 0b110
|
||||
Reserved_Future, // 0b111
|
||||
}
|
||||
|
||||
// Name string is a fully-qualified domain name.
|
||||
@(rodata)
|
||||
Namespace_DNS := Identifier {
|
||||
0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1,
|
||||
0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
|
||||
}
|
||||
|
||||
// Name string is a URL.
|
||||
@(rodata)
|
||||
Namespace_URL := Identifier {
|
||||
0x6b, 0xa7, 0xb8, 0x11, 0x9d, 0xad, 0x11, 0xd1,
|
||||
0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
|
||||
}
|
||||
|
||||
// Name string is an ISO OID.
|
||||
@(rodata)
|
||||
Namespace_OID := Identifier {
|
||||
0x6b, 0xa7, 0xb8, 0x12, 0x9d, 0xad, 0x11, 0xd1,
|
||||
0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
|
||||
}
|
||||
|
||||
// Name string is an X.500 DN (in DER or a text output format).
|
||||
@(rodata)
|
||||
Namespace_X500 := Identifier {
|
||||
0x6b, 0xa7, 0xb8, 0x14, 0x9d, 0xad, 0x11, 0xd1,
|
||||
0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
package uuid implements Universally Unique Identifiers according to the
|
||||
standard originally outlined in RFC 4122 with additions from RFC 9562.
|
||||
|
||||
The UUIDs are textually represented and read in the following string format:
|
||||
`00000000-0000-v000-V000-000000000000`
|
||||
|
||||
`v` is where the version bits reside, and `V` is where the variant bits reside.
|
||||
The meaning of the other bits is version-dependent.
|
||||
|
||||
Outside of string representations, UUIDs are represented in memory by a 128-bit
|
||||
structure organized as an array of 16 bytes.
|
||||
|
||||
|
||||
Of the UUID versions which may make use of random number generation, a
|
||||
requirement is placed upon them that the underlying generator be
|
||||
cryptographically-secure, per RFC 9562's suggestion.
|
||||
|
||||
- Version 1 without a node argument.
|
||||
- Version 4 in all cases.
|
||||
- Version 6 without either a clock or node argument.
|
||||
- Version 7 in all cases.
|
||||
|
||||
Here's an example of how to set up one:
|
||||
|
||||
import "core:crypto"
|
||||
import "core:encoding/uuid"
|
||||
|
||||
main :: proc() {
|
||||
my_uuid: uuid.Identifier
|
||||
|
||||
{
|
||||
// This scope will have a CSPRNG.
|
||||
context.random_generator = crypto.random_generator()
|
||||
my_uuid = uuid.generate_v7()
|
||||
}
|
||||
|
||||
// Back to the default random number generator.
|
||||
}
|
||||
|
||||
|
||||
For more information on the specifications, see here:
|
||||
- https://www.rfc-editor.org/rfc/rfc4122.html
|
||||
- https://www.rfc-editor.org/rfc/rfc9562.html
|
||||
*/
|
||||
package uuid
|
||||
@@ -0,0 +1,333 @@
|
||||
package uuid
|
||||
|
||||
import "base:runtime"
|
||||
import "core:crypto/hash"
|
||||
import "core:math/rand"
|
||||
import "core:time"
|
||||
|
||||
/*
|
||||
Generate a version 1 UUID.
|
||||
|
||||
Inputs:
|
||||
- clock_seq: The clock sequence, a number which must be initialized to a random number once in the lifetime of a system.
|
||||
- node: An optional 48-bit spatially unique identifier, specified to be the IEEE 802 address of the system.
|
||||
If one is not provided or available, 48 bits of random state will take its place.
|
||||
- timestamp: A timestamp from the `core:time` package, or `nil` to use the current time.
|
||||
|
||||
Returns:
|
||||
- result: The generated UUID.
|
||||
*/
|
||||
generate_v1 :: proc(clock_seq: u16, node: Maybe([6]u8) = nil, timestamp: Maybe(time.Time) = nil) -> (result: Identifier) {
|
||||
assert(clock_seq <= 0x3FFF, BIG_CLOCK_ERROR)
|
||||
unix_time_in_hns_intervals := time.to_unix_nanoseconds(timestamp.? or_else time.now()) / 100
|
||||
|
||||
uuid_timestamp := cast(u64le)(HNS_INTERVALS_BETWEEN_GREG_AND_UNIX + unix_time_in_hns_intervals)
|
||||
uuid_timestamp_octets := transmute([8]u8)uuid_timestamp
|
||||
|
||||
result[0] = uuid_timestamp_octets[0]
|
||||
result[1] = uuid_timestamp_octets[1]
|
||||
result[2] = uuid_timestamp_octets[2]
|
||||
result[3] = uuid_timestamp_octets[3]
|
||||
result[4] = uuid_timestamp_octets[4]
|
||||
result[5] = uuid_timestamp_octets[5]
|
||||
|
||||
result[6] = uuid_timestamp_octets[6] >> 4
|
||||
result[7] = uuid_timestamp_octets[6] << 4 | uuid_timestamp_octets[7]
|
||||
|
||||
if realized_node, ok := node.?; ok {
|
||||
mutable_node := realized_node
|
||||
runtime.mem_copy_non_overlapping(&result[10], &mutable_node[0], 6)
|
||||
} else {
|
||||
assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR)
|
||||
bytes_generated := rand.read(result[10:])
|
||||
assert(bytes_generated == 6, "RNG failed to generate 6 bytes for UUID v1.")
|
||||
}
|
||||
|
||||
result[VERSION_BYTE_INDEX] |= 0x10
|
||||
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||
|
||||
result[8] |= cast(u8)(clock_seq & 0x3F00 >> 8)
|
||||
result[9] = cast(u8)clock_seq
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Generate a version 4 UUID.
|
||||
|
||||
This UUID will be pseudorandom, save for 6 pre-determined version and variant bits.
|
||||
|
||||
Returns:
|
||||
- result: The generated UUID.
|
||||
*/
|
||||
generate_v4 :: proc() -> (result: Identifier) {
|
||||
assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR)
|
||||
bytes_generated := rand.read(result[:])
|
||||
assert(bytes_generated == 16, "RNG failed to generate 16 bytes for UUID v4.")
|
||||
|
||||
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||
result[VERSION_BYTE_INDEX] |= 0x40
|
||||
|
||||
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Generate a version 6 UUID.
|
||||
|
||||
Inputs:
|
||||
- clock_seq: The clock sequence from version 1, now made optional.
|
||||
If unspecified, it will be replaced with random bits.
|
||||
- node: An optional 48-bit spatially unique identifier, specified to be the IEEE 802 address of the system.
|
||||
If one is not provided or available, 48 bits of random state will take its place.
|
||||
- timestamp: A timestamp from the `core:time` package, or `nil` to use the current time.
|
||||
|
||||
Returns:
|
||||
- result: The generated UUID.
|
||||
*/
|
||||
generate_v6 :: proc(clock_seq: Maybe(u16) = nil, node: Maybe([6]u8) = nil, timestamp: Maybe(time.Time) = nil) -> (result: Identifier) {
|
||||
unix_time_in_hns_intervals := time.to_unix_nanoseconds(timestamp.? or_else time.now()) / 100
|
||||
|
||||
uuid_timestamp := cast(u128be)(HNS_INTERVALS_BETWEEN_GREG_AND_UNIX + unix_time_in_hns_intervals)
|
||||
|
||||
result = transmute(Identifier)(
|
||||
uuid_timestamp & 0x0FFFFFFF_FFFFF000 << 68 |
|
||||
uuid_timestamp & 0x00000000_00000FFF << 64
|
||||
)
|
||||
|
||||
if realized_clock_seq, ok := clock_seq.?; ok {
|
||||
assert(realized_clock_seq <= 0x3FFF, BIG_CLOCK_ERROR)
|
||||
result[8] |= cast(u8)(realized_clock_seq & 0x3F00 >> 8)
|
||||
result[9] = cast(u8)realized_clock_seq
|
||||
} else {
|
||||
assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR)
|
||||
temporary: [2]u8
|
||||
bytes_generated := rand.read(temporary[:])
|
||||
assert(bytes_generated == 2, "RNG failed to generate 2 bytes for UUID v1.")
|
||||
result[8] |= temporary[0] & 0x3F
|
||||
result[9] = temporary[1]
|
||||
}
|
||||
|
||||
if realized_node, ok := node.?; ok {
|
||||
mutable_node := realized_node
|
||||
runtime.mem_copy_non_overlapping(&result[10], &mutable_node[0], 6)
|
||||
} else {
|
||||
assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR)
|
||||
bytes_generated := rand.read(result[10:])
|
||||
assert(bytes_generated == 6, "RNG failed to generate 6 bytes for UUID v1.")
|
||||
}
|
||||
|
||||
result[VERSION_BYTE_INDEX] |= 0x60
|
||||
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Generate a version 7 UUID.
|
||||
|
||||
This UUID will be pseudorandom, save for 6 pre-determined version and variant
|
||||
bits and a 48-bit timestamp.
|
||||
|
||||
It is designed with time-based sorting in mind, such as for database usage, as
|
||||
the highest bits are allocated from the timestamp of when it is created.
|
||||
|
||||
Inputs:
|
||||
- timestamp: A timestamp from the `core:time` package, or `nil` to use the current time.
|
||||
|
||||
Returns:
|
||||
- result: The generated UUID.
|
||||
*/
|
||||
generate_v7_basic :: proc(timestamp: Maybe(time.Time) = nil) -> (result: Identifier) {
|
||||
assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR)
|
||||
unix_time_in_milliseconds := time.to_unix_nanoseconds(timestamp.? or_else time.now()) / 1e6
|
||||
|
||||
result = transmute(Identifier)(cast(u128be)unix_time_in_milliseconds << VERSION_7_TIME_SHIFT)
|
||||
|
||||
bytes_generated := rand.read(result[6:])
|
||||
assert(bytes_generated == 10, "RNG failed to generate 10 bytes for UUID v7.")
|
||||
|
||||
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||
result[VERSION_BYTE_INDEX] |= 0x70
|
||||
|
||||
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Generate a version 7 UUID that has an incremented counter.
|
||||
|
||||
This UUID will be pseudorandom, save for 6 pre-determined version and variant
|
||||
bits, a 48-bit timestamp, and 12 bits of counter state.
|
||||
|
||||
It is designed with time-based sorting in mind, such as for database usage, as
|
||||
the highest bits are allocated from the timestamp of when it is created.
|
||||
|
||||
This procedure is preferable if you are generating hundreds or thousands of
|
||||
UUIDs as a batch within the span of a millisecond. Do note that the counter
|
||||
only has 12 bits of state, thus `counter` cannot exceed the number 4,095.
|
||||
|
||||
Example:
|
||||
|
||||
import "core:uuid"
|
||||
|
||||
// Create a batch of UUIDs all at once.
|
||||
batch: [dynamic]uuid.Identifier
|
||||
|
||||
for i: u16 = 0; i < 1000; i += 1 {
|
||||
my_uuid := uuid.generate_v7_counter(i)
|
||||
append(&batch, my_uuid)
|
||||
}
|
||||
|
||||
Inputs:
|
||||
- counter: A 12-bit value which should be incremented each time a UUID is generated in a batch.
|
||||
- timestamp: A timestamp from the `core:time` package, or `nil` to use the current time.
|
||||
|
||||
Returns:
|
||||
- result: The generated UUID.
|
||||
*/
|
||||
generate_v7_with_counter :: proc(counter: u16, timestamp: Maybe(time.Time) = nil) -> (result: Identifier) {
|
||||
assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR)
|
||||
assert(counter <= 0x0fff, VERSION_7_BIG_COUNTER_ERROR)
|
||||
unix_time_in_milliseconds := time.to_unix_nanoseconds(timestamp.? or_else time.now()) / 1e6
|
||||
|
||||
result = transmute(Identifier)(
|
||||
cast(u128be)unix_time_in_milliseconds << VERSION_7_TIME_SHIFT |
|
||||
cast(u128be)counter << VERSION_7_COUNTER_SHIFT
|
||||
)
|
||||
|
||||
bytes_generated := rand.read(result[8:])
|
||||
assert(bytes_generated == 8, "RNG failed to generate 8 bytes for UUID v7.")
|
||||
|
||||
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||
result[VERSION_BYTE_INDEX] |= 0x70
|
||||
|
||||
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
generate_v7 :: proc {
|
||||
generate_v7_basic,
|
||||
generate_v7_with_counter,
|
||||
}
|
||||
|
||||
/*
|
||||
Generate a version 8 UUID using a specific hashing algorithm.
|
||||
|
||||
This UUID is generated by hashing a name with a namespace.
|
||||
|
||||
Note that all version 8 UUIDs are for experimental or vendor-specific use
|
||||
cases, per the specification. This use case in particular is for offering a
|
||||
non-legacy alternative to UUID versions 3 and 5.
|
||||
|
||||
Inputs:
|
||||
- namespace: An `Identifier` that is used to represent the underlying namespace.
|
||||
This can be any one of the `Namespace_*` values provided in this package.
|
||||
- name: The byte slice which will be hashed with the namespace.
|
||||
- algorithm: A hashing algorithm from `core:crypto/hash`.
|
||||
|
||||
Returns:
|
||||
- result: The generated UUID.
|
||||
|
||||
Example:
|
||||
import "core:crypto/hash"
|
||||
import "core:encoding/uuid"
|
||||
import "core:fmt"
|
||||
|
||||
main :: proc() {
|
||||
my_uuid := uuid.generate_v8_hash(uuid.Namespace_DNS, "www.odin-lang.org", .SHA256)
|
||||
my_uuid_string := uuid.to_string(my_uuid, context.temp_allocator)
|
||||
fmt.println(my_uuid_string)
|
||||
}
|
||||
|
||||
Output:
|
||||
|
||||
3730f688-4bff-8dce-9cbf-74a3960c5703
|
||||
|
||||
*/
|
||||
generate_v8_hash_bytes :: proc(
|
||||
namespace: Identifier,
|
||||
name: []byte,
|
||||
algorithm: hash.Algorithm,
|
||||
) -> (
|
||||
result: Identifier,
|
||||
) {
|
||||
// 128 bytes should be enough for the foreseeable future.
|
||||
digest: [128]byte
|
||||
|
||||
assert(hash.DIGEST_SIZES[algorithm] >= 16, "Per RFC 9562, the hashing algorithm used must generate a digest of 128 bits or larger.")
|
||||
assert(hash.DIGEST_SIZES[algorithm] < len(digest), "Digest size is too small for this algorithm. The buffer must be increased.")
|
||||
|
||||
hash_context: hash.Context
|
||||
hash.init(&hash_context, algorithm)
|
||||
|
||||
mutable_namespace := namespace
|
||||
hash.update(&hash_context, mutable_namespace[:])
|
||||
hash.update(&hash_context, name[:])
|
||||
hash.final(&hash_context, digest[:])
|
||||
|
||||
runtime.mem_copy_non_overlapping(&result, &digest, 16)
|
||||
|
||||
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||
result[VERSION_BYTE_INDEX] |= 0x80
|
||||
|
||||
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Generate a version 8 UUID using a specific hashing algorithm.
|
||||
|
||||
This UUID is generated by hashing a name with a namespace.
|
||||
|
||||
Note that all version 8 UUIDs are for experimental or vendor-specific use
|
||||
cases, per the specification. This use case in particular is for offering a
|
||||
non-legacy alternative to UUID versions 3 and 5.
|
||||
|
||||
Inputs:
|
||||
- namespace: An `Identifier` that is used to represent the underlying namespace.
|
||||
This can be any one of the `Namespace_*` values provided in this package.
|
||||
- name: The string which will be hashed with the namespace.
|
||||
- algorithm: A hashing algorithm from `core:crypto/hash`.
|
||||
|
||||
Returns:
|
||||
- result: The generated UUID.
|
||||
|
||||
Example:
|
||||
import "core:crypto/hash"
|
||||
import "core:encoding/uuid"
|
||||
import "core:fmt"
|
||||
|
||||
main :: proc() {
|
||||
my_uuid := uuid.generate_v8_hash(uuid.Namespace_DNS, "www.odin-lang.org", .SHA256)
|
||||
my_uuid_string := uuid.to_string(my_uuid, context.temp_allocator)
|
||||
fmt.println(my_uuid_string)
|
||||
}
|
||||
|
||||
Output:
|
||||
|
||||
3730f688-4bff-8dce-9cbf-74a3960c5703
|
||||
|
||||
*/
|
||||
generate_v8_hash_string :: proc(
|
||||
namespace: Identifier,
|
||||
name: string,
|
||||
algorithm: hash.Algorithm,
|
||||
) -> (
|
||||
result: Identifier,
|
||||
) {
|
||||
return generate_v8_hash_bytes(namespace, transmute([]byte)name, algorithm)
|
||||
}
|
||||
|
||||
generate_v8_hash :: proc {
|
||||
generate_v8_hash_bytes,
|
||||
generate_v8_hash_string,
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
package uuid/legacy implements versions 3 and 5 of UUID generation, both of
|
||||
which are using hashing algorithms (MD5 and SHA1, respectively) that are known
|
||||
these days to no longer be secure.
|
||||
*/
|
||||
package uuid_legacy
|
||||
|
||||
import "base:runtime"
|
||||
import "core:crypto/legacy/md5"
|
||||
import "core:crypto/legacy/sha1"
|
||||
import "core:encoding/uuid"
|
||||
|
||||
Identifier :: uuid.Identifier
|
||||
VERSION_BYTE_INDEX :: uuid.VERSION_BYTE_INDEX
|
||||
VARIANT_BYTE_INDEX :: uuid.VARIANT_BYTE_INDEX
|
||||
|
||||
|
||||
/*
|
||||
Generate a version 3 UUID.
|
||||
|
||||
This UUID is generated with a MD5 hash of a name and a namespace.
|
||||
|
||||
Inputs:
|
||||
- namespace: An `Identifier` that is used to represent the underlying namespace.
|
||||
This can be any one of the `Namespace_*` values provided in the `uuid` package.
|
||||
- name: The byte slice which will be hashed with the namespace.
|
||||
|
||||
Returns:
|
||||
- result: The generated UUID.
|
||||
*/
|
||||
generate_v3_bytes :: proc(
|
||||
namespace: Identifier,
|
||||
name: []byte,
|
||||
) -> (
|
||||
result: Identifier,
|
||||
) {
|
||||
namespace := namespace
|
||||
|
||||
ctx: md5.Context
|
||||
md5.init(&ctx)
|
||||
md5.update(&ctx, namespace[:])
|
||||
md5.update(&ctx, name)
|
||||
md5.final(&ctx, result[:])
|
||||
|
||||
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||
result[VERSION_BYTE_INDEX] |= 0x30
|
||||
|
||||
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Generate a version 3 UUID.
|
||||
|
||||
This UUID is generated with a MD5 hash of a name and a namespace.
|
||||
|
||||
Inputs:
|
||||
- namespace: An `Identifier` that is used to represent the underlying namespace.
|
||||
This can be any one of the `Namespace_*` values provided in the `uuid` package.
|
||||
- name: The string which will be hashed with the namespace.
|
||||
|
||||
Returns:
|
||||
- result: The generated UUID.
|
||||
*/
|
||||
generate_v3_string :: proc(
|
||||
namespace: Identifier,
|
||||
name: string,
|
||||
) -> (
|
||||
result: Identifier,
|
||||
) {
|
||||
return generate_v3_bytes(namespace, transmute([]byte)name)
|
||||
}
|
||||
|
||||
generate_v3 :: proc {
|
||||
generate_v3_bytes,
|
||||
generate_v3_string,
|
||||
}
|
||||
|
||||
/*
|
||||
Generate a version 5 UUID.
|
||||
|
||||
This UUID is generated with a SHA1 hash of a name and a namespace.
|
||||
|
||||
Inputs:
|
||||
- namespace: An `Identifier` that is used to represent the underlying namespace.
|
||||
This can be any one of the `Namespace_*` values provided in the `uuid` package.
|
||||
- name: The byte slice which will be hashed with the namespace.
|
||||
|
||||
Returns:
|
||||
- result: The generated UUID.
|
||||
*/
|
||||
generate_v5_bytes :: proc(
|
||||
namespace: Identifier,
|
||||
name: []byte,
|
||||
) -> (
|
||||
result: Identifier,
|
||||
) {
|
||||
namespace := namespace
|
||||
digest: [sha1.DIGEST_SIZE]byte
|
||||
|
||||
ctx: sha1.Context
|
||||
sha1.init(&ctx)
|
||||
sha1.update(&ctx, namespace[:])
|
||||
sha1.update(&ctx, name)
|
||||
sha1.final(&ctx, digest[:])
|
||||
|
||||
runtime.mem_copy_non_overlapping(&result, &digest, 16)
|
||||
|
||||
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||
result[VERSION_BYTE_INDEX] |= 0x50
|
||||
|
||||
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Generate a version 5 UUID.
|
||||
|
||||
This UUID is generated with a SHA1 hash of a name and a namespace.
|
||||
|
||||
Inputs:
|
||||
- namespace: An `Identifier` that is used to represent the underlying namespace.
|
||||
This can be any one of the `Namespace_*` values provided in the `uuid` package.
|
||||
- name: The string which will be hashed with the namespace.
|
||||
|
||||
Returns:
|
||||
- result: The generated UUID.
|
||||
*/
|
||||
generate_v5_string :: proc(
|
||||
namespace: Identifier,
|
||||
name: string,
|
||||
) -> (
|
||||
result: Identifier,
|
||||
) {
|
||||
return generate_v5_bytes(namespace, transmute([]byte)name)
|
||||
}
|
||||
|
||||
generate_v5 :: proc {
|
||||
generate_v5_bytes,
|
||||
generate_v5_string,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
package uuid
|
||||
|
||||
import "base:runtime"
|
||||
import "core:time"
|
||||
|
||||
/*
|
||||
Convert a string to a UUID.
|
||||
|
||||
Inputs:
|
||||
- str: A string in the 8-4-4-4-12 format.
|
||||
|
||||
Returns:
|
||||
- id: The converted identifier, or `nil` if there is an error.
|
||||
- error: A description of the error, or `nil` if successful.
|
||||
*/
|
||||
read :: proc "contextless" (str: string) -> (id: Identifier, error: Read_Error) #no_bounds_check {
|
||||
// Only exact-length strings are acceptable.
|
||||
if len(str) != EXPECTED_LENGTH {
|
||||
return {}, .Invalid_Length
|
||||
}
|
||||
|
||||
// Check ahead to see if the separators are in the right places.
|
||||
if str[8] != '-' || str[13] != '-' || str[18] != '-' || str[23] != '-' {
|
||||
return {}, .Invalid_Separator
|
||||
}
|
||||
|
||||
read_nibble :: proc "contextless" (nibble: u8) -> u8 {
|
||||
switch nibble {
|
||||
case '0' ..= '9':
|
||||
return nibble - '0'
|
||||
case 'A' ..= 'F':
|
||||
return nibble - 'A' + 10
|
||||
case 'a' ..= 'f':
|
||||
return nibble - 'a' + 10
|
||||
case:
|
||||
// Return an error value.
|
||||
return 0xFF
|
||||
}
|
||||
}
|
||||
|
||||
index := 0
|
||||
octet_index := 0
|
||||
|
||||
CHUNKS :: [5]int{8, 4, 4, 4, 12}
|
||||
|
||||
for chunk in CHUNKS {
|
||||
for i := index; i < index + chunk; i += 2 {
|
||||
high := read_nibble(str[i])
|
||||
low := read_nibble(str[i + 1])
|
||||
|
||||
if high | low > 0xF {
|
||||
return {}, .Invalid_Hexadecimal
|
||||
}
|
||||
|
||||
id[octet_index] = low | high << 4
|
||||
octet_index += 1
|
||||
}
|
||||
|
||||
index += chunk + 1
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Get the version of a UUID.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier.
|
||||
|
||||
Returns:
|
||||
- number: The version number.
|
||||
*/
|
||||
version :: proc "contextless" (id: Identifier) -> (number: int) #no_bounds_check {
|
||||
return cast(int)(id[VERSION_BYTE_INDEX] & 0xF0 >> 4)
|
||||
}
|
||||
|
||||
/*
|
||||
Get the variant of a UUID.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier.
|
||||
|
||||
Returns:
|
||||
- variant: The variant type.
|
||||
*/
|
||||
variant :: proc "contextless" (id: Identifier) -> (variant: Variant_Type) #no_bounds_check {
|
||||
switch {
|
||||
case id[VARIANT_BYTE_INDEX] & 0x80 == 0:
|
||||
return .Reserved_Apollo_NCS
|
||||
case id[VARIANT_BYTE_INDEX] & 0xC0 == 0x80:
|
||||
return .RFC_4122
|
||||
case id[VARIANT_BYTE_INDEX] & 0xE0 == 0xC0:
|
||||
return .Reserved_Microsoft_COM
|
||||
case id[VARIANT_BYTE_INDEX] & 0xF0 == 0xE0:
|
||||
return .Reserved_Future
|
||||
case:
|
||||
return .Unknown
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Get the clock sequence of a version 1 or version 6 UUID.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier.
|
||||
|
||||
Returns:
|
||||
- clock_seq: The 14-bit clock sequence field.
|
||||
*/
|
||||
clock_seq :: proc "contextless" (id: Identifier) -> (clock_seq: u16) {
|
||||
return cast(u16)id[9] | cast(u16)id[8] & 0x3F << 8
|
||||
}
|
||||
|
||||
/*
|
||||
Get the node of a version 1 or version 6 UUID.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier.
|
||||
|
||||
Returns:
|
||||
- node: The 48-bit spatially unique identifier.
|
||||
*/
|
||||
node :: proc "contextless" (id: Identifier) -> (node: [6]u8) {
|
||||
mutable_id := id
|
||||
runtime.mem_copy_non_overlapping(&node, &mutable_id[10], 6)
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Get the raw timestamp of a version 1 UUID.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier.
|
||||
|
||||
Returns:
|
||||
- timestamp: The timestamp, in 100-nanosecond intervals since 1582-10-15.
|
||||
*/
|
||||
raw_time_v1 :: proc "contextless" (id: Identifier) -> (timestamp: u64) {
|
||||
timestamp_octets: [8]u8
|
||||
|
||||
timestamp_octets[0] = id[0]
|
||||
timestamp_octets[1] = id[1]
|
||||
timestamp_octets[2] = id[2]
|
||||
timestamp_octets[3] = id[3]
|
||||
timestamp_octets[4] = id[4]
|
||||
timestamp_octets[5] = id[5]
|
||||
|
||||
timestamp_octets[6] = id[6] << 4 | id[7] >> 4
|
||||
timestamp_octets[7] = id[7] & 0xF
|
||||
|
||||
return cast(u64)transmute(u64le)timestamp_octets
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Get the timestamp of a version 1 UUID.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier.
|
||||
|
||||
Returns:
|
||||
- timestamp: The timestamp of the UUID.
|
||||
*/
|
||||
time_v1 :: proc "contextless" (id: Identifier) -> (timestamp: time.Time) {
|
||||
return time.from_nanoseconds(cast(i64)(raw_time_v1(id) - HNS_INTERVALS_BETWEEN_GREG_AND_UNIX) * 100)
|
||||
}
|
||||
|
||||
/*
|
||||
Get the raw timestamp of a version 6 UUID.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier.
|
||||
|
||||
Returns:
|
||||
- timestamp: The timestamp, in 100-nanosecond intervals since 1582-10-15.
|
||||
*/
|
||||
raw_time_v6 :: proc "contextless" (id: Identifier) -> (timestamp: u64) {
|
||||
temporary := transmute(u128be)id
|
||||
|
||||
timestamp |= cast(u64)(temporary & 0xFFFFFFFF_FFFF0000_00000000_00000000 >> 68)
|
||||
timestamp |= cast(u64)(temporary & 0x00000000_00000FFF_00000000_00000000 >> 64)
|
||||
|
||||
return timestamp
|
||||
}
|
||||
|
||||
/*
|
||||
Get the timestamp of a version 6 UUID.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier.
|
||||
|
||||
Returns:
|
||||
- timestamp: The timestamp, in 100-nanosecond intervals since 1582-10-15.
|
||||
*/
|
||||
time_v6 :: proc "contextless" (id: Identifier) -> (timestamp: time.Time) {
|
||||
return time.from_nanoseconds(cast(i64)(raw_time_v6(id) - HNS_INTERVALS_BETWEEN_GREG_AND_UNIX) * 100)
|
||||
}
|
||||
|
||||
/*
|
||||
Get the raw timestamp of a version 7 UUID.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier.
|
||||
|
||||
Returns:
|
||||
- timestamp: The timestamp, in milliseconds since the UNIX epoch.
|
||||
*/
|
||||
raw_time_v7 :: proc "contextless" (id: Identifier) -> (timestamp: u64) {
|
||||
time_bits := transmute(u128be)id & VERSION_7_TIME_MASK
|
||||
return cast(u64)(time_bits >> VERSION_7_TIME_SHIFT)
|
||||
}
|
||||
|
||||
/*
|
||||
Get the timestamp of a version 7 UUID.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier.
|
||||
|
||||
Returns:
|
||||
- timestamp: The timestamp, in milliseconds since the UNIX epoch.
|
||||
*/
|
||||
time_v7 :: proc "contextless" (id: Identifier) -> (timestamp: time.Time) {
|
||||
return time.from_nanoseconds(cast(i64)raw_time_v7(id) * 1e6)
|
||||
}
|
||||
|
||||
/*
|
||||
Get the 12-bit counter value of a version 7 UUID.
|
||||
|
||||
The UUID must have been generated with a counter, otherwise this procedure will
|
||||
return random bits.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier.
|
||||
|
||||
Returns:
|
||||
- counter: The 12-bit counter value.
|
||||
*/
|
||||
counter_v7 :: proc "contextless" (id: Identifier) -> (counter: u16) {
|
||||
counter_bits := transmute(u128be)id & VERSION_7_COUNTER_MASK
|
||||
return cast(u16)(counter_bits >> VERSION_7_COUNTER_SHIFT)
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package uuid
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
/*
|
||||
Stamp a 128-bit integer as being a valid version 8 UUID.
|
||||
|
||||
Per the specification, all version 8 UUIDs are either for experimental or
|
||||
vendor-specific purposes. This procedure allows for converting arbitrary data
|
||||
into custom UUIDs.
|
||||
|
||||
Inputs:
|
||||
- integer: Any integer type.
|
||||
|
||||
Returns:
|
||||
- result: A valid version 8 UUID.
|
||||
*/
|
||||
stamp_v8_int :: proc(#any_int integer: u128) -> (result: Identifier) {
|
||||
result = transmute(Identifier)cast(u128be)integer
|
||||
|
||||
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||
result[VERSION_BYTE_INDEX] |= 0x80
|
||||
|
||||
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Stamp an array of 16 bytes as being a valid version 8 UUID.
|
||||
|
||||
Per the specification, all version 8 UUIDs are either for experimental or
|
||||
vendor-specific purposes. This procedure allows for converting arbitrary data
|
||||
into custom UUIDs.
|
||||
|
||||
Inputs:
|
||||
- array: An array of 16 bytes.
|
||||
|
||||
Returns:
|
||||
- result: A valid version 8 UUID.
|
||||
*/
|
||||
stamp_v8_array :: proc(array: [16]u8) -> (result: Identifier) {
|
||||
result = Identifier(array)
|
||||
|
||||
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||
result[VERSION_BYTE_INDEX] |= 0x80
|
||||
|
||||
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Stamp a slice of bytes as being a valid version 8 UUID.
|
||||
|
||||
If the slice is less than 16 bytes long, the data available will be used.
|
||||
If it is longer than 16 bytes, only the first 16 will be used.
|
||||
|
||||
This procedure does not modify the underlying slice.
|
||||
|
||||
Per the specification, all version 8 UUIDs are either for experimental or
|
||||
vendor-specific purposes. This procedure allows for converting arbitrary data
|
||||
into custom UUIDs.
|
||||
|
||||
Inputs:
|
||||
- slice: A slice of bytes.
|
||||
|
||||
Returns:
|
||||
- result: A valid version 8 UUID.
|
||||
*/
|
||||
stamp_v8_slice :: proc(slice: []u8) -> (result: Identifier) {
|
||||
runtime.mem_copy_non_overlapping(&result, &slice[0], min(16, len(slice)))
|
||||
|
||||
result[VERSION_BYTE_INDEX] &= 0x0F
|
||||
result[VERSION_BYTE_INDEX] |= 0x80
|
||||
|
||||
result[VARIANT_BYTE_INDEX] &= 0x3F
|
||||
result[VARIANT_BYTE_INDEX] |= 0x80
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
stamp_v8 :: proc {
|
||||
stamp_v8_int,
|
||||
stamp_v8_array,
|
||||
stamp_v8_slice,
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package uuid
|
||||
|
||||
import "base:runtime"
|
||||
import "core:io"
|
||||
import "core:strconv"
|
||||
import "core:strings"
|
||||
|
||||
/*
|
||||
Write a UUID in the 8-4-4-4-12 format.
|
||||
|
||||
This procedure performs error checking with every byte written.
|
||||
|
||||
If you can guarantee beforehand that your stream has enough space to hold the
|
||||
UUID (32 bytes), then it is better to use `unsafe_write` instead as that will
|
||||
be faster.
|
||||
|
||||
Inputs:
|
||||
- w: A writable stream.
|
||||
- id: The identifier to convert.
|
||||
|
||||
Returns:
|
||||
- error: An `io` error, if one occurred, otherwise `nil`.
|
||||
*/
|
||||
write :: proc(w: io.Writer, id: Identifier) -> (error: io.Error) #no_bounds_check {
|
||||
write_octet :: proc (w: io.Writer, octet: u8) -> io.Error #no_bounds_check {
|
||||
high_nibble := octet >> 4
|
||||
low_nibble := octet & 0xF
|
||||
|
||||
io.write_byte(w, strconv.digits[high_nibble]) or_return
|
||||
io.write_byte(w, strconv.digits[low_nibble]) or_return
|
||||
return nil
|
||||
}
|
||||
|
||||
for index in 0 ..< 4 { write_octet(w, id[index]) or_return }
|
||||
io.write_byte(w, '-') or_return
|
||||
for index in 4 ..< 6 { write_octet(w, id[index]) or_return }
|
||||
io.write_byte(w, '-') or_return
|
||||
for index in 6 ..< 8 { write_octet(w, id[index]) or_return }
|
||||
io.write_byte(w, '-') or_return
|
||||
for index in 8 ..< 10 { write_octet(w, id[index]) or_return }
|
||||
io.write_byte(w, '-') or_return
|
||||
for index in 10 ..< 16 { write_octet(w, id[index]) or_return }
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
Write a UUID in the 8-4-4-4-12 format.
|
||||
|
||||
This procedure performs no error checking on the underlying stream.
|
||||
|
||||
Inputs:
|
||||
- w: A writable stream.
|
||||
- id: The identifier to convert.
|
||||
*/
|
||||
unsafe_write :: proc(w: io.Writer, id: Identifier) #no_bounds_check {
|
||||
write_octet :: proc (w: io.Writer, octet: u8) #no_bounds_check {
|
||||
high_nibble := octet >> 4
|
||||
low_nibble := octet & 0xF
|
||||
|
||||
io.write_byte(w, strconv.digits[high_nibble])
|
||||
io.write_byte(w, strconv.digits[low_nibble])
|
||||
}
|
||||
|
||||
for index in 0 ..< 4 { write_octet(w, id[index]) }
|
||||
io.write_byte(w, '-')
|
||||
for index in 4 ..< 6 { write_octet(w, id[index]) }
|
||||
io.write_byte(w, '-')
|
||||
for index in 6 ..< 8 { write_octet(w, id[index]) }
|
||||
io.write_byte(w, '-')
|
||||
for index in 8 ..< 10 { write_octet(w, id[index]) }
|
||||
io.write_byte(w, '-')
|
||||
for index in 10 ..< 16 { write_octet(w, id[index]) }
|
||||
}
|
||||
|
||||
/*
|
||||
Convert a UUID to a string in the 8-4-4-4-12 format.
|
||||
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
Inputs:
|
||||
- id: The identifier to convert.
|
||||
- allocator: (default: context.allocator)
|
||||
- loc: The caller location for debugging purposes (default: #caller_location)
|
||||
|
||||
Returns:
|
||||
- str: The allocated and converted string.
|
||||
- error: An optional allocator error if one occured, `nil` otherwise.
|
||||
*/
|
||||
to_string_allocated :: proc(
|
||||
id: Identifier,
|
||||
allocator := context.allocator,
|
||||
loc := #caller_location,
|
||||
) -> (
|
||||
str: string,
|
||||
error: runtime.Allocator_Error,
|
||||
) #optional_allocator_error {
|
||||
buf := make([]byte, EXPECTED_LENGTH, allocator, loc) or_return
|
||||
builder := strings.builder_from_bytes(buf[:])
|
||||
unsafe_write(strings.to_writer(&builder), id)
|
||||
return strings.to_string(builder), nil
|
||||
}
|
||||
|
||||
/*
|
||||
Convert a UUID to a string in the 8-4-4-4-12 format.
|
||||
|
||||
Inputs:
|
||||
- id: The identifier to convert.
|
||||
- buffer: A byte buffer to store the result. Must be at least 32 bytes large.
|
||||
- loc: The caller location for debugging purposes (default: #caller_location)
|
||||
|
||||
Returns:
|
||||
- str: The converted string which will be stored in `buffer`.
|
||||
*/
|
||||
to_string_buffer :: proc(
|
||||
id: Identifier,
|
||||
buffer: []byte,
|
||||
loc := #caller_location,
|
||||
) -> (
|
||||
str: string,
|
||||
) {
|
||||
assert(len(buffer) >= EXPECTED_LENGTH, "The buffer provided is not at least 32 bytes large.", loc)
|
||||
builder := strings.builder_from_bytes(buffer)
|
||||
unsafe_write(strings.to_writer(&builder), id)
|
||||
return strings.to_string(builder)
|
||||
}
|
||||
|
||||
to_string :: proc {
|
||||
to_string_allocated,
|
||||
to_string_buffer,
|
||||
}
|
||||
@@ -33,7 +33,7 @@ print :: proc(writer: io.Writer, doc: ^Document) -> (written: int, err: io.Error
|
||||
written += fmt.wprintf(writer, "[DOCTYPE] %v\n", doc.doctype.ident)
|
||||
|
||||
if len(doc.doctype.rest) > 0 {
|
||||
fmt.wprintf(writer, "\t%v\n", doc.doctype.rest)
|
||||
fmt.wprintf(writer, "\t%v\n", doc.doctype.rest)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,10 +42,10 @@ print :: proc(writer: io.Writer, doc: ^Document) -> (written: int, err: io.Error
|
||||
}
|
||||
|
||||
if len(doc.elements) > 0 {
|
||||
fmt.wprintln(writer, " --- ")
|
||||
print_element(writer, doc, 0)
|
||||
fmt.wprintln(writer, " --- ")
|
||||
}
|
||||
fmt.wprintln(writer, " --- ")
|
||||
print_element(writer, doc, 0)
|
||||
fmt.wprintln(writer, " --- ")
|
||||
}
|
||||
|
||||
return written, .None
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ error :: proc(t: ^Tokenizer, offset: int, msg: string, args: ..any) {
|
||||
t.error_count += 1
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
advance_rune :: proc(t: ^Tokenizer) {
|
||||
#no_bounds_check {
|
||||
/*
|
||||
@@ -170,7 +170,7 @@ peek_byte :: proc(t: ^Tokenizer, offset := 0) -> byte {
|
||||
return 0
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
skip_whitespace :: proc(t: ^Tokenizer) {
|
||||
for {
|
||||
switch t.ch {
|
||||
@@ -182,7 +182,7 @@ skip_whitespace :: proc(t: ^Tokenizer) {
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
is_letter :: proc(r: rune) -> bool {
|
||||
if r < utf8.RUNE_SELF {
|
||||
switch r {
|
||||
@@ -296,7 +296,7 @@ skip_cdata :: proc(t: ^Tokenizer) -> (err: Error) {
|
||||
return
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
scan_string :: proc(t: ^Tokenizer, offset: int, close: rune = '<', consume_close := false, multiline := true) -> (value: string, err: Error) {
|
||||
err = .None
|
||||
|
||||
@@ -414,4 +414,4 @@ scan :: proc(t: ^Tokenizer, multiline_string := false) -> Token {
|
||||
lit = string(t.src[offset : t.offset])
|
||||
}
|
||||
return Token{kind, lit, pos}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2024, Feoramund
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,38 @@
|
||||
package flags
|
||||
|
||||
import "core:time"
|
||||
|
||||
// Set to true to compile with support for core named types disabled, as a
|
||||
// fallback in the event your platform does not support one of the types, or
|
||||
// you have no need for them and want a smaller binary.
|
||||
NO_CORE_NAMED_TYPES :: #config(ODIN_CORE_FLAGS_NO_CORE_NAMED_TYPES, false)
|
||||
|
||||
// Override support for parsing `time` types.
|
||||
IMPORTING_TIME :: #config(ODIN_CORE_FLAGS_USE_TIME, time.IS_SUPPORTED)
|
||||
|
||||
// Override support for parsing `net` types.
|
||||
// TODO: Update this when the BSDs are supported.
|
||||
IMPORTING_NET :: #config(ODIN_CORE_FLAGS_USE_NET, ODIN_OS == .Windows || ODIN_OS == .Linux || ODIN_OS == .Darwin)
|
||||
|
||||
TAG_ARGS :: "args"
|
||||
SUBTAG_NAME :: "name"
|
||||
SUBTAG_POS :: "pos"
|
||||
SUBTAG_REQUIRED :: "required"
|
||||
SUBTAG_HIDDEN :: "hidden"
|
||||
SUBTAG_VARIADIC :: "variadic"
|
||||
SUBTAG_FILE :: "file"
|
||||
SUBTAG_PERMS :: "perms"
|
||||
SUBTAG_INDISTINCT :: "indistinct"
|
||||
|
||||
TAG_USAGE :: "usage"
|
||||
|
||||
UNDOCUMENTED_FLAG :: "<This flag has not been documented yet.>"
|
||||
|
||||
INTERNAL_VARIADIC_FLAG :: "varg"
|
||||
|
||||
RESERVED_HELP_FLAG :: "help"
|
||||
RESERVED_HELP_FLAG_SHORT :: "h"
|
||||
|
||||
// If there are more than this number of flags in total, only the required and
|
||||
// positional flags will be shown in the one-line usage summary.
|
||||
ONE_LINE_FLAG_CUTOFF_COUNT :: 16
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
package flags implements a command-line argument parser.
|
||||
|
||||
It works by using Odin's run-time type information to determine where and how
|
||||
to store data on a struct provided by the program. Type conversion is handled
|
||||
automatically and errors are reported with useful messages.
|
||||
|
||||
|
||||
Command-Line Syntax:
|
||||
|
||||
Arguments are treated differently depending on how they're formatted.
|
||||
The format is similar to the Odin binary's way of handling compiler flags.
|
||||
|
||||
```
|
||||
type handling
|
||||
------------ ------------------------
|
||||
<positional> depends on struct layout
|
||||
-<flag> set a bool true
|
||||
-<flag:option> set flag to option
|
||||
-<flag=option> set flag to option, alternative syntax
|
||||
-<map>:<key>=<value> set map[key] to value
|
||||
```
|
||||
|
||||
|
||||
Struct Tags:
|
||||
|
||||
Users of the `core:encoding/json` package may be familiar with using tags to
|
||||
annotate struct metadata. The same technique is used here to annotate where
|
||||
arguments should go and which are required.
|
||||
|
||||
Under the `args` tag, there are the following subtags:
|
||||
|
||||
- `name=S`: set `S` as the flag's name.
|
||||
- `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.
|
||||
- `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.
|
||||
|
||||
`required` may be given a range specifier in the following formats:
|
||||
```
|
||||
min
|
||||
<max
|
||||
min<max
|
||||
```
|
||||
|
||||
`max` is not inclusive in this range, as noted by the less-than `<` sign, so if
|
||||
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.
|
||||
|
||||
|
||||
`file` determines the file open mode for an `os.Handle`.
|
||||
It accepts a string of flags that can be mixed together:
|
||||
- r: read
|
||||
- w: write
|
||||
- c: create, create the file if it doesn't exist
|
||||
- a: append, add any new writes to the end of the file
|
||||
- t: truncate, erase the file on open
|
||||
|
||||
|
||||
`perms` determines the file open permissions for an `os.Handle`.
|
||||
|
||||
The permissions are represented by three numbers in octal format. The first
|
||||
number is the owner, the second is the group, and the third is other. Read is
|
||||
represented by 4, write by 2, and execute by 1.
|
||||
|
||||
These numbers are added together to get combined permissions. For example, 644
|
||||
represents read/write for the owner, read for the group, and read for other.
|
||||
|
||||
Note that this may only have effect on UNIX-like platforms. By default, `perms`
|
||||
is set to 444 when only reading and 644 when writing.
|
||||
|
||||
|
||||
`indistinct` tells the parser that it's okay to treat distinct types as their
|
||||
underlying base type. Normally, the parser will hand those types off to the
|
||||
custom type setter (more about that later) if one is available, if it doesn't
|
||||
know how to handle the type.
|
||||
|
||||
|
||||
Usage Tag:
|
||||
|
||||
There is also the `usage` tag, which is a plain string to be printed alongside
|
||||
the flag in the usage output. If `usage` contains a newline, it will be
|
||||
properly aligned when printed.
|
||||
|
||||
All surrounding whitespace is trimmed when formatting with multiple lines.
|
||||
|
||||
|
||||
Supported Flag Data Types:
|
||||
|
||||
- all booleans
|
||||
- all integers
|
||||
- all floats
|
||||
- all enums
|
||||
- all complex numbers
|
||||
- all quaternions
|
||||
- all bit_sets
|
||||
- `string` and `cstring`
|
||||
- `rune`
|
||||
- `os.Handle`
|
||||
- `time.Time`
|
||||
- `datetime.DateTime`
|
||||
- `net.Host_Or_Endpoint`,
|
||||
- additional custom types, see Custom Types below
|
||||
- `dynamic` arrays with element types of the above
|
||||
- `map[string]`s or `map[cstring]`s with value types of the above
|
||||
|
||||
|
||||
Validation:
|
||||
|
||||
The parser will ensure `required` arguments are set, if no errors occurred
|
||||
during parsing. This is on by default.
|
||||
|
||||
Additionally, you may call `register_flag_checker` to set your own argument
|
||||
validation procedure that will be called after the default checker.
|
||||
|
||||
|
||||
Strict:
|
||||
|
||||
The parser will return on the first error and stop parsing. This is on by
|
||||
default. Otherwise, all arguments that can be parsed, will be, and only the
|
||||
last error is returned.
|
||||
|
||||
|
||||
Error Messages:
|
||||
|
||||
All error message strings are allocated using the context's `temp_allocator`,
|
||||
so if you need them to persist, make sure to clone the underlying `message`.
|
||||
|
||||
|
||||
Help:
|
||||
|
||||
By default, `-h` and `-help` are reserved flags which raise their own error
|
||||
type when set, allowing the program to handle the request differently from
|
||||
other errors.
|
||||
|
||||
|
||||
Custom Types:
|
||||
|
||||
You may specify your own type setter for program-specific structs and other
|
||||
named types. Call `register_type_setter` with an appropriate proc before
|
||||
calling any of the parsing procs.
|
||||
|
||||
A compliant `Custom_Type_Setter` must return three values:
|
||||
- an error message if one occurred,
|
||||
- a boolean indicating if the proc handles the type, and
|
||||
- an `Allocator_Error` if any occurred.
|
||||
|
||||
If the setter does not handle the type, simply return without setting any of
|
||||
the values.
|
||||
|
||||
|
||||
UNIX-style:
|
||||
|
||||
This package also supports parsing arguments in a limited flavor of UNIX.
|
||||
Odin and UNIX style are mutually exclusive, and which one to be used is chosen
|
||||
at parse time.
|
||||
|
||||
```
|
||||
--flag
|
||||
--flag=argument
|
||||
--flag argument
|
||||
--flag argument repeating-argument
|
||||
```
|
||||
|
||||
`-flag` may also be substituted for `--flag`.
|
||||
|
||||
Do note that map flags are not currently supported in this parsing style.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
A complete example is given in the `example` subdirectory.
|
||||
*/
|
||||
package flags
|
||||
@@ -0,0 +1,50 @@
|
||||
package flags
|
||||
|
||||
import "core:os"
|
||||
|
||||
Parse_Error_Reason :: enum {
|
||||
None,
|
||||
// An extra positional argument was given, and there is no `varg` field.
|
||||
Extra_Positional,
|
||||
// The underlying type does not support the string value it is being set to.
|
||||
Bad_Value,
|
||||
// No flag was given by the user.
|
||||
No_Flag,
|
||||
// No value was given by the user.
|
||||
No_Value,
|
||||
// The flag on the struct is missing.
|
||||
Missing_Flag,
|
||||
// The type itself isn't supported.
|
||||
Unsupported_Type,
|
||||
}
|
||||
|
||||
// Raised during parsing, naturally.
|
||||
Parse_Error :: struct {
|
||||
reason: Unified_Parse_Error_Reason,
|
||||
message: string,
|
||||
}
|
||||
|
||||
// Raised during parsing.
|
||||
// Provides more granular information than what just a string could hold.
|
||||
Open_File_Error :: struct {
|
||||
filename: string,
|
||||
errno: os.Errno,
|
||||
mode: int,
|
||||
perms: int,
|
||||
}
|
||||
|
||||
// Raised during parsing.
|
||||
Help_Request :: distinct bool
|
||||
|
||||
|
||||
// Raised after parsing, during validation.
|
||||
Validation_Error :: struct {
|
||||
message: string,
|
||||
}
|
||||
|
||||
Error :: union {
|
||||
Parse_Error,
|
||||
Open_File_Error,
|
||||
Help_Request,
|
||||
Validation_Error,
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
//+build freebsd, netbsd, openbsd
|
||||
package flags
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
Unified_Parse_Error_Reason :: union #shared_nil {
|
||||
Parse_Error_Reason,
|
||||
runtime.Allocator_Error,
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
//+build !freebsd !netbsd !openbsd
|
||||
package flags
|
||||
|
||||
import "base:runtime"
|
||||
import "core:net"
|
||||
|
||||
Unified_Parse_Error_Reason :: union #shared_nil {
|
||||
Parse_Error_Reason,
|
||||
runtime.Allocator_Error,
|
||||
net.Parse_Endpoint_Error,
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package core_flags_example
|
||||
|
||||
import "base:runtime"
|
||||
import "core:flags"
|
||||
import "core:fmt"
|
||||
import "core:net"
|
||||
import "core:os"
|
||||
import "core:time/datetime"
|
||||
|
||||
|
||||
Fixed_Point1_1 :: struct {
|
||||
integer: u8,
|
||||
fractional: u8,
|
||||
}
|
||||
|
||||
Optimization_Level :: enum {
|
||||
Slow,
|
||||
Fast,
|
||||
Warp_Speed,
|
||||
Ludicrous_Speed,
|
||||
}
|
||||
|
||||
// It's simple but powerful.
|
||||
my_custom_type_setter :: proc(
|
||||
data: rawptr,
|
||||
data_type: typeid,
|
||||
unparsed_value: string,
|
||||
args_tag: string,
|
||||
) -> (
|
||||
error: string,
|
||||
handled: bool,
|
||||
alloc_error: runtime.Allocator_Error,
|
||||
) {
|
||||
if data_type == Fixed_Point1_1 {
|
||||
handled = true
|
||||
ptr := cast(^Fixed_Point1_1)data
|
||||
|
||||
// precision := flags.get_subtag(args_tag, "precision")
|
||||
|
||||
if len(unparsed_value) == 3 {
|
||||
ptr.integer = unparsed_value[0] - '0'
|
||||
ptr.fractional = unparsed_value[2] - '0'
|
||||
} else {
|
||||
error = "Incorrect format. Must be in the form of `i.f`."
|
||||
}
|
||||
|
||||
// Perform sanity checking here in the type parsing phase.
|
||||
//
|
||||
// The validation phase is flag-specific.
|
||||
if !(0 <= ptr.integer && ptr.integer < 10) || !(0 <= ptr.fractional && ptr.fractional < 10) {
|
||||
error = "Incorrect format. Must be between `0.0` and `9.9`."
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
my_custom_flag_checker :: proc(
|
||||
model: rawptr,
|
||||
name: string,
|
||||
value: any,
|
||||
args_tag: string,
|
||||
) -> (error: string) {
|
||||
if name == "iterations" {
|
||||
v := value.(int)
|
||||
if !(1 <= v && v < 5) {
|
||||
error = "Iterations only supports 1 ..< 5."
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
Distinct_Int :: distinct int
|
||||
|
||||
main :: proc() {
|
||||
Options :: struct {
|
||||
|
||||
file: os.Handle `args:"pos=0,required,file=r" usage:"Input file."`,
|
||||
output: os.Handle `args:"pos=1,file=cw" usage:"Output file."`,
|
||||
|
||||
hub: net.Host_Or_Endpoint `usage:"Internet address to contact for updates."`,
|
||||
schedule: datetime.DateTime `usage:"Launch tasks at this time."`,
|
||||
|
||||
opt: Optimization_Level `usage:"Optimization level."`,
|
||||
todo: [dynamic]string `usage:"Todo items."`,
|
||||
|
||||
accuracy: Fixed_Point1_1 `args:"required" usage:"Lenience in FLOP calculations."`,
|
||||
iterations: int `usage:"Run this many times."`,
|
||||
|
||||
// Note how the parser will transform this flag's name into `special-int`.
|
||||
special_int: Distinct_Int `args:"indistinct" usage:"Able to set distinct types."`,
|
||||
|
||||
quat: quaternion256,
|
||||
|
||||
bits: bit_set[0..<8],
|
||||
|
||||
// Many different requirement styles:
|
||||
|
||||
// gadgets: [dynamic]string `args:"required=1" usage:"gadgets"`,
|
||||
// widgets: [dynamic]string `args:"required=<3" usage:"widgets"`,
|
||||
// foos: [dynamic]string `args:"required=2<4"`,
|
||||
// bars: [dynamic]string `args:"required=3<4"`,
|
||||
// bots: [dynamic]string `args:"required"`,
|
||||
|
||||
// (Maps) Only available in Odin style:
|
||||
|
||||
// assignments: map[string]u8 `args:"name=assign" usage:"Number of jobs per worker."`,
|
||||
|
||||
// (Variadic) Only available in UNIX style:
|
||||
|
||||
// bots: [dynamic]string `args:"variadic=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."`,
|
||||
}
|
||||
|
||||
opt: Options
|
||||
style : flags.Parsing_Style = .Odin
|
||||
|
||||
flags.register_type_setter(my_custom_type_setter)
|
||||
flags.register_flag_checker(my_custom_flag_checker)
|
||||
flags.parse_or_exit(&opt, os.args, style)
|
||||
|
||||
fmt.printfln("%#v", opt)
|
||||
|
||||
if opt.output != 0 {
|
||||
os.write_string(opt.output, "Hellope!\n")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
//+private
|
||||
package flags
|
||||
|
||||
import "base:intrinsics"
|
||||
@require import "base:runtime"
|
||||
import "core:container/bit_array"
|
||||
@require import "core:fmt"
|
||||
@require import "core:mem"
|
||||
import "core:reflect"
|
||||
@require import "core:strconv"
|
||||
@require import "core:strings"
|
||||
|
||||
// Push a positional argument onto a data struct, checking for specified
|
||||
// positionals first before adding it to a fallback field.
|
||||
@(optimization_mode="favor_size")
|
||||
push_positional :: #force_no_inline proc (model: ^$T, parser: ^Parser, arg: string) -> (error: Error) {
|
||||
if bit_array.get(&parser.filled_pos, parser.filled_pos.max_index) {
|
||||
// The max index is set, which means we're out of space.
|
||||
// Add one free bit by setting the index above to false.
|
||||
bit_array.set(&parser.filled_pos, 1 + parser.filled_pos.max_index, false)
|
||||
}
|
||||
|
||||
pos: int = ---
|
||||
{
|
||||
iter := bit_array.make_iterator(&parser.filled_pos)
|
||||
ok: bool
|
||||
pos, ok = bit_array.iterate_by_unset(&iter)
|
||||
|
||||
// This may be an allocator error.
|
||||
assert(ok, "Unable to find a free spot in the positional bit_array.")
|
||||
}
|
||||
|
||||
field, index, has_pos_assigned := get_field_by_pos(model, pos)
|
||||
|
||||
if !has_pos_assigned {
|
||||
when intrinsics.type_has_field(T, INTERNAL_VARIADIC_FLAG) {
|
||||
// Add it to the fallback array.
|
||||
field = reflect.struct_field_by_name(T, INTERNAL_VARIADIC_FLAG)
|
||||
} else {
|
||||
return Parse_Error {
|
||||
.Extra_Positional,
|
||||
fmt.tprintf("Got extra positional argument `%s` with nowhere to store it.", arg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ptr := cast(rawptr)(cast(uintptr)model + field.offset)
|
||||
args_tag, _ := reflect.struct_tag_lookup(field.tag, TAG_ARGS)
|
||||
field_name := get_field_name(field)
|
||||
error = parse_and_set_pointer_by_type(ptr, arg, field.type, args_tag)
|
||||
#partial switch &specific_error in error {
|
||||
case Parse_Error:
|
||||
specific_error.message = fmt.tprintf("Unable to set positional #%i (%s) of type %v to `%s`.%s%s",
|
||||
pos,
|
||||
field_name,
|
||||
field.type,
|
||||
arg,
|
||||
" " if len(specific_error.message) > 0 else "",
|
||||
specific_error.message)
|
||||
case nil:
|
||||
bit_array.set(&parser.filled_pos, pos)
|
||||
bit_array.set(&parser.fields_set, index)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
register_field :: proc(parser: ^Parser, field: reflect.Struct_Field, index: int) {
|
||||
if pos, ok := get_field_pos(field); ok {
|
||||
bit_array.set(&parser.filled_pos, pos)
|
||||
}
|
||||
|
||||
bit_array.set(&parser.fields_set, index)
|
||||
}
|
||||
|
||||
// Set a `-flag` argument, Odin-style.
|
||||
@(optimization_mode="favor_size")
|
||||
set_odin_flag :: proc(model: ^$T, parser: ^Parser, name: string) -> (error: Error) {
|
||||
// We make a special case for help requests.
|
||||
switch name {
|
||||
case RESERVED_HELP_FLAG, RESERVED_HELP_FLAG_SHORT:
|
||||
return Help_Request{}
|
||||
}
|
||||
|
||||
field, index := get_field_by_name(model, name) or_return
|
||||
|
||||
#partial switch specific_type_info in field.type.variant {
|
||||
case runtime.Type_Info_Boolean:
|
||||
ptr := cast(^bool)(cast(uintptr)model + field.offset)
|
||||
ptr^ = true
|
||||
case:
|
||||
return Parse_Error {
|
||||
.Bad_Value,
|
||||
fmt.tprintf("Unable to set `%s` of type %v to true.", name, field.type),
|
||||
}
|
||||
}
|
||||
|
||||
register_field(parser, field, index)
|
||||
return
|
||||
}
|
||||
|
||||
// Set a `-flag` argument, UNIX-style.
|
||||
@(optimization_mode="favor_size")
|
||||
set_unix_flag :: proc(model: ^$T, parser: ^Parser, name: string) -> (future_args: int, error: Error) {
|
||||
// We make a special case for help requests.
|
||||
switch name {
|
||||
case RESERVED_HELP_FLAG, RESERVED_HELP_FLAG_SHORT:
|
||||
return 0, Help_Request{}
|
||||
}
|
||||
|
||||
field, index := get_field_by_name(model, name) or_return
|
||||
|
||||
#partial switch specific_type_info in field.type.variant {
|
||||
case runtime.Type_Info_Boolean:
|
||||
ptr := cast(^bool)(cast(uintptr)model + field.offset)
|
||||
ptr^ = true
|
||||
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.
|
||||
// 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
|
||||
} else {
|
||||
future_args = max(int)
|
||||
}
|
||||
}
|
||||
}
|
||||
case:
|
||||
// `--flag`, waiting on its value.
|
||||
future_args = 1
|
||||
}
|
||||
|
||||
register_field(parser, field, index)
|
||||
return
|
||||
}
|
||||
|
||||
// Set a `-flag:option` argument.
|
||||
@(optimization_mode="favor_size")
|
||||
set_option :: proc(model: ^$T, parser: ^Parser, name, option: string) -> (error: Error) {
|
||||
field, index := get_field_by_name(model, name) or_return
|
||||
|
||||
if len(option) == 0 {
|
||||
return Parse_Error {
|
||||
.No_Value,
|
||||
fmt.tprintf("Setting `%s` to an empty value is meaningless.", name),
|
||||
}
|
||||
}
|
||||
|
||||
// Guard against incorrect syntax.
|
||||
#partial switch specific_type_info in field.type.variant {
|
||||
case runtime.Type_Info_Map:
|
||||
return Parse_Error {
|
||||
.No_Value,
|
||||
fmt.tprintf("Unable to set `%s` of type %v to `%s`. Are you missing an `=`? The correct format is `map:key=value`.", name, field.type, option),
|
||||
}
|
||||
}
|
||||
|
||||
ptr := cast(rawptr)(cast(uintptr)model + field.offset)
|
||||
args_tag := reflect.struct_tag_get(field.tag, TAG_ARGS)
|
||||
error = parse_and_set_pointer_by_type(ptr, option, field.type, args_tag)
|
||||
#partial switch &specific_error in error {
|
||||
case Parse_Error:
|
||||
specific_error.message = fmt.tprintf("Unable to set `%s` of type %v to `%s`.%s%s",
|
||||
name,
|
||||
field.type,
|
||||
option,
|
||||
" " if len(specific_error.message) > 0 else "",
|
||||
specific_error.message)
|
||||
case nil:
|
||||
register_field(parser, field, index)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Set a `-map:key=value` argument.
|
||||
@(optimization_mode="favor_size")
|
||||
set_key_value :: proc(model: ^$T, parser: ^Parser, name, key, value: string) -> (error: Error) {
|
||||
field, index := get_field_by_name(model, name) or_return
|
||||
|
||||
#partial switch specific_type_info in field.type.variant {
|
||||
case runtime.Type_Info_Map:
|
||||
key := key
|
||||
key_ptr := cast(rawptr)&key
|
||||
key_cstr: cstring
|
||||
if reflect.is_cstring(specific_type_info.key) {
|
||||
// We clone the key here, because it's liable to be a slice of an
|
||||
// Odin string, and we need to put a NUL terminator in it.
|
||||
key_cstr = strings.clone_to_cstring(key)
|
||||
key_ptr = &key_cstr
|
||||
}
|
||||
defer if key_cstr != nil {
|
||||
delete(key_cstr)
|
||||
}
|
||||
|
||||
raw_map := (^runtime.Raw_Map)(cast(uintptr)model + field.offset)
|
||||
|
||||
hash := specific_type_info.map_info.key_hasher(key_ptr, runtime.map_seed(raw_map^))
|
||||
|
||||
backing_alloc := false
|
||||
elem_backing: []byte
|
||||
value_ptr: rawptr
|
||||
|
||||
if raw_map.allocator.procedure == nil {
|
||||
raw_map.allocator = context.allocator
|
||||
} else {
|
||||
value_ptr = runtime.__dynamic_map_get(raw_map,
|
||||
specific_type_info.map_info,
|
||||
hash,
|
||||
key_ptr,
|
||||
)
|
||||
}
|
||||
|
||||
if value_ptr == nil {
|
||||
alloc_error: runtime.Allocator_Error = ---
|
||||
elem_backing, alloc_error = mem.alloc_bytes(specific_type_info.value.size, specific_type_info.value.align)
|
||||
if elem_backing == nil {
|
||||
return Parse_Error {
|
||||
alloc_error,
|
||||
"Failed to allocate element backing for map value.",
|
||||
}
|
||||
}
|
||||
|
||||
backing_alloc = true
|
||||
value_ptr = raw_data(elem_backing)
|
||||
}
|
||||
|
||||
args_tag, _ := reflect.struct_tag_lookup(field.tag, TAG_ARGS)
|
||||
error = parse_and_set_pointer_by_type(value_ptr, value, specific_type_info.value, args_tag)
|
||||
#partial switch &specific_error in error {
|
||||
case Parse_Error:
|
||||
specific_error.message = fmt.tprintf("Unable to set `%s` of type %v with key=value: `%s`=`%s`.%s%s",
|
||||
name,
|
||||
field.type,
|
||||
key,
|
||||
value,
|
||||
" " if len(specific_error.message) > 0 else "",
|
||||
specific_error.message)
|
||||
}
|
||||
|
||||
if backing_alloc {
|
||||
runtime.__dynamic_map_set(raw_map,
|
||||
specific_type_info.map_info,
|
||||
hash,
|
||||
key_ptr,
|
||||
value_ptr,
|
||||
)
|
||||
|
||||
delete(elem_backing)
|
||||
}
|
||||
|
||||
register_field(parser, field, index)
|
||||
return
|
||||
}
|
||||
|
||||
return Parse_Error {
|
||||
.Bad_Value,
|
||||
fmt.tprintf("Unable to set `%s` of type %v with key=value: `%s`=`%s`.", name, field.type, key, value),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
//+private
|
||||
package flags
|
||||
|
||||
import "core:container/bit_array"
|
||||
import "core:strconv"
|
||||
import "core:strings"
|
||||
|
||||
// Used to group state together.
|
||||
Parser :: struct {
|
||||
// `fields_set` tracks which arguments have been set.
|
||||
// It uses their struct field index.
|
||||
fields_set: bit_array.Bit_Array,
|
||||
|
||||
// `filled_pos` tracks which arguments have been filled into positional
|
||||
// spots, much like how `fmt` treats them.
|
||||
filled_pos: bit_array.Bit_Array,
|
||||
}
|
||||
|
||||
parse_one_odin_arg :: proc(model: ^$T, parser: ^Parser, arg: string) -> (error: Error) {
|
||||
arg := arg
|
||||
|
||||
if strings.has_prefix(arg, "-") {
|
||||
arg = arg[1:]
|
||||
|
||||
flag: string
|
||||
assignment_rune: rune
|
||||
find_assignment: for r, i in arg {
|
||||
switch r {
|
||||
case ':', '=':
|
||||
assignment_rune = r
|
||||
flag = arg[:i]
|
||||
arg = arg[1 + i:]
|
||||
break find_assignment
|
||||
case:
|
||||
continue find_assignment
|
||||
}
|
||||
}
|
||||
|
||||
if assignment_rune == 0 {
|
||||
if len(arg) == 0 {
|
||||
return Parse_Error {
|
||||
.No_Flag,
|
||||
"No flag was given.",
|
||||
}
|
||||
}
|
||||
|
||||
// -flag
|
||||
set_odin_flag(model, parser, arg) or_return
|
||||
|
||||
} else if assignment_rune == ':' {
|
||||
// -flag:option <OR> -map:key=value
|
||||
error = set_option(model, parser, flag, arg)
|
||||
|
||||
if error != nil {
|
||||
// -flag:option did not work, so this may be a -map:key=value set.
|
||||
find_equals: for r, i in arg {
|
||||
if r == '=' {
|
||||
key := arg[:i]
|
||||
arg = arg[1 + i:]
|
||||
error = set_key_value(model, parser, flag, key, arg)
|
||||
break find_equals
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// -flag=option, alternative syntax
|
||||
set_option(model, parser, flag, arg) or_return
|
||||
}
|
||||
|
||||
} else {
|
||||
// positional
|
||||
error = push_positional(model, parser, arg)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
parse_one_unix_arg :: proc(model: ^$T, parser: ^Parser, arg: string) -> (
|
||||
future_args: int,
|
||||
current_flag: string,
|
||||
error: Error,
|
||||
) {
|
||||
arg := arg
|
||||
|
||||
if strings.has_prefix(arg, "-") {
|
||||
// -flag
|
||||
arg = arg[1:]
|
||||
|
||||
if strings.has_prefix(arg, "-") {
|
||||
// Allow `--` to function as `-`.
|
||||
arg = arg[1:]
|
||||
|
||||
if len(arg) == 0 {
|
||||
// `--`, and only `--`.
|
||||
// Everything from now on will be treated as an argument.
|
||||
future_args = max(int)
|
||||
current_flag = INTERNAL_VARIADIC_FLAG
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
flag: string
|
||||
find_assignment: for r, i in arg {
|
||||
if r == '=' {
|
||||
// --flag=option
|
||||
flag = arg[:i]
|
||||
arg = arg[1 + i:]
|
||||
error = set_option(model, parser, flag, arg)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// --flag option, potentially
|
||||
future_args = set_unix_flag(model, parser, arg) or_return
|
||||
current_flag = arg
|
||||
|
||||
} else {
|
||||
// positional
|
||||
error = push_positional(model, parser, arg)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Parse a number of requirements specifier.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// `min`
|
||||
// `<max`
|
||||
// `min<max`
|
||||
parse_requirements :: proc(str: string) -> (minimum, maximum: int, ok: bool) {
|
||||
if len(str) == 0 {
|
||||
return 1, max(int), true
|
||||
}
|
||||
|
||||
if less_than := strings.index_byte(str, '<'); less_than != -1 {
|
||||
if len(str) == 1 {
|
||||
return 0, 0, false
|
||||
}
|
||||
|
||||
#no_bounds_check left := str[:less_than]
|
||||
#no_bounds_check right := str[1 + less_than:]
|
||||
|
||||
if left_value, parse_ok := strconv.parse_u64_of_base(left, 10); parse_ok {
|
||||
minimum = cast(int)left_value
|
||||
} else if len(left) > 0 {
|
||||
return 0, 0, false
|
||||
}
|
||||
|
||||
if right_value, parse_ok := strconv.parse_u64_of_base(right, 10); parse_ok {
|
||||
maximum = cast(int)right_value
|
||||
} else if len(right) > 0 {
|
||||
return 0, 0, false
|
||||
} else {
|
||||
maximum = max(int)
|
||||
}
|
||||
} else {
|
||||
if value, parse_ok := strconv.parse_u64_of_base(str, 10); parse_ok {
|
||||
minimum = cast(int)value
|
||||
maximum = max(int)
|
||||
} else {
|
||||
return 0, 0, false
|
||||
}
|
||||
}
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,529 @@
|
||||
//+private
|
||||
package flags
|
||||
|
||||
import "base:intrinsics"
|
||||
import "base:runtime"
|
||||
import "core:fmt"
|
||||
import "core:mem"
|
||||
import "core:os"
|
||||
import "core:reflect"
|
||||
import "core:strconv"
|
||||
import "core:strings"
|
||||
@require import "core:time"
|
||||
@require import "core:time/datetime"
|
||||
import "core:unicode/utf8"
|
||||
|
||||
@(optimization_mode="favor_size")
|
||||
parse_and_set_pointer_by_base_type :: proc(ptr: rawptr, str: string, type_info: ^runtime.Type_Info) -> bool {
|
||||
bounded_int :: proc(value, min, max: i128) -> (result: i128, ok: bool) {
|
||||
return value, min <= value && value <= max
|
||||
}
|
||||
|
||||
bounded_uint :: proc(value, max: u128) -> (result: u128, ok: bool) {
|
||||
return value, value <= max
|
||||
}
|
||||
|
||||
// NOTE(Feoramund): This procedure has been written with the goal in mind
|
||||
// of generating the least amount of assembly, given that this library is
|
||||
// likely to be called once and forgotten.
|
||||
//
|
||||
// I've rewritten the switch tables below in 3 different ways, and the
|
||||
// current one generates the least amount of code for me on Linux AMD64.
|
||||
//
|
||||
// The other two ways were:
|
||||
//
|
||||
// - the original implementation: use of parametric polymorphism which led
|
||||
// to dozens of functions generated, one for each type.
|
||||
//
|
||||
// - a `value, ok` assignment statement with the `or_return` done at the
|
||||
// end of the switch, instead of inline.
|
||||
//
|
||||
// This seems to be the smallest way for now.
|
||||
|
||||
#partial switch specific_type_info in type_info.variant {
|
||||
case runtime.Type_Info_Integer:
|
||||
if specific_type_info.signed {
|
||||
value := strconv.parse_i128(str) or_return
|
||||
switch type_info.id {
|
||||
case i8: (^i8) (ptr)^ = cast(i8) bounded_int(value, cast(i128)min(i8), cast(i128)max(i8) ) or_return
|
||||
case i16: (^i16) (ptr)^ = cast(i16) bounded_int(value, cast(i128)min(i16), cast(i128)max(i16) ) or_return
|
||||
case i32: (^i32) (ptr)^ = cast(i32) bounded_int(value, cast(i128)min(i32), cast(i128)max(i32) ) or_return
|
||||
case i64: (^i64) (ptr)^ = cast(i64) bounded_int(value, cast(i128)min(i64), cast(i128)max(i64) ) or_return
|
||||
case i128: (^i128) (ptr)^ = value
|
||||
|
||||
case int: (^int) (ptr)^ = cast(int) bounded_int(value, cast(i128)min(int), cast(i128)max(int) ) or_return
|
||||
|
||||
case i16le: (^i16le) (ptr)^ = cast(i16le) bounded_int(value, cast(i128)min(i16le), cast(i128)max(i16le) ) or_return
|
||||
case i32le: (^i32le) (ptr)^ = cast(i32le) bounded_int(value, cast(i128)min(i32le), cast(i128)max(i32le) ) or_return
|
||||
case i64le: (^i64le) (ptr)^ = cast(i64le) bounded_int(value, cast(i128)min(i64le), cast(i128)max(i64le) ) or_return
|
||||
case i128le: (^i128le)(ptr)^ = cast(i128le) bounded_int(value, cast(i128)min(i128le), cast(i128)max(i128le)) or_return
|
||||
|
||||
case i16be: (^i16be) (ptr)^ = cast(i16be) bounded_int(value, cast(i128)min(i16be), cast(i128)max(i16be) ) or_return
|
||||
case i32be: (^i32be) (ptr)^ = cast(i32be) bounded_int(value, cast(i128)min(i32be), cast(i128)max(i32be) ) or_return
|
||||
case i64be: (^i64be) (ptr)^ = cast(i64be) bounded_int(value, cast(i128)min(i64be), cast(i128)max(i64be) ) or_return
|
||||
case i128be: (^i128be)(ptr)^ = cast(i128be) bounded_int(value, cast(i128)min(i128be), cast(i128)max(i128be)) or_return
|
||||
}
|
||||
} else {
|
||||
value := strconv.parse_u128(str) or_return
|
||||
switch type_info.id {
|
||||
case u8: (^u8) (ptr)^ = cast(u8) bounded_uint(value, cast(u128)max(u8) ) or_return
|
||||
case u16: (^u16) (ptr)^ = cast(u16) bounded_uint(value, cast(u128)max(u16) ) or_return
|
||||
case u32: (^u32) (ptr)^ = cast(u32) bounded_uint(value, cast(u128)max(u32) ) or_return
|
||||
case u64: (^u64) (ptr)^ = cast(u64) bounded_uint(value, cast(u128)max(u64) ) or_return
|
||||
case u128: (^u128) (ptr)^ = value
|
||||
|
||||
case uint: (^uint) (ptr)^ = cast(uint) bounded_uint(value, cast(u128)max(uint) ) or_return
|
||||
case uintptr: (^uintptr)(ptr)^ = cast(uintptr) bounded_uint(value, cast(u128)max(uintptr)) or_return
|
||||
|
||||
case u16le: (^u16le) (ptr)^ = cast(u16le) bounded_uint(value, cast(u128)max(u16le) ) or_return
|
||||
case u32le: (^u32le) (ptr)^ = cast(u32le) bounded_uint(value, cast(u128)max(u32le) ) or_return
|
||||
case u64le: (^u64le) (ptr)^ = cast(u64le) bounded_uint(value, cast(u128)max(u64le) ) or_return
|
||||
case u128le: (^u128le) (ptr)^ = cast(u128le) bounded_uint(value, cast(u128)max(u128le) ) or_return
|
||||
|
||||
case u16be: (^u16be) (ptr)^ = cast(u16be) bounded_uint(value, cast(u128)max(u16be) ) or_return
|
||||
case u32be: (^u32be) (ptr)^ = cast(u32be) bounded_uint(value, cast(u128)max(u32be) ) or_return
|
||||
case u64be: (^u64be) (ptr)^ = cast(u64be) bounded_uint(value, cast(u128)max(u64be) ) or_return
|
||||
case u128be: (^u128be) (ptr)^ = cast(u128be) bounded_uint(value, cast(u128)max(u128be) ) or_return
|
||||
}
|
||||
}
|
||||
|
||||
case runtime.Type_Info_Rune:
|
||||
if utf8.rune_count_in_string(str) != 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
(^rune)(ptr)^ = utf8.rune_at_pos(str, 0)
|
||||
|
||||
case runtime.Type_Info_Float:
|
||||
value := strconv.parse_f64(str) or_return
|
||||
switch type_info.id {
|
||||
case f16: (^f16) (ptr)^ = cast(f16) value
|
||||
case f32: (^f32) (ptr)^ = cast(f32) value
|
||||
case f64: (^f64) (ptr)^ = value
|
||||
|
||||
case f16le: (^f16le)(ptr)^ = cast(f16le) value
|
||||
case f32le: (^f32le)(ptr)^ = cast(f32le) value
|
||||
case f64le: (^f64le)(ptr)^ = cast(f64le) value
|
||||
|
||||
case f16be: (^f16be)(ptr)^ = cast(f16be) value
|
||||
case f32be: (^f32be)(ptr)^ = cast(f32be) value
|
||||
case f64be: (^f64be)(ptr)^ = cast(f64be) value
|
||||
}
|
||||
|
||||
case runtime.Type_Info_Complex:
|
||||
value := strconv.parse_complex128(str) or_return
|
||||
switch type_info.id {
|
||||
case complex32: (^complex32) (ptr)^ = (complex32)(value)
|
||||
case complex64: (^complex64) (ptr)^ = (complex64)(value)
|
||||
case complex128: (^complex128)(ptr)^ = value
|
||||
}
|
||||
|
||||
case runtime.Type_Info_Quaternion:
|
||||
value := strconv.parse_quaternion256(str) or_return
|
||||
switch type_info.id {
|
||||
case quaternion64: (^quaternion64) (ptr)^ = (quaternion64)(value)
|
||||
case quaternion128: (^quaternion128)(ptr)^ = (quaternion128)(value)
|
||||
case quaternion256: (^quaternion256)(ptr)^ = value
|
||||
}
|
||||
|
||||
case runtime.Type_Info_String:
|
||||
if specific_type_info.is_cstring {
|
||||
cstr_ptr := (^cstring)(ptr)
|
||||
if cstr_ptr != nil {
|
||||
// Prevent memory leaks from us setting this value multiple times.
|
||||
delete(cstr_ptr^)
|
||||
}
|
||||
cstr_ptr^ = strings.clone_to_cstring(str)
|
||||
} else {
|
||||
(^string)(ptr)^ = str
|
||||
}
|
||||
|
||||
case runtime.Type_Info_Boolean:
|
||||
value := strconv.parse_bool(str) or_return
|
||||
switch type_info.id {
|
||||
case bool: (^bool)(ptr)^ = value
|
||||
case b8: (^b8) (ptr)^ = b8(value)
|
||||
case b16: (^b16) (ptr)^ = b16(value)
|
||||
case b32: (^b32) (ptr)^ = b32(value)
|
||||
case b64: (^b64) (ptr)^ = b64(value)
|
||||
}
|
||||
|
||||
case runtime.Type_Info_Bit_Set:
|
||||
// Parse a string of 1's and 0's, from left to right,
|
||||
// least significant bit to most significant bit.
|
||||
value: u128
|
||||
|
||||
// NOTE: `upper` is inclusive, i.e: `0..=31`
|
||||
max_bit_index := u128(1 + specific_type_info.upper - specific_type_info.lower)
|
||||
bit_index := u128(0)
|
||||
#no_bounds_check for string_index in 0..<uint(len(str)) {
|
||||
if bit_index == max_bit_index {
|
||||
// The string's too long for this bit_set.
|
||||
return false
|
||||
}
|
||||
|
||||
switch str[string_index] {
|
||||
case '1':
|
||||
value |= 1 << bit_index
|
||||
bit_index += 1
|
||||
case '0':
|
||||
bit_index += 1
|
||||
continue
|
||||
case '_':
|
||||
continue
|
||||
case:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if specific_type_info.underlying != nil {
|
||||
set_unbounded_integer_by_type(ptr, value, specific_type_info.underlying.id)
|
||||
} else {
|
||||
switch 8*type_info.size {
|
||||
case 8: (^u8) (ptr)^ = cast(u8) value
|
||||
case 16: (^u16) (ptr)^ = cast(u16) value
|
||||
case 32: (^u32) (ptr)^ = cast(u32) value
|
||||
case 64: (^u64) (ptr)^ = cast(u64) value
|
||||
case 128: (^u128)(ptr)^ = value
|
||||
}
|
||||
}
|
||||
|
||||
case:
|
||||
fmt.panicf("Unsupported base data type: %v", specific_type_info)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// This proc exists to make error handling easier, since everything in the base
|
||||
// type one above works on booleans. It's a simple parsing error if it's false.
|
||||
//
|
||||
// However, here we have to be more careful about how we handle errors,
|
||||
// especially with files.
|
||||
//
|
||||
// We want to provide as informative as an error as we can.
|
||||
@(optimization_mode="favor_size", disabled=NO_CORE_NAMED_TYPES)
|
||||
parse_and_set_pointer_by_named_type :: proc(ptr: rawptr, str: string, data_type: typeid, arg_tag: string, out_error: ^Error) {
|
||||
// Core types currently supported:
|
||||
//
|
||||
// - os.Handle
|
||||
// - time.Time
|
||||
// - datetime.DateTime
|
||||
// - net.Host_Or_Endpoint
|
||||
|
||||
GENERIC_RFC_3339_ERROR :: "Invalid RFC 3339 string. Try this format: `yyyy-mm-ddThh:mm:ssZ`, for example `2024-02-29T16:30:00Z`."
|
||||
|
||||
out_error^ = nil
|
||||
|
||||
if data_type == os.Handle {
|
||||
// NOTE: `os` is hopefully available everywhere, even if it might panic on some calls.
|
||||
wants_read := false
|
||||
wants_write := false
|
||||
mode: int
|
||||
|
||||
if file, ok := get_struct_subtag(arg_tag, SUBTAG_FILE); ok {
|
||||
for i in 0..<len(file) {
|
||||
#no_bounds_check switch file[i] {
|
||||
case 'r': wants_read = true
|
||||
case 'w': wants_write = true
|
||||
case 'c': mode |= os.O_CREATE
|
||||
case 'a': mode |= os.O_APPEND
|
||||
case 't': mode |= os.O_TRUNC
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sane default.
|
||||
// owner/group/other: r--r--r--
|
||||
perms: int = 0o444
|
||||
|
||||
if wants_read && wants_write {
|
||||
mode |= os.O_RDWR
|
||||
perms |= 0o200
|
||||
} else if wants_write {
|
||||
mode |= os.O_WRONLY
|
||||
perms |= 0o200
|
||||
} else {
|
||||
mode |= os.O_RDONLY
|
||||
}
|
||||
|
||||
if permstr, ok := get_struct_subtag(arg_tag, SUBTAG_PERMS); ok {
|
||||
if value, parse_ok := strconv.parse_u64_of_base(permstr, 8); parse_ok {
|
||||
perms = int(value)
|
||||
}
|
||||
}
|
||||
|
||||
handle, errno := os.open(str, mode, perms)
|
||||
if errno != 0 {
|
||||
// NOTE(Feoramund): os.Errno is system-dependent, and there's
|
||||
// currently no good way to translate them all into strings.
|
||||
//
|
||||
// The upcoming `os2` package will hopefully solve this.
|
||||
//
|
||||
// We can at least provide the number for now, so the user can look
|
||||
// it up.
|
||||
out_error^ = Open_File_Error {
|
||||
str,
|
||||
errno,
|
||||
mode,
|
||||
perms,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
(^os.Handle)(ptr)^ = handle
|
||||
return
|
||||
}
|
||||
|
||||
when IMPORTING_TIME {
|
||||
if data_type == time.Time {
|
||||
// NOTE: The leap second data is discarded.
|
||||
res, consumed := time.rfc3339_to_time_utc(str)
|
||||
if consumed == 0 {
|
||||
// The RFC 3339 parsing facilities provide no indication as to what
|
||||
// went wrong, so just treat it as a regular parsing error.
|
||||
out_error^ = Parse_Error {
|
||||
.Bad_Value,
|
||||
GENERIC_RFC_3339_ERROR,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
(^time.Time)(ptr)^ = res
|
||||
return
|
||||
} else if data_type == datetime.DateTime {
|
||||
// NOTE: The UTC offset and leap second data are discarded.
|
||||
res, _, _, consumed := time.rfc3339_to_components(str)
|
||||
if consumed == 0 {
|
||||
out_error^ = Parse_Error {
|
||||
.Bad_Value,
|
||||
GENERIC_RFC_3339_ERROR,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
(^datetime.DateTime)(ptr)^ = res
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
when IMPORTING_NET {
|
||||
if try_net_parse_workaround(data_type, str, ptr, out_error) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
out_error ^= Parse_Error {
|
||||
// The caller will add more details.
|
||||
.Unsupported_Type,
|
||||
"",
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="favor_size")
|
||||
set_unbounded_integer_by_type :: proc(ptr: rawptr, value: $T, data_type: typeid) where intrinsics.type_is_integer(T) {
|
||||
switch data_type {
|
||||
case i8: (^i8) (ptr)^ = cast(i8) value
|
||||
case i16: (^i16) (ptr)^ = cast(i16) value
|
||||
case i32: (^i32) (ptr)^ = cast(i32) value
|
||||
case i64: (^i64) (ptr)^ = cast(i64) value
|
||||
case i128: (^i128) (ptr)^ = cast(i128) value
|
||||
|
||||
case int: (^int) (ptr)^ = cast(int) value
|
||||
|
||||
case i16le: (^i16le) (ptr)^ = cast(i16le) value
|
||||
case i32le: (^i32le) (ptr)^ = cast(i32le) value
|
||||
case i64le: (^i64le) (ptr)^ = cast(i64le) value
|
||||
case i128le: (^i128le) (ptr)^ = cast(i128le) value
|
||||
|
||||
case i16be: (^i16be) (ptr)^ = cast(i16be) value
|
||||
case i32be: (^i32be) (ptr)^ = cast(i32be) value
|
||||
case i64be: (^i64be) (ptr)^ = cast(i64be) value
|
||||
case i128be: (^i128be) (ptr)^ = cast(i128be) value
|
||||
|
||||
case u8: (^u8) (ptr)^ = cast(u8) value
|
||||
case u16: (^u16) (ptr)^ = cast(u16) value
|
||||
case u32: (^u32) (ptr)^ = cast(u32) value
|
||||
case u64: (^u64) (ptr)^ = cast(u64) value
|
||||
case u128: (^u128) (ptr)^ = cast(u128) value
|
||||
|
||||
case uint: (^uint) (ptr)^ = cast(uint) value
|
||||
case uintptr: (^uintptr)(ptr)^ = cast(uintptr) value
|
||||
|
||||
case u16le: (^u16le) (ptr)^ = cast(u16le) value
|
||||
case u32le: (^u32le) (ptr)^ = cast(u32le) value
|
||||
case u64le: (^u64le) (ptr)^ = cast(u64le) value
|
||||
case u128le: (^u128le) (ptr)^ = cast(u128le) value
|
||||
|
||||
case u16be: (^u16be) (ptr)^ = cast(u16be) value
|
||||
case u32be: (^u32be) (ptr)^ = cast(u32be) value
|
||||
case u64be: (^u64be) (ptr)^ = cast(u64be) value
|
||||
case u128be: (^u128be) (ptr)^ = cast(u128be) value
|
||||
|
||||
case rune: (^rune) (ptr)^ = cast(rune) value
|
||||
|
||||
case:
|
||||
fmt.panicf("Unsupported integer backing type: %v", data_type)
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="favor_size")
|
||||
parse_and_set_pointer_by_type :: proc(ptr: rawptr, str: string, type_info: ^runtime.Type_Info, arg_tag: string) -> (error: Error) {
|
||||
#partial switch specific_type_info in type_info.variant {
|
||||
case runtime.Type_Info_Named:
|
||||
if global_custom_type_setter != nil {
|
||||
// The program gets to go first.
|
||||
error_message, handled, alloc_error := global_custom_type_setter(ptr, type_info.id, str, arg_tag)
|
||||
|
||||
if alloc_error != nil {
|
||||
// There was an allocation error. Bail out.
|
||||
return Parse_Error {
|
||||
alloc_error,
|
||||
"Custom type setter encountered allocation error.",
|
||||
}
|
||||
}
|
||||
|
||||
if handled {
|
||||
// The program handled the type.
|
||||
|
||||
if len(error_message) != 0 {
|
||||
// However, there was an error. Pass it along.
|
||||
error = Parse_Error {
|
||||
.Bad_Value,
|
||||
error_message,
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Might be a named enum. Need to check here first, since we handle all enums.
|
||||
if enum_type_info, is_enum := specific_type_info.base.variant.(runtime.Type_Info_Enum); is_enum {
|
||||
if value, ok := reflect.enum_from_name_any(type_info.id, str); ok {
|
||||
set_unbounded_integer_by_type(ptr, value, enum_type_info.base.id)
|
||||
} else {
|
||||
return Parse_Error {
|
||||
.Bad_Value,
|
||||
fmt.tprintf("Invalid value name. Valid names are: %s", enum_type_info.names),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
parse_and_set_pointer_by_named_type(ptr, str, type_info.id, arg_tag, &error)
|
||||
|
||||
if error != nil {
|
||||
// So far, it's none of the types that we recognize.
|
||||
// Check to see if we can set it by base type, if allowed.
|
||||
if _, is_indistinct := get_struct_subtag(arg_tag, SUBTAG_INDISTINCT); is_indistinct {
|
||||
return parse_and_set_pointer_by_type(ptr, str, specific_type_info.base, arg_tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case runtime.Type_Info_Dynamic_Array:
|
||||
ptr := cast(^runtime.Raw_Dynamic_Array)ptr
|
||||
|
||||
// Try to convert the value first.
|
||||
elem_backing, alloc_error := mem.alloc_bytes(specific_type_info.elem.size, specific_type_info.elem.align)
|
||||
if alloc_error != nil {
|
||||
return Parse_Error {
|
||||
alloc_error,
|
||||
"Failed to allocate element backing for dynamic array.",
|
||||
}
|
||||
}
|
||||
defer delete(elem_backing)
|
||||
parse_and_set_pointer_by_type(raw_data(elem_backing), str, specific_type_info.elem, arg_tag) or_return
|
||||
|
||||
if !runtime.__dynamic_array_resize(ptr, specific_type_info.elem.size, specific_type_info.elem.align, ptr.len + 1) {
|
||||
// NOTE: This is purely an assumption that it's OOM.
|
||||
// Regardless, the resize failed.
|
||||
return Parse_Error {
|
||||
runtime.Allocator_Error.Out_Of_Memory,
|
||||
"Failed to resize dynamic array.",
|
||||
}
|
||||
}
|
||||
|
||||
subptr := rawptr(
|
||||
uintptr(ptr.data) +
|
||||
uintptr((ptr.len - 1) * specific_type_info.elem.size))
|
||||
mem.copy(subptr, raw_data(elem_backing), len(elem_backing))
|
||||
|
||||
case runtime.Type_Info_Enum:
|
||||
// This is a nameless enum.
|
||||
// The code here is virtually the same as above for named enums.
|
||||
if value, ok := reflect.enum_from_name_any(type_info.id, str); ok {
|
||||
set_unbounded_integer_by_type(ptr, value, specific_type_info.base.id)
|
||||
} else {
|
||||
return Parse_Error {
|
||||
.Bad_Value,
|
||||
fmt.tprintf("Invalid value name. Valid names are: %s", specific_type_info.names),
|
||||
}
|
||||
}
|
||||
|
||||
case:
|
||||
if !parse_and_set_pointer_by_base_type(ptr, str, type_info) {
|
||||
return Parse_Error {
|
||||
// The caller will add more details.
|
||||
.Bad_Value,
|
||||
"",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
get_struct_subtag :: get_subtag
|
||||
|
||||
get_field_name :: proc(field: reflect.Struct_Field) -> string {
|
||||
if args_tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok {
|
||||
if name_subtag, name_ok := get_struct_subtag(args_tag, SUBTAG_NAME); name_ok {
|
||||
return name_subtag
|
||||
}
|
||||
}
|
||||
|
||||
name, _ := strings.replace_all(field.name, "_", "-", context.temp_allocator)
|
||||
return name
|
||||
}
|
||||
|
||||
get_field_pos :: proc(field: reflect.Struct_Field) -> (int, bool) {
|
||||
if args_tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok {
|
||||
if pos_subtag, pos_ok := get_struct_subtag(args_tag, SUBTAG_POS); pos_ok {
|
||||
if value, parse_ok := strconv.parse_u64_of_base(pos_subtag, 10); parse_ok {
|
||||
return int(value), true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Get a struct field by its field name or `name` subtag.
|
||||
get_field_by_name :: proc(model: ^$T, name: string) -> (result: reflect.Struct_Field, index: int, error: Error) {
|
||||
for field, i in reflect.struct_fields_zipped(T) {
|
||||
if get_field_name(field) == name {
|
||||
return field, i, nil
|
||||
}
|
||||
}
|
||||
|
||||
error = Parse_Error {
|
||||
.Missing_Flag,
|
||||
fmt.tprintf("Unable to find any flag named `%s`.", name),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Get a struct field by its `pos` subtag.
|
||||
get_field_by_pos :: proc(model: ^$T, pos: int) -> (result: reflect.Struct_Field, index: int, ok: bool) {
|
||||
for field, i in reflect.struct_fields_zipped(T) {
|
||||
args_tag := reflect.struct_tag_lookup(field.tag, TAG_ARGS) or_continue
|
||||
pos_subtag := get_struct_subtag(args_tag, SUBTAG_POS) or_continue
|
||||
|
||||
value, parse_ok := strconv.parse_u64_of_base(pos_subtag, 10)
|
||||
if parse_ok && cast(int)value == pos {
|
||||
return field, i, true
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
//+private
|
||||
//+build !freebsd !netbsd !openbsd
|
||||
package flags
|
||||
|
||||
import "core:net"
|
||||
|
||||
// This proc exists purely as a workaround for import restrictions.
|
||||
// Returns true if caller should return early.
|
||||
try_net_parse_workaround :: #force_inline proc (
|
||||
data_type: typeid,
|
||||
str: string,
|
||||
ptr: rawptr,
|
||||
out_error: ^Error,
|
||||
) -> bool {
|
||||
if data_type == net.Host_Or_Endpoint {
|
||||
addr, net_error := net.parse_hostname_or_endpoint(str)
|
||||
if net_error != nil {
|
||||
// We pass along `net.Error` here.
|
||||
out_error^ = Parse_Error {
|
||||
net_error,
|
||||
"Invalid Host/Endpoint.",
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
(cast(^net.Host_Or_Endpoint)ptr)^ = addr
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
//+private
|
||||
package flags
|
||||
|
||||
@require import "base:runtime"
|
||||
@require import "core:container/bit_array"
|
||||
@require import "core:fmt"
|
||||
@require import "core:mem"
|
||||
@require import "core:os"
|
||||
@require import "core:reflect"
|
||||
@require import "core:strconv"
|
||||
@require import "core:strings"
|
||||
|
||||
// This proc is used to assert that `T` meets the expectations of the library.
|
||||
@(optimization_mode="favor_size", disabled=ODIN_DISABLE_ASSERT)
|
||||
validate_structure :: proc(model_type: $T, style: Parsing_Style, loc := #caller_location) {
|
||||
positionals_assigned_so_far: bit_array.Bit_Array
|
||||
defer bit_array.destroy(&positionals_assigned_so_far)
|
||||
|
||||
check_fields: for field in reflect.struct_fields_zipped(T) {
|
||||
if style == .Unix {
|
||||
#partial switch specific_type_info in field.type.variant {
|
||||
case runtime.Type_Info_Map:
|
||||
fmt.panicf("%T.%s is a map type, and these are not supported in UNIX-style parsing mode.",
|
||||
model_type, field.name, loc = loc)
|
||||
}
|
||||
}
|
||||
|
||||
name_is_safe := true
|
||||
defer {
|
||||
fmt.assertf(name_is_safe, "%T.%s is using a reserved name.",
|
||||
model_type, field.name, loc = loc)
|
||||
}
|
||||
|
||||
switch field.name {
|
||||
case RESERVED_HELP_FLAG, RESERVED_HELP_FLAG_SHORT:
|
||||
name_is_safe = false
|
||||
}
|
||||
|
||||
args_tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS)
|
||||
if !ok {
|
||||
// If it has no args tag, then we've checked all we need to.
|
||||
// Most of this proc is validating that the subtags are sane.
|
||||
continue
|
||||
}
|
||||
|
||||
if name, has_name := get_struct_subtag(args_tag, SUBTAG_NAME); has_name {
|
||||
fmt.assertf(len(name) > 0, "%T.%s has a zero-length `%s`.",
|
||||
model_type, field.name, SUBTAG_NAME, loc = loc)
|
||||
|
||||
fmt.assertf(strings.index(name, " ") == -1, "%T.%s has a `%s` with spaces in it.",
|
||||
model_type, field.name, SUBTAG_NAME, loc = loc)
|
||||
|
||||
switch name {
|
||||
case RESERVED_HELP_FLAG, RESERVED_HELP_FLAG_SHORT:
|
||||
name_is_safe = false
|
||||
continue check_fields
|
||||
case:
|
||||
name_is_safe = true
|
||||
}
|
||||
}
|
||||
|
||||
if pos_str, has_pos := get_struct_subtag(args_tag, SUBTAG_POS); 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.",
|
||||
model_type, field.name, SUBTAG_POS, loc = loc)
|
||||
}
|
||||
|
||||
pos_value, pos_ok := strconv.parse_u64_of_base(pos_str, 10)
|
||||
fmt.assertf(pos_ok, "%T.%s has `%s` defined as %q but cannot be parsed a base-10 integer >= 0.",
|
||||
model_type, field.name, SUBTAG_POS, pos_str, loc = loc)
|
||||
fmt.assertf(!bit_array.get(&positionals_assigned_so_far, pos_value), "%T.%s has `%s` set to #%i, but that position has already been assigned to another flag.",
|
||||
model_type, field.name, SUBTAG_POS, pos_value, loc = loc)
|
||||
bit_array.set(&positionals_assigned_so_far, pos_value)
|
||||
}
|
||||
|
||||
required_min, required_max: int
|
||||
if requirement, is_required := get_struct_subtag(args_tag, SUBTAG_REQUIRED); is_required {
|
||||
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.",
|
||||
model_type, field.name, loc = loc)
|
||||
|
||||
if len(requirement) > 0 {
|
||||
if required_min, required_max, ok = parse_requirements(requirement); ok {
|
||||
#partial switch specific_type_info in field.type.variant {
|
||||
case runtime.Type_Info_Dynamic_Array:
|
||||
fmt.assertf(required_min != required_max, "%T.%s has `%s` defined as %q, but the minimum and maximum are the same. Increase the maximum by 1 for an exact number of arguments: (%i<%i)",
|
||||
model_type,
|
||||
field.name,
|
||||
SUBTAG_REQUIRED,
|
||||
requirement,
|
||||
required_min,
|
||||
1 + required_max,
|
||||
loc = loc)
|
||||
|
||||
fmt.assertf(required_min < required_max, "%T.%s has `%s` defined as %q, but the minimum and maximum are swapped.",
|
||||
model_type, field.name, SUBTAG_REQUIRED, requirement, loc = loc)
|
||||
|
||||
case:
|
||||
fmt.panicf("%T.%s has `%s` defined as %q, but ranges are only supported on dynamic arrays.",
|
||||
model_type, field.name, SUBTAG_REQUIRED, requirement, loc = loc)
|
||||
}
|
||||
} else {
|
||||
fmt.panicf("%T.%s has `%s` defined as %q, but it cannot be parsed as a valid range.",
|
||||
model_type, field.name, SUBTAG_REQUIRED, requirement, loc = loc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if length, is_variadic := get_struct_subtag(args_tag, SUBTAG_VARIADIC); is_variadic {
|
||||
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)
|
||||
fmt.assertf(value != 1,
|
||||
"%T.%s has `%s` set to 1. This has no effect.",
|
||||
model_type, field.name, SUBTAG_VARIADIC, 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)
|
||||
case:
|
||||
fmt.panicf("%T.%s has `%s` defined, but this only makes sense on dynamic arrays.",
|
||||
model_type, field.name, SUBTAG_VARIADIC, loc = loc)
|
||||
}
|
||||
}
|
||||
|
||||
allowed_to_define_file_perms: bool = ---
|
||||
#partial switch specific_type_info in field.type.variant {
|
||||
case runtime.Type_Info_Map:
|
||||
allowed_to_define_file_perms = specific_type_info.value.id == os.Handle
|
||||
case runtime.Type_Info_Dynamic_Array:
|
||||
allowed_to_define_file_perms = specific_type_info.elem.id == os.Handle
|
||||
case:
|
||||
allowed_to_define_file_perms = field.type.id == os.Handle
|
||||
}
|
||||
|
||||
if _, has_file := get_struct_subtag(args_tag, SUBTAG_FILE); has_file {
|
||||
fmt.assertf(allowed_to_define_file_perms, "%T.%s has `%s` defined, but it is not nor does it contain an `os.Handle` type.",
|
||||
model_type, field.name, SUBTAG_FILE, loc = loc)
|
||||
}
|
||||
|
||||
if _, has_perms := get_struct_subtag(args_tag, SUBTAG_PERMS); has_perms {
|
||||
fmt.assertf(allowed_to_define_file_perms, "%T.%s has `%s` defined, but it is not nor does it contain an `os.Handle` type.",
|
||||
model_type, field.name, SUBTAG_PERMS, loc = loc)
|
||||
}
|
||||
|
||||
#partial switch specific_type_info in field.type.variant {
|
||||
case runtime.Type_Info_Map:
|
||||
fmt.assertf(reflect.is_string(specific_type_info.key), "%T.%s is defined as a map[%T]. Only string types are currently supported as map keys.",
|
||||
model_type,
|
||||
field.name,
|
||||
specific_type_info.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate that all the required arguments are set and that the set arguments
|
||||
// are up to the program's expectations.
|
||||
@(optimization_mode="favor_size")
|
||||
validate_arguments :: proc(model: ^$T, parser: ^Parser) -> Error {
|
||||
check_fields: for field, index in reflect.struct_fields_zipped(T) {
|
||||
was_set := bit_array.get(&parser.fields_set, index)
|
||||
|
||||
field_name := get_field_name(field)
|
||||
args_tag := reflect.struct_tag_get(field.tag, TAG_ARGS)
|
||||
requirement, is_required := get_struct_subtag(args_tag, SUBTAG_REQUIRED)
|
||||
|
||||
required_min, required_max: int
|
||||
has_requirements: bool
|
||||
if is_required {
|
||||
required_min, required_max, has_requirements = parse_requirements(requirement)
|
||||
}
|
||||
|
||||
if has_requirements && required_min == 0 {
|
||||
// Allow `0<n` or `<n` to bypass the required condition.
|
||||
is_required = false
|
||||
}
|
||||
|
||||
if _, is_array := field.type.variant.(runtime.Type_Info_Dynamic_Array); is_array && has_requirements {
|
||||
// If it's an array, make sure it meets the required number of arguments.
|
||||
ptr := cast(^runtime.Raw_Dynamic_Array)(cast(uintptr)model + field.offset)
|
||||
if required_min == required_max - 1 && ptr.len != required_min {
|
||||
return Validation_Error {
|
||||
fmt.tprintf("The flag `%s` had %i option%s set, but it requires exactly %i.",
|
||||
field_name,
|
||||
ptr.len,
|
||||
"" if ptr.len == 1 else "s",
|
||||
required_min),
|
||||
}
|
||||
} else if required_min > ptr.len || ptr.len >= required_max {
|
||||
if required_max == max(int) {
|
||||
return Validation_Error {
|
||||
fmt.tprintf("The flag `%s` had %i option%s set, but it requires at least %i.",
|
||||
field_name,
|
||||
ptr.len,
|
||||
"" if ptr.len == 1 else "s",
|
||||
required_min),
|
||||
}
|
||||
} else {
|
||||
return Validation_Error {
|
||||
fmt.tprintf("The flag `%s` had %i option%s set, but it requires at least %i and at most %i.",
|
||||
field_name,
|
||||
ptr.len,
|
||||
"" if ptr.len == 1 else "s",
|
||||
required_min,
|
||||
required_max - 1),
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if !was_set {
|
||||
if is_required {
|
||||
return Validation_Error {
|
||||
fmt.tprintf("The required flag `%s` was not set.", field_name),
|
||||
}
|
||||
}
|
||||
|
||||
// Not set, not required; moving on.
|
||||
continue
|
||||
}
|
||||
|
||||
// All default checks have passed. The program gets a look at it now.
|
||||
|
||||
if global_custom_flag_checker != nil {
|
||||
ptr := cast(rawptr)(cast(uintptr)model + field.offset)
|
||||
error := global_custom_flag_checker(model,
|
||||
field.name,
|
||||
mem.make_any(ptr, field.type.id),
|
||||
args_tag)
|
||||
|
||||
if len(error) > 0 {
|
||||
// The program reported an error message.
|
||||
return Validation_Error { error }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package flags
|
||||
|
||||
@require import "core:container/bit_array"
|
||||
@require import "core:fmt"
|
||||
|
||||
Parsing_Style :: enum {
|
||||
// Odin-style: `-flag`, `-flag:option`, `-map:key=value`
|
||||
Odin,
|
||||
// UNIX-style: `-flag` or `--flag`, `--flag=argument`, `--flag argument repeating-argument`
|
||||
Unix,
|
||||
}
|
||||
|
||||
/*
|
||||
Parse a slice of command-line arguments into an annotated struct.
|
||||
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
By default, this proc will only allocate memory outside of its lifetime if it
|
||||
has to append to a dynamic array, set a map value, or set a cstring.
|
||||
|
||||
The program is expected to free any allocations on `model` as a result of parsing.
|
||||
|
||||
Inputs:
|
||||
- model: A pointer to an annotated struct with flag definitions.
|
||||
- args: A slice of strings, usually `os.args[1:]`.
|
||||
- style: The argument parsing style.
|
||||
- validate_args: If `true`, will ensure that all required arguments are set if no errors occurred.
|
||||
- strict: If `true`, will return on first error. Otherwise, parsing continues.
|
||||
- allocator: (default: context.allocator)
|
||||
- loc: The caller location for debugging purposes (default: #caller_location)
|
||||
|
||||
Returns:
|
||||
- error: A union of errors; parsing, file open, a help request, or validation.
|
||||
*/
|
||||
@(optimization_mode="favor_size")
|
||||
parse :: proc(
|
||||
model: ^$T,
|
||||
args: []string,
|
||||
style: Parsing_Style = .Odin,
|
||||
validate_args: bool = true,
|
||||
strict: bool = true,
|
||||
allocator := context.allocator,
|
||||
loc := #caller_location,
|
||||
) -> (error: Error) {
|
||||
context.allocator = allocator
|
||||
validate_structure(model^, style, loc)
|
||||
|
||||
parser: Parser
|
||||
defer {
|
||||
bit_array.destroy(&parser.filled_pos)
|
||||
bit_array.destroy(&parser.fields_set)
|
||||
}
|
||||
|
||||
switch style {
|
||||
case .Odin:
|
||||
for arg in args {
|
||||
error = parse_one_odin_arg(model, &parser, arg)
|
||||
if strict && error != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
case .Unix:
|
||||
// Support for `-flag argument (repeating-argument ...)`
|
||||
future_args: int
|
||||
current_flag: string
|
||||
|
||||
for i := 0; i < len(args); i += 1 {
|
||||
#no_bounds_check arg := args[i]
|
||||
future_args, current_flag, error = parse_one_unix_arg(model, &parser, arg)
|
||||
if strict && error != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for starting_future_args := future_args; future_args > 0; future_args -= 1 {
|
||||
i += 1
|
||||
if i == len(args) {
|
||||
if future_args == starting_future_args {
|
||||
return Parse_Error {
|
||||
.No_Value,
|
||||
fmt.tprintf("Expected a value for `%s` but none was given.", current_flag),
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
#no_bounds_check arg = args[i]
|
||||
|
||||
error = set_option(model, &parser, current_flag, arg)
|
||||
if strict && error != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if error == nil && validate_args {
|
||||
return validate_arguments(model, &parser)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package flags
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
/*
|
||||
Handle setting custom data types.
|
||||
|
||||
Inputs:
|
||||
- data: A raw pointer to the field where the data will go.
|
||||
- data_type: Type information on the underlying field.
|
||||
- unparsed_value: The unparsed string that the flag is being set to.
|
||||
- args_tag: The `args` tag from the struct's field.
|
||||
|
||||
Returns:
|
||||
- error: An error message, or an empty string if no error occurred.
|
||||
- handled: A boolean indicating if the setter handles this type.
|
||||
- alloc_error: If an allocation error occurred, return it here.
|
||||
*/
|
||||
Custom_Type_Setter :: #type proc(
|
||||
data: rawptr,
|
||||
data_type: typeid,
|
||||
unparsed_value: string,
|
||||
args_tag: string,
|
||||
) -> (
|
||||
error: string,
|
||||
handled: bool,
|
||||
alloc_error: runtime.Allocator_Error,
|
||||
)
|
||||
|
||||
@(private)
|
||||
global_custom_type_setter: Custom_Type_Setter
|
||||
|
||||
/*
|
||||
Set the global custom type setter.
|
||||
|
||||
Note that only one can be active at a time.
|
||||
|
||||
Inputs:
|
||||
- setter: The type setter. Pass `nil` to disable any previously set setter.
|
||||
*/
|
||||
register_type_setter :: proc(setter: Custom_Type_Setter) {
|
||||
global_custom_type_setter = setter
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
package flags
|
||||
|
||||
import "base:runtime"
|
||||
import "core:fmt"
|
||||
import "core:io"
|
||||
import "core:reflect"
|
||||
import "core:slice"
|
||||
import "core:strconv"
|
||||
import "core:strings"
|
||||
|
||||
/*
|
||||
Write out the documentation for the command-line arguments to a stream.
|
||||
|
||||
Inputs:
|
||||
- out: The stream to write to.
|
||||
- data_type: The typeid of the data structure to describe.
|
||||
- program: The name of the program, usually the first argument to `os.args`.
|
||||
- style: The argument parsing style, required to show flags in the proper style.
|
||||
*/
|
||||
@(optimization_mode="favor_size")
|
||||
write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", style: Parsing_Style = .Odin) {
|
||||
// All flags get their tags parsed so they can be reasoned about later.
|
||||
Flag :: struct {
|
||||
name: string,
|
||||
usage: string,
|
||||
type_description: string,
|
||||
full_length: int,
|
||||
pos: int,
|
||||
required_min, required_max: int,
|
||||
is_positional: bool,
|
||||
is_required: bool,
|
||||
is_boolean: bool,
|
||||
is_variadic: bool,
|
||||
variadic_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 {
|
||||
return .Greater
|
||||
} else if j.name == INTERNAL_VARIADIC_FLAG {
|
||||
return .Less
|
||||
}
|
||||
|
||||
// Handle positionals.
|
||||
if i.is_positional {
|
||||
if j.is_positional {
|
||||
return slice.cmp(i.pos, j.pos)
|
||||
} else {
|
||||
return .Less
|
||||
}
|
||||
} else {
|
||||
if j.is_positional {
|
||||
return .Greater
|
||||
}
|
||||
}
|
||||
|
||||
// Then required flags.
|
||||
if i.is_required {
|
||||
if !j.is_required {
|
||||
return .Less
|
||||
}
|
||||
} else if j.is_required {
|
||||
return .Greater
|
||||
}
|
||||
|
||||
// Finally, sort by name.
|
||||
return slice.cmp(i.name, j.name)
|
||||
}
|
||||
|
||||
describe_array_requirements :: proc(flag: Flag) -> (spec: string) {
|
||||
if flag.is_required {
|
||||
if flag.required_min == flag.required_max - 1 {
|
||||
spec = fmt.tprintf(", exactly %i", flag.required_min)
|
||||
} else if flag.required_min > 0 && flag.required_max == max(int) {
|
||||
spec = fmt.tprintf(", at least %i", flag.required_min)
|
||||
} else if flag.required_min == 0 && flag.required_max > 1 {
|
||||
spec = fmt.tprintf(", at most %i", flag.required_max - 1)
|
||||
} else if flag.required_min > 0 && flag.required_max > 1 {
|
||||
spec = fmt.tprintf(", between %i and %i", flag.required_min, flag.required_max - 1)
|
||||
} else {
|
||||
spec = ", required"
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
builder := strings.builder_make()
|
||||
defer strings.builder_destroy(&builder)
|
||||
|
||||
flag_prefix, flag_assignment: string = ---, ---
|
||||
switch style {
|
||||
case .Odin: flag_prefix = "-"; flag_assignment = ":"
|
||||
case .Unix: flag_prefix = "--"; flag_assignment = " "
|
||||
}
|
||||
|
||||
visible_flags: [dynamic]Flag
|
||||
defer delete(visible_flags)
|
||||
|
||||
longest_flag_length: int
|
||||
|
||||
for field in reflect.struct_fields_zipped(data_type) {
|
||||
flag: Flag
|
||||
|
||||
if args_tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok {
|
||||
if _, is_hidden := get_struct_subtag(args_tag, SUBTAG_HIDDEN); is_hidden {
|
||||
// Hidden flags stay hidden.
|
||||
continue
|
||||
}
|
||||
if pos_str, is_pos := get_struct_subtag(args_tag, SUBTAG_POS); is_pos {
|
||||
flag.is_positional = true
|
||||
if pos, parse_ok := strconv.parse_u64_of_base(pos_str, 10); parse_ok {
|
||||
flag.pos = cast(int)pos
|
||||
}
|
||||
}
|
||||
if requirement, is_required := get_struct_subtag(args_tag, SUBTAG_REQUIRED); is_required {
|
||||
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, parse_ok := strconv.parse_u64_of_base(length_str, 10); parse_ok {
|
||||
flag.variadic_length = cast(int)length
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flag.name = get_field_name(field)
|
||||
flag.is_boolean = reflect.is_boolean(field.type)
|
||||
|
||||
if usage, ok := reflect.struct_tag_lookup(field.tag, TAG_USAGE); ok {
|
||||
flag.usage = usage
|
||||
} else {
|
||||
flag.usage = UNDOCUMENTED_FLAG
|
||||
}
|
||||
|
||||
#partial switch specific_type_info in field.type.variant {
|
||||
case runtime.Type_Info_Map:
|
||||
flag.type_description = fmt.tprintf("<%v>=<%v>%s",
|
||||
specific_type_info.key.id,
|
||||
specific_type_info.value.id,
|
||||
", required" if flag.is_required else "")
|
||||
|
||||
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 {
|
||||
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,
|
||||
requirement_spec)
|
||||
}
|
||||
} else {
|
||||
flag.type_description = fmt.tprintf("<%v>%s", specific_type_info.elem.id,
|
||||
requirement_spec if len(requirement_spec) > 0 else ", multiple")
|
||||
}
|
||||
|
||||
case:
|
||||
if flag.is_boolean {
|
||||
/*
|
||||
if flag.is_required {
|
||||
flag.type_description = ", required"
|
||||
}
|
||||
*/
|
||||
} else {
|
||||
flag.type_description = fmt.tprintf("<%v>%s",
|
||||
field.type.id,
|
||||
", required" if flag.is_required else "")
|
||||
}
|
||||
}
|
||||
|
||||
if flag.name == INTERNAL_VARIADIC_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)
|
||||
} else {
|
||||
flag.full_length = len(flag_prefix) + len(flag.name) + len(flag_assignment) + len(flag.type_description)
|
||||
}
|
||||
|
||||
longest_flag_length = max(longest_flag_length, flag.full_length)
|
||||
|
||||
append(&visible_flags, flag)
|
||||
}
|
||||
|
||||
slice.sort_by_cmp(visible_flags[:], sort_flags)
|
||||
|
||||
// All the flags have been figured out now.
|
||||
|
||||
if len(program) > 0 {
|
||||
keep_it_short := len(visible_flags) >= ONE_LINE_FLAG_CUTOFF_COUNT
|
||||
|
||||
strings.write_string(&builder, "Usage:\n\t")
|
||||
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) {
|
||||
continue
|
||||
}
|
||||
|
||||
strings.write_byte(&builder, ' ')
|
||||
|
||||
if flag.name == INTERNAL_VARIADIC_FLAG {
|
||||
strings.write_string(&builder, "...")
|
||||
continue
|
||||
}
|
||||
|
||||
if !flag.is_required { strings.write_byte(&builder, '[') }
|
||||
if !flag.is_positional { strings.write_string(&builder, flag_prefix) }
|
||||
strings.write_string(&builder, flag.name)
|
||||
if !flag.is_required { strings.write_byte(&builder, ']') }
|
||||
}
|
||||
|
||||
strings.write_byte(&builder, '\n')
|
||||
}
|
||||
|
||||
if len(visible_flags) == 0 {
|
||||
// No visible flags. An unusual situation, but prevent any extra work.
|
||||
fmt.wprint(out, strings.to_string(builder))
|
||||
return
|
||||
}
|
||||
|
||||
strings.write_string(&builder, "Flags:\n")
|
||||
|
||||
// Divide the positional/required arguments and the non-required arguments.
|
||||
divider_index := -1
|
||||
for flag, i in visible_flags {
|
||||
if !flag.is_positional && !flag.is_required {
|
||||
divider_index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if divider_index == 0 {
|
||||
divider_index = -1
|
||||
}
|
||||
|
||||
for flag, i in visible_flags {
|
||||
if i == divider_index {
|
||||
SPACING :: 2 // Number of spaces before the '|' from below.
|
||||
strings.write_byte(&builder, '\t')
|
||||
spacing := strings.repeat(" ", SPACING + longest_flag_length, context.temp_allocator)
|
||||
strings.write_string(&builder, spacing)
|
||||
strings.write_string(&builder, "|\n")
|
||||
}
|
||||
|
||||
strings.write_byte(&builder, '\t')
|
||||
|
||||
if flag.name == INTERNAL_VARIADIC_FLAG {
|
||||
strings.write_string(&builder, flag.type_description)
|
||||
} else {
|
||||
strings.write_string(&builder, flag_prefix)
|
||||
strings.write_string(&builder, flag.name)
|
||||
if !flag.is_boolean {
|
||||
strings.write_string(&builder, flag_assignment)
|
||||
}
|
||||
strings.write_string(&builder, flag.type_description)
|
||||
}
|
||||
|
||||
if strings.contains_rune(flag.usage, '\n') {
|
||||
// Multi-line usage documentation. Let's make it look nice.
|
||||
usage_builder := strings.builder_make(context.temp_allocator)
|
||||
|
||||
strings.write_byte(&usage_builder, '\n')
|
||||
iter := strings.trim_space(flag.usage)
|
||||
for line in strings.split_lines_iterator(&iter) {
|
||||
strings.write_string(&usage_builder, "\t\t")
|
||||
strings.write_string(&usage_builder, strings.trim_left_space(line))
|
||||
strings.write_byte(&usage_builder, '\n')
|
||||
}
|
||||
|
||||
strings.write_string(&builder, strings.to_string(usage_builder))
|
||||
} else {
|
||||
// Single-line usage documentation.
|
||||
spacing := strings.repeat(" ",
|
||||
(longest_flag_length) - flag.full_length,
|
||||
context.temp_allocator)
|
||||
|
||||
strings.write_string(&builder, spacing)
|
||||
strings.write_string(&builder, " | ")
|
||||
strings.write_string(&builder, flag.usage)
|
||||
strings.write_byte(&builder, '\n')
|
||||
}
|
||||
}
|
||||
|
||||
fmt.wprint(out, strings.to_string(builder))
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package flags
|
||||
|
||||
import "core:fmt"
|
||||
@require import "core:os"
|
||||
@require import "core:path/filepath"
|
||||
import "core:strings"
|
||||
|
||||
/*
|
||||
Parse any arguments into an annotated struct or exit if there was an error.
|
||||
|
||||
*Allocates Using Provided Allocator*
|
||||
|
||||
This is a convenience wrapper over `parse` and `print_errors`.
|
||||
|
||||
Inputs:
|
||||
- model: A pointer to an annotated struct.
|
||||
- program_args: A slice of strings, usually `os.args`.
|
||||
- style: The argument parsing style.
|
||||
- allocator: (default: context.allocator)
|
||||
- loc: The caller location for debugging purposes (default: #caller_location)
|
||||
*/
|
||||
@(optimization_mode="favor_size")
|
||||
parse_or_exit :: proc(
|
||||
model: ^$T,
|
||||
program_args: []string,
|
||||
style: Parsing_Style = .Odin,
|
||||
allocator := context.allocator,
|
||||
loc := #caller_location,
|
||||
) {
|
||||
assert(len(program_args) > 0, "Program arguments slice is empty.", loc)
|
||||
|
||||
program := filepath.base(program_args[0])
|
||||
args: []string
|
||||
|
||||
if len(program_args) > 1 {
|
||||
args = program_args[1:]
|
||||
}
|
||||
|
||||
error := parse(model, args, style)
|
||||
if error != nil {
|
||||
stderr := os.stream_from_handle(os.stderr)
|
||||
|
||||
if len(args) == 0 {
|
||||
// No arguments entered, and there was an error; show the usage,
|
||||
// specifically on STDERR.
|
||||
write_usage(stderr, T, program, style)
|
||||
fmt.wprintln(stderr)
|
||||
}
|
||||
|
||||
print_errors(T, error, program, style)
|
||||
|
||||
_, was_help_request := error.(Help_Request)
|
||||
os.exit(0 if was_help_request else 1)
|
||||
}
|
||||
}
|
||||
/*
|
||||
Print out any errors that may have resulted from parsing.
|
||||
|
||||
All error messages print to STDERR, while usage goes to STDOUT, if requested.
|
||||
|
||||
Inputs:
|
||||
- data_type: The typeid of the data structure to describe, if usage is requested.
|
||||
- error: The error returned from `parse`.
|
||||
- style: The argument parsing style, required to show flags in the proper style, when usage is shown.
|
||||
*/
|
||||
@(optimization_mode="favor_size")
|
||||
print_errors :: proc(data_type: typeid, error: Error, program: string, style: Parsing_Style = .Odin) {
|
||||
stderr := os.stream_from_handle(os.stderr)
|
||||
stdout := os.stream_from_handle(os.stdout)
|
||||
|
||||
switch specific_error in error {
|
||||
case Parse_Error:
|
||||
fmt.wprintfln(stderr, "[%T.%v] %s", specific_error, specific_error.reason, specific_error.message)
|
||||
case Open_File_Error:
|
||||
fmt.wprintfln(stderr, "[%T#%i] Unable to open file with perms 0o%o in mode 0x%x: %s",
|
||||
specific_error,
|
||||
specific_error.errno,
|
||||
specific_error.perms,
|
||||
specific_error.mode,
|
||||
specific_error.filename)
|
||||
case Validation_Error:
|
||||
fmt.wprintfln(stderr, "[%T] %s", specific_error, specific_error.message)
|
||||
case Help_Request:
|
||||
write_usage(stdout, data_type, program, style)
|
||||
}
|
||||
}
|
||||
/*
|
||||
Get the value for a subtag.
|
||||
|
||||
This is useful if you need to parse through the `args` tag for a struct field
|
||||
on a custom type setter or custom flag checker.
|
||||
|
||||
Example:
|
||||
|
||||
import "core:flags"
|
||||
import "core:fmt"
|
||||
|
||||
subtag_example :: proc() {
|
||||
args_tag := "precision=3,signed"
|
||||
|
||||
precision, has_precision := flags.get_subtag(args_tag, "precision")
|
||||
signed, is_signed := flags.get_subtag(args_tag, "signed")
|
||||
|
||||
fmt.printfln("precision = %q, %t", precision, has_precision)
|
||||
fmt.printfln("signed = %q, %t", signed, is_signed)
|
||||
}
|
||||
|
||||
Output:
|
||||
|
||||
precision = "3", true
|
||||
signed = "", true
|
||||
|
||||
*/
|
||||
get_subtag :: proc(tag, id: string) -> (value: string, ok: bool) {
|
||||
// This proc was initially private in `internal_rtti.odin`, but given how
|
||||
// useful it would be to custom type setters and flag checkers, it lives
|
||||
// here now.
|
||||
|
||||
tag := tag
|
||||
|
||||
for subtag in strings.split_iterator(&tag, ",") {
|
||||
if equals := strings.index_byte(subtag, '='); equals != -1 && id == subtag[:equals] {
|
||||
return subtag[1 + equals:], true
|
||||
} else if id == subtag {
|
||||
return "", true
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package flags
|
||||
|
||||
/*
|
||||
Check a flag after parsing, during the validation stage.
|
||||
|
||||
Inputs:
|
||||
- model: A raw pointer to the data structure provided to `parse`.
|
||||
- name: The name of the flag being checked.
|
||||
- value: An `any` type that contains the value to be checked.
|
||||
- args_tag: The `args` tag from within the struct.
|
||||
|
||||
Returns:
|
||||
- error: An error message, or an empty string if no error occurred.
|
||||
*/
|
||||
Custom_Flag_Checker :: #type proc(
|
||||
model: rawptr,
|
||||
name: string,
|
||||
value: any,
|
||||
args_tag: string,
|
||||
) -> (
|
||||
error: string,
|
||||
)
|
||||
|
||||
@(private)
|
||||
global_custom_flag_checker: Custom_Flag_Checker
|
||||
|
||||
/*
|
||||
Set the global custom flag checker.
|
||||
|
||||
Note that only one can be active at a time.
|
||||
|
||||
Inputs:
|
||||
- checker: The flag checker. Pass `nil` to disable any previously set checker.
|
||||
*/
|
||||
register_flag_checker :: proc(checker: Custom_Flag_Checker) {
|
||||
global_custom_flag_checker = checker
|
||||
}
|
||||
+46
-24
@@ -92,7 +92,7 @@ _user_formatters: ^map[typeid]User_Formatter
|
||||
//
|
||||
set_user_formatters :: proc(m: ^map[typeid]User_Formatter) {
|
||||
assert(_user_formatters == nil, "set_user_formatters must not be called more than once.")
|
||||
_user_formatters = m
|
||||
_user_formatters = m
|
||||
}
|
||||
// Registers a user-defined formatter for a specific typeid
|
||||
//
|
||||
@@ -368,6 +368,25 @@ caprintf :: proc(format: string, args: ..any, newline := false) -> cstring {
|
||||
caprintfln :: proc(format: string, args: ..any) -> cstring {
|
||||
return caprintf(format, ..args, newline=true)
|
||||
}
|
||||
// Creates a formatted C string
|
||||
//
|
||||
// *Allocates Using Context's Temporary Allocator*
|
||||
//
|
||||
// Inputs:
|
||||
// - args: A variadic list of arguments to be formatted.
|
||||
// - sep: An optional separator string (default is a single space).
|
||||
//
|
||||
// Returns: A formatted C string.
|
||||
//
|
||||
@(require_results)
|
||||
ctprint :: proc(args: ..any, sep := " ") -> cstring {
|
||||
str: strings.Builder
|
||||
strings.builder_init(&str, context.temp_allocator)
|
||||
sbprint(&str, ..args, sep=sep)
|
||||
strings.write_byte(&str, 0)
|
||||
s := strings.to_string(str)
|
||||
return cstring(raw_data(s))
|
||||
}
|
||||
// Creates a formatted C string
|
||||
//
|
||||
// *Allocates Using Context's Temporary Allocator*
|
||||
@@ -932,10 +951,10 @@ fmt_bad_verb :: proc(fi: ^Info, verb: rune) {
|
||||
io.write_string(fi.writer, "%!", &fi.n)
|
||||
io.write_rune(fi.writer, verb, &fi.n)
|
||||
io.write_byte(fi.writer, '(', &fi.n)
|
||||
if fi.arg.id != nil {
|
||||
reflect.write_typeid(fi.writer, fi.arg.id, &fi.n)
|
||||
if arg := fi.arg; arg != nil {
|
||||
reflect.write_typeid(fi.writer, arg.id, &fi.n)
|
||||
io.write_byte(fi.writer, '=', &fi.n)
|
||||
fmt_value(fi, fi.arg, 'v')
|
||||
fmt_value(fi, arg, 'v')
|
||||
} else {
|
||||
io.write_string(fi.writer, "<nil>", &fi.n)
|
||||
}
|
||||
@@ -1053,8 +1072,8 @@ _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} }
|
||||
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)
|
||||
prev_zero := fi.zero
|
||||
defer fi.zero = prev_zero
|
||||
@@ -1138,8 +1157,8 @@ _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} }
|
||||
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)
|
||||
|
||||
if fi.hash && fi.zero && fi.indent == 0 {
|
||||
@@ -1210,10 +1229,10 @@ _fmt_memory :: proc(fi: ^Info, u: u64, is_signed: bool, bit_size: int, units: st
|
||||
// Add the unit at the end.
|
||||
copy(buf[len(str):], units[off:off+unit_len])
|
||||
str = string(buf[:len(str)+unit_len])
|
||||
|
||||
if !fi.plus {
|
||||
// Strip sign from "+<value>" but not "+Inf".
|
||||
if str[0] == '+' && str[1] != 'I' {
|
||||
|
||||
if !fi.plus {
|
||||
// Strip sign from "+<value>" but not "+Inf".
|
||||
if str[0] == '+' && str[1] != 'I' {
|
||||
str = str[1:]
|
||||
}
|
||||
}
|
||||
@@ -1441,13 +1460,10 @@ fmt_string :: proc(fi: ^Info, s: string, verb: rune) {
|
||||
if !fi.minus {
|
||||
io.write_string(fi.writer, s, &fi.n)
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
io.write_string(fi.writer, s, &fi.n)
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
io.write_string(fi.writer, s, &fi.n)
|
||||
}
|
||||
|
||||
@@ -1749,7 +1765,7 @@ fmt_bit_set :: proc(fi: ^Info, v: any, name: string = "", verb: rune = 'v') {
|
||||
|
||||
if is_enum {
|
||||
enum_name: string
|
||||
if ti_named, is_named := info.elem.variant.(runtime.Type_Info_Named); is_named {
|
||||
if ti_named, is_named := info.elem.variant.(runtime.Type_Info_Named); is_named {
|
||||
enum_name = ti_named.name
|
||||
}
|
||||
for ev, evi in e.values {
|
||||
@@ -1973,11 +1989,13 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St
|
||||
// fi.hash = false;
|
||||
fi.indent += 1
|
||||
|
||||
if !is_soa && hash {
|
||||
is_empty := len(info.names) == 0
|
||||
|
||||
if !is_soa && hash && !is_empty {
|
||||
io.write_byte(fi.writer, '\n', &fi.n)
|
||||
}
|
||||
defer {
|
||||
if hash {
|
||||
if !is_soa && hash && !is_empty {
|
||||
for _ in 0..<indent { io.write_byte(fi.writer, '\t', &fi.n) }
|
||||
}
|
||||
io.write_byte(fi.writer, ']' if is_soa && the_verb == 'v' else '}', &fi.n)
|
||||
@@ -2025,9 +2043,9 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St
|
||||
}
|
||||
io.write_string(fi.writer, base_type_name, &fi.n)
|
||||
io.write_byte(fi.writer, '{', &fi.n)
|
||||
if hash { io.write_byte(fi.writer, '\n', &fi.n) }
|
||||
if hash && !is_empty { io.write_byte(fi.writer, '\n', &fi.n) }
|
||||
defer {
|
||||
if hash {
|
||||
if hash && !is_empty {
|
||||
fi.indent -= 1
|
||||
fmt_write_indent(fi)
|
||||
fi.indent += 1
|
||||
@@ -2075,6 +2093,10 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St
|
||||
if hash { io.write_string(fi.writer, ",\n", &fi.n) }
|
||||
}
|
||||
}
|
||||
|
||||
if hash && n > 0 {
|
||||
for _ in 0..<indent { io.write_byte(fi.writer, '\t', &fi.n) }
|
||||
}
|
||||
} else {
|
||||
field_count := -1
|
||||
for name, i in info.names {
|
||||
@@ -2687,7 +2709,7 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
|
||||
return
|
||||
}
|
||||
if fi.indirection_level < 1 {
|
||||
fi.indirection_level += 1
|
||||
fi.indirection_level += 1
|
||||
defer fi.indirection_level -= 1
|
||||
io.write_byte(fi.writer, '&')
|
||||
fmt_value(fi, a, verb)
|
||||
@@ -2756,7 +2778,7 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
|
||||
runtime.Type_Info_Dynamic_Array,
|
||||
runtime.Type_Info_Map:
|
||||
if fi.indirection_level < 1 {
|
||||
fi.indirection_level += 1
|
||||
fi.indirection_level += 1
|
||||
defer fi.indirection_level -= 1
|
||||
io.write_byte(fi.writer, '&', &fi.n)
|
||||
fmt_value(fi, a, verb)
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
package hash
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
crc64_ecma_182 :: proc "contextless" (data: []byte, seed := u64(0)) -> (result: u64) #no_bounds_check {
|
||||
result = seed
|
||||
#no_bounds_check for b in data {
|
||||
@@ -14,7 +14,7 @@ crc64_ecma_182 :: proc "contextless" (data: []byte, seed := u64(0)) -> (result:
|
||||
bit-reversed, with one's complement pre and post processing.
|
||||
Based on Mark Adler's v1.4 implementation in C under the ZLIB license.
|
||||
*/
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
crc64_xz :: proc "contextless" (data: []byte, seed := u64(0)) -> u64 #no_bounds_check {
|
||||
data := data
|
||||
result := ~u64le(seed)
|
||||
@@ -52,7 +52,7 @@ crc64_xz :: proc "contextless" (data: []byte, seed := u64(0)) -> u64 #no_bounds_
|
||||
/*
|
||||
Generator polynomial: x^64 + x^4 + x^3 + x + 1
|
||||
*/
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
crc64_iso_3306 :: proc "contextless" (data: []byte, seed := u64(0)) -> u64 #no_bounds_check {
|
||||
|
||||
result := seed
|
||||
@@ -738,4 +738,4 @@ crc64_iso_3306_inverse :: proc "contextless" (data: []byte, seed := u64(0)) -> u
|
||||
0x9fc0, 0x9e70, 0x9ca0, 0x9d10,
|
||||
0x9480, 0x9530, 0x97e0, 0x9650,
|
||||
0x9240, 0x93f0, 0x9120, 0x9090,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package hash
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
crc32 :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 #no_bounds_check {
|
||||
crc := ~seed
|
||||
buffer := raw_data(data)
|
||||
|
||||
+11
-11
@@ -3,7 +3,7 @@ package hash
|
||||
import "core:mem"
|
||||
import "base:intrinsics"
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
adler32 :: proc "contextless" (data: []byte, seed := u32(1)) -> u32 #no_bounds_check {
|
||||
|
||||
ADLER_CONST :: 65521
|
||||
@@ -46,7 +46,7 @@ adler32 :: proc "contextless" (data: []byte, seed := u32(1)) -> u32 #no_bounds_c
|
||||
return (u32(b) << 16) | u32(a)
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
djb2 :: proc "contextless" (data: []byte, seed := u32(5381)) -> u32 {
|
||||
hash: u32 = seed
|
||||
for b in data {
|
||||
@@ -73,7 +73,7 @@ djbx33a :: proc "contextless" (data: []byte, seed := u32(5381)) -> (result: [16]
|
||||
}
|
||||
|
||||
// If you have a choice, prefer fnv32a
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
fnv32_no_a :: proc "contextless" (data: []byte, seed := u32(0x811c9dc5)) -> u32 {
|
||||
h: u32 = seed
|
||||
for b in data {
|
||||
@@ -86,7 +86,7 @@ fnv32 :: fnv32_no_a // NOTE(bill): Not a fan of these aliases but seems necessar
|
||||
fnv64 :: fnv64_no_a // NOTE(bill): Not a fan of these aliases but seems necessary
|
||||
|
||||
// If you have a choice, prefer fnv64a
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
fnv64_no_a :: proc "contextless" (data: []byte, seed := u64(0xcbf29ce484222325)) -> u64 {
|
||||
h: u64 = seed
|
||||
for b in data {
|
||||
@@ -94,7 +94,7 @@ fnv64_no_a :: proc "contextless" (data: []byte, seed := u64(0xcbf29ce484222325))
|
||||
}
|
||||
return h
|
||||
}
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
fnv32a :: proc "contextless" (data: []byte, seed := u32(0x811c9dc5)) -> u32 {
|
||||
h: u32 = seed
|
||||
for b in data {
|
||||
@@ -103,7 +103,7 @@ fnv32a :: proc "contextless" (data: []byte, seed := u32(0x811c9dc5)) -> u32 {
|
||||
return h
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
fnv64a :: proc "contextless" (data: []byte, seed := u64(0xcbf29ce484222325)) -> u64 {
|
||||
h: u64 = seed
|
||||
for b in data {
|
||||
@@ -112,7 +112,7 @@ fnv64a :: proc "contextless" (data: []byte, seed := u64(0xcbf29ce484222325)) ->
|
||||
return h
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
jenkins :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 {
|
||||
hash: u32 = seed
|
||||
for b in data {
|
||||
@@ -126,7 +126,7 @@ jenkins :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 {
|
||||
return hash
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
murmur32 :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 {
|
||||
c1_32: u32 : 0xcc9e2d51
|
||||
c2_32: u32 : 0x1b873593
|
||||
@@ -177,7 +177,7 @@ murmur32 :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 {
|
||||
}
|
||||
|
||||
// See https://github.com/aappleby/smhasher/blob/master/src/MurmurHash2.cpp#L96
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
murmur64a :: proc "contextless" (data: []byte, seed := u64(0x9747b28c)) -> u64 {
|
||||
m :: 0xc6a4a7935bd1e995
|
||||
r :: 47
|
||||
@@ -218,7 +218,7 @@ murmur64a :: proc "contextless" (data: []byte, seed := u64(0x9747b28c)) -> u64 {
|
||||
}
|
||||
|
||||
// See https://github.com/aappleby/smhasher/blob/master/src/MurmurHash2.cpp#L140
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
murmur64b :: proc "contextless" (data: []byte, seed := u64(0x9747b28c)) -> u64 {
|
||||
m :: 0x5bd1e995
|
||||
r :: 24
|
||||
@@ -286,7 +286,7 @@ murmur64b :: proc "contextless" (data: []byte, seed := u64(0x9747b28c)) -> u64 {
|
||||
return u64(h1)<<32 | u64(h2)
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
sdbm :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 {
|
||||
hash: u32 = seed
|
||||
for b in data {
|
||||
|
||||
@@ -67,17 +67,17 @@ when !XXH_DISABLE_PREFETCH {
|
||||
}
|
||||
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH_rotl32 :: #force_inline proc(x, r: u32) -> (res: u32) {
|
||||
return ((x << r) | (x >> (32 - r)))
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH_rotl64 :: #force_inline proc(x, r: u64) -> (res: u64) {
|
||||
return ((x << r) | (x >> (64 - r)))
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH32_read32 :: #force_inline proc(buf: []u8, alignment := Alignment.Unaligned) -> (res: u32) {
|
||||
if XXH_FORCE_MEMORY_ACCESS == 2 || alignment == .Aligned {
|
||||
#no_bounds_check b := (^u32le)(&buf[0])^
|
||||
@@ -89,7 +89,7 @@ XXH32_read32 :: #force_inline proc(buf: []u8, alignment := Alignment.Unaligned)
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH64_read64 :: #force_inline proc(buf: []u8, alignment := Alignment.Unaligned) -> (res: u64) {
|
||||
if XXH_FORCE_MEMORY_ACCESS == 2 || alignment == .Aligned {
|
||||
#no_bounds_check b := (^u64le)(&buf[0])^
|
||||
@@ -99,4 +99,4 @@ XXH64_read64 :: #force_inline proc(buf: []u8, alignment := Alignment.Unaligned)
|
||||
mem_copy(&b, raw_data(buf[:]), 8)
|
||||
return u64(b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,13 +111,13 @@ XXH128_canonical :: struct {
|
||||
@param lhs, rhs The 64-bit integers to multiply
|
||||
@return The low 64 bits of the product XOR'd by the high 64 bits.
|
||||
*/
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH_mul_64_to_128_fold_64 :: #force_inline proc(lhs, rhs: xxh_u64) -> (res: xxh_u64) {
|
||||
t := u128(lhs) * u128(rhs)
|
||||
return u64(t & 0xFFFFFFFFFFFFFFFF) ~ u64(t >> 64)
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH_xorshift_64 :: #force_inline proc(v: xxh_u64, #any_int shift: uint) -> (res: xxh_u64) {
|
||||
return v ~ (v >> shift)
|
||||
}
|
||||
@@ -125,7 +125,7 @@ XXH_xorshift_64 :: #force_inline proc(v: xxh_u64, #any_int shift: uint) -> (res:
|
||||
/*
|
||||
This is a fast avalanche stage, suitable when input bits are already partially mixed
|
||||
*/
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_avalanche :: #force_inline proc(h64: xxh_u64) -> (res: xxh_u64) {
|
||||
res = XXH_xorshift_64(h64, 37)
|
||||
res *= 0x165667919E3779F9
|
||||
@@ -137,7 +137,7 @@ XXH3_avalanche :: #force_inline proc(h64: xxh_u64) -> (res: xxh_u64) {
|
||||
This is a stronger avalanche, inspired by Pelle Evensen's rrmxmx
|
||||
preferable when input has not been previously mixed
|
||||
*/
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_rrmxmx :: #force_inline proc(h64, length: xxh_u64) -> (res: xxh_u64) {
|
||||
/* this mix is inspired by Pelle Evensen's rrmxmx */
|
||||
res = h64
|
||||
@@ -166,7 +166,7 @@ XXH3_rrmxmx :: #force_inline proc(h64, length: xxh_u64) -> (res: xxh_u64) {
|
||||
fast for a _128-bit_ hash on 32-bit (it usually clears XXH64).
|
||||
*/
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_len_1to3_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u128) {
|
||||
/* A doubled version of 1to3_64b with different constants. */
|
||||
length := len(input)
|
||||
@@ -190,7 +190,7 @@ XXH3_len_1to3_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u6
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_len_4to8_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u128) {
|
||||
length := len(input)
|
||||
seed := seed
|
||||
@@ -219,7 +219,7 @@ XXH3_len_4to8_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u6
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_len_9to16_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u128) {
|
||||
length := len(input)
|
||||
|
||||
@@ -261,7 +261,7 @@ XXH3_len_9to16_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u
|
||||
/*
|
||||
Assumption: `secret` size is >= XXH3_SECRET_SIZE_MIN
|
||||
*/
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_len_0to16_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u128) {
|
||||
length := len(input)
|
||||
|
||||
@@ -279,7 +279,7 @@ XXH3_len_0to16_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u
|
||||
/*
|
||||
A bit slower than XXH3_mix16B, but handles multiply by zero better.
|
||||
*/
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH128_mix32B :: #force_inline proc(acc: xxh_u128, input_1: []u8, input_2: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u128) {
|
||||
acc128 := XXH128_hash_t{
|
||||
h = acc,
|
||||
@@ -293,7 +293,7 @@ XXH128_mix32B :: #force_inline proc(acc: xxh_u128, input_1: []u8, input_2: []u8,
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_len_17to128_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u128) {
|
||||
length := len(input)
|
||||
|
||||
@@ -323,7 +323,7 @@ XXH3_len_17to128_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh
|
||||
unreachable()
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_len_129to240_128b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u128) {
|
||||
length := len(input)
|
||||
|
||||
@@ -379,7 +379,7 @@ XXH3_INIT_ACC :: [XXH_ACC_NB]xxh_u64{
|
||||
|
||||
XXH_SECRET_MERGEACCS_START :: 11
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_hashLong_128b_internal :: #force_inline proc(
|
||||
input: []u8,
|
||||
secret: []u8,
|
||||
@@ -407,7 +407,7 @@ XXH3_hashLong_128b_internal :: #force_inline proc(
|
||||
/*
|
||||
* It's important for performance that XXH3_hashLong is not inlined.
|
||||
*/
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_hashLong_128b_default :: #force_no_inline proc(input: []u8, seed: xxh_u64, secret: []u8) -> (res: XXH3_128_hash) {
|
||||
return XXH3_hashLong_128b_internal(input, XXH3_kSecret[:], XXH3_accumulate_512, XXH3_scramble_accumulator)
|
||||
}
|
||||
@@ -415,12 +415,12 @@ XXH3_hashLong_128b_default :: #force_no_inline proc(input: []u8, seed: xxh_u64,
|
||||
/*
|
||||
* It's important for performance that XXH3_hashLong is not inlined.
|
||||
*/
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_hashLong_128b_withSecret :: #force_no_inline proc(input: []u8, seed: xxh_u64, secret: []u8) -> (res: XXH3_128_hash) {
|
||||
return XXH3_hashLong_128b_internal(input, secret, XXH3_accumulate_512, XXH3_scramble_accumulator)
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_hashLong_128b_withSeed_internal :: #force_inline proc(
|
||||
input: []u8, seed: xxh_u64, secret: []u8,
|
||||
f_acc512: XXH3_accumulate_512_f,
|
||||
@@ -441,14 +441,14 @@ XXH3_hashLong_128b_withSeed_internal :: #force_inline proc(
|
||||
/*
|
||||
* It's important for performance that XXH3_hashLong is not inlined.
|
||||
*/
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_hashLong_128b_withSeed :: #force_no_inline proc(input: []u8, seed: xxh_u64, secret: []u8) -> (res: XXH3_128_hash) {
|
||||
return XXH3_hashLong_128b_withSeed_internal(input, seed, secret, XXH3_accumulate_512, XXH3_scramble_accumulator , XXH3_init_custom_secret)
|
||||
}
|
||||
|
||||
XXH3_hashLong128_f :: #type proc(input: []u8, seed: xxh_u64, secret: []u8) -> (res: XXH3_128_hash)
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_128bits_internal :: #force_inline proc(
|
||||
input: []u8, seed: xxh_u64, secret: []u8, f_hl128: XXH3_hashLong128_f) -> (res: XXH3_128_hash) {
|
||||
|
||||
@@ -474,17 +474,17 @@ XXH3_128bits_internal :: #force_inline proc(
|
||||
}
|
||||
|
||||
/* === Public XXH128 API === */
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_128_default :: proc(input: []u8) -> (hash: XXH3_128_hash) {
|
||||
return XXH3_128bits_internal(input, 0, XXH3_kSecret[:], XXH3_hashLong_128b_withSeed)
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_128_with_seed :: proc(input: []u8, seed: xxh_u64) -> (hash: XXH3_128_hash) {
|
||||
return XXH3_128bits_internal(input, seed, XXH3_kSecret[:], XXH3_hashLong_128b_withSeed)
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_128_with_secret :: proc(input: []u8, secret: []u8) -> (hash: XXH3_128_hash) {
|
||||
return XXH3_128bits_internal(input, 0, secret, XXH3_hashLong_128b_withSecret)
|
||||
}
|
||||
@@ -519,7 +519,7 @@ XXH3_128 :: proc { XXH3_128_default, XXH3_128_with_seed, XXH3_128_with_secret }
|
||||
The XOR mixing hides individual parts of the secret and increases entropy.
|
||||
This adds an extra layer of strength for custom secrets.
|
||||
*/
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_len_1to3_64b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u64) {
|
||||
length := u32(len(input))
|
||||
assert(input != nil)
|
||||
@@ -542,7 +542,7 @@ XXH3_len_1to3_64b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_len_4to8_64b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u64) {
|
||||
length := u32(len(input))
|
||||
assert(input != nil)
|
||||
@@ -562,7 +562,7 @@ XXH3_len_4to8_64b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_len_9to16_64b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u64) {
|
||||
length := u64(len(input))
|
||||
assert(input != nil)
|
||||
@@ -579,7 +579,7 @@ XXH3_len_9to16_64b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u6
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_len_0to16_64b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u64) {
|
||||
length := u64(len(input))
|
||||
assert(input != nil)
|
||||
@@ -621,7 +621,7 @@ XXH3_len_0to16_64b :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u6
|
||||
by this, although it is always a good idea to use a proper seed if you care
|
||||
about strength.
|
||||
*/
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_mix16B :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u64) {
|
||||
input_lo := XXH64_read64(input[0:])
|
||||
input_hi := XXH64_read64(input[8:])
|
||||
@@ -632,7 +632,7 @@ XXH3_mix16B :: #force_inline proc(input: []u8, secret: []u8, seed: xxh_u64) -> (
|
||||
}
|
||||
|
||||
/* For mid range keys, XXH3 uses a Mum-hash variant. */
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_len_17to128_64b :: proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u64) {
|
||||
assert(len(secret) >= XXH3_SECRET_SIZE_MIN)
|
||||
length := len(input)
|
||||
@@ -665,7 +665,7 @@ XXH3_MIDSIZE_MAX :: 240
|
||||
XXH3_MIDSIZE_STARTOFFSET :: 3
|
||||
XXH3_MIDSIZE_LASTOFFSET :: 17
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_len_129to240_64b :: proc(input: []u8, secret: []u8, seed: xxh_u64) -> (res: xxh_u64) {
|
||||
assert(len(secret) >= XXH3_SECRET_SIZE_MIN)
|
||||
length := len(input)
|
||||
@@ -699,7 +699,7 @@ XXH_SECRET_CONSUME_RATE :: 8 /* nb of secret bytes consumed at each accumulatio
|
||||
XXH_ACC_NB :: (XXH_STRIPE_LEN / size_of(xxh_u64))
|
||||
XXH_SECRET_LASTACC_START :: 7 /* not aligned on 8, last secret is different from acc & scrambler */
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH_writeLE64 :: #force_inline proc(dst: []u8, v64: u64le) {
|
||||
v := v64
|
||||
mem_copy(raw_data(dst), &v, size_of(v64))
|
||||
@@ -737,7 +737,7 @@ XXH3_scramble_accumulator : XXH3_scramble_accumulator_f = XXH3_scramble_accumula
|
||||
XXH3_init_custom_secret : XXH3_init_custom_secret_f = XXH3_init_custom_secret_scalar
|
||||
|
||||
/* scalar variants - universal */
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_accumulate_512_scalar :: #force_inline proc(acc: []xxh_u64, input: []u8, secret: []u8) {
|
||||
xacc := acc /* presumed aligned */
|
||||
xinput := input /* no alignment restriction */
|
||||
@@ -754,7 +754,7 @@ XXH3_accumulate_512_scalar :: #force_inline proc(acc: []xxh_u64, input: []u8, se
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_scramble_accumulator_scalar :: #force_inline proc(acc: []xxh_u64, secret: []u8) {
|
||||
xacc := acc /* presumed aligned */
|
||||
xsecret := secret /* no alignment restriction */
|
||||
@@ -771,7 +771,7 @@ XXH3_scramble_accumulator_scalar :: #force_inline proc(acc: []xxh_u64, secret: [
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_init_custom_secret_scalar :: #force_inline proc(custom_secret: []u8, seed64: xxh_u64) {
|
||||
#assert((XXH_SECRET_DEFAULT_SIZE & 15) == 0)
|
||||
|
||||
@@ -791,7 +791,7 @@ XXH_PREFETCH_DIST :: 320
|
||||
* Loops over XXH3_accumulate_512().
|
||||
* Assumption: nbStripes will not overflow the secret size
|
||||
*/
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_accumulate :: #force_inline proc(
|
||||
acc: []xxh_u64, input: []u8, secret: []u8, nbStripes: uint, f_acc512: XXH3_accumulate_512_f) {
|
||||
|
||||
@@ -804,7 +804,7 @@ XXH3_accumulate :: #force_inline proc(
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_hashLong_internal_loop :: #force_inline proc(acc: []xxh_u64, input: []u8, secret: []u8,
|
||||
f_acc512: XXH3_accumulate_512_f, f_scramble: XXH3_scramble_accumulator_f) {
|
||||
|
||||
@@ -833,14 +833,14 @@ XXH3_hashLong_internal_loop :: #force_inline proc(acc: []xxh_u64, input: []u8, s
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_mix2Accs :: #force_inline proc(acc: []xxh_u64, secret: []u8) -> (res: xxh_u64) {
|
||||
return XXH_mul_64_to_128_fold_64(
|
||||
acc[0] ~ XXH64_read64(secret),
|
||||
acc[1] ~ XXH64_read64(secret[8:]))
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_mergeAccs :: #force_inline proc(acc: []xxh_u64, secret: []u8, start: xxh_u64) -> (res: xxh_u64) {
|
||||
result64 := start
|
||||
#no_bounds_check for i := 0; i < 4; i += 1 {
|
||||
@@ -849,7 +849,7 @@ XXH3_mergeAccs :: #force_inline proc(acc: []xxh_u64, secret: []u8, start: xxh_u6
|
||||
return XXH3_avalanche(result64)
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_hashLong_64b_internal :: #force_inline proc(input: []u8, secret: []u8,
|
||||
f_acc512: XXH3_accumulate_512_f, f_scramble: XXH3_scramble_accumulator_f) -> (hash: xxh_u64) {
|
||||
|
||||
@@ -868,7 +868,7 @@ XXH3_hashLong_64b_internal :: #force_inline proc(input: []u8, secret: []u8,
|
||||
/*
|
||||
It's important for performance that XXH3_hashLong is not inlined.
|
||||
*/
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_hashLong_64b_withSecret :: #force_no_inline proc(input: []u8, seed64: xxh_u64, secret: []u8) -> (hash: xxh_u64) {
|
||||
return XXH3_hashLong_64b_internal(input, secret, XXH3_accumulate_512, XXH3_scramble_accumulator)
|
||||
}
|
||||
@@ -880,7 +880,7 @@ XXH3_hashLong_64b_withSecret :: #force_no_inline proc(input: []u8, seed64: xxh_u
|
||||
This variant enforces that the compiler can detect that,
|
||||
and uses this opportunity to streamline the generated code for better performance.
|
||||
*/
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_hashLong_64b_default :: #force_no_inline proc(input: []u8, seed64: xxh_u64, secret: []u8) -> (hash: xxh_u64) {
|
||||
return XXH3_hashLong_64b_internal(input, XXH3_kSecret[:], XXH3_accumulate_512, XXH3_scramble_accumulator)
|
||||
}
|
||||
@@ -896,26 +896,27 @@ XXH3_hashLong_64b_default :: #force_no_inline proc(input: []u8, seed64: xxh_u64,
|
||||
It's important for performance that XXH3_hashLong is not inlined. Not sure
|
||||
why (uop cache maybe?), but the difference is large and easily measurable.
|
||||
*/
|
||||
@(optimization_mode="speed")
|
||||
XXH3_hashLong_64b_withSeed_internal :: #force_no_inline proc(input: []u8,
|
||||
seed: xxh_u64,
|
||||
f_acc512: XXH3_accumulate_512_f,
|
||||
f_scramble: XXH3_scramble_accumulator_f,
|
||||
f_init_sec: XXH3_init_custom_secret_f) -> (hash: xxh_u64) {
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_hashLong_64b_withSeed_internal :: #force_no_inline proc(
|
||||
input: []u8,
|
||||
seed: xxh_u64,
|
||||
f_acc512: XXH3_accumulate_512_f,
|
||||
f_scramble: XXH3_scramble_accumulator_f,
|
||||
f_init_sec: XXH3_init_custom_secret_f,
|
||||
) -> (hash: xxh_u64) {
|
||||
if seed == 0 {
|
||||
return XXH3_hashLong_64b_internal(input, XXH3_kSecret[:], f_acc512, f_scramble)
|
||||
}
|
||||
{
|
||||
secret: [XXH_SECRET_DEFAULT_SIZE]u8
|
||||
f_init_sec(secret[:], seed)
|
||||
return XXH3_hashLong_64b_internal(input, secret[:], f_acc512, f_scramble)
|
||||
}
|
||||
|
||||
secret: [XXH_SECRET_DEFAULT_SIZE]u8
|
||||
f_init_sec(secret[:], seed)
|
||||
return XXH3_hashLong_64b_internal(input, secret[:], f_acc512, f_scramble)
|
||||
}
|
||||
|
||||
/*
|
||||
It's important for performance that XXH3_hashLong is not inlined.
|
||||
*/
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_hashLong_64b_withSeed :: #force_no_inline proc(input: []u8, seed: xxh_u64, secret: []u8) -> (hash: xxh_u64) {
|
||||
return XXH3_hashLong_64b_withSeed_internal(input, seed, XXH3_accumulate_512, XXH3_scramble_accumulator, XXH3_init_custom_secret)
|
||||
}
|
||||
@@ -923,7 +924,7 @@ XXH3_hashLong_64b_withSeed :: #force_no_inline proc(input: []u8, seed: xxh_u64,
|
||||
|
||||
XXH3_hashLong64_f :: #type proc(input: []u8, seed: xxh_u64, secret: []u8) -> (res: xxh_u64)
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_64bits_internal :: proc(input: []u8, seed: xxh_u64, secret: []u8, f_hashLong: XXH3_hashLong64_f) -> (hash: xxh_u64) {
|
||||
assert(len(secret) >= XXH3_SECRET_SIZE_MIN)
|
||||
/*
|
||||
@@ -943,19 +944,19 @@ XXH3_64bits_internal :: proc(input: []u8, seed: xxh_u64, secret: []u8, f_hashLon
|
||||
}
|
||||
|
||||
/* === Public entry point === */
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_64_default :: proc(input: []u8) -> (hash: xxh_u64) {
|
||||
return XXH3_64bits_internal(input, 0, XXH3_kSecret[:], XXH3_hashLong_64b_default)
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_64_with_seed :: proc(input: []u8, seed: xxh_u64) -> (hash: xxh_u64) {
|
||||
return XXH3_64bits_internal(input, seed, XXH3_kSecret[:], XXH3_hashLong_64b_withSeed)
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH3_64_with_secret :: proc(input, secret: []u8) -> (hash: xxh_u64) {
|
||||
return XXH3_64bits_internal(input, 0, secret, XXH3_hashLong_64b_withSecret)
|
||||
}
|
||||
|
||||
XXH3_64 :: proc { XXH3_64_default, XXH3_64_with_seed, XXH3_64_with_secret }
|
||||
XXH3_64 :: proc { XXH3_64_default, XXH3_64_with_seed, XXH3_64_with_secret }
|
||||
|
||||
@@ -40,7 +40,7 @@ XXH_PRIME32_3 :: 0xC2B2AE3D /*!< 0b11000010101100101010111000111101 */
|
||||
XXH_PRIME32_4 :: 0x27D4EB2F /*!< 0b00100111110101001110101100101111 */
|
||||
XXH_PRIME32_5 :: 0x165667B1 /*!< 0b00010110010101100110011110110001 */
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH32_round :: #force_inline proc(seed, input: XXH32_hash) -> (res: XXH32_hash) {
|
||||
seed := seed
|
||||
|
||||
@@ -53,7 +53,7 @@ XXH32_round :: #force_inline proc(seed, input: XXH32_hash) -> (res: XXH32_hash)
|
||||
/*
|
||||
Mix all bits
|
||||
*/
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH32_avalanche :: #force_inline proc(h32: u32) -> (res: u32) {
|
||||
h32 := h32
|
||||
|
||||
@@ -65,7 +65,7 @@ XXH32_avalanche :: #force_inline proc(h32: u32) -> (res: u32) {
|
||||
return h32
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH32_finalize :: #force_inline proc(h32: u32, buf: []u8, alignment: Alignment) -> (res: u32) {
|
||||
process_1 :: #force_inline proc(h32: u32, buf: []u8) -> (h32_res: u32, buf_res: []u8) {
|
||||
#no_bounds_check b := u32(buf[0])
|
||||
@@ -143,7 +143,7 @@ XXH32_finalize :: #force_inline proc(h32: u32, buf: []u8, alignment: Alignment)
|
||||
unreachable()
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
@(optimization_mode="favor_size")
|
||||
XXH32_endian_align :: #force_inline proc(input: []u8, seed := XXH32_DEFAULT_SEED, alignment: Alignment) -> (res: XXH32_hash) {
|
||||
buf := input
|
||||
length := len(input)
|
||||
@@ -318,4 +318,4 @@ XXH32_canonical_from_hash :: proc(hash: XXH32_hash) -> (canonical: XXH32_canonic
|
||||
XXH32_hash_from_canonical :: proc(canonical: ^XXH32_canonical) -> (hash: XXH32_hash) {
|
||||
h := (^u32be)(&canonical.digest)^
|
||||
return XXH32_hash(h)
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user