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:
Thomas la Cour
2024-07-11 21:15:08 +02:00
343 changed files with 36936 additions and 9191 deletions
+14 -8
View File
@@ -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
+6 -6
View File
@@ -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
View File
@@ -303,7 +303,7 @@ bin/
# - Linux/MacOS
odin
!odin/
odin.dSYM
**/*.dSYM
*.bin
demo.bin
libLLVM*.so*
BIN
View File
Binary file not shown.
+4 -1
View File
@@ -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 ---
+28
View File
@@ -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) -> ! {
+56 -13
View File
@@ -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
+64 -50
View File
@@ -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 {
@@ -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,
+5
View File
@@ -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()
}
+2 -1
View File
@@ -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:
+3 -3
View File
@@ -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)
}
+3 -3
View File
@@ -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)
}
+6 -3
View File
@@ -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")
}
+3 -3
View File
@@ -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)
}
+3 -3
View File
@@ -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
View File
@@ -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)
+18 -5
View File
@@ -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
}
+47 -3
View File
@@ -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)
}
}
+1 -1
View File
@@ -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")
+127
View File
@@ -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,
}
}
+3 -3
View File
@@ -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
}
+3 -3
View File
@@ -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.
+4 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+4 -7
View File
@@ -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
}
+11 -11
View File
@@ -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
+1 -1
View File
@@ -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)
}
+6 -3
View File
@@ -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).
+1 -2
View File
@@ -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
}
+2 -2
View File
@@ -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))
+2 -2
View File
@@ -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)
}
+4 -4
View File
@@ -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))
}
+2 -2
View File
@@ -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" (
+2 -2
View File
@@ -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" (
+22
View File
@@ -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,
}
}
+5 -5
View File
@@ -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))
}
+5 -5
View File
@@ -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))
}
+2 -2
View File
@@ -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)
}
}
+3 -3
View File
@@ -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_)
}
+5 -5
View File
@@ -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))
}
+8 -8
View File
@@ -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))
}
+7 -7
View File
@@ -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)
+1 -1
View File
@@ -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
+4 -2
View File
@@ -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
View File
@@ -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()
}
}
+1 -1
View File
@@ -16,4 +16,4 @@ _symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found
_last_error :: proc() -> string {
return ""
}
}
+1 -1
View File
@@ -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
}
}
+7 -9
View File
@@ -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
View File
@@ -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
}
+16 -16
View File
@@ -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.
+33 -5
View File
@@ -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
+57 -17
View File
@@ -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)
-1
View File
@@ -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)
+13 -21
View File
@@ -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
{
+4 -2
View File
@@ -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
File diff suppressed because it is too large Load Diff
+21 -19
View File
@@ -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
+28
View File
@@ -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.
+67
View File
@@ -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,
}
+46
View File
@@ -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
+333
View File
@@ -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,
}
+146
View File
@@ -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,
}
+242
View File
@@ -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)
}
+89
View File
@@ -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,
}
+131
View File
@@ -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,
}
+5 -5
View File
@@ -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
}
+5 -5
View File
@@ -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}
}
}
+28
View File
@@ -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.
+38
View File
@@ -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
+181
View File
@@ -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
+50
View File
@@ -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,
}
+9
View File
@@ -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,
}
+11
View File
@@ -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,
}
+132
View File
@@ -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")
}
}
+262
View File
@@ -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),
}
}
+170
View File
@@ -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
}
+529
View File
@@ -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
}
+31
View File
@@ -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
}
+244
View File
@@ -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
}
+101
View File
@@ -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
}
+43
View File
@@ -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
}
+293
View File
@@ -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))
}
+130
View File
@@ -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
}
+37
View File
@@ -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
View File
@@ -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
View File
@@ -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,
}
}
+1 -1
View File
@@ -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
View File
@@ -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 {
+5 -5
View File
@@ -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)
}
}
}
+56 -55
View File
@@ -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 }
+5 -5
View File
@@ -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