mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-06 07:38:48 +00:00
Merge remote-tracking branch 'offical/master'
This commit is contained in:
@@ -2,6 +2,38 @@ name: CI
|
|||||||
on: [push, pull_request, workflow_dispatch]
|
on: [push, pull_request, workflow_dispatch]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
build_netbsd:
|
||||||
|
name: NetBSD Build, Check, and Test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
PKGSRC_BRANCH: 2024Q1
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Build, Check, and Test
|
||||||
|
timeout-minutes: 25
|
||||||
|
uses: vmactions/netbsd-vm@v1
|
||||||
|
with:
|
||||||
|
release: "10.0"
|
||||||
|
envs: PKGSRC_BRANCH
|
||||||
|
usesh: true
|
||||||
|
copyback: false
|
||||||
|
prepare: |
|
||||||
|
PKG_PATH="https://cdn.NetBSD.org/pub/pkgsrc/packages/NetBSD/$(uname -p)/$(uname -r | cut -d_ -f1)_${PKGSRC_BRANCH}/All" /usr/sbin/pkg_add pkgin
|
||||||
|
pkgin -y in gmake git bash python311
|
||||||
|
pkgin -y in libxml2 perl zstd
|
||||||
|
/usr/sbin/pkg_add https://github.com/andreas-jonsson/llvm17-netbsd-bin/releases/download/pkgsrc-current/llvm-17.0.6.tgz
|
||||||
|
/usr/sbin/pkg_add https://github.com/andreas-jonsson/llvm17-netbsd-bin/releases/download/pkgsrc-current/clang-17.0.6.tgz
|
||||||
|
ln -s /usr/pkg/bin/python3.11 /usr/bin/python3
|
||||||
|
ln -s /usr/pkg/bin/bash /bin/bash
|
||||||
|
run: |
|
||||||
|
git config --global --add safe.directory $(pwd)
|
||||||
|
gmake release
|
||||||
|
./odin version
|
||||||
|
./odin report
|
||||||
|
./odin check examples/all -vet -strict-style -target:netbsd_amd64
|
||||||
|
(cd tests/core; gmake all_bsd)
|
||||||
|
(cd tests/internal; gmake all_bsd)
|
||||||
|
(cd tests/issues; ./run.sh)
|
||||||
build_linux:
|
build_linux:
|
||||||
name: Ubuntu Build, Check, and Test
|
name: Ubuntu Build, Check, and Test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
@@ -167,6 +167,9 @@ type_is_matrix :: proc($T: typeid) -> bool ---
|
|||||||
|
|
||||||
type_has_nil :: proc($T: typeid) -> bool ---
|
type_has_nil :: proc($T: typeid) -> bool ---
|
||||||
|
|
||||||
|
type_is_matrix_row_major :: proc($T: typeid) -> bool where type_is_matrix(T) ---
|
||||||
|
type_is_matrix_column_major :: proc($T: typeid) -> bool where type_is_matrix(T) ---
|
||||||
|
|
||||||
type_is_specialization_of :: proc($T, $S: typeid) -> bool ---
|
type_is_specialization_of :: proc($T, $S: typeid) -> bool ---
|
||||||
|
|
||||||
type_is_variant_of :: proc($U, $V: typeid) -> bool where type_is_union(U) ---
|
type_is_variant_of :: proc($U, $V: typeid) -> bool where type_is_union(U) ---
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ when ODIN_DEFAULT_TO_NIL_ALLOCATOR {
|
|||||||
} else when ODIN_DEFAULT_TO_PANIC_ALLOCATOR {
|
} else when ODIN_DEFAULT_TO_PANIC_ALLOCATOR {
|
||||||
default_allocator_proc :: panic_allocator_proc
|
default_allocator_proc :: panic_allocator_proc
|
||||||
default_allocator :: panic_allocator
|
default_allocator :: panic_allocator
|
||||||
|
} else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 {
|
||||||
|
default_allocator :: default_wasm_allocator
|
||||||
|
default_allocator_proc :: wasm_allocator_proc
|
||||||
} else {
|
} else {
|
||||||
default_allocator :: heap_allocator
|
default_allocator :: heap_allocator
|
||||||
default_allocator_proc :: heap_allocator_proc
|
default_allocator_proc :: heap_allocator_proc
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE: int : #config(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE, 4 * Megabyte)
|
DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE: int : #config(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE, 4 * Megabyte)
|
||||||
NO_DEFAULT_TEMP_ALLOCATOR: bool : ODIN_OS == .Freestanding || ODIN_OS == .JS || ODIN_DEFAULT_TO_NIL_ALLOCATOR
|
NO_DEFAULT_TEMP_ALLOCATOR: bool : ODIN_OS == .Freestanding || ODIN_DEFAULT_TO_NIL_ALLOCATOR
|
||||||
|
|
||||||
when NO_DEFAULT_TEMP_ALLOCATOR {
|
when NO_DEFAULT_TEMP_ALLOCATOR {
|
||||||
Default_Temp_Allocator :: struct {}
|
Default_Temp_Allocator :: struct {}
|
||||||
|
|||||||
@@ -40,6 +40,24 @@ align_forward_int :: #force_inline proc(ptr, align: int) -> int {
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is_power_of_two_uint :: #force_inline proc "contextless" (x: uint) -> bool {
|
||||||
|
if x <= 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return (x & (x-1)) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
align_forward_uint :: #force_inline proc(ptr, align: uint) -> uint {
|
||||||
|
assert(is_power_of_two_uint(align))
|
||||||
|
|
||||||
|
p := ptr
|
||||||
|
modulo := p & (align-1)
|
||||||
|
if modulo != 0 {
|
||||||
|
p += align - modulo
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
is_power_of_two_uintptr :: #force_inline proc "contextless" (x: uintptr) -> bool {
|
is_power_of_two_uintptr :: #force_inline proc "contextless" (x: uintptr) -> bool {
|
||||||
if x <= 0 {
|
if x <= 0 {
|
||||||
return false
|
return false
|
||||||
@@ -58,6 +76,18 @@ align_forward_uintptr :: #force_inline proc(ptr, align: uintptr) -> uintptr {
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is_power_of_two :: proc {
|
||||||
|
is_power_of_two_int,
|
||||||
|
is_power_of_two_uint,
|
||||||
|
is_power_of_two_uintptr,
|
||||||
|
}
|
||||||
|
|
||||||
|
align_forward :: proc {
|
||||||
|
align_forward_int,
|
||||||
|
align_forward_uint,
|
||||||
|
align_forward_uintptr,
|
||||||
|
}
|
||||||
|
|
||||||
mem_zero :: proc "contextless" (data: rawptr, len: int) -> rawptr {
|
mem_zero :: proc "contextless" (data: rawptr, len: int) -> rawptr {
|
||||||
if data == nil {
|
if data == nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -801,6 +831,10 @@ truncsfhf2 :: proc "c" (value: f32) -> __float16 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(link_name="__aeabi_d2h", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||||
|
aeabi_d2h :: proc "c" (value: f64) -> __float16 {
|
||||||
|
return truncsfhf2(f32(value))
|
||||||
|
}
|
||||||
|
|
||||||
@(link_name="__truncdfhf2", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
@(link_name="__truncdfhf2", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
|
||||||
truncdfhf2 :: proc "c" (value: f64) -> __float16 {
|
truncdfhf2 :: proc "c" (value: f64) -> __float16 {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ package runtime
|
|||||||
import "core:sys/wasm/wasi"
|
import "core:sys/wasm/wasi"
|
||||||
|
|
||||||
_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
|
_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
|
||||||
data := (wasi.ciovec_t)(data)
|
data_iovec := (wasi.ciovec_t)(data)
|
||||||
n, err := wasi.fd_write(1, {data})
|
n, err := wasi.fd_write(1, {data_iovec})
|
||||||
return int(n), _OS_Errno(err)
|
return int(n), _OS_Errno(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,870 @@
|
|||||||
|
//+build wasm32, wasm64p32
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import "base:intrinsics"
|
||||||
|
|
||||||
|
/*
|
||||||
|
Port of emmalloc, modified for use in Odin.
|
||||||
|
|
||||||
|
Invariants:
|
||||||
|
- Per-allocation header overhead is 8 bytes, smallest allocated payload
|
||||||
|
amount is 8 bytes, and a multiple of 4 bytes.
|
||||||
|
- Acquired memory blocks are subdivided into disjoint regions that lie
|
||||||
|
next to each other.
|
||||||
|
- A region is either in used or free.
|
||||||
|
Used regions may be adjacent, and a used and unused region
|
||||||
|
may be adjacent, but not two unused ones - they would be
|
||||||
|
merged.
|
||||||
|
- Memory allocation takes constant time, unless the alloc needs to wasm_memory_grow()
|
||||||
|
or memory is very close to being exhausted.
|
||||||
|
- Free and used regions are managed inside "root regions", which are slabs
|
||||||
|
of memory acquired via wasm_memory_grow().
|
||||||
|
- Memory retrieved using wasm_memory_grow() can not be given back to the OS.
|
||||||
|
Therefore, frees are internal to the allocator.
|
||||||
|
|
||||||
|
Copyright (c) 2010-2014 Emscripten authors, see AUTHORS file.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
WASM_Allocator :: struct #no_copy {
|
||||||
|
// The minimum alignment of allocations.
|
||||||
|
alignment: uint,
|
||||||
|
// A region that contains as payload a single forward linked list of pointers to
|
||||||
|
// root regions of each disjoint region blocks.
|
||||||
|
list_of_all_regions: ^Root_Region,
|
||||||
|
// For each of the buckets, maintain a linked list head node. The head node for each
|
||||||
|
// free region is a sentinel node that does not actually represent any free space, but
|
||||||
|
// the sentinel is used to avoid awkward testing against (if node == freeRegionHeadNode)
|
||||||
|
// when adding and removing elements from the linked list, i.e. we are guaranteed that
|
||||||
|
// the sentinel node is always fixed and there, and the actual free region list elements
|
||||||
|
// start at free_region_buckets[i].next each.
|
||||||
|
free_region_buckets: [NUM_FREE_BUCKETS]Region,
|
||||||
|
// A bitmask that tracks the population status for each of the 64 distinct memory regions:
|
||||||
|
// a zero at bit position i means that the free list bucket i is empty. This bitmask is
|
||||||
|
// used to avoid redundant scanning of the 64 different free region buckets: instead by
|
||||||
|
// looking at the bitmask we can find in constant time an index to a free region bucket
|
||||||
|
// that contains free memory of desired size.
|
||||||
|
free_region_buckets_used: BUCKET_BITMASK_T,
|
||||||
|
// Because wasm memory can only be allocated in pages of 64k at a time, we keep any
|
||||||
|
// spilled/unused bytes that are left from the allocated pages here, first using this
|
||||||
|
// when bytes are needed.
|
||||||
|
spill: []byte,
|
||||||
|
// Mutex for thread safety, only used if the target feature "atomics" is enabled.
|
||||||
|
mu: Mutex_State,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not required to be called, called on first allocation otherwise.
|
||||||
|
wasm_allocator_init :: proc(a: ^WASM_Allocator, alignment: uint = 8) {
|
||||||
|
assert(is_power_of_two(alignment), "alignment must be a power of two")
|
||||||
|
assert(alignment > 4, "alignment must be more than 4")
|
||||||
|
|
||||||
|
a.alignment = alignment
|
||||||
|
|
||||||
|
for i in 0..<NUM_FREE_BUCKETS {
|
||||||
|
a.free_region_buckets[i].next = &a.free_region_buckets[i]
|
||||||
|
a.free_region_buckets[i].prev = a.free_region_buckets[i].next
|
||||||
|
}
|
||||||
|
|
||||||
|
if !claim_more_memory(a, 3*size_of(Region)) {
|
||||||
|
panic("wasm_allocator: initial memory could not be allocated")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
global_default_wasm_allocator_data: WASM_Allocator
|
||||||
|
|
||||||
|
default_wasm_allocator :: proc() -> Allocator {
|
||||||
|
return wasm_allocator(&global_default_wasm_allocator_data)
|
||||||
|
}
|
||||||
|
|
||||||
|
wasm_allocator :: proc(a: ^WASM_Allocator) -> Allocator {
|
||||||
|
return {
|
||||||
|
data = a,
|
||||||
|
procedure = wasm_allocator_proc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wasm_allocator_proc :: proc(a: rawptr, mode: Allocator_Mode, size, alignment: int, old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) {
|
||||||
|
a := (^WASM_Allocator)(a)
|
||||||
|
if a == nil {
|
||||||
|
a = &global_default_wasm_allocator_data
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.alignment == 0 {
|
||||||
|
wasm_allocator_init(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch mode {
|
||||||
|
case .Alloc:
|
||||||
|
ptr := aligned_alloc(a, uint(alignment), uint(size), loc)
|
||||||
|
if ptr == nil {
|
||||||
|
return nil, .Out_Of_Memory
|
||||||
|
}
|
||||||
|
intrinsics.mem_zero(ptr, size)
|
||||||
|
return ([^]byte)(ptr)[:size], nil
|
||||||
|
|
||||||
|
case .Alloc_Non_Zeroed:
|
||||||
|
ptr := aligned_alloc(a, uint(alignment), uint(size), loc)
|
||||||
|
if ptr == nil {
|
||||||
|
return nil, .Out_Of_Memory
|
||||||
|
}
|
||||||
|
return ([^]byte)(ptr)[:size], nil
|
||||||
|
|
||||||
|
case .Resize:
|
||||||
|
ptr := aligned_realloc(a, old_memory, uint(alignment), uint(size), loc)
|
||||||
|
if ptr == nil {
|
||||||
|
return nil, .Out_Of_Memory
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes := ([^]byte)(ptr)[:size]
|
||||||
|
|
||||||
|
if size > old_size {
|
||||||
|
new_region := raw_data(bytes[old_size:])
|
||||||
|
intrinsics.mem_zero(new_region, size - old_size)
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes, nil
|
||||||
|
|
||||||
|
case .Resize_Non_Zeroed:
|
||||||
|
ptr := aligned_realloc(a, old_memory, uint(alignment), uint(size), loc)
|
||||||
|
if ptr == nil {
|
||||||
|
return nil, .Out_Of_Memory
|
||||||
|
}
|
||||||
|
return ([^]byte)(ptr)[:size], nil
|
||||||
|
|
||||||
|
case .Free:
|
||||||
|
free(a, old_memory, loc)
|
||||||
|
return nil, nil
|
||||||
|
|
||||||
|
case .Free_All, .Query_Info:
|
||||||
|
return nil, .Mode_Not_Implemented
|
||||||
|
|
||||||
|
case .Query_Features:
|
||||||
|
set := (^Allocator_Mode_Set)(old_memory)
|
||||||
|
if set != nil {
|
||||||
|
set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Resize, .Resize_Non_Zeroed, .Query_Features }
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
unreachable()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the allocated size of the allocator (both free and used).
|
||||||
|
// If `nil` is given, the global allocator is used.
|
||||||
|
wasm_allocator_size :: proc(a: ^WASM_Allocator = nil) -> (size: uint) {
|
||||||
|
a := a
|
||||||
|
if a == nil {
|
||||||
|
a = &global_default_wasm_allocator_data
|
||||||
|
}
|
||||||
|
|
||||||
|
lock(a)
|
||||||
|
defer unlock(a)
|
||||||
|
|
||||||
|
root := a.list_of_all_regions
|
||||||
|
for root != nil {
|
||||||
|
size += uint(uintptr(root.end_ptr) - uintptr(root))
|
||||||
|
root = root.next
|
||||||
|
}
|
||||||
|
|
||||||
|
size += len(a.spill)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the amount of free memory on the allocator.
|
||||||
|
// If `nil` is given, the global allocator is used.
|
||||||
|
wasm_allocator_free_space :: proc(a: ^WASM_Allocator = nil) -> (free: uint) {
|
||||||
|
a := a
|
||||||
|
if a == nil {
|
||||||
|
a = &global_default_wasm_allocator_data
|
||||||
|
}
|
||||||
|
|
||||||
|
lock(a)
|
||||||
|
defer unlock(a)
|
||||||
|
|
||||||
|
bucket_index: u64 = 0
|
||||||
|
bucket_mask := a.free_region_buckets_used
|
||||||
|
|
||||||
|
for bucket_mask != 0 {
|
||||||
|
index_add := intrinsics.count_trailing_zeros(bucket_mask)
|
||||||
|
bucket_index += index_add
|
||||||
|
bucket_mask >>= index_add
|
||||||
|
for free_region := a.free_region_buckets[bucket_index].next; free_region != &a.free_region_buckets[bucket_index]; free_region = free_region.next {
|
||||||
|
free += free_region.size - REGION_HEADER_SIZE
|
||||||
|
}
|
||||||
|
bucket_index += 1
|
||||||
|
bucket_mask >>= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
free += len(a.spill)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
NUM_FREE_BUCKETS :: 64
|
||||||
|
@(private="file")
|
||||||
|
BUCKET_BITMASK_T :: u64
|
||||||
|
|
||||||
|
// Dynamic memory is subdivided into regions, in the format
|
||||||
|
|
||||||
|
// <size:u32> ..... <size:u32> | <size:u32> ..... <size:u32> | <size:u32> ..... <size:u32> | .....
|
||||||
|
|
||||||
|
// That is, at the bottom and top end of each memory region, the size of that region is stored. That allows traversing the
|
||||||
|
// memory regions backwards and forwards. Because each allocation must be at least a multiple of 4 bytes, the lowest two bits of
|
||||||
|
// each size field is unused. Free regions are distinguished by used regions by having the FREE_REGION_FLAG bit present
|
||||||
|
// in the size field. I.e. for free regions, the size field is odd, and for used regions, the size field reads even.
|
||||||
|
@(private="file")
|
||||||
|
FREE_REGION_FLAG :: 0x1
|
||||||
|
|
||||||
|
// Attempts to alloc more than this many bytes would cause an overflow when calculating the size of a region,
|
||||||
|
// therefore allocations larger than this are short-circuited immediately on entry.
|
||||||
|
@(private="file")
|
||||||
|
MAX_ALLOC_SIZE :: 0xFFFFFFC7
|
||||||
|
|
||||||
|
// A free region has the following structure:
|
||||||
|
// <size:uint> <prevptr> <nextptr> ... <size:uint>
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
Region :: struct {
|
||||||
|
size: uint,
|
||||||
|
prev, next: ^Region,
|
||||||
|
_at_the_end_of_this_struct_size: uint,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each memory block starts with a Root_Region at the beginning.
|
||||||
|
// The Root_Region specifies the size of the region block, and forms a linked
|
||||||
|
// list of all Root_Regions in the program, starting with `list_of_all_regions`
|
||||||
|
// below.
|
||||||
|
@(private="file")
|
||||||
|
Root_Region :: struct {
|
||||||
|
size: u32,
|
||||||
|
next: ^Root_Region,
|
||||||
|
end_ptr: ^byte,
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
Mutex_State :: enum u32 {
|
||||||
|
Unlocked = 0,
|
||||||
|
Locked = 1,
|
||||||
|
Waiting = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
lock :: proc(a: ^WASM_Allocator) {
|
||||||
|
when intrinsics.has_target_feature("atomics") {
|
||||||
|
@(cold)
|
||||||
|
lock_slow :: proc(a: ^WASM_Allocator, curr_state: Mutex_State) {
|
||||||
|
new_state := curr_state // Make a copy of it
|
||||||
|
|
||||||
|
spin_lock: for spin in 0..<i32(100) {
|
||||||
|
state, ok := intrinsics.atomic_compare_exchange_weak_explicit(&a.mu, .Unlocked, new_state, .Acquire, .Consume)
|
||||||
|
if ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if state == .Waiting {
|
||||||
|
break spin_lock
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := min(spin+1, 32); i > 0; i -= 1 {
|
||||||
|
intrinsics.cpu_relax()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set just in case 100 iterations did not do it
|
||||||
|
new_state = .Waiting
|
||||||
|
|
||||||
|
for {
|
||||||
|
if intrinsics.atomic_exchange_explicit(&a.mu, .Waiting, .Acquire) == .Unlocked {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(intrinsics.wasm_memory_atomic_wait32((^u32)(&a.mu), u32(new_state), -1) != 0)
|
||||||
|
intrinsics.cpu_relax()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if v := intrinsics.atomic_exchange_explicit(&a.mu, .Locked, .Acquire); v != .Unlocked {
|
||||||
|
lock_slow(a, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
unlock :: proc(a: ^WASM_Allocator) {
|
||||||
|
when intrinsics.has_target_feature("atomics") {
|
||||||
|
@(cold)
|
||||||
|
unlock_slow :: proc(a: ^WASM_Allocator) {
|
||||||
|
for {
|
||||||
|
s := intrinsics.wasm_memory_atomic_notify32((^u32)(&a.mu), 1)
|
||||||
|
if s >= 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch intrinsics.atomic_exchange_explicit(&a.mu, .Unlocked, .Release) {
|
||||||
|
case .Unlocked:
|
||||||
|
unreachable()
|
||||||
|
case .Locked:
|
||||||
|
// Okay
|
||||||
|
case .Waiting:
|
||||||
|
unlock_slow(a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
assert_locked :: proc(a: ^WASM_Allocator) {
|
||||||
|
when intrinsics.has_target_feature("atomics") {
|
||||||
|
assert(intrinsics.atomic_load(&a.mu) != .Unlocked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
has_alignment_uintptr :: proc(ptr: uintptr, #any_int alignment: uintptr) -> bool {
|
||||||
|
return ptr & (alignment-1) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
has_alignment_uint :: proc(ptr: uint, alignment: uint) -> bool {
|
||||||
|
return ptr & (alignment-1) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
has_alignment :: proc {
|
||||||
|
has_alignment_uintptr,
|
||||||
|
has_alignment_uint,
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
REGION_HEADER_SIZE :: 2*size_of(uint)
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
SMALLEST_ALLOCATION_SIZE :: 2*size_of(rawptr)
|
||||||
|
|
||||||
|
// Subdivide regions of free space into distinct circular doubly linked lists, where each linked list
|
||||||
|
// represents a range of free space blocks. The following function compute_free_list_bucket() converts
|
||||||
|
// an allocation size to the bucket index that should be looked at.
|
||||||
|
#assert(NUM_FREE_BUCKETS == 64, "Following function is tailored specifically for the NUM_FREE_BUCKETS == 64 case")
|
||||||
|
@(private="file")
|
||||||
|
compute_free_list_bucket :: proc(size: uint) -> uint {
|
||||||
|
if size < 128 { return (size >> 3) - 1 }
|
||||||
|
|
||||||
|
clz := intrinsics.count_leading_zeros(i32(size))
|
||||||
|
bucket_index: i32 = ((clz > 19) \
|
||||||
|
? 110 - (clz<<2) + ((i32)(size >> (u32)(29-clz)) ~ 4) \
|
||||||
|
: min( 71 - (clz<<1) + ((i32)(size >> (u32)(30-clz)) ~ 2), NUM_FREE_BUCKETS-1))
|
||||||
|
|
||||||
|
assert(bucket_index >= 0)
|
||||||
|
assert(bucket_index < NUM_FREE_BUCKETS)
|
||||||
|
return uint(bucket_index)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
prev_region :: proc(region: ^Region) -> ^Region {
|
||||||
|
prev_region_size := ([^]uint)(region)[-1]
|
||||||
|
prev_region_size = prev_region_size & ~uint(FREE_REGION_FLAG)
|
||||||
|
return (^Region)(uintptr(region)-uintptr(prev_region_size))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
next_region :: proc(region: ^Region) -> ^Region {
|
||||||
|
return (^Region)(uintptr(region)+uintptr(region.size))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
region_ceiling_size :: proc(region: ^Region) -> uint {
|
||||||
|
return ([^]uint)(uintptr(region)+uintptr(region.size))[-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
region_is_free :: proc(r: ^Region) -> bool {
|
||||||
|
return region_ceiling_size(r) & FREE_REGION_FLAG >= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
region_is_in_use :: proc(r: ^Region) -> bool {
|
||||||
|
return r.size == region_ceiling_size(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
region_payload_start_ptr :: proc(r: ^Region) -> [^]byte {
|
||||||
|
return ([^]byte)(r)[size_of(uint):]
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
region_payload_end_ptr :: proc(r: ^Region) -> [^]byte {
|
||||||
|
return ([^]byte)(r)[r.size-size_of(uint):]
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
create_used_region :: proc(ptr: rawptr, size: uint) {
|
||||||
|
assert(has_alignment(uintptr(ptr), size_of(uint)))
|
||||||
|
assert(has_alignment(size, size_of(uint)))
|
||||||
|
assert(size >= size_of(Region))
|
||||||
|
|
||||||
|
uptr := ([^]uint)(ptr)
|
||||||
|
uptr[0] = size
|
||||||
|
uptr[size/size_of(uint)-1] = size
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
create_free_region :: proc(ptr: rawptr, size: uint) {
|
||||||
|
assert(has_alignment(uintptr(ptr), size_of(uint)))
|
||||||
|
assert(has_alignment(size, size_of(uint)))
|
||||||
|
assert(size >= size_of(Region))
|
||||||
|
|
||||||
|
free_region := (^Region)(ptr)
|
||||||
|
free_region.size = size
|
||||||
|
([^]uint)(ptr)[size/size_of(uint)-1] = size | FREE_REGION_FLAG
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
prepend_to_free_list :: proc(region: ^Region, prepend_to: ^Region) {
|
||||||
|
assert(region_is_free(region))
|
||||||
|
region.next = prepend_to
|
||||||
|
region.prev = prepend_to.prev
|
||||||
|
prepend_to.prev = region
|
||||||
|
region.prev.next = region
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
unlink_from_free_list :: proc(region: ^Region) {
|
||||||
|
assert(region_is_free(region))
|
||||||
|
region.prev.next = region.next
|
||||||
|
region.next.prev = region.prev
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
link_to_free_list :: proc(a: ^WASM_Allocator, free_region: ^Region) {
|
||||||
|
assert(free_region.size >= size_of(Region))
|
||||||
|
bucket_index := compute_free_list_bucket(free_region.size-REGION_HEADER_SIZE)
|
||||||
|
free_list_head := &a.free_region_buckets[bucket_index]
|
||||||
|
free_region.prev = free_list_head
|
||||||
|
free_region.next = free_list_head.next
|
||||||
|
free_list_head.next = free_region
|
||||||
|
free_region.next.prev = free_region
|
||||||
|
a.free_region_buckets_used |= BUCKET_BITMASK_T(1) << bucket_index
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
claim_more_memory :: proc(a: ^WASM_Allocator, num_bytes: uint) -> bool {
|
||||||
|
|
||||||
|
PAGE_SIZE :: 64 * 1024
|
||||||
|
|
||||||
|
page_alloc :: proc(page_count: int) -> []byte {
|
||||||
|
prev_page_count := intrinsics.wasm_memory_grow(0, uintptr(page_count))
|
||||||
|
if prev_page_count < 0 { return nil }
|
||||||
|
|
||||||
|
ptr := ([^]byte)(uintptr(prev_page_count) * PAGE_SIZE)
|
||||||
|
return ptr[:page_count * PAGE_SIZE]
|
||||||
|
}
|
||||||
|
|
||||||
|
alloc :: proc(a: ^WASM_Allocator, num_bytes: uint) -> (bytes: [^]byte) #no_bounds_check {
|
||||||
|
if uint(len(a.spill)) >= num_bytes {
|
||||||
|
bytes = raw_data(a.spill[:num_bytes])
|
||||||
|
a.spill = a.spill[num_bytes:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pages := int((num_bytes / PAGE_SIZE) + 1)
|
||||||
|
allocated := page_alloc(pages)
|
||||||
|
if allocated == nil { return nil }
|
||||||
|
|
||||||
|
// If the allocated memory is a direct continuation of the spill from before,
|
||||||
|
// 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.len += len(allocated)
|
||||||
|
} else {
|
||||||
|
// Otherwise, we have to "waste" the previous spill.
|
||||||
|
// Now this is probably uncommon, and will only happen if another code path
|
||||||
|
// is also requesting pages.
|
||||||
|
a.spill = allocated
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes = raw_data(a.spill)
|
||||||
|
a.spill = a.spill[num_bytes:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
num_bytes := num_bytes
|
||||||
|
num_bytes = align_forward(num_bytes, a.alignment)
|
||||||
|
|
||||||
|
start_ptr := alloc(a, uint(num_bytes))
|
||||||
|
if start_ptr == nil { return false }
|
||||||
|
|
||||||
|
assert(has_alignment(uintptr(start_ptr), align_of(uint)))
|
||||||
|
end_ptr := start_ptr[num_bytes:]
|
||||||
|
|
||||||
|
end_sentinel_region := (^Region)(end_ptr[-size_of(Region):])
|
||||||
|
create_used_region(end_sentinel_region, size_of(Region))
|
||||||
|
|
||||||
|
// If we are the sole user of wasm_memory_grow(), it will feed us continuous/consecutive memory addresses - take advantage
|
||||||
|
// of that if so: instead of creating two disjoint memory regions blocks, expand the previous one to a larger size.
|
||||||
|
prev_alloc_end_address := a.list_of_all_regions != nil ? a.list_of_all_regions.end_ptr : nil
|
||||||
|
if start_ptr == prev_alloc_end_address {
|
||||||
|
prev_end_sentinel := prev_region((^Region)(start_ptr))
|
||||||
|
assert(region_is_in_use(prev_end_sentinel))
|
||||||
|
prev_region := prev_region(prev_end_sentinel)
|
||||||
|
|
||||||
|
a.list_of_all_regions.end_ptr = end_ptr
|
||||||
|
|
||||||
|
// Two scenarios, either the last region of the previous block was in use, in which case we need to create
|
||||||
|
// a new free region in the newly allocated space; or it was free, in which case we can extend that region
|
||||||
|
// to cover a larger size.
|
||||||
|
if region_is_free(prev_region) {
|
||||||
|
new_free_region_size := uint(uintptr(end_sentinel_region) - uintptr(prev_region))
|
||||||
|
unlink_from_free_list(prev_region)
|
||||||
|
create_free_region(prev_region, new_free_region_size)
|
||||||
|
link_to_free_list(a, prev_region)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
start_ptr = start_ptr[-size_of(Region):]
|
||||||
|
} else {
|
||||||
|
create_used_region(start_ptr, size_of(Region))
|
||||||
|
|
||||||
|
new_region_block := (^Root_Region)(start_ptr)
|
||||||
|
new_region_block.next = a.list_of_all_regions
|
||||||
|
new_region_block.end_ptr = end_ptr
|
||||||
|
a.list_of_all_regions = new_region_block
|
||||||
|
start_ptr = start_ptr[size_of(Region):]
|
||||||
|
}
|
||||||
|
|
||||||
|
create_free_region(start_ptr, uint(uintptr(end_sentinel_region)-uintptr(start_ptr)))
|
||||||
|
link_to_free_list(a, (^Region)(start_ptr))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
validate_alloc_size :: proc(size: uint) -> uint {
|
||||||
|
#assert(size_of(uint) >= size_of(uintptr))
|
||||||
|
#assert(size_of(uint) % size_of(uintptr) == 0)
|
||||||
|
|
||||||
|
// NOTE: emmalloc aligns this forward on pointer size, but I think that is a mistake and will
|
||||||
|
// do bad on wasm64p32.
|
||||||
|
|
||||||
|
validated_size := size > SMALLEST_ALLOCATION_SIZE ? align_forward(size, size_of(uint)) : SMALLEST_ALLOCATION_SIZE
|
||||||
|
assert(validated_size >= size) // Assert we haven't wrapped.
|
||||||
|
|
||||||
|
return validated_size
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
allocate_memory :: proc(a: ^WASM_Allocator, alignment: uint, size: uint, loc := #caller_location) -> rawptr {
|
||||||
|
|
||||||
|
attempt_allocate :: proc(a: ^WASM_Allocator, free_region: ^Region, alignment, size: uint) -> rawptr {
|
||||||
|
assert_locked(a)
|
||||||
|
free_region := free_region
|
||||||
|
|
||||||
|
payload_start_ptr := uintptr(region_payload_start_ptr(free_region))
|
||||||
|
payload_start_ptr_aligned := align_forward(payload_start_ptr, uintptr(alignment))
|
||||||
|
payload_end_ptr := uintptr(region_payload_end_ptr(free_region))
|
||||||
|
|
||||||
|
if payload_start_ptr_aligned + uintptr(size) > payload_end_ptr {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have enough free space, so the memory allocation will be made into this region. Remove this free region
|
||||||
|
// from the list of free regions: whatever slop remains will be later added back to the free region pool.
|
||||||
|
unlink_from_free_list(free_region)
|
||||||
|
|
||||||
|
// Before we proceed further, fix up the boundary between this and the preceding region,
|
||||||
|
// so that the boundary between the two regions happens at a right spot for the payload to be aligned.
|
||||||
|
if payload_start_ptr != payload_start_ptr_aligned {
|
||||||
|
prev := prev_region(free_region)
|
||||||
|
assert(region_is_in_use(prev))
|
||||||
|
region_boundary_bump_amount := payload_start_ptr_aligned - payload_start_ptr
|
||||||
|
new_this_region_size := free_region.size - uint(region_boundary_bump_amount)
|
||||||
|
create_used_region(prev, prev.size + uint(region_boundary_bump_amount))
|
||||||
|
free_region = (^Region)(uintptr(free_region) + region_boundary_bump_amount)
|
||||||
|
free_region.size = new_this_region_size
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next, we need to decide whether this region is so large that it should be split into two regions,
|
||||||
|
// one representing the newly used memory area, and at the high end a remaining leftover free area.
|
||||||
|
// This splitting to two is done always if there is enough space for the high end to fit a region.
|
||||||
|
// Carve 'size' bytes of payload off this region. So,
|
||||||
|
// [sz prev next sz]
|
||||||
|
// becomes
|
||||||
|
// [sz payload sz] [sz prev next sz]
|
||||||
|
if size_of(Region) + REGION_HEADER_SIZE + size <= free_region.size {
|
||||||
|
new_free_region := (^Region)(uintptr(free_region) + REGION_HEADER_SIZE + uintptr(size))
|
||||||
|
create_free_region(new_free_region, free_region.size - size - REGION_HEADER_SIZE)
|
||||||
|
link_to_free_list(a, new_free_region)
|
||||||
|
create_used_region(free_region, size + REGION_HEADER_SIZE)
|
||||||
|
} else {
|
||||||
|
// There is not enough space to split the free memory region into used+free parts, so consume the whole
|
||||||
|
// region as used memory, not leaving a free memory region behind.
|
||||||
|
// Initialize the free region as used by resetting the ceiling size to the same value as the size at bottom.
|
||||||
|
([^]uint)(uintptr(free_region) + uintptr(free_region.size))[-1] = free_region.size
|
||||||
|
}
|
||||||
|
|
||||||
|
return rawptr(uintptr(free_region) + size_of(uint))
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_locked(a)
|
||||||
|
assert(is_power_of_two(alignment))
|
||||||
|
assert(size <= MAX_ALLOC_SIZE, "allocation too big", loc=loc)
|
||||||
|
|
||||||
|
alignment := alignment
|
||||||
|
alignment = max(alignment, a.alignment)
|
||||||
|
|
||||||
|
size := size
|
||||||
|
size = validate_alloc_size(size)
|
||||||
|
|
||||||
|
// Attempt to allocate memory starting from smallest bucket that can contain the required amount of memory.
|
||||||
|
// Under normal alignment conditions this should always be the first or second bucket we look at, but if
|
||||||
|
// performing an allocation with complex alignment, we may need to look at multiple buckets.
|
||||||
|
bucket_index := compute_free_list_bucket(size)
|
||||||
|
bucket_mask := a.free_region_buckets_used >> bucket_index
|
||||||
|
|
||||||
|
// Loop through each bucket that has free regions in it, based on bits set in free_region_buckets_used bitmap.
|
||||||
|
for bucket_mask != 0 {
|
||||||
|
index_add := intrinsics.count_trailing_zeros(bucket_mask)
|
||||||
|
bucket_index += uint(index_add)
|
||||||
|
bucket_mask >>= index_add
|
||||||
|
assert(bucket_index <= NUM_FREE_BUCKETS-1)
|
||||||
|
assert(a.free_region_buckets_used & (BUCKET_BITMASK_T(1) << bucket_index) > 0)
|
||||||
|
|
||||||
|
free_region := a.free_region_buckets[bucket_index].next
|
||||||
|
assert(free_region != nil)
|
||||||
|
if free_region != &a.free_region_buckets[bucket_index] {
|
||||||
|
ptr := attempt_allocate(a, free_region, alignment, size)
|
||||||
|
if ptr != nil {
|
||||||
|
return ptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// We were not able to allocate from the first region found in this bucket, so penalize
|
||||||
|
// the region by cycling it to the end of the doubly circular linked list. (constant time)
|
||||||
|
// This provides a randomized guarantee that when performing allocations of size k to a
|
||||||
|
// bucket of [k-something, k+something] range, we will not always attempt to satisfy the
|
||||||
|
// allocation from the same available region at the front of the list, but we try each
|
||||||
|
// region in turn.
|
||||||
|
unlink_from_free_list(free_region)
|
||||||
|
prepend_to_free_list(free_region, &a.free_region_buckets[bucket_index])
|
||||||
|
// But do not stick around to attempt to look at other regions in this bucket - move
|
||||||
|
// to search the next populated bucket index if this did not fit. This gives a practical
|
||||||
|
// "allocation in constant time" guarantee, since the next higher bucket will only have
|
||||||
|
// regions that are all of strictly larger size than the requested allocation. Only if
|
||||||
|
// there is a difficult alignment requirement we may fail to perform the allocation from
|
||||||
|
// a region in the next bucket, and if so, we keep trying higher buckets until one of them
|
||||||
|
// works.
|
||||||
|
bucket_index += 1
|
||||||
|
bucket_mask >>= 1
|
||||||
|
} else {
|
||||||
|
// This bucket was not populated after all with any regions,
|
||||||
|
// 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)
|
||||||
|
bucket_mask ~= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
assert((bucket_index == NUM_FREE_BUCKETS && bucket_mask == 0) || (bucket_mask == a.free_region_buckets_used >> bucket_index))
|
||||||
|
}
|
||||||
|
|
||||||
|
// None of the buckets were able to accommodate an allocation. If this happens we are almost out of memory.
|
||||||
|
// The largest bucket might contain some suitable regions, but we only looked at one region in that bucket, so
|
||||||
|
// as a last resort, loop through more free regions in the bucket that represents the largest allocations available.
|
||||||
|
// But only if the bucket representing largest allocations available is not any of the first thirty buckets,
|
||||||
|
// these represent allocatable areas less than <1024 bytes - which could be a lot of scrap.
|
||||||
|
// In such case, prefer to claim more memory right away.
|
||||||
|
largest_bucket_index := NUM_FREE_BUCKETS - 1 - intrinsics.count_leading_zeros(a.free_region_buckets_used)
|
||||||
|
// free_region will be null if there is absolutely no memory left. (all buckets are 100% used)
|
||||||
|
free_region := a.free_region_buckets_used > 0 ? a.free_region_buckets[largest_bucket_index].next : nil
|
||||||
|
// The 30 first free region buckets cover memory blocks < 2048 bytes, so skip looking at those here (too small)
|
||||||
|
if a.free_region_buckets_used >> 30 > 0 {
|
||||||
|
// Look only at a constant number of regions in this bucket max, to avoid bad worst case behavior.
|
||||||
|
// If this many regions cannot find free space, we give up and prefer to claim more memory instead.
|
||||||
|
max_regions_to_try_before_giving_up :: 99
|
||||||
|
num_tries_left := max_regions_to_try_before_giving_up
|
||||||
|
for ; free_region != &a.free_region_buckets[largest_bucket_index] && num_tries_left > 0; num_tries_left -= 1 {
|
||||||
|
ptr := attempt_allocate(a, free_region, alignment, size)
|
||||||
|
if ptr != nil {
|
||||||
|
return ptr
|
||||||
|
}
|
||||||
|
free_region = free_region.next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We were unable to find a free memory region. Must claim more memory!
|
||||||
|
num_bytes_to_claim := size+size_of(Region)*3
|
||||||
|
if alignment > a.alignment {
|
||||||
|
num_bytes_to_claim += alignment
|
||||||
|
}
|
||||||
|
success := claim_more_memory(a, num_bytes_to_claim)
|
||||||
|
if (success) {
|
||||||
|
// Try allocate again with the newly available memory.
|
||||||
|
return allocate_memory(a, alignment, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
// also claim_more_memory failed, we are really really constrained :( As a last resort, go back to looking at the
|
||||||
|
// bucket we already looked at above, continuing where the above search left off - perhaps there are
|
||||||
|
// regions we overlooked the first time that might be able to satisfy the allocation.
|
||||||
|
if free_region != nil {
|
||||||
|
for free_region != &a.free_region_buckets[largest_bucket_index] {
|
||||||
|
ptr := attempt_allocate(a, free_region, alignment, size)
|
||||||
|
if ptr != nil {
|
||||||
|
return ptr
|
||||||
|
}
|
||||||
|
free_region = free_region.next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fully out of memory.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
aligned_alloc :: proc(a: ^WASM_Allocator, alignment, size: uint, loc := #caller_location) -> rawptr {
|
||||||
|
lock(a)
|
||||||
|
defer unlock(a)
|
||||||
|
|
||||||
|
return allocate_memory(a, alignment, size, loc)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
free :: proc(a: ^WASM_Allocator, ptr: rawptr, loc := #caller_location) {
|
||||||
|
if ptr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
region_start_ptr := uintptr(ptr) - size_of(uint)
|
||||||
|
region := (^Region)(region_start_ptr)
|
||||||
|
assert(has_alignment(region_start_ptr, size_of(uint)))
|
||||||
|
|
||||||
|
lock(a)
|
||||||
|
defer unlock(a)
|
||||||
|
|
||||||
|
size := region.size
|
||||||
|
assert(region_is_in_use(region), "double free", loc=loc)
|
||||||
|
|
||||||
|
prev_region_size_field := ([^]uint)(region)[-1]
|
||||||
|
prev_region_size := prev_region_size_field & ~uint(FREE_REGION_FLAG)
|
||||||
|
if prev_region_size_field != prev_region_size {
|
||||||
|
prev_region := (^Region)(uintptr(region) - uintptr(prev_region_size))
|
||||||
|
unlink_from_free_list(prev_region)
|
||||||
|
region_start_ptr = uintptr(prev_region)
|
||||||
|
size += prev_region_size
|
||||||
|
}
|
||||||
|
|
||||||
|
next_reg := next_region(region)
|
||||||
|
size_at_end := (^uint)(region_payload_end_ptr(next_reg))^
|
||||||
|
if next_reg.size != size_at_end {
|
||||||
|
unlink_from_free_list(next_reg)
|
||||||
|
size += next_reg.size
|
||||||
|
}
|
||||||
|
|
||||||
|
create_free_region(rawptr(region_start_ptr), size)
|
||||||
|
link_to_free_list(a, (^Region)(region_start_ptr))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
aligned_realloc :: proc(a: ^WASM_Allocator, ptr: rawptr, alignment, size: uint, loc := #caller_location) -> rawptr {
|
||||||
|
|
||||||
|
attempt_region_resize :: proc(a: ^WASM_Allocator, region: ^Region, size: uint) -> bool {
|
||||||
|
lock(a)
|
||||||
|
defer unlock(a)
|
||||||
|
|
||||||
|
// First attempt to resize this region, if the next region that follows this one
|
||||||
|
// is a free region.
|
||||||
|
next_reg := next_region(region)
|
||||||
|
next_region_end_ptr := uintptr(next_reg) + uintptr(next_reg.size)
|
||||||
|
size_at_ceiling := ([^]uint)(next_region_end_ptr)[-1]
|
||||||
|
if next_reg.size != size_at_ceiling { // Next region is free?
|
||||||
|
assert(region_is_free(next_reg))
|
||||||
|
new_next_region_start_ptr := uintptr(region) + uintptr(size)
|
||||||
|
assert(has_alignment(new_next_region_start_ptr, size_of(uint)))
|
||||||
|
// Next region does not shrink to too small size?
|
||||||
|
if new_next_region_start_ptr + size_of(Region) <= next_region_end_ptr {
|
||||||
|
unlink_from_free_list(next_reg)
|
||||||
|
create_free_region(rawptr(new_next_region_start_ptr), uint(next_region_end_ptr - new_next_region_start_ptr))
|
||||||
|
link_to_free_list(a, (^Region)(new_next_region_start_ptr))
|
||||||
|
create_used_region(region, uint(new_next_region_start_ptr - uintptr(region)))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// If we remove the next region altogether, allocation is satisfied?
|
||||||
|
if new_next_region_start_ptr <= next_region_end_ptr {
|
||||||
|
unlink_from_free_list(next_reg)
|
||||||
|
create_used_region(region, region.size + next_reg.size)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Next region is an used region - we cannot change its starting address. However if we are shrinking the
|
||||||
|
// size of this region, we can create a new free region between this and the next used region.
|
||||||
|
if size + size_of(Region) <= region.size {
|
||||||
|
free_region_size := region.size - size
|
||||||
|
create_used_region(region, size)
|
||||||
|
free_region := (^Region)(uintptr(region) + uintptr(size))
|
||||||
|
create_free_region(free_region, free_region_size)
|
||||||
|
link_to_free_list(a, free_region)
|
||||||
|
return true
|
||||||
|
} else if size <= region.size {
|
||||||
|
// Caller was asking to shrink the size, but due to not being able to fit a full Region in the shrunk
|
||||||
|
// area, we cannot actually do anything. This occurs if the shrink amount is really small. In such case,
|
||||||
|
// just call it success without doing any work.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if ptr == nil {
|
||||||
|
return aligned_alloc(a, alignment, size, loc)
|
||||||
|
}
|
||||||
|
|
||||||
|
if size == 0 {
|
||||||
|
free(a, ptr, loc)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if size > MAX_ALLOC_SIZE {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(is_power_of_two(alignment))
|
||||||
|
assert(has_alignment(uintptr(ptr), alignment), "realloc on different alignment than original allocation", loc=loc)
|
||||||
|
|
||||||
|
size := size
|
||||||
|
size = validate_alloc_size(size)
|
||||||
|
|
||||||
|
region := (^Region)(uintptr(ptr) - size_of(uint))
|
||||||
|
|
||||||
|
// Attempt an in-place resize.
|
||||||
|
if attempt_region_resize(a, region, size + REGION_HEADER_SIZE) {
|
||||||
|
return ptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can't do it in-place, allocate new region and copy over.
|
||||||
|
newptr := aligned_alloc(a, alignment, size, loc)
|
||||||
|
if newptr != nil {
|
||||||
|
intrinsics.mem_copy(newptr, ptr, min(size, region.size - REGION_HEADER_SIZE))
|
||||||
|
free(a, ptr, loc=loc)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newptr
|
||||||
|
}
|
||||||
+10
-1
@@ -64,7 +64,16 @@ Darwin)
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
|
darwin_sysroot=
|
||||||
|
if [ $(which xcode-select) ]; then
|
||||||
|
darwin_sysroot="--sysroot $(xcode-select -p)/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk"
|
||||||
|
elif [[ -e "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk" ]]; then
|
||||||
|
darwin_sysroot="--sysroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk"
|
||||||
|
else
|
||||||
|
echo "Warning: MacOSX.sdk not found."
|
||||||
|
fi
|
||||||
|
|
||||||
|
CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags) ${darwin_sysroot}"
|
||||||
LDFLAGS="$LDFLAGS -liconv -ldl -framework System -lLLVM"
|
LDFLAGS="$LDFLAGS -liconv -ldl -framework System -lLLVM"
|
||||||
;;
|
;;
|
||||||
FreeBSD)
|
FreeBSD)
|
||||||
|
|||||||
@@ -5,13 +5,10 @@ The implementation is non-intrusive, and non-recursive.
|
|||||||
*/
|
*/
|
||||||
package container_avl
|
package container_avl
|
||||||
|
|
||||||
import "base:intrinsics"
|
@(require) import "base:intrinsics"
|
||||||
import "base:runtime"
|
@(require) import "base:runtime"
|
||||||
import "core:slice"
|
import "core:slice"
|
||||||
|
|
||||||
_ :: intrinsics
|
|
||||||
_ :: runtime
|
|
||||||
|
|
||||||
// Originally based on the CC0 implementation by Eric Biggers
|
// Originally based on the CC0 implementation by Eric Biggers
|
||||||
// See: https://github.com/ebiggers/avl_tree/
|
// See: https://github.com/ebiggers/avl_tree/
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,568 @@
|
|||||||
|
// This package implements a red-black tree
|
||||||
|
package container_rbtree
|
||||||
|
|
||||||
|
@(require) import "base:intrinsics"
|
||||||
|
@(require) import "base:runtime"
|
||||||
|
import "core:slice"
|
||||||
|
|
||||||
|
// Originally based on the CC0 implementation from literateprograms.org
|
||||||
|
// But with API design mimicking `core:container/avl` for ease of use.
|
||||||
|
|
||||||
|
// Direction specifies the traversal direction for a tree iterator.
|
||||||
|
Direction :: enum i8 {
|
||||||
|
// Backward is the in-order backwards direction.
|
||||||
|
Backward = -1,
|
||||||
|
// Forward is the in-order forwards direction.
|
||||||
|
Forward = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
Ordering :: slice.Ordering
|
||||||
|
|
||||||
|
// Tree is a red-black tree
|
||||||
|
Tree :: struct($Key: typeid, $Value: typeid) {
|
||||||
|
// user_data is a parameter that will be passed to the on_remove
|
||||||
|
// callback.
|
||||||
|
user_data: rawptr,
|
||||||
|
// on_remove is an optional callback that can be called immediately
|
||||||
|
// after a node is removed from the tree.
|
||||||
|
on_remove: proc(key: Key, value: Value, user_data: rawptr),
|
||||||
|
|
||||||
|
_root: ^Node(Key, Value),
|
||||||
|
_node_allocator: runtime.Allocator,
|
||||||
|
_cmp_fn: proc(Key, Key) -> Ordering,
|
||||||
|
_size: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Node is a red-black tree node.
|
||||||
|
//
|
||||||
|
// WARNING: It is unsafe to mutate value if the node is part of a tree
|
||||||
|
// if doing so will alter the Node's sort position relative to other
|
||||||
|
// elements in the tree.
|
||||||
|
Node :: struct($Key: typeid, $Value: typeid) {
|
||||||
|
key: Key,
|
||||||
|
value: Value,
|
||||||
|
|
||||||
|
_parent: ^Node(Key, Value),
|
||||||
|
_left: ^Node(Key, Value),
|
||||||
|
_right: ^Node(Key, Value),
|
||||||
|
_color: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Might store this in the node pointer in the future, but that'll require a decent amount of rework to pass ^^N instead of ^N
|
||||||
|
Color :: enum uintptr {Black = 0, Red = 1}
|
||||||
|
|
||||||
|
// Iterator is a tree iterator.
|
||||||
|
//
|
||||||
|
// WARNING: It is unsafe to modify the tree while iterating, except via
|
||||||
|
// the iterator_remove method.
|
||||||
|
Iterator :: struct($Key: typeid, $Value: typeid) {
|
||||||
|
_tree: ^Tree(Key, Value),
|
||||||
|
_cur: ^Node(Key, Value),
|
||||||
|
_next: ^Node(Key, Value),
|
||||||
|
_direction: Direction,
|
||||||
|
_called_next: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
// init initializes a tree.
|
||||||
|
init :: proc {
|
||||||
|
init_ordered,
|
||||||
|
init_cmp,
|
||||||
|
}
|
||||||
|
|
||||||
|
// init_cmp initializes a tree.
|
||||||
|
init_cmp :: proc(t: ^$T/Tree($Key, $Value), cmp_fn: proc(a, b: Key) -> Ordering, node_allocator := context.allocator) {
|
||||||
|
t._root = nil
|
||||||
|
t._node_allocator = node_allocator
|
||||||
|
t._cmp_fn = cmp_fn
|
||||||
|
t._size = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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_cmp(t, slice.cmp_proc(Key), node_allocator)
|
||||||
|
}
|
||||||
|
|
||||||
|
// destroy de-initializes a tree.
|
||||||
|
destroy :: proc(t: ^$T/Tree($Key, $Value), call_on_remove: bool = true) {
|
||||||
|
iter := iterator(t, .Forward)
|
||||||
|
for _ in iterator_next(&iter) {
|
||||||
|
iterator_remove(&iter, call_on_remove)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
len :: proc "contextless" (t: ^$T/Tree($Key, $Value)) -> (node_count: int) {
|
||||||
|
return t._size
|
||||||
|
}
|
||||||
|
|
||||||
|
// first returns the first node in the tree (in-order) or nil iff
|
||||||
|
// the tree is empty.
|
||||||
|
first :: proc "contextless" (t: ^$T/Tree($Key, $Value)) -> ^Node(Key, Value) {
|
||||||
|
return tree_first_or_last_in_order(t, Direction.Backward)
|
||||||
|
}
|
||||||
|
|
||||||
|
// last returns the last element in the tree (in-order) or nil iff
|
||||||
|
// the tree is empty.
|
||||||
|
last :: proc "contextless" (t: ^$T/Tree($Key, $Value)) -> ^Node(Key, Value) {
|
||||||
|
return tree_first_or_last_in_order(t, Direction.Forward)
|
||||||
|
}
|
||||||
|
|
||||||
|
// find finds the key in the tree, and returns the corresponding node, or nil iff the value is not present.
|
||||||
|
find :: proc(t: ^$T/Tree($Key, $Value), key: Key) -> (node: ^Node(Key, Value)) {
|
||||||
|
node = t._root
|
||||||
|
for node != nil {
|
||||||
|
switch t._cmp_fn(key, node.key) {
|
||||||
|
case .Equal: return node
|
||||||
|
case .Less: node = node._left
|
||||||
|
case .Greater: node = node._right
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
// find_value finds the key in the tree, and returns the corresponding value, or nil iff the value is not present.
|
||||||
|
find_value :: proc(t: ^$T/Tree($Key, $Value), key: Key) -> (value: Value, ok: bool) #optional_ok {
|
||||||
|
if n := find(t, key); n != nil {
|
||||||
|
return n.value, true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// find_or_insert attempts to insert the value into the tree, and returns
|
||||||
|
// the node, a boolean indicating if the value was inserted, and the
|
||||||
|
// node allocator error if relevant. If the value is already present, the existing node is updated.
|
||||||
|
find_or_insert :: proc(t: ^$T/Tree($Key, $Value), key: Key, value: Value) -> (n: ^Node(Key, Value), inserted: bool, err: runtime.Allocator_Error) {
|
||||||
|
n_ptr := &t._root
|
||||||
|
for n_ptr^ != nil {
|
||||||
|
n = n_ptr^
|
||||||
|
switch t._cmp_fn(key, n.key) {
|
||||||
|
case .Less:
|
||||||
|
n_ptr = &n._left
|
||||||
|
case .Greater:
|
||||||
|
n_ptr = &n._right
|
||||||
|
case .Equal:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_parent := n
|
||||||
|
|
||||||
|
n = new_clone(Node(Key, Value){key=key, value=value, _parent=_parent, _color=.Red}, t._node_allocator) or_return
|
||||||
|
n_ptr^ = n
|
||||||
|
insert_case1(t, n)
|
||||||
|
t._size += 1
|
||||||
|
return n, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove removes a node or value from the tree, and returns true iff the
|
||||||
|
// removal was successful. While the node's value will be left intact,
|
||||||
|
// the node itself will be freed via the tree's node allocator.
|
||||||
|
remove :: proc {
|
||||||
|
remove_key,
|
||||||
|
remove_node,
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove_value removes a value from the tree, and returns true iff the
|
||||||
|
// removal was successful. While the node's key + value will be left intact,
|
||||||
|
// the node itself will be freed via the tree's node allocator.
|
||||||
|
remove_key :: proc(t: ^$T/Tree($Key, $Value), key: Key, call_on_remove := true) -> bool {
|
||||||
|
n := find(t, key)
|
||||||
|
if n == nil {
|
||||||
|
return false // Key not found, nothing to do
|
||||||
|
}
|
||||||
|
return remove_node(t, n, call_on_remove)
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove_node removes a node from the tree, and returns true iff the
|
||||||
|
// removal was successful. While the node's key + value will be left intact,
|
||||||
|
// the node itself will be freed via the tree's node allocator.
|
||||||
|
remove_node :: proc(t: ^$T/Tree($Key, $Value), node: ^$N/Node(Key, Value), call_on_remove := true) -> (found: bool) {
|
||||||
|
if node._parent == node || (node._parent == nil && t._root != node) {
|
||||||
|
return false // Don't touch self-parented or dangling nodes.
|
||||||
|
}
|
||||||
|
node := node
|
||||||
|
if node._left != nil && node._right != nil {
|
||||||
|
// Copy key + value from predecessor and delete it instead
|
||||||
|
predecessor := maximum_node(node._left)
|
||||||
|
node.key = predecessor.key
|
||||||
|
node.value = predecessor.value
|
||||||
|
node = predecessor
|
||||||
|
}
|
||||||
|
|
||||||
|
child := node._right == nil ? node._left : node._right
|
||||||
|
if node_color(node) == .Black {
|
||||||
|
node._color = node_color(child)
|
||||||
|
remove_case1(t, node)
|
||||||
|
}
|
||||||
|
replace_node(t, node, child)
|
||||||
|
if node._parent == nil && child != nil {
|
||||||
|
child._color = .Black // root should be black
|
||||||
|
}
|
||||||
|
|
||||||
|
if call_on_remove && t.on_remove != nil {
|
||||||
|
t.on_remove(node.key, node.value, t.user_data)
|
||||||
|
}
|
||||||
|
free(node, t._node_allocator)
|
||||||
|
t._size -= 1
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterator returns a tree iterator in the specified direction.
|
||||||
|
iterator :: proc "contextless" (t: ^$T/Tree($Key, $Value), direction: Direction) -> Iterator(Key, Value) {
|
||||||
|
it: Iterator(Key, Value)
|
||||||
|
it._tree = cast(^Tree(Key, Value))t
|
||||||
|
it._direction = direction
|
||||||
|
|
||||||
|
iterator_first(&it)
|
||||||
|
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterator_from_pos returns a tree iterator in the specified direction,
|
||||||
|
// spanning the range [pos, last] (inclusive).
|
||||||
|
iterator_from_pos :: proc "contextless" (t: ^$T/Tree($Key, $Value), pos: ^Node(Key, Value), direction: Direction) -> Iterator(Key, Value) {
|
||||||
|
it: Iterator(Key, Value)
|
||||||
|
it._tree = transmute(^Tree(Key, Value))t
|
||||||
|
it._direction = direction
|
||||||
|
it._next = nil
|
||||||
|
it._called_next = false
|
||||||
|
|
||||||
|
if it._cur = pos; pos != nil {
|
||||||
|
it._next = node_next_or_prev_in_order(it._cur, it._direction)
|
||||||
|
}
|
||||||
|
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterator_get returns the node currently pointed to by the iterator,
|
||||||
|
// or nil iff the node has been removed, the tree is empty, or the end
|
||||||
|
// of the tree has been reached.
|
||||||
|
iterator_get :: proc "contextless" (it: ^$I/Iterator($Key, $Value)) -> ^Node(Key, Value) {
|
||||||
|
return it._cur
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterator_remove removes the node currently pointed to by the iterator,
|
||||||
|
// and returns true iff the removal was successful. Semantics are the
|
||||||
|
// same as the Tree remove.
|
||||||
|
iterator_remove :: proc(it: ^$I/Iterator($Key, $Value), call_on_remove: bool = true) -> bool {
|
||||||
|
if it._cur == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
ok := remove_node(it._tree, it._cur , call_on_remove)
|
||||||
|
if ok {
|
||||||
|
it._cur = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterator_next advances the iterator and returns the (node, true) or
|
||||||
|
// or (nil, false) iff the end of the tree has been reached.
|
||||||
|
//
|
||||||
|
// Note: The first call to iterator_next will return the first node instead
|
||||||
|
// of advancing the iterator.
|
||||||
|
iterator_next :: proc "contextless" (it: ^$I/Iterator($Key, $Value)) -> (^Node(Key, Value), bool) {
|
||||||
|
// This check is needed so that the first element gets returned from
|
||||||
|
// a brand-new iterator, and so that the somewhat contrived case where
|
||||||
|
// iterator_remove is called before the first call to iterator_next
|
||||||
|
// returns the correct value.
|
||||||
|
if !it._called_next {
|
||||||
|
it._called_next = true
|
||||||
|
|
||||||
|
// There can be the contrived case where iterator_remove is
|
||||||
|
// called before ever calling iterator_next, which needs to be
|
||||||
|
// handled as an actual call to next.
|
||||||
|
//
|
||||||
|
// If this happens it._cur will be nil, so only return the
|
||||||
|
// first value, if it._cur is valid.
|
||||||
|
if it._cur != nil {
|
||||||
|
return it._cur, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if it._next == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
it._cur = it._next
|
||||||
|
it._next = node_next_or_prev_in_order(it._cur, it._direction)
|
||||||
|
|
||||||
|
return it._cur, true
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
tree_first_or_last_in_order :: proc "contextless" (t: ^$T/Tree($Key, $Value), direction: Direction) -> ^Node(Key, Value) {
|
||||||
|
first, sign := t._root, i8(direction)
|
||||||
|
if first != nil {
|
||||||
|
for {
|
||||||
|
tmp := node_get_child(first, sign)
|
||||||
|
if tmp == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
first = tmp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return first
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
node_get_child :: #force_inline proc "contextless" (n: ^Node($Key, $Value), sign: i8) -> ^Node(Key, Value) {
|
||||||
|
if sign < 0 {
|
||||||
|
return n._left
|
||||||
|
}
|
||||||
|
return n._right
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
node_next_or_prev_in_order :: proc "contextless" (n: ^Node($Key, $Value), direction: Direction) -> ^Node(Key, Value) {
|
||||||
|
next, tmp: ^Node(Key, Value)
|
||||||
|
sign := i8(direction)
|
||||||
|
|
||||||
|
if next = node_get_child(n, +sign); next != nil {
|
||||||
|
for {
|
||||||
|
tmp = node_get_child(next, -sign)
|
||||||
|
if tmp == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
next = tmp
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tmp, next = n, n._parent
|
||||||
|
for next != nil && tmp == node_get_child(next, +sign) {
|
||||||
|
tmp, next = next, next._parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
iterator_first :: proc "contextless" (it: ^Iterator($Key, $Value)) {
|
||||||
|
// This is private because behavior when the user manually calls
|
||||||
|
// iterator_first followed by iterator_next is unintuitive, since
|
||||||
|
// the first call to iterator_next MUST return the first node
|
||||||
|
// instead of advancing so that `for node in iterator_next(&next)`
|
||||||
|
// works as expected.
|
||||||
|
|
||||||
|
switch it._direction {
|
||||||
|
case .Forward:
|
||||||
|
it._cur = tree_first_or_last_in_order(it._tree, .Backward)
|
||||||
|
case .Backward:
|
||||||
|
it._cur = tree_first_or_last_in_order(it._tree, .Forward)
|
||||||
|
}
|
||||||
|
|
||||||
|
it._next = nil
|
||||||
|
it._called_next = false
|
||||||
|
|
||||||
|
if it._cur != nil {
|
||||||
|
it._next = node_next_or_prev_in_order(it._cur, it._direction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
grand_parent :: proc(n: ^$N/Node($Key, $Value)) -> (g: ^N) {
|
||||||
|
return n._parent._parent
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
sibling :: proc(n: ^$N/Node($Key, $Value)) -> (s: ^N) {
|
||||||
|
if n == n._parent._left {
|
||||||
|
return n._parent._right
|
||||||
|
} else {
|
||||||
|
return n._parent._left
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
uncle :: proc(n: ^$N/Node($Key, $Value)) -> (u: ^N) {
|
||||||
|
return sibling(n._parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
rotate__left :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||||
|
r := n._right
|
||||||
|
replace_node(t, n, r)
|
||||||
|
n._right = r._left
|
||||||
|
if r._left != nil {
|
||||||
|
r._left._parent = n
|
||||||
|
}
|
||||||
|
r._left = n
|
||||||
|
n._parent = r
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
rotate__right :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||||
|
l := n._left
|
||||||
|
replace_node(t, n, l)
|
||||||
|
n._left = l._right
|
||||||
|
if l._right != nil {
|
||||||
|
l._right._parent = n
|
||||||
|
}
|
||||||
|
l._right = n
|
||||||
|
n._parent = l
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
replace_node :: proc(t: ^$T/Tree($Key, $Value), old_n: ^$N/Node(Key, Value), new_n: ^N) {
|
||||||
|
if old_n._parent == nil {
|
||||||
|
t._root = new_n
|
||||||
|
} else {
|
||||||
|
if (old_n == old_n._parent._left) {
|
||||||
|
old_n._parent._left = new_n
|
||||||
|
} else {
|
||||||
|
old_n._parent._right = new_n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if new_n != nil {
|
||||||
|
new_n._parent = old_n._parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
insert_case1 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||||
|
if n._parent == nil {
|
||||||
|
n._color = .Black
|
||||||
|
} else {
|
||||||
|
insert_case2(t, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
insert_case2 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||||
|
if node_color(n._parent) == .Black {
|
||||||
|
return // Tree is still valid
|
||||||
|
} else {
|
||||||
|
insert_case3(t, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
insert_case3 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||||
|
if node_color(uncle(n)) == .Red {
|
||||||
|
n._parent._color = .Black
|
||||||
|
uncle(n)._color = .Black
|
||||||
|
grand_parent(n)._color = .Red
|
||||||
|
insert_case1(t, grand_parent(n))
|
||||||
|
} else {
|
||||||
|
insert_case4(t, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
insert_case4 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||||
|
n := n
|
||||||
|
if n == n._parent._right && n._parent == grand_parent(n)._left {
|
||||||
|
rotate__left(t, n._parent)
|
||||||
|
n = n._left
|
||||||
|
} else if n == n._parent._left && n._parent == grand_parent(n)._right {
|
||||||
|
rotate__right(t, n._parent)
|
||||||
|
n = n._right
|
||||||
|
}
|
||||||
|
insert_case5(t, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
insert_case5 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||||
|
n._parent._color = .Black
|
||||||
|
grand_parent(n)._color = .Red
|
||||||
|
if n == n._parent._left && n._parent == grand_parent(n)._left {
|
||||||
|
rotate__right(t, grand_parent(n))
|
||||||
|
} else {
|
||||||
|
rotate__left(t, grand_parent(n))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The maximum_node() helper function just walks _right until it reaches the last non-leaf:
|
||||||
|
@(private)
|
||||||
|
maximum_node :: proc(n: ^$N/Node($Key, $Value)) -> (max_node: ^N) {
|
||||||
|
n := n
|
||||||
|
for n._right != nil {
|
||||||
|
n = n._right
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
remove_case1 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||||
|
if n._parent == nil {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
remove_case2(t, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
remove_case2 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||||
|
if node_color(sibling(n)) == .Red {
|
||||||
|
n._parent._color = .Red
|
||||||
|
sibling(n)._color = .Black
|
||||||
|
if n == n._parent._left {
|
||||||
|
rotate__left(t, n._parent)
|
||||||
|
} else {
|
||||||
|
rotate__right(t, n._parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
remove_case3(t, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
remove_case3 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||||
|
if node_color(n._parent) == .Black &&
|
||||||
|
node_color(sibling(n)) == .Black &&
|
||||||
|
node_color(sibling(n)._left) == .Black &&
|
||||||
|
node_color(sibling(n)._right) == .Black {
|
||||||
|
sibling(n)._color = .Red
|
||||||
|
remove_case1(t, n._parent)
|
||||||
|
} else {
|
||||||
|
remove_case4(t, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
remove_case4 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||||
|
if node_color(n._parent) == .Red &&
|
||||||
|
node_color(sibling(n)) == .Black &&
|
||||||
|
node_color(sibling(n)._left) == .Black &&
|
||||||
|
node_color(sibling(n)._right) == .Black {
|
||||||
|
sibling(n)._color = .Red
|
||||||
|
n._parent._color = .Black
|
||||||
|
} else {
|
||||||
|
remove_case5(t, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
remove_case5 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||||
|
if n == n._parent._left &&
|
||||||
|
node_color(sibling(n)) == .Black &&
|
||||||
|
node_color(sibling(n)._left) == .Red &&
|
||||||
|
node_color(sibling(n)._right) == .Black {
|
||||||
|
sibling(n)._color = .Red
|
||||||
|
sibling(n)._left._color = .Black
|
||||||
|
rotate__right(t, sibling(n))
|
||||||
|
} else if n == n._parent._right &&
|
||||||
|
node_color(sibling(n)) == .Black &&
|
||||||
|
node_color(sibling(n)._right) == .Red &&
|
||||||
|
node_color(sibling(n)._left) == .Black {
|
||||||
|
sibling(n)._color = .Red
|
||||||
|
sibling(n)._right._color = .Black
|
||||||
|
rotate__left(t, sibling(n))
|
||||||
|
}
|
||||||
|
remove_case6(t, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
remove_case6 :: proc(t: ^$T/Tree($Key, $Value), n: ^$N/Node(Key, Value)) {
|
||||||
|
sibling(n)._color = node_color(n._parent)
|
||||||
|
n._parent._color = .Black
|
||||||
|
if n == n._parent._left {
|
||||||
|
sibling(n)._right._color = .Black
|
||||||
|
rotate__left(t, n._parent)
|
||||||
|
} else {
|
||||||
|
sibling(n)._left._color = .Black
|
||||||
|
rotate__right(t, n._parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node_color :: proc(n: ^$N/Node($Key, $Value)) -> (c: Color) {
|
||||||
|
return n == nil ? .Black : n._color
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
//+build ignore
|
||||||
|
package encoding_csv
|
||||||
|
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:encoding/csv"
|
||||||
|
import "core:os"
|
||||||
|
|
||||||
|
// Requires keeping the entire CSV file in memory at once
|
||||||
|
iterate_csv_from_string :: proc(filename: string) {
|
||||||
|
r: csv.Reader
|
||||||
|
r.trim_leading_space = true
|
||||||
|
r.reuse_record = true // Without it you have to delete(record)
|
||||||
|
r.reuse_record_buffer = true // Without it you have to each of the fields within it
|
||||||
|
defer csv.reader_destroy(&r)
|
||||||
|
|
||||||
|
if csv_data, ok := os.read_entire_file(filename); ok {
|
||||||
|
csv.reader_init_with_string(&r, string(csv_data))
|
||||||
|
defer delete(csv_data)
|
||||||
|
} else {
|
||||||
|
fmt.printfln("Unable to open file: %v", filename)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for r, i, err in csv.iterator_next(&r) {
|
||||||
|
if err != nil { /* Do something with error */ }
|
||||||
|
for f, j in r {
|
||||||
|
fmt.printfln("Record %v, field %v: %q", i, j, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reads the CSV as it's processed (with a small buffer)
|
||||||
|
iterate_csv_from_stream :: proc(filename: string) {
|
||||||
|
fmt.printfln("Hellope from %v", filename)
|
||||||
|
r: csv.Reader
|
||||||
|
r.trim_leading_space = true
|
||||||
|
r.reuse_record = true // Without it you have to delete(record)
|
||||||
|
r.reuse_record_buffer = true // Without it you have to each of the fields within it
|
||||||
|
defer csv.reader_destroy(&r)
|
||||||
|
|
||||||
|
handle, errno := os.open(filename)
|
||||||
|
if errno != os.ERROR_NONE {
|
||||||
|
fmt.printfln("Error opening file: %v", filename)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer os.close(handle)
|
||||||
|
csv.reader_init(&r, os.stream_from_handle(handle))
|
||||||
|
|
||||||
|
for r, i in csv.iterator_next(&r) {
|
||||||
|
for f, j in r {
|
||||||
|
fmt.printfln("Record %v, field %v: %q", i, j, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.printfln("Error: %v", csv.iterator_last_error(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read all records at once
|
||||||
|
read_csv_from_string :: proc(filename: string) {
|
||||||
|
r: csv.Reader
|
||||||
|
r.trim_leading_space = true
|
||||||
|
r.reuse_record = true // Without it you have to delete(record)
|
||||||
|
r.reuse_record_buffer = true // Without it you have to each of the fields within it
|
||||||
|
defer csv.reader_destroy(&r)
|
||||||
|
|
||||||
|
if csv_data, ok := os.read_entire_file(filename); ok {
|
||||||
|
csv.reader_init_with_string(&r, string(csv_data))
|
||||||
|
defer delete(csv_data)
|
||||||
|
} else {
|
||||||
|
fmt.printfln("Unable to open file: %v", filename)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
records, err := csv.read_all(&r)
|
||||||
|
if err != nil { /* Do something with CSV parse error */ }
|
||||||
|
|
||||||
|
defer {
|
||||||
|
for rec in records {
|
||||||
|
delete(rec)
|
||||||
|
}
|
||||||
|
delete(records)
|
||||||
|
}
|
||||||
|
|
||||||
|
for r, i in records {
|
||||||
|
for f, j in r {
|
||||||
|
fmt.printfln("Record %v, field %v: %q", i, j, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -57,6 +57,9 @@ Reader :: struct {
|
|||||||
field_indices: [dynamic]int,
|
field_indices: [dynamic]int,
|
||||||
last_record: [dynamic]string,
|
last_record: [dynamic]string,
|
||||||
sr: strings.Reader, // used by reader_init_with_string
|
sr: strings.Reader, // used by reader_init_with_string
|
||||||
|
|
||||||
|
// Set and used by the iterator. Query using `iterator_last_error`
|
||||||
|
last_iterator_error: Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -121,6 +124,25 @@ reader_destroy :: proc(r: ^Reader) {
|
|||||||
bufio.reader_destroy(&r.r)
|
bufio.reader_destroy(&r.r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Returns a record at a time.
|
||||||
|
|
||||||
|
for record, row_idx in csv.iterator_next(&r) { ... }
|
||||||
|
|
||||||
|
TIP: If you process the results within the loop and don't need to own the results,
|
||||||
|
you can set the Reader's `reuse_record` and `reuse_record_reuse_record_buffer` to true;
|
||||||
|
you won't need to delete the record or its fields.
|
||||||
|
*/
|
||||||
|
iterator_next :: proc(r: ^Reader) -> (record: []string, idx: int, err: Error, more: bool) {
|
||||||
|
record, r.last_iterator_error = read(r)
|
||||||
|
return record, r.line_count - 1, r.last_iterator_error, r.last_iterator_error == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get last error if we the iterator
|
||||||
|
iterator_last_error :: proc(r: Reader) -> (err: Error) {
|
||||||
|
return r.last_iterator_error
|
||||||
|
}
|
||||||
|
|
||||||
// read reads a single record (a slice of fields) from r
|
// read reads a single record (a slice of fields) from r
|
||||||
//
|
//
|
||||||
// All \r\n sequences are normalized to \n, including multi-line field
|
// All \r\n sequences are normalized to \n, including multi-line field
|
||||||
@@ -460,5 +482,4 @@ _read_record :: proc(r: ^Reader, dst: ^[dynamic]string, allocator := context.all
|
|||||||
r.fields_per_record = len(dst)
|
r.fields_per_record = len(dst)
|
||||||
}
|
}
|
||||||
return dst[:], err
|
return dst[:], err
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -539,8 +539,6 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
|||||||
case: panic("unknown bit_size size")
|
case: panic("unknown bit_size size")
|
||||||
}
|
}
|
||||||
io.write_u64(w, bit_data) or_return
|
io.write_u64(w, bit_data) or_return
|
||||||
|
|
||||||
return .Unsupported_Type
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|||||||
+148
-54
@@ -13,20 +13,7 @@ import "core:unicode/utf8"
|
|||||||
|
|
||||||
// Internal data structure that stores the required information for formatted printing
|
// Internal data structure that stores the required information for formatted printing
|
||||||
Info :: struct {
|
Info :: struct {
|
||||||
minus: bool,
|
using state: Info_State,
|
||||||
plus: bool,
|
|
||||||
space: bool,
|
|
||||||
zero: bool,
|
|
||||||
hash: bool,
|
|
||||||
width_set: bool,
|
|
||||||
prec_set: bool,
|
|
||||||
|
|
||||||
width: int,
|
|
||||||
prec: int,
|
|
||||||
indent: int,
|
|
||||||
|
|
||||||
ignore_user_formatters: bool,
|
|
||||||
in_bad: bool,
|
|
||||||
|
|
||||||
writer: io.Writer,
|
writer: io.Writer,
|
||||||
arg: any, // Temporary
|
arg: any, // Temporary
|
||||||
@@ -39,6 +26,24 @@ Info :: struct {
|
|||||||
n: int, // bytes written
|
n: int, // bytes written
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Info_State :: struct {
|
||||||
|
minus: bool,
|
||||||
|
plus: bool,
|
||||||
|
space: bool,
|
||||||
|
zero: bool,
|
||||||
|
hash: bool,
|
||||||
|
width_set: bool,
|
||||||
|
prec_set: bool,
|
||||||
|
|
||||||
|
ignore_user_formatters: bool,
|
||||||
|
in_bad: bool,
|
||||||
|
|
||||||
|
width: int,
|
||||||
|
prec: int,
|
||||||
|
indent: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Custom formatter signature. It returns true if the formatting was successful and false when it could not be done
|
// Custom formatter signature. It returns true if the formatting was successful and false when it could not be done
|
||||||
User_Formatter :: #type proc(fi: ^Info, arg: any, verb: rune) -> bool
|
User_Formatter :: #type proc(fi: ^Info, arg: any, verb: rune) -> bool
|
||||||
|
|
||||||
@@ -994,6 +999,33 @@ _fmt_int :: proc(fi: ^Info, u: u64, base: int, is_signed: bool, bit_size: int, d
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buf: [256]byte
|
||||||
|
start := 0
|
||||||
|
|
||||||
|
if fi.hash && !is_signed {
|
||||||
|
switch base {
|
||||||
|
case 2:
|
||||||
|
io.write_byte(fi.writer, '0', &fi.n)
|
||||||
|
io.write_byte(fi.writer, 'b', &fi.n)
|
||||||
|
start = 2
|
||||||
|
|
||||||
|
case 8:
|
||||||
|
io.write_byte(fi.writer, '0', &fi.n)
|
||||||
|
io.write_byte(fi.writer, 'o', &fi.n)
|
||||||
|
start = 2
|
||||||
|
|
||||||
|
case 12:
|
||||||
|
io.write_byte(fi.writer, '0', &fi.n)
|
||||||
|
io.write_byte(fi.writer, 'o', &fi.n)
|
||||||
|
start = 2
|
||||||
|
|
||||||
|
case 16:
|
||||||
|
io.write_byte(fi.writer, '0', &fi.n)
|
||||||
|
io.write_byte(fi.writer, 'x', &fi.n)
|
||||||
|
start = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
prec := 0
|
prec := 0
|
||||||
if fi.prec_set {
|
if fi.prec_set {
|
||||||
prec = fi.prec
|
prec = fi.prec
|
||||||
@@ -1019,14 +1051,10 @@ _fmt_int :: proc(fi: ^Info, u: u64, base: int, is_signed: bool, bit_size: int, d
|
|||||||
panic("_fmt_int: unknown base, whoops")
|
panic("_fmt_int: unknown base, whoops")
|
||||||
}
|
}
|
||||||
|
|
||||||
buf: [256]byte
|
|
||||||
start := 0
|
|
||||||
|
|
||||||
flags: strconv.Int_Flags
|
flags: strconv.Int_Flags
|
||||||
if fi.hash { flags |= {.Prefix} }
|
if fi.hash && !fi.zero && start == 0 { flags |= {.Prefix} }
|
||||||
if fi.plus { flags |= {.Plus} }
|
if fi.plus { flags |= {.Plus} }
|
||||||
s := strconv.append_bits(buf[start:], u, base, is_signed, bit_size, digits, flags)
|
s := strconv.append_bits(buf[start:], u, base, is_signed, bit_size, digits, flags)
|
||||||
|
|
||||||
prev_zero := fi.zero
|
prev_zero := fi.zero
|
||||||
defer fi.zero = prev_zero
|
defer fi.zero = prev_zero
|
||||||
fi.zero = false
|
fi.zero = false
|
||||||
@@ -1056,6 +1084,33 @@ _fmt_int_128 :: proc(fi: ^Info, u: u128, base: int, is_signed: bool, bit_size: i
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buf: [256]byte
|
||||||
|
start := 0
|
||||||
|
|
||||||
|
if fi.hash && !is_signed {
|
||||||
|
switch base {
|
||||||
|
case 2:
|
||||||
|
io.write_byte(fi.writer, '0', &fi.n)
|
||||||
|
io.write_byte(fi.writer, 'b', &fi.n)
|
||||||
|
start = 2
|
||||||
|
|
||||||
|
case 8:
|
||||||
|
io.write_byte(fi.writer, '0', &fi.n)
|
||||||
|
io.write_byte(fi.writer, 'o', &fi.n)
|
||||||
|
start = 2
|
||||||
|
|
||||||
|
case 12:
|
||||||
|
io.write_byte(fi.writer, '0', &fi.n)
|
||||||
|
io.write_byte(fi.writer, 'o', &fi.n)
|
||||||
|
start = 2
|
||||||
|
|
||||||
|
case 16:
|
||||||
|
io.write_byte(fi.writer, '0', &fi.n)
|
||||||
|
io.write_byte(fi.writer, 'x', &fi.n)
|
||||||
|
start = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
prec := 0
|
prec := 0
|
||||||
if fi.prec_set {
|
if fi.prec_set {
|
||||||
prec = fi.prec
|
prec = fi.prec
|
||||||
@@ -1081,12 +1136,9 @@ _fmt_int_128 :: proc(fi: ^Info, u: u128, base: int, is_signed: bool, bit_size: i
|
|||||||
panic("_fmt_int: unknown base, whoops")
|
panic("_fmt_int: unknown base, whoops")
|
||||||
}
|
}
|
||||||
|
|
||||||
buf: [256]byte
|
|
||||||
start := 0
|
|
||||||
|
|
||||||
flags: strconv.Int_Flags
|
flags: strconv.Int_Flags
|
||||||
if fi.hash && !fi.zero { flags |= {.Prefix} }
|
if fi.hash && !fi.zero && start == 0 { flags |= {.Prefix} }
|
||||||
if fi.plus { flags |= {.Plus} }
|
if fi.plus { flags |= {.Plus} }
|
||||||
s := strconv.append_bits_128(buf[start:], u, base, is_signed, bit_size, digits, flags)
|
s := strconv.append_bits_128(buf[start:], u, base, is_signed, bit_size, digits, flags)
|
||||||
|
|
||||||
if fi.hash && fi.zero && fi.indent == 0 {
|
if fi.hash && fi.zero && fi.indent == 0 {
|
||||||
@@ -1777,7 +1829,7 @@ fmt_write_array :: proc(fi: ^Info, array_data: rawptr, count: int, elem_size: in
|
|||||||
// Returns: A boolean value indicating whether to continue processing the tag
|
// Returns: A boolean value indicating whether to continue processing the tag
|
||||||
//
|
//
|
||||||
@(private)
|
@(private)
|
||||||
handle_tag :: proc(data: rawptr, info: reflect.Type_Info_Struct, idx: int, verb: ^rune, optional_len: ^int, use_nul_termination: ^bool) -> (do_continue: bool) {
|
handle_tag :: proc(state: ^Info_State, data: rawptr, info: reflect.Type_Info_Struct, idx: int, verb: ^rune, optional_len: ^int, use_nul_termination: ^bool) -> (do_continue: bool) {
|
||||||
handle_optional_len :: proc(data: rawptr, info: reflect.Type_Info_Struct, field_name: string, optional_len: ^int) {
|
handle_optional_len :: proc(data: rawptr, info: reflect.Type_Info_Struct, field_name: string, optional_len: ^int) {
|
||||||
if optional_len == nil {
|
if optional_len == nil {
|
||||||
return
|
return
|
||||||
@@ -1794,45 +1846,83 @@ handle_tag :: proc(data: rawptr, info: reflect.Type_Info_Struct, idx: int, verb:
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tag := info.tags[idx]
|
tag := info.tags[idx]
|
||||||
if vt, ok := reflect.struct_tag_lookup(reflect.Struct_Tag(tag), "fmt"); ok {
|
if vt, ok := reflect.struct_tag_lookup(reflect.Struct_Tag(tag), "fmt"); ok {
|
||||||
value := strings.trim_space(string(vt))
|
value := strings.trim_space(string(vt))
|
||||||
switch value {
|
switch value {
|
||||||
case "": return false
|
case "": return false
|
||||||
case "-": return true
|
case "-": return true
|
||||||
}
|
}
|
||||||
r, w := utf8.decode_rune_in_string(value)
|
|
||||||
value = value[w:]
|
fi := state
|
||||||
if value == "" || value[0] == ',' {
|
|
||||||
if verb^ == 'w' {
|
head, _, tail := strings.partition(value, ",")
|
||||||
// TODO(bill): is this a good idea overriding that field tags if 'w' is used?
|
|
||||||
switch r {
|
i := 0
|
||||||
case 's': r = 'q'
|
prefix_loop: for ; i < len(head); i += 1 {
|
||||||
case: r = 'w'
|
switch head[i] {
|
||||||
}
|
case '+':
|
||||||
|
fi.plus = true
|
||||||
|
case '-':
|
||||||
|
fi.minus = true
|
||||||
|
fi.zero = false
|
||||||
|
case ' ':
|
||||||
|
fi.space = true
|
||||||
|
case '#':
|
||||||
|
fi.hash = true
|
||||||
|
case '0':
|
||||||
|
fi.zero = !fi.minus
|
||||||
|
case:
|
||||||
|
break prefix_loop
|
||||||
}
|
}
|
||||||
verb^ = r
|
}
|
||||||
if len(value) > 0 && value[0] == ',' {
|
|
||||||
field_name := value[1:]
|
fi.width, i, fi.width_set = _parse_int(head, i)
|
||||||
if field_name == "0" {
|
if i < len(head) && head[i] == '.' {
|
||||||
if use_nul_termination != nil {
|
i += 1
|
||||||
use_nul_termination^ = true
|
prev_i := i
|
||||||
}
|
fi.prec, i, fi.prec_set = _parse_int(head, i)
|
||||||
} else {
|
if i == prev_i {
|
||||||
switch r {
|
fi.prec = 0
|
||||||
case 's', 'q':
|
fi.prec_set = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r: rune
|
||||||
|
if i >= len(head) || head[i] == ' ' {
|
||||||
|
r = 'v'
|
||||||
|
} else {
|
||||||
|
r, _ = utf8.decode_rune_in_string(head[i:])
|
||||||
|
}
|
||||||
|
if verb^ == 'w' {
|
||||||
|
// TODO(bill): is this a good idea overriding that field tags if 'w' is used?
|
||||||
|
switch r {
|
||||||
|
case 's': r = 'q'
|
||||||
|
case: r = 'w'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
verb^ = r
|
||||||
|
if tail != "" {
|
||||||
|
field_name := tail
|
||||||
|
if field_name == "0" {
|
||||||
|
if use_nul_termination != nil {
|
||||||
|
use_nul_termination^ = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch r {
|
||||||
|
case 's', 'q':
|
||||||
|
handle_optional_len(data, info, field_name, optional_len)
|
||||||
|
case 'v', 'w':
|
||||||
|
#partial switch reflect.type_kind(info.types[idx].id) {
|
||||||
|
case .String, .Multi_Pointer, .Array, .Slice, .Dynamic_Array:
|
||||||
handle_optional_len(data, info, field_name, optional_len)
|
handle_optional_len(data, info, field_name, optional_len)
|
||||||
case 'v', 'w':
|
|
||||||
#partial switch reflect.type_kind(info.types[idx].id) {
|
|
||||||
case .String, .Multi_Pointer, .Array, .Slice, .Dynamic_Array:
|
|
||||||
handle_optional_len(data, info, field_name, optional_len)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return
|
||||||
}
|
}
|
||||||
// Formats a struct for output, handling various struct types (e.g., SOA, raw unions)
|
// Formats a struct for output, handling various struct types (e.g., SOA, raw unions)
|
||||||
//
|
//
|
||||||
@@ -1980,7 +2070,9 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St
|
|||||||
optional_len: int = -1
|
optional_len: int = -1
|
||||||
use_nul_termination: bool = false
|
use_nul_termination: bool = false
|
||||||
verb := the_verb if the_verb == 'w' else 'v'
|
verb := the_verb if the_verb == 'w' else 'v'
|
||||||
if handle_tag(v.data, info, i, &verb, &optional_len, &use_nul_termination) {
|
|
||||||
|
new_state := fi.state
|
||||||
|
if handle_tag(&new_state, v.data, info, i, &verb, &optional_len, &use_nul_termination) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
field_count += 1
|
field_count += 1
|
||||||
@@ -2005,8 +2097,11 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St
|
|||||||
if t := info.types[i]; reflect.is_any(t) {
|
if t := info.types[i]; reflect.is_any(t) {
|
||||||
io.write_string(fi.writer, "any{}", &fi.n)
|
io.write_string(fi.writer, "any{}", &fi.n)
|
||||||
} else {
|
} else {
|
||||||
|
prev_state := fi.state
|
||||||
|
fi.state = new_state
|
||||||
data := rawptr(uintptr(v.data) + info.offsets[i])
|
data := rawptr(uintptr(v.data) + info.offsets[i])
|
||||||
fmt_arg(fi, any{data, t.id}, verb)
|
fmt_arg(fi, any{data, t.id}, verb)
|
||||||
|
fi.state = prev_state
|
||||||
}
|
}
|
||||||
|
|
||||||
if do_trailing_comma { io.write_string(fi.writer, ",\n", &fi.n) }
|
if do_trailing_comma { io.write_string(fi.writer, ",\n", &fi.n) }
|
||||||
@@ -2679,7 +2774,6 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
|
|||||||
io.write_byte(fi.writer, '[' if verb != 'w' else '{', &fi.n)
|
io.write_byte(fi.writer, '[' if verb != 'w' else '{', &fi.n)
|
||||||
io.write_byte(fi.writer, '\n', &fi.n)
|
io.write_byte(fi.writer, '\n', &fi.n)
|
||||||
defer {
|
defer {
|
||||||
io.write_byte(fi.writer, '\n', &fi.n)
|
|
||||||
fmt_write_indent(fi)
|
fmt_write_indent(fi)
|
||||||
io.write_byte(fi.writer, ']' if verb != 'w' else '}', &fi.n)
|
io.write_byte(fi.writer, ']' if verb != 'w' else '}', &fi.n)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,8 +76,6 @@ save_to_buffer :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}
|
|||||||
pix := RGBA_Pixel{0, 0, 0, 255}
|
pix := RGBA_Pixel{0, 0, 0, 255}
|
||||||
prev := pix
|
prev := pix
|
||||||
|
|
||||||
seen[qoi_hash(pix)] = pix
|
|
||||||
|
|
||||||
input := img.pixels.buf[:]
|
input := img.pixels.buf[:]
|
||||||
run := u8(0)
|
run := u8(0)
|
||||||
|
|
||||||
@@ -241,8 +239,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
|||||||
Decode loop starts here.
|
Decode loop starts here.
|
||||||
*/
|
*/
|
||||||
seen: [64]RGBA_Pixel
|
seen: [64]RGBA_Pixel
|
||||||
pix := RGBA_Pixel{0, 0, 0, 255}
|
pix := RGBA_Pixel{0, 0, 0, 255}
|
||||||
seen[qoi_hash(pix)] = pix
|
|
||||||
pixels := img.pixels.buf[:]
|
pixels := img.pixels.buf[:]
|
||||||
|
|
||||||
decode: for len(pixels) > 0 {
|
decode: for len(pixels) > 0 {
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package odin_format
|
||||||
|
|
||||||
|
#panic("The format package has been deprecated. Please look at https://github.com/DanielGavin/ols")
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package odin_format
|
|
||||||
|
|
||||||
import "core:odin/printer"
|
|
||||||
import "core:odin/parser"
|
|
||||||
import "core:odin/ast"
|
|
||||||
|
|
||||||
default_style := printer.default_style
|
|
||||||
|
|
||||||
simplify :: proc(file: ^ast.File) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
format :: proc(filepath: string, source: string, config: printer.Config, parser_flags := parser.Flags{}, allocator := context.allocator) -> (string, bool) {
|
|
||||||
config := config
|
|
||||||
|
|
||||||
pkg := ast.Package {
|
|
||||||
kind = .Normal,
|
|
||||||
}
|
|
||||||
|
|
||||||
file := ast.File {
|
|
||||||
pkg = &pkg,
|
|
||||||
src = source,
|
|
||||||
fullpath = filepath,
|
|
||||||
}
|
|
||||||
|
|
||||||
config.newline_limit = clamp(config.newline_limit, 0, 16)
|
|
||||||
config.spaces = clamp(config.spaces, 1, 16)
|
|
||||||
config.align_length_break = clamp(config.align_length_break, 0, 64)
|
|
||||||
|
|
||||||
p := parser.default_parser(parser_flags)
|
|
||||||
|
|
||||||
ok := parser.parse_file(&p, &file)
|
|
||||||
|
|
||||||
if !ok || file.syntax_error_count > 0 {
|
|
||||||
return {}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
prnt := printer.make_printer(config, allocator)
|
|
||||||
|
|
||||||
return printer.print(&prnt, &file), true
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package odin_printer
|
||||||
|
|
||||||
|
#panic("The printer package has been deprecated. Please look at https://github.com/DanielGavin/ols")
|
||||||
@@ -1,922 +0,0 @@
|
|||||||
package odin_printer
|
|
||||||
|
|
||||||
import "core:odin/ast"
|
|
||||||
import "core:odin/tokenizer"
|
|
||||||
import "core:strings"
|
|
||||||
import "core:fmt"
|
|
||||||
import "core:mem"
|
|
||||||
|
|
||||||
Type_Enum :: enum {Line_Comment, Value_Decl, Switch_Stmt, Struct, Assign, Call, Enum, If, For, Proc_Lit}
|
|
||||||
|
|
||||||
Line_Type :: bit_set[Type_Enum]
|
|
||||||
|
|
||||||
/*
|
|
||||||
Represents an unwrapped line
|
|
||||||
*/
|
|
||||||
Line :: struct {
|
|
||||||
format_tokens: [dynamic]Format_Token,
|
|
||||||
finalized: bool,
|
|
||||||
used: bool,
|
|
||||||
depth: int,
|
|
||||||
types: Line_Type, //for performance, so you don't have to verify what types are in it by going through the tokens - might give problems when adding linebreaking
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Represents a singular token in a unwrapped line
|
|
||||||
*/
|
|
||||||
Format_Token :: struct {
|
|
||||||
kind: tokenizer.Token_Kind,
|
|
||||||
text: string,
|
|
||||||
type: Type_Enum,
|
|
||||||
spaces_before: int,
|
|
||||||
parameter_count: int,
|
|
||||||
}
|
|
||||||
|
|
||||||
Printer :: struct {
|
|
||||||
string_builder: strings.Builder,
|
|
||||||
config: Config,
|
|
||||||
depth: int, //the identation depth
|
|
||||||
comments: [dynamic]^ast.Comment_Group,
|
|
||||||
latest_comment_index: int,
|
|
||||||
allocator: mem.Allocator,
|
|
||||||
file: ^ast.File,
|
|
||||||
source_position: tokenizer.Pos,
|
|
||||||
last_source_position: tokenizer.Pos,
|
|
||||||
lines: [dynamic]Line, //need to look into a better data structure, one that can handle inserting lines rather than appending
|
|
||||||
skip_semicolon: bool,
|
|
||||||
current_line: ^Line,
|
|
||||||
current_line_index: int,
|
|
||||||
last_line_index: int,
|
|
||||||
last_token: ^Format_Token,
|
|
||||||
merge_next_token: bool,
|
|
||||||
space_next_token: bool,
|
|
||||||
debug: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
Config :: struct {
|
|
||||||
spaces: int, //Spaces per indentation
|
|
||||||
newline_limit: int, //The limit of newlines between statements and declarations.
|
|
||||||
tabs: bool, //Enable or disable tabs
|
|
||||||
convert_do: bool, //Convert all do statements to brace blocks
|
|
||||||
semicolons: bool, //Enable semicolons
|
|
||||||
split_multiple_stmts: bool,
|
|
||||||
align_switch: bool,
|
|
||||||
brace_style: Brace_Style,
|
|
||||||
align_assignments: bool,
|
|
||||||
align_structs: bool,
|
|
||||||
align_style: Alignment_Style,
|
|
||||||
align_enums: bool,
|
|
||||||
align_length_break: int,
|
|
||||||
indent_cases: bool,
|
|
||||||
newline_style: Newline_Style,
|
|
||||||
}
|
|
||||||
|
|
||||||
Brace_Style :: enum {
|
|
||||||
_1TBS,
|
|
||||||
Allman,
|
|
||||||
Stroustrup,
|
|
||||||
K_And_R,
|
|
||||||
}
|
|
||||||
|
|
||||||
Block_Type :: enum {
|
|
||||||
None,
|
|
||||||
If_Stmt,
|
|
||||||
Proc,
|
|
||||||
Generic,
|
|
||||||
Comp_Lit,
|
|
||||||
Switch_Stmt,
|
|
||||||
}
|
|
||||||
|
|
||||||
Alignment_Style :: enum {
|
|
||||||
Align_On_Type_And_Equals,
|
|
||||||
Align_On_Colon_And_Equals,
|
|
||||||
}
|
|
||||||
|
|
||||||
Newline_Style :: enum {
|
|
||||||
CRLF,
|
|
||||||
LF,
|
|
||||||
}
|
|
||||||
|
|
||||||
default_style := Config {
|
|
||||||
spaces = 4,
|
|
||||||
newline_limit = 2,
|
|
||||||
convert_do = false,
|
|
||||||
semicolons = false,
|
|
||||||
tabs = true,
|
|
||||||
brace_style = ._1TBS,
|
|
||||||
split_multiple_stmts = true,
|
|
||||||
align_assignments = true,
|
|
||||||
align_style = .Align_On_Type_And_Equals,
|
|
||||||
indent_cases = false,
|
|
||||||
align_switch = true,
|
|
||||||
align_structs = true,
|
|
||||||
align_enums = true,
|
|
||||||
newline_style = .CRLF,
|
|
||||||
align_length_break = 9,
|
|
||||||
}
|
|
||||||
|
|
||||||
make_printer :: proc(config: Config, allocator := context.allocator) -> Printer {
|
|
||||||
return {
|
|
||||||
config = config,
|
|
||||||
allocator = allocator,
|
|
||||||
debug = false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
print :: proc(p: ^Printer, file: ^ast.File) -> string {
|
|
||||||
p.comments = file.comments
|
|
||||||
|
|
||||||
if len(file.decls) > 0 {
|
|
||||||
p.lines = make([dynamic]Line, 0, (file.decls[len(file.decls) - 1].end.line - file.decls[0].pos.line) * 2, context.temp_allocator)
|
|
||||||
}
|
|
||||||
|
|
||||||
set_source_position(p, file.pkg_token.pos)
|
|
||||||
|
|
||||||
p.last_source_position.line = 1
|
|
||||||
|
|
||||||
set_line(p, 0)
|
|
||||||
|
|
||||||
push_generic_token(p, .Package, 0)
|
|
||||||
push_ident_token(p, file.pkg_name, 1)
|
|
||||||
|
|
||||||
for decl in file.decls {
|
|
||||||
visit_decl(p, cast(^ast.Decl)decl)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(p.comments) > 0 {
|
|
||||||
infinite := p.comments[len(p.comments) - 1].end
|
|
||||||
infinite.offset = 9999999
|
|
||||||
push_comments(p, infinite)
|
|
||||||
}
|
|
||||||
|
|
||||||
fix_lines(p)
|
|
||||||
|
|
||||||
builder := strings.builder_make(0, 5 * mem.Megabyte, p.allocator)
|
|
||||||
|
|
||||||
last_line := 0
|
|
||||||
|
|
||||||
newline: string
|
|
||||||
|
|
||||||
if p.config.newline_style == .LF {
|
|
||||||
newline = "\n"
|
|
||||||
} else {
|
|
||||||
newline = "\r\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
for line, line_index in p.lines {
|
|
||||||
diff_line := line_index - last_line
|
|
||||||
|
|
||||||
for i := 0; i < diff_line; i += 1 {
|
|
||||||
strings.write_string(&builder, newline)
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.config.tabs {
|
|
||||||
for i := 0; i < line.depth; i += 1 {
|
|
||||||
strings.write_byte(&builder, '\t')
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for i := 0; i < line.depth * p.config.spaces; i += 1 {
|
|
||||||
strings.write_byte(&builder, ' ')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.debug {
|
|
||||||
strings.write_string(&builder, fmt.tprintf("line %v: ", line_index))
|
|
||||||
}
|
|
||||||
|
|
||||||
for format_token in line.format_tokens {
|
|
||||||
|
|
||||||
for i := 0; i < format_token.spaces_before; i += 1 {
|
|
||||||
strings.write_byte(&builder, ' ')
|
|
||||||
}
|
|
||||||
|
|
||||||
strings.write_string(&builder, format_token.text)
|
|
||||||
}
|
|
||||||
|
|
||||||
last_line = line_index
|
|
||||||
}
|
|
||||||
|
|
||||||
strings.write_string(&builder, newline)
|
|
||||||
|
|
||||||
return strings.to_string(builder)
|
|
||||||
}
|
|
||||||
|
|
||||||
fix_lines :: proc(p: ^Printer) {
|
|
||||||
align_var_decls(p)
|
|
||||||
format_generic(p)
|
|
||||||
align_comments(p) //align them last since they rely on the other alignments
|
|
||||||
}
|
|
||||||
|
|
||||||
format_value_decl :: proc(p: ^Printer, index: int) {
|
|
||||||
|
|
||||||
eq_found := false
|
|
||||||
eq_token: Format_Token
|
|
||||||
eq_line: int
|
|
||||||
largest := 0
|
|
||||||
|
|
||||||
found_eq: for line, line_index in p.lines[index:] {
|
|
||||||
for format_token in line.format_tokens {
|
|
||||||
|
|
||||||
largest += len(format_token.text) + format_token.spaces_before
|
|
||||||
|
|
||||||
if format_token.kind == .Eq {
|
|
||||||
eq_token = format_token
|
|
||||||
eq_line = line_index + index
|
|
||||||
eq_found = true
|
|
||||||
break found_eq
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !eq_found {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
align_next := false
|
|
||||||
|
|
||||||
//check to see if there is a binary operator in the last token(this is guaranteed by the ast visit), otherwise it's not multilined
|
|
||||||
for line in p.lines[eq_line:] {
|
|
||||||
|
|
||||||
if len(line.format_tokens) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if align_next {
|
|
||||||
line.format_tokens[0].spaces_before = largest + 1
|
|
||||||
align_next = false
|
|
||||||
}
|
|
||||||
|
|
||||||
kind := find_last_token(line.format_tokens).kind
|
|
||||||
|
|
||||||
if tokenizer.Token_Kind.B_Operator_Begin < kind && kind <= tokenizer.Token_Kind.Cmp_Or {
|
|
||||||
align_next = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if !align_next {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
find_last_token :: proc(format_tokens: [dynamic]Format_Token) -> Format_Token {
|
|
||||||
|
|
||||||
for i := len(format_tokens) - 1; i >= 0; i -= 1 {
|
|
||||||
|
|
||||||
if format_tokens[i].kind != .Comment {
|
|
||||||
return format_tokens[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
panic("not possible")
|
|
||||||
}
|
|
||||||
|
|
||||||
format_assignment :: proc(p: ^Printer, index: int) {
|
|
||||||
}
|
|
||||||
|
|
||||||
format_call :: proc(p: ^Printer, line_index: int, format_index: int) {
|
|
||||||
|
|
||||||
paren_found := false
|
|
||||||
paren_token: Format_Token
|
|
||||||
paren_line: int
|
|
||||||
paren_token_index: int
|
|
||||||
largest := 0
|
|
||||||
|
|
||||||
found_paren: for line, i in p.lines[line_index:] {
|
|
||||||
for format_token, j in line.format_tokens {
|
|
||||||
|
|
||||||
largest += len(format_token.text) + format_token.spaces_before
|
|
||||||
|
|
||||||
if i == 0 && j < format_index {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if format_token.kind == .Open_Paren && format_token.type == .Call {
|
|
||||||
paren_token = format_token
|
|
||||||
paren_line = line_index + i
|
|
||||||
paren_found = true
|
|
||||||
paren_token_index = j
|
|
||||||
break found_paren
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !paren_found {
|
|
||||||
panic("Should not be possible")
|
|
||||||
}
|
|
||||||
|
|
||||||
paren_count := 1
|
|
||||||
done := false
|
|
||||||
|
|
||||||
for line in p.lines[paren_line:] {
|
|
||||||
|
|
||||||
if len(line.format_tokens) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for format_token, i in line.format_tokens {
|
|
||||||
|
|
||||||
if format_token.kind == .Comment {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if line_index == 0 && i <= paren_token_index {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if format_token.kind == .Open_Paren {
|
|
||||||
paren_count += 1
|
|
||||||
} else if format_token.kind == .Close_Paren {
|
|
||||||
paren_count -= 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if paren_count == 0 {
|
|
||||||
done = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if line_index != 0 {
|
|
||||||
line.format_tokens[0].spaces_before = largest
|
|
||||||
}
|
|
||||||
|
|
||||||
if done {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
format_keyword_to_brace :: proc(p: ^Printer, line_index: int, format_index: int, keyword: tokenizer.Token_Kind) {
|
|
||||||
|
|
||||||
keyword_found := false
|
|
||||||
keyword_token: Format_Token
|
|
||||||
keyword_line: int
|
|
||||||
|
|
||||||
largest := 0
|
|
||||||
brace_count := 0
|
|
||||||
done := false
|
|
||||||
|
|
||||||
found_keyword: for line, i in p.lines[line_index:] {
|
|
||||||
for format_token in line.format_tokens {
|
|
||||||
|
|
||||||
largest += len(format_token.text) + format_token.spaces_before
|
|
||||||
|
|
||||||
if format_token.kind == keyword {
|
|
||||||
keyword_token = format_token
|
|
||||||
keyword_line = line_index + i
|
|
||||||
keyword_found = true
|
|
||||||
break found_keyword
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !keyword_found {
|
|
||||||
panic("Should not be possible")
|
|
||||||
}
|
|
||||||
|
|
||||||
for line, line_idx in p.lines[keyword_line:] {
|
|
||||||
|
|
||||||
if len(line.format_tokens) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for format_token, i in line.format_tokens {
|
|
||||||
|
|
||||||
if format_token.kind == .Comment {
|
|
||||||
break
|
|
||||||
} else if format_token.kind == .Undef {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if line_idx == 0 && i <= format_index {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if format_token.kind == .Open_Brace {
|
|
||||||
brace_count += 1
|
|
||||||
} else if format_token.kind == .Close_Brace {
|
|
||||||
brace_count -= 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if brace_count == 1 {
|
|
||||||
done = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if line_idx != 0 {
|
|
||||||
line.format_tokens[0].spaces_before = largest + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if done {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
format_generic :: proc(p: ^Printer) {
|
|
||||||
next_struct_line := 0
|
|
||||||
|
|
||||||
for line, line_index in p.lines {
|
|
||||||
|
|
||||||
if len(line.format_tokens) <= 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for format_token, token_index in line.format_tokens {
|
|
||||||
#partial switch format_token.kind {
|
|
||||||
case .For, .If, .When, .Switch:
|
|
||||||
format_keyword_to_brace(p, line_index, token_index, format_token.kind)
|
|
||||||
case .Proc:
|
|
||||||
if format_token.type == .Proc_Lit {
|
|
||||||
format_keyword_to_brace(p, line_index, token_index, format_token.kind)
|
|
||||||
}
|
|
||||||
case:
|
|
||||||
if format_token.type == .Call {
|
|
||||||
format_call(p, line_index, token_index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if .Switch_Stmt in line.types && p.config.align_switch {
|
|
||||||
align_switch_stmt(p, line_index)
|
|
||||||
}
|
|
||||||
|
|
||||||
if .Enum in line.types && p.config.align_enums {
|
|
||||||
align_enum(p, line_index)
|
|
||||||
}
|
|
||||||
|
|
||||||
if .Struct in line.types && p.config.align_structs && next_struct_line <= 0 {
|
|
||||||
next_struct_line = align_struct(p, line_index)
|
|
||||||
}
|
|
||||||
|
|
||||||
if .Value_Decl in line.types {
|
|
||||||
format_value_decl(p, line_index)
|
|
||||||
}
|
|
||||||
|
|
||||||
if .Assign in line.types {
|
|
||||||
format_assignment(p, line_index)
|
|
||||||
}
|
|
||||||
|
|
||||||
next_struct_line -= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
align_var_decls :: proc(p: ^Printer) {
|
|
||||||
|
|
||||||
current_line: int
|
|
||||||
current_typed: bool
|
|
||||||
current_not_mutable: bool
|
|
||||||
|
|
||||||
largest_lhs := 0
|
|
||||||
largest_rhs := 0
|
|
||||||
|
|
||||||
TokenAndLength :: struct {
|
|
||||||
format_token: ^Format_Token,
|
|
||||||
length: int,
|
|
||||||
}
|
|
||||||
|
|
||||||
colon_tokens := make([dynamic]TokenAndLength, 0, 10, context.temp_allocator)
|
|
||||||
type_tokens := make([dynamic]TokenAndLength, 0, 10, context.temp_allocator)
|
|
||||||
equal_tokens := make([dynamic]TokenAndLength, 0, 10, context.temp_allocator)
|
|
||||||
|
|
||||||
for line, line_index in p.lines {
|
|
||||||
|
|
||||||
//It is only possible to align value decls that are one one line, otherwise just ignore them
|
|
||||||
if .Value_Decl not_in line.types {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
typed := true
|
|
||||||
not_mutable := false
|
|
||||||
continue_flag := false
|
|
||||||
|
|
||||||
for i := 0; i < len(line.format_tokens); i += 1 {
|
|
||||||
if line.format_tokens[i].kind == .Colon && line.format_tokens[min(i + 1, len(line.format_tokens) - 1)].kind == .Eq {
|
|
||||||
typed = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if line.format_tokens[i].kind == .Colon && line.format_tokens[min(i + 1, len(line.format_tokens) - 1)].kind == .Colon {
|
|
||||||
not_mutable = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if line.format_tokens[i].kind == .Union ||
|
|
||||||
line.format_tokens[i].kind == .Enum ||
|
|
||||||
line.format_tokens[i].kind == .Struct ||
|
|
||||||
line.format_tokens[i].kind == .For ||
|
|
||||||
line.format_tokens[i].kind == .If ||
|
|
||||||
line.format_tokens[i].kind == .Comment {
|
|
||||||
continue_flag = true
|
|
||||||
}
|
|
||||||
|
|
||||||
//enforced undef is always on the last line, if it exists
|
|
||||||
if line.format_tokens[i].kind == .Proc && line.format_tokens[len(line.format_tokens)-1].kind != .Undef {
|
|
||||||
continue_flag = true
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if continue_flag {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if line_index != current_line + 1 || typed != current_typed || not_mutable != current_not_mutable {
|
|
||||||
|
|
||||||
if p.config.align_style == .Align_On_Colon_And_Equals || !current_typed || current_not_mutable {
|
|
||||||
for colon_token in colon_tokens {
|
|
||||||
colon_token.format_token.spaces_before = largest_lhs - colon_token.length + 1
|
|
||||||
}
|
|
||||||
} else if p.config.align_style == .Align_On_Type_And_Equals {
|
|
||||||
for type_token in type_tokens {
|
|
||||||
type_token.format_token.spaces_before = largest_lhs - type_token.length + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if current_typed {
|
|
||||||
for equal_token in equal_tokens {
|
|
||||||
equal_token.format_token.spaces_before = largest_rhs - equal_token.length + 1
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for equal_token in equal_tokens {
|
|
||||||
equal_token.format_token.spaces_before = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
clear(&colon_tokens)
|
|
||||||
clear(&type_tokens)
|
|
||||||
clear(&equal_tokens)
|
|
||||||
|
|
||||||
largest_rhs = 0
|
|
||||||
largest_lhs = 0
|
|
||||||
current_typed = typed
|
|
||||||
current_not_mutable = not_mutable
|
|
||||||
}
|
|
||||||
|
|
||||||
current_line = line_index
|
|
||||||
|
|
||||||
current_token_index := 0
|
|
||||||
lhs_length := 0
|
|
||||||
rhs_length := 0
|
|
||||||
|
|
||||||
//calcuate the length of lhs of a value decl i.e. `a, b:`
|
|
||||||
for; current_token_index < len(line.format_tokens); current_token_index += 1 {
|
|
||||||
|
|
||||||
lhs_length += len(line.format_tokens[current_token_index].text) + line.format_tokens[current_token_index].spaces_before
|
|
||||||
|
|
||||||
if line.format_tokens[current_token_index].kind == .Colon {
|
|
||||||
append(&colon_tokens, TokenAndLength {format_token = &line.format_tokens[current_token_index], length = lhs_length})
|
|
||||||
|
|
||||||
if len(line.format_tokens) > current_token_index && line.format_tokens[current_token_index + 1].kind != .Eq {
|
|
||||||
append(&type_tokens, TokenAndLength {format_token = &line.format_tokens[current_token_index + 1], length = lhs_length})
|
|
||||||
}
|
|
||||||
|
|
||||||
current_token_index += 1
|
|
||||||
largest_lhs = max(largest_lhs, lhs_length)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//calcuate the length of the rhs i.e. `[dynamic]int = 123123`
|
|
||||||
for; current_token_index < len(line.format_tokens); current_token_index += 1 {
|
|
||||||
|
|
||||||
rhs_length += len(line.format_tokens[current_token_index].text) + line.format_tokens[current_token_index].spaces_before
|
|
||||||
|
|
||||||
if line.format_tokens[current_token_index].kind == .Eq {
|
|
||||||
append(&equal_tokens, TokenAndLength {format_token = &line.format_tokens[current_token_index], length = rhs_length})
|
|
||||||
largest_rhs = max(largest_rhs, rhs_length)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//repeating myself, move to sub procedure
|
|
||||||
if p.config.align_style == .Align_On_Colon_And_Equals || !current_typed || current_not_mutable {
|
|
||||||
for colon_token in colon_tokens {
|
|
||||||
colon_token.format_token.spaces_before = largest_lhs - colon_token.length + 1
|
|
||||||
}
|
|
||||||
} else if p.config.align_style == .Align_On_Type_And_Equals {
|
|
||||||
for type_token in type_tokens {
|
|
||||||
type_token.format_token.spaces_before = largest_lhs - type_token.length + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if current_typed {
|
|
||||||
for equal_token in equal_tokens {
|
|
||||||
equal_token.format_token.spaces_before = largest_rhs - equal_token.length + 1
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for equal_token in equal_tokens {
|
|
||||||
equal_token.format_token.spaces_before = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
align_switch_stmt :: proc(p: ^Printer, index: int) {
|
|
||||||
switch_found := false
|
|
||||||
brace_token: Format_Token
|
|
||||||
brace_line: int
|
|
||||||
|
|
||||||
found_switch_brace: for line, line_index in p.lines[index:] {
|
|
||||||
for format_token in line.format_tokens {
|
|
||||||
if format_token.kind == .Open_Brace && switch_found {
|
|
||||||
brace_token = format_token
|
|
||||||
brace_line = line_index + index
|
|
||||||
break found_switch_brace
|
|
||||||
} else if format_token.kind == .Open_Brace {
|
|
||||||
break
|
|
||||||
} else if format_token.kind == .Switch {
|
|
||||||
switch_found = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !switch_found {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
largest := 0
|
|
||||||
case_count := 0
|
|
||||||
|
|
||||||
TokenAndLength :: struct {
|
|
||||||
format_token: ^Format_Token,
|
|
||||||
length: int,
|
|
||||||
}
|
|
||||||
|
|
||||||
format_tokens := make([dynamic]TokenAndLength, 0, brace_token.parameter_count, context.temp_allocator)
|
|
||||||
|
|
||||||
//find all the switch cases that are one lined
|
|
||||||
for line in p.lines[brace_line + 1:] {
|
|
||||||
|
|
||||||
case_found := false
|
|
||||||
colon_found := false
|
|
||||||
length := 0
|
|
||||||
|
|
||||||
for format_token, i in line.format_tokens {
|
|
||||||
|
|
||||||
if format_token.kind == .Comment {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
//this will only happen if the case is one lined
|
|
||||||
if case_found && colon_found {
|
|
||||||
append(&format_tokens, TokenAndLength {format_token = &line.format_tokens[i], length = length})
|
|
||||||
largest = max(length, largest)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if format_token.kind == .Case {
|
|
||||||
case_found = true
|
|
||||||
case_count += 1
|
|
||||||
} else if format_token.kind == .Colon {
|
|
||||||
colon_found = true
|
|
||||||
}
|
|
||||||
|
|
||||||
length += len(format_token.text) + format_token.spaces_before
|
|
||||||
}
|
|
||||||
|
|
||||||
if case_count >= brace_token.parameter_count {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for token in format_tokens {
|
|
||||||
token.format_token.spaces_before = largest - token.length + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
align_enum :: proc(p: ^Printer, index: int) {
|
|
||||||
enum_found := false
|
|
||||||
brace_token: Format_Token
|
|
||||||
brace_line: int
|
|
||||||
|
|
||||||
found_enum_brace: for line, line_index in p.lines[index:] {
|
|
||||||
for format_token in line.format_tokens {
|
|
||||||
if format_token.kind == .Open_Brace && enum_found {
|
|
||||||
brace_token = format_token
|
|
||||||
brace_line = line_index + index
|
|
||||||
break found_enum_brace
|
|
||||||
} else if format_token.kind == .Open_Brace {
|
|
||||||
break
|
|
||||||
} else if format_token.kind == .Enum {
|
|
||||||
enum_found = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !enum_found {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
largest := 0
|
|
||||||
comma_count := 0
|
|
||||||
|
|
||||||
TokenAndLength :: struct {
|
|
||||||
format_token: ^Format_Token,
|
|
||||||
length: int,
|
|
||||||
}
|
|
||||||
|
|
||||||
format_tokens := make([dynamic]TokenAndLength, 0, brace_token.parameter_count, context.temp_allocator)
|
|
||||||
|
|
||||||
for line in p.lines[brace_line + 1:] {
|
|
||||||
length := 0
|
|
||||||
|
|
||||||
for format_token, i in line.format_tokens {
|
|
||||||
if format_token.kind == .Comment {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if format_token.kind == .Eq {
|
|
||||||
append(&format_tokens, TokenAndLength {format_token = &line.format_tokens[i], length = length})
|
|
||||||
largest = max(length, largest)
|
|
||||||
break
|
|
||||||
} else if format_token.kind == .Comma {
|
|
||||||
comma_count += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
length += len(format_token.text) + format_token.spaces_before
|
|
||||||
}
|
|
||||||
|
|
||||||
if comma_count >= brace_token.parameter_count {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for token in format_tokens {
|
|
||||||
token.format_token.spaces_before = largest - token.length + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
align_struct :: proc(p: ^Printer, index: int) -> int {
|
|
||||||
struct_found := false
|
|
||||||
brace_token: Format_Token
|
|
||||||
brace_line: int
|
|
||||||
|
|
||||||
found_struct_brace: for line, line_index in p.lines[index:] {
|
|
||||||
for format_token in line.format_tokens {
|
|
||||||
if format_token.kind == .Open_Brace && struct_found {
|
|
||||||
brace_token = format_token
|
|
||||||
brace_line = line_index + index
|
|
||||||
break found_struct_brace
|
|
||||||
} else if format_token.kind == .Open_Brace {
|
|
||||||
break
|
|
||||||
} else if format_token.kind == .Struct {
|
|
||||||
struct_found = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !struct_found {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
largest := 0
|
|
||||||
colon_count := 0
|
|
||||||
nested := false
|
|
||||||
seen_brace := false
|
|
||||||
|
|
||||||
TokenAndLength :: struct {
|
|
||||||
format_token: ^Format_Token,
|
|
||||||
length: int,
|
|
||||||
}
|
|
||||||
|
|
||||||
format_tokens := make([]TokenAndLength, brace_token.parameter_count, context.temp_allocator)
|
|
||||||
|
|
||||||
if brace_token.parameter_count == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
end_line_index := 0
|
|
||||||
|
|
||||||
for line, line_index in p.lines[brace_line + 1:] {
|
|
||||||
length := 0
|
|
||||||
|
|
||||||
for format_token, i in line.format_tokens {
|
|
||||||
|
|
||||||
//give up on nested structs
|
|
||||||
if format_token.kind == .Comment {
|
|
||||||
break
|
|
||||||
} else if format_token.kind == .Open_Paren {
|
|
||||||
break
|
|
||||||
} else if format_token.kind == .Open_Brace {
|
|
||||||
seen_brace = true
|
|
||||||
} else if format_token.kind == .Close_Brace {
|
|
||||||
seen_brace = false
|
|
||||||
} else if seen_brace {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if format_token.kind == .Colon {
|
|
||||||
format_tokens[colon_count] = {format_token = &line.format_tokens[i + 1], length = length}
|
|
||||||
|
|
||||||
if format_tokens[colon_count].format_token.kind == .Struct {
|
|
||||||
nested = true
|
|
||||||
}
|
|
||||||
|
|
||||||
colon_count += 1
|
|
||||||
largest = max(length, largest)
|
|
||||||
}
|
|
||||||
|
|
||||||
length += len(format_token.text) + format_token.spaces_before
|
|
||||||
}
|
|
||||||
|
|
||||||
if nested {
|
|
||||||
end_line_index = line_index + brace_line + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if colon_count >= brace_token.parameter_count {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//give up aligning nested, it never looks good
|
|
||||||
if nested {
|
|
||||||
for line, line_index in p.lines[end_line_index:] {
|
|
||||||
for format_token in line.format_tokens {
|
|
||||||
if format_token.kind == .Close_Brace {
|
|
||||||
return end_line_index + line_index - index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for token in format_tokens {
|
|
||||||
token.format_token.spaces_before = largest - token.length + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
align_comments :: proc(p: ^Printer) {
|
|
||||||
|
|
||||||
Comment_Align_Info :: struct {
|
|
||||||
length: int,
|
|
||||||
begin: int,
|
|
||||||
end: int,
|
|
||||||
depth: int,
|
|
||||||
}
|
|
||||||
|
|
||||||
comment_infos := make([dynamic]Comment_Align_Info, 0, context.temp_allocator)
|
|
||||||
|
|
||||||
current_info: Comment_Align_Info
|
|
||||||
|
|
||||||
for line, line_index in p.lines {
|
|
||||||
if len(line.format_tokens) <= 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if .Line_Comment in line.types {
|
|
||||||
if current_info.end + 1 != line_index || current_info.depth != line.depth ||
|
|
||||||
(current_info.begin == current_info.end && current_info.length == 0) {
|
|
||||||
|
|
||||||
if (current_info.begin != 0 && current_info.end != 0) || current_info.length > 0 {
|
|
||||||
append(&comment_infos, current_info)
|
|
||||||
}
|
|
||||||
|
|
||||||
current_info.begin = line_index
|
|
||||||
current_info.end = line_index
|
|
||||||
current_info.depth = line.depth
|
|
||||||
current_info.length = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
length := 0
|
|
||||||
|
|
||||||
for format_token in line.format_tokens {
|
|
||||||
if format_token.kind == .Comment {
|
|
||||||
current_info.length = max(current_info.length, length)
|
|
||||||
current_info.end = line_index
|
|
||||||
}
|
|
||||||
|
|
||||||
length += format_token.spaces_before + len(format_token.text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current_info.begin != 0 && current_info.end != 0) || current_info.length > 0 {
|
|
||||||
append(&comment_infos, current_info)
|
|
||||||
}
|
|
||||||
|
|
||||||
for info in comment_infos {
|
|
||||||
|
|
||||||
if info.begin == info.end || info.length == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := info.begin; i <= info.end; i += 1 {
|
|
||||||
l := p.lines[i]
|
|
||||||
|
|
||||||
length := 0
|
|
||||||
|
|
||||||
for format_token in l.format_tokens {
|
|
||||||
if format_token.kind == .Comment {
|
|
||||||
if len(l.format_tokens) == 1 {
|
|
||||||
l.format_tokens[i].spaces_before = info.length + 1
|
|
||||||
} else {
|
|
||||||
l.format_tokens[i].spaces_before = info.length - length + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
length += format_token.spaces_before + len(format_token.text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -222,7 +222,7 @@ prefix_length :: proc(a, b: $T/[]$E) -> (n: int) where intrinsics.type_is_compar
|
|||||||
}
|
}
|
||||||
|
|
||||||
@(require_results)
|
@(require_results)
|
||||||
has_prefix :: proc(array: $T/[]$E, needle: E) -> bool where intrinsics.type_is_comparable(E) {
|
has_prefix :: proc(array: $T/[]$E, needle: T) -> bool where intrinsics.type_is_comparable(E) {
|
||||||
n := len(needle)
|
n := len(needle)
|
||||||
if len(array) >= n {
|
if len(array) >= n {
|
||||||
return equal(array[:n], needle)
|
return equal(array[:n], needle)
|
||||||
@@ -232,7 +232,7 @@ has_prefix :: proc(array: $T/[]$E, needle: E) -> bool where intrinsics.type_is_c
|
|||||||
|
|
||||||
|
|
||||||
@(require_results)
|
@(require_results)
|
||||||
has_suffix :: proc(array: $T/[]$E, needle: E) -> bool where intrinsics.type_is_comparable(E) {
|
has_suffix :: proc(array: $T/[]$E, needle: T) -> bool where intrinsics.type_is_comparable(E) {
|
||||||
array := array
|
array := array
|
||||||
m, n := len(array), len(needle)
|
m, n := len(array), len(needle)
|
||||||
if m >= n {
|
if m >= n {
|
||||||
|
|||||||
@@ -17,9 +17,6 @@ when ODIN_OS == .Darwin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
os_sync_wait_on_address_flag :: enum u32 {
|
os_sync_wait_on_address_flag :: enum u32 {
|
||||||
// This flag should be used as a default flag when no other flags listed below are required.
|
|
||||||
NONE,
|
|
||||||
|
|
||||||
// This flag should be used when synchronizing among multiple processes by
|
// This flag should be used when synchronizing among multiple processes by
|
||||||
// placing the @addr passed to os_sync_wait_on_address and its variants
|
// placing the @addr passed to os_sync_wait_on_address and its variants
|
||||||
// in a shared memory region.
|
// in a shared memory region.
|
||||||
@@ -31,15 +28,12 @@ os_sync_wait_on_address_flag :: enum u32 {
|
|||||||
// This flag should not be used when synchronizing among multiple threads of
|
// This flag should not be used when synchronizing among multiple threads of
|
||||||
// a single process. It allows the kernel to perform performance optimizations
|
// a single process. It allows the kernel to perform performance optimizations
|
||||||
// as the @addr is local to the calling process.
|
// as the @addr is local to the calling process.
|
||||||
SHARED,
|
SHARED = 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
os_sync_wait_on_address_flags :: bit_set[os_sync_wait_on_address_flag; u32]
|
os_sync_wait_on_address_flags :: distinct bit_set[os_sync_wait_on_address_flag; u32]
|
||||||
|
|
||||||
os_sync_wake_by_address_flag :: enum u32 {
|
os_sync_wake_by_address_flag :: enum u32 {
|
||||||
// This flag should be used as a default flag when no other flags listed below are required.
|
|
||||||
NONE,
|
|
||||||
|
|
||||||
// This flag should be used when synchronizing among multiple processes by
|
// This flag should be used when synchronizing among multiple processes by
|
||||||
// placing the @addr passed to os_sync_wake_by_address_any and its variants
|
// placing the @addr passed to os_sync_wake_by_address_any and its variants
|
||||||
// in a shared memory region.
|
// in a shared memory region.
|
||||||
@@ -51,10 +45,10 @@ os_sync_wake_by_address_flag :: enum u32 {
|
|||||||
// This flag should not be used when synchronizing among multiple threads of
|
// This flag should not be used when synchronizing among multiple threads of
|
||||||
// a single process. It allows the kernel to perform performance optimizations
|
// a single process. It allows the kernel to perform performance optimizations
|
||||||
// as the @addr is local the calling process.
|
// as the @addr is local the calling process.
|
||||||
SHARED,
|
SHARED = 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
os_sync_wake_by_address_flags :: bit_set[os_sync_wake_by_address_flag; u32]
|
os_sync_wake_by_address_flags :: distinct bit_set[os_sync_wake_by_address_flag; u32]
|
||||||
|
|
||||||
os_clockid :: enum u32 {
|
os_clockid :: enum u32 {
|
||||||
MACH_ABSOLUTE_TIME = 32,
|
MACH_ABSOLUTE_TIME = 32,
|
||||||
@@ -283,7 +277,7 @@ foreign system {
|
|||||||
// and the shared memory specification
|
// and the shared memory specification
|
||||||
// (See os_sync_wake_by_address_flags_t).
|
// (See os_sync_wake_by_address_flags_t).
|
||||||
// ENOENT : No waiter(s) found waiting on the @addr.
|
// ENOENT : No waiter(s) found waiting on the @addr.
|
||||||
os_sync_wake_by_address_any :: proc(addr: rawptr, size: uint, flags: os_sync_wait_on_address_flags) -> i32 ---
|
os_sync_wake_by_address_any :: proc(addr: rawptr, size: uint, flags: os_sync_wake_by_address_flags) -> i32 ---
|
||||||
|
|
||||||
// This function is a variant of os_sync_wake_by_address_any that wakes up all waiters
|
// This function is a variant of os_sync_wake_by_address_any that wakes up all waiters
|
||||||
// blocked in os_sync_wait_on_address or its variants.
|
// blocked in os_sync_wait_on_address or its variants.
|
||||||
@@ -305,5 +299,5 @@ foreign system {
|
|||||||
// In the event of an error, returns -1 with errno set to indicate the error.
|
// In the event of an error, returns -1 with errno set to indicate the error.
|
||||||
//
|
//
|
||||||
// This function returns same error codes as returned by os_sync_wait_on_address.
|
// This function returns same error codes as returned by os_sync_wait_on_address.
|
||||||
os_sync_wake_by_address_all :: proc(addr: rawptr, size: uint, flags: os_sync_wait_on_address_flags) -> i32 ---
|
os_sync_wake_by_address_all :: proc(addr: rawptr, size: uint, flags: os_sync_wake_by_address_flags) -> i32 ---
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -183,16 +183,17 @@ undo_check :: proc(s: ^State) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// insert text into the edit state - deletes the current selection
|
// insert text into the edit state - deletes the current selection
|
||||||
input_text :: proc(s: ^State, text: string) {
|
input_text :: proc(s: ^State, text: string) -> int {
|
||||||
if len(text) == 0 {
|
if len(text) == 0 {
|
||||||
return
|
return 0
|
||||||
}
|
}
|
||||||
if has_selection(s) {
|
if has_selection(s) {
|
||||||
selection_delete(s)
|
selection_delete(s)
|
||||||
}
|
}
|
||||||
insert(s, s.selection[0], text)
|
n := insert(s, s.selection[0], text)
|
||||||
offset := s.selection[0] + len(text)
|
offset := s.selection[0] + n
|
||||||
s.selection = {offset, offset}
|
s.selection = {offset, offset}
|
||||||
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
// insert slice of runes into the edit state - deletes the current selection
|
// insert slice of runes into the edit state - deletes the current selection
|
||||||
@@ -206,8 +207,11 @@ input_runes :: proc(s: ^State, text: []rune) {
|
|||||||
offset := s.selection[0]
|
offset := s.selection[0]
|
||||||
for r in text {
|
for r in text {
|
||||||
b, w := utf8.encode_rune(r)
|
b, w := utf8.encode_rune(r)
|
||||||
insert(s, offset, string(b[:w]))
|
n := insert(s, offset, string(b[:w]))
|
||||||
offset += w
|
offset += n
|
||||||
|
if n != w {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
s.selection = {offset, offset}
|
s.selection = {offset, offset}
|
||||||
}
|
}
|
||||||
@@ -219,17 +223,29 @@ input_rune :: proc(s: ^State, r: rune) {
|
|||||||
}
|
}
|
||||||
offset := s.selection[0]
|
offset := s.selection[0]
|
||||||
b, w := utf8.encode_rune(r)
|
b, w := utf8.encode_rune(r)
|
||||||
insert(s, offset, string(b[:w]))
|
n := insert(s, offset, string(b[:w]))
|
||||||
offset += w
|
offset += n
|
||||||
s.selection = {offset, offset}
|
s.selection = {offset, offset}
|
||||||
}
|
}
|
||||||
|
|
||||||
// insert a single rune into the edit state - deletes the current selection
|
// insert a single rune into the edit state - deletes the current selection
|
||||||
insert :: proc(s: ^State, at: int, text: string) {
|
insert :: proc(s: ^State, at: int, text: string) -> int {
|
||||||
undo_check(s)
|
undo_check(s)
|
||||||
if s.builder != nil {
|
if s.builder != nil {
|
||||||
inject_at(&s.builder.buf, at, text)
|
if ok, _ := inject_at(&s.builder.buf, at, text); !ok {
|
||||||
|
n := cap(s.builder.buf) - len(s.builder.buf)
|
||||||
|
assert(n < len(text))
|
||||||
|
for is_continuation_byte(text[n]) {
|
||||||
|
n -= 1
|
||||||
|
}
|
||||||
|
if ok2, _ := inject_at(&s.builder.buf, at, text[:n]); !ok2 {
|
||||||
|
n = 0
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
return len(text)
|
||||||
}
|
}
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove the wanted range withing, usually the selection within byte indices
|
// remove the wanted range withing, usually the selection within byte indices
|
||||||
@@ -263,11 +279,12 @@ selection_delete :: proc(s: ^State) {
|
|||||||
s.selection = {lo, lo}
|
s.selection = {lo, lo}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is_continuation_byte :: proc(b: byte) -> bool {
|
||||||
|
return b >= 0x80 && b < 0xc0
|
||||||
|
}
|
||||||
|
|
||||||
// translates the caret position
|
// translates the caret position
|
||||||
translate_position :: proc(s: ^State, t: Translation) -> int {
|
translate_position :: proc(s: ^State, t: Translation) -> int {
|
||||||
is_continuation_byte :: proc(b: byte) -> bool {
|
|
||||||
return b >= 0x80 && b < 0xc0
|
|
||||||
}
|
|
||||||
is_space :: proc(b: byte) -> bool {
|
is_space :: proc(b: byte) -> bool {
|
||||||
return b == ' ' || b == '\t' || b == '\n'
|
return b == ' ' || b == '\t' || b == '\n'
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-17
@@ -60,10 +60,6 @@ parse_mo_from_bytes :: proc(data: []byte, options := DEFAULT_PARSE_OPTIONS, plur
|
|||||||
translation.pluralize = pluralizer
|
translation.pluralize = pluralizer
|
||||||
strings.intern_init(&translation.intern, allocator, allocator)
|
strings.intern_init(&translation.intern, allocator, allocator)
|
||||||
|
|
||||||
// Gettext MO files only have one section.
|
|
||||||
translation.k_v[""] = {}
|
|
||||||
section := &translation.k_v[""]
|
|
||||||
|
|
||||||
for n := u32(0); n < count; n += 1 {
|
for n := u32(0); n < count; n += 1 {
|
||||||
/*
|
/*
|
||||||
Grab string's original length and offset.
|
Grab string's original length and offset.
|
||||||
@@ -83,37 +79,60 @@ parse_mo_from_bytes :: proc(data: []byte, options := DEFAULT_PARSE_OPTIONS, plur
|
|||||||
max_offset := int(max(o_offset + o_length + 1, t_offset + t_length + 1))
|
max_offset := int(max(o_offset + o_length + 1, t_offset + t_length + 1))
|
||||||
if len(data) < max_offset { return translation, .Premature_EOF }
|
if len(data) < max_offset { return translation, .Premature_EOF }
|
||||||
|
|
||||||
key := data[o_offset:][:o_length]
|
key_data := data[o_offset:][:o_length]
|
||||||
val := data[t_offset:][:t_length]
|
val_data := data[t_offset:][:t_length]
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Could be a pluralized string.
|
Could be a pluralized string.
|
||||||
*/
|
*/
|
||||||
zero := []byte{0}
|
zero := []byte{0}
|
||||||
|
keys := bytes.split(key_data, zero); defer delete(keys)
|
||||||
|
vals := bytes.split(val_data, zero); defer delete(vals)
|
||||||
|
|
||||||
keys := bytes.split(key, zero)
|
if (len(keys) != 1 && len(keys) != 2) || len(vals) > MAX_PLURALS {
|
||||||
vals := bytes.split(val, zero)
|
|
||||||
|
|
||||||
if len(keys) != len(vals) || max(len(keys), len(vals)) > MAX_PLURALS {
|
|
||||||
return translation, .MO_File_Incorrect_Plural_Count
|
return translation, .MO_File_Incorrect_Plural_Count
|
||||||
}
|
}
|
||||||
|
|
||||||
for k in keys {
|
for k in keys {
|
||||||
interned_key, _ := strings.intern_get(&translation.intern, string(k))
|
section_name := ""
|
||||||
|
key := string(k)
|
||||||
|
|
||||||
interned_vals := make([]string, len(keys))
|
// Scan for <context>EOT<key>
|
||||||
|
for ch, i in k {
|
||||||
|
if ch == 0x04 {
|
||||||
|
section_name = string(k[:i])
|
||||||
|
key = string(k[i+1:])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we merge sections, then all entries end in the "" context.
|
||||||
|
if options.merge_sections {
|
||||||
|
section_name = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
section_name, _ = strings.intern_get(&translation.intern, section_name)
|
||||||
|
if section_name not_in translation.k_v {
|
||||||
|
translation.k_v[section_name] = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
section := &translation.k_v[section_name]
|
||||||
|
interned_key, _ := strings.intern_get(&translation.intern, string(key))
|
||||||
|
|
||||||
|
// Duplicate key should not be allowed.
|
||||||
|
if interned_key in section {
|
||||||
|
return translation, .Duplicate_Key
|
||||||
|
}
|
||||||
|
|
||||||
|
interned_vals := make([]string, len(vals))
|
||||||
last_val: string
|
last_val: string
|
||||||
|
|
||||||
i := 0
|
for v, i in vals {
|
||||||
for v in vals {
|
|
||||||
interned_vals[i], _ = strings.intern_get(&translation.intern, string(v))
|
interned_vals[i], _ = strings.intern_get(&translation.intern, string(v))
|
||||||
last_val = interned_vals[i]
|
last_val = interned_vals[i]
|
||||||
i += 1
|
|
||||||
}
|
}
|
||||||
section[interned_key] = interned_vals
|
section[interned_key] = interned_vals
|
||||||
}
|
}
|
||||||
delete(vals)
|
|
||||||
delete(keys)
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package time
|
||||||
|
// Parsing ISO 8601 date/time strings into time.Time.
|
||||||
|
|
||||||
|
import dt "core:time/datetime"
|
||||||
|
|
||||||
|
// Parses an ISO 8601 string and returns Time in UTC, with any UTC offset applied to it.
|
||||||
|
// Only 4-digit years are accepted.
|
||||||
|
// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second.
|
||||||
|
// Leap seconds are smeared into 23:59:59.
|
||||||
|
iso8601_to_time_utc :: proc(iso_datetime: string, is_leap: ^bool = nil) -> (res: Time, consumed: int) {
|
||||||
|
offset: int
|
||||||
|
|
||||||
|
res, offset, consumed = iso8601_to_time_and_offset(iso_datetime, is_leap)
|
||||||
|
res._nsec += (i64(-offset) * i64(Minute))
|
||||||
|
return res, consumed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses an ISO 8601 string and returns Time and a UTC offset in minutes.
|
||||||
|
// e.g. 1985-04-12T23:20:50.52Z
|
||||||
|
// Note: Only 4-digit years are accepted.
|
||||||
|
// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second.
|
||||||
|
// Leap seconds are smeared into 23:59:59.
|
||||||
|
iso8601_to_time_and_offset :: proc(iso_datetime: string, is_leap: ^bool = nil) -> (res: Time, utc_offset: int, consumed: int) {
|
||||||
|
moment, offset, leap_second, count := iso8601_to_components(iso_datetime)
|
||||||
|
if count == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_leap != nil {
|
||||||
|
is_leap^ = leap_second
|
||||||
|
}
|
||||||
|
|
||||||
|
if _res, ok := datetime_to_time(moment.year, moment.month, moment.day, moment.hour, moment.minute, moment.second, moment.nano); !ok {
|
||||||
|
return {}, 0, 0
|
||||||
|
} else {
|
||||||
|
return _res, offset, count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses an ISO 8601 string and returns Time and a UTC offset in minutes.
|
||||||
|
// e.g. 1985-04-12T23:20:50.52Z
|
||||||
|
// Performs no validation on whether components are valid, e.g. it'll return hour = 25 if that's what it's given
|
||||||
|
iso8601_to_components :: proc(iso_datetime: string) -> (res: dt.DateTime, utc_offset: int, is_leap: bool, consumed: int) {
|
||||||
|
moment, offset, count, leap_second, ok := _iso8601_to_components(iso_datetime)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return moment, offset, leap_second, count
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses an ISO 8601 string and returns datetime.DateTime.
|
||||||
|
// Performs no validation on whether components are valid, e.g. it'll return hour = 25 if that's what it's given
|
||||||
|
@(private)
|
||||||
|
_iso8601_to_components :: proc(iso_datetime: string) -> (res: dt.DateTime, utc_offset: int, consumed: int, is_leap: bool, ok: bool) {
|
||||||
|
// A compliant date is at minimum 20 characters long, e.g. YYYY-MM-DDThh:mm:ssZ
|
||||||
|
(len(iso_datetime) >= 20) or_return
|
||||||
|
|
||||||
|
// Scan and eat YYYY-MM-DD[Tt], then scan and eat HH:MM:SS, leave separator
|
||||||
|
year := scan_digits(iso_datetime[0:], "-", 4) or_return
|
||||||
|
month := scan_digits(iso_datetime[5:], "-", 2) or_return
|
||||||
|
day := scan_digits(iso_datetime[8:], "Tt ", 2) or_return
|
||||||
|
hour := scan_digits(iso_datetime[11:], ":", 2) or_return
|
||||||
|
minute := scan_digits(iso_datetime[14:], ":", 2) or_return
|
||||||
|
second := scan_digits(iso_datetime[17:], "", 2) or_return
|
||||||
|
nanos := 0
|
||||||
|
count := 19
|
||||||
|
|
||||||
|
// Scan fractional seconds
|
||||||
|
if iso_datetime[count] == '.' {
|
||||||
|
count += 1 // consume '.'
|
||||||
|
multiplier := 100_000_000
|
||||||
|
for digit in iso_datetime[count:] {
|
||||||
|
if multiplier >= 1 && int(digit) >= '0' && int(digit) <= '9' {
|
||||||
|
nanos += int(digit - '0') * multiplier
|
||||||
|
multiplier /= 10
|
||||||
|
count += 1
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Leap second handling
|
||||||
|
if minute == 59 && second == 60 {
|
||||||
|
second = 59
|
||||||
|
is_leap = true
|
||||||
|
}
|
||||||
|
|
||||||
|
err: dt.Error
|
||||||
|
if res, err = dt.components_to_datetime(year, month, day, hour, minute, second, nanos); err != .None {
|
||||||
|
return {}, 0, 0, false, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(iso_datetime[count:]) == 0 {
|
||||||
|
return res, utc_offset, count, is_leap, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan UTC offset
|
||||||
|
switch iso_datetime[count] {
|
||||||
|
case 'Z', 'z':
|
||||||
|
utc_offset = 0
|
||||||
|
count += 1
|
||||||
|
case '+', '-':
|
||||||
|
(len(iso_datetime[count:]) >= 6) or_return
|
||||||
|
offset_hour := scan_digits(iso_datetime[count+1:], ":", 2) or_return
|
||||||
|
offset_minute := scan_digits(iso_datetime[count+4:], "", 2) or_return
|
||||||
|
|
||||||
|
utc_offset = 60 * offset_hour + offset_minute
|
||||||
|
utc_offset *= -1 if iso_datetime[count] == '-' else 1
|
||||||
|
count += 6
|
||||||
|
}
|
||||||
|
return res, utc_offset, count, is_leap, true
|
||||||
|
}
|
||||||
@@ -57,12 +57,12 @@ _rfc3339_to_components :: proc(rfc_datetime: string) -> (res: dt.DateTime, utc_o
|
|||||||
(len(rfc_datetime) >= 20) or_return
|
(len(rfc_datetime) >= 20) or_return
|
||||||
|
|
||||||
// Scan and eat YYYY-MM-DD[Tt], then scan and eat HH:MM:SS, leave separator
|
// Scan and eat YYYY-MM-DD[Tt], then scan and eat HH:MM:SS, leave separator
|
||||||
year := scan_digits(rfc_datetime[0:], "-", 4) or_return
|
year := scan_digits(rfc_datetime[0:], "-", 4) or_return
|
||||||
month := scan_digits(rfc_datetime[5:], "-", 2) or_return
|
month := scan_digits(rfc_datetime[5:], "-", 2) or_return
|
||||||
day := scan_digits(rfc_datetime[8:], "Tt", 2) or_return
|
day := scan_digits(rfc_datetime[8:], "Tt ", 2) or_return
|
||||||
hour := scan_digits(rfc_datetime[11:], ":", 2) or_return
|
hour := scan_digits(rfc_datetime[11:], ":", 2) or_return
|
||||||
minute := scan_digits(rfc_datetime[14:], ":", 2) or_return
|
minute := scan_digits(rfc_datetime[14:], ":", 2) or_return
|
||||||
second := scan_digits(rfc_datetime[17:], "", 2) or_return
|
second := scan_digits(rfc_datetime[17:], "", 2) or_return
|
||||||
nanos := 0
|
nanos := 0
|
||||||
count := 19
|
count := 19
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ _rfc3339_to_components :: proc(rfc_datetime: string) -> (res: dt.DateTime, utc_o
|
|||||||
|
|
||||||
// Scan UTC offset
|
// Scan UTC offset
|
||||||
switch rfc_datetime[count] {
|
switch rfc_datetime[count] {
|
||||||
case 'Z':
|
case 'Z', 'z':
|
||||||
utc_offset = 0
|
utc_offset = 0
|
||||||
count += 1
|
count += 1
|
||||||
case '+', '-':
|
case '+', '-':
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
//+build wasi
|
//+build wasi
|
||||||
package time
|
package time
|
||||||
|
|
||||||
import wasi "core:sys/wasm/wasi"
|
|
||||||
|
|
||||||
_IS_SUPPORTED :: false
|
_IS_SUPPORTED :: false
|
||||||
|
|
||||||
_now :: proc "contextless" () -> Time {
|
_now :: proc "contextless" () -> Time {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import queue "core:container/queue"
|
|||||||
import small_array "core:container/small_array"
|
import small_array "core:container/small_array"
|
||||||
import lru "core:container/lru"
|
import lru "core:container/lru"
|
||||||
import list "core:container/intrusive/list"
|
import list "core:container/intrusive/list"
|
||||||
|
import rbtree "core:container/rbtree"
|
||||||
import topological_sort "core:container/topological_sort"
|
import topological_sort "core:container/topological_sort"
|
||||||
|
|
||||||
import crypto "core:crypto"
|
import crypto "core:crypto"
|
||||||
@@ -91,9 +92,7 @@ import virtual "core:mem/virtual"
|
|||||||
|
|
||||||
import ast "core:odin/ast"
|
import ast "core:odin/ast"
|
||||||
import doc_format "core:odin/doc-format"
|
import doc_format "core:odin/doc-format"
|
||||||
import odin_format "core:odin/format"
|
|
||||||
import odin_parser "core:odin/parser"
|
import odin_parser "core:odin/parser"
|
||||||
import odin_printer "core:odin/printer"
|
|
||||||
import odin_tokenizer "core:odin/tokenizer"
|
import odin_tokenizer "core:odin/tokenizer"
|
||||||
|
|
||||||
import spall "core:prof/spall"
|
import spall "core:prof/spall"
|
||||||
@@ -147,6 +146,7 @@ _ :: queue
|
|||||||
_ :: small_array
|
_ :: small_array
|
||||||
_ :: lru
|
_ :: lru
|
||||||
_ :: list
|
_ :: list
|
||||||
|
_ :: rbtree
|
||||||
_ :: topological_sort
|
_ :: topological_sort
|
||||||
_ :: crypto
|
_ :: crypto
|
||||||
_ :: crypto_hash
|
_ :: crypto_hash
|
||||||
@@ -207,9 +207,7 @@ _ :: mem
|
|||||||
_ :: virtual
|
_ :: virtual
|
||||||
_ :: ast
|
_ :: ast
|
||||||
_ :: doc_format
|
_ :: doc_format
|
||||||
_ :: odin_format
|
|
||||||
_ :: odin_parser
|
_ :: odin_parser
|
||||||
_ :: odin_printer
|
|
||||||
_ :: odin_tokenizer
|
_ :: odin_tokenizer
|
||||||
_ :: os
|
_ :: os
|
||||||
_ :: spall
|
_ :: spall
|
||||||
|
|||||||
+1
-1
@@ -251,7 +251,7 @@ gb_internal void report_ram_info() {
|
|||||||
int result = sysinfo(&info);
|
int result = sysinfo(&info);
|
||||||
|
|
||||||
if (result == 0x0) {
|
if (result == 0x0) {
|
||||||
gb_printf("%lu MiB\n", info.totalram * info.mem_unit / gb_megabytes(1));
|
gb_printf("%lu MiB\n", (unsigned long)(info.totalram * info.mem_unit / gb_megabytes(1)));
|
||||||
} else {
|
} else {
|
||||||
gb_printf("Unknown.\n");
|
gb_printf("Unknown.\n");
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-9
@@ -978,7 +978,7 @@ gb_global TargetMetrics target_linux_arm32 = {
|
|||||||
TargetOs_linux,
|
TargetOs_linux,
|
||||||
TargetArch_arm32,
|
TargetArch_arm32,
|
||||||
4, 4, 4, 8,
|
4, 4, 4, 8,
|
||||||
str_lit("arm-linux-gnu"),
|
str_lit("arm-unknown-linux-gnueabihf"),
|
||||||
};
|
};
|
||||||
|
|
||||||
gb_global TargetMetrics target_darwin_amd64 = {
|
gb_global TargetMetrics target_darwin_amd64 = {
|
||||||
@@ -1906,6 +1906,16 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta
|
|||||||
#else
|
#else
|
||||||
metrics = &target_linux_amd64;
|
metrics = &target_linux_amd64;
|
||||||
#endif
|
#endif
|
||||||
|
#elif defined(GB_CPU_ARM)
|
||||||
|
#if defined(GB_SYSTEM_WINDOWS)
|
||||||
|
#error "Build Error: Unsupported architecture"
|
||||||
|
#elif defined(GB_SYSTEM_OSX)
|
||||||
|
#error "Build Error: Unsupported architecture"
|
||||||
|
#elif defined(GB_SYSTEM_FREEBSD)
|
||||||
|
#error "Build Error: Unsupported architecture"
|
||||||
|
#else
|
||||||
|
metrics = &target_linux_arm32;
|
||||||
|
#endif
|
||||||
#else
|
#else
|
||||||
#if defined(GB_SYSTEM_WINDOWS)
|
#if defined(GB_SYSTEM_WINDOWS)
|
||||||
metrics = &target_windows_i386;
|
metrics = &target_windows_i386;
|
||||||
@@ -2052,14 +2062,6 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta
|
|||||||
|
|
||||||
if (bc->metrics.os == TargetOs_freestanding) {
|
if (bc->metrics.os == TargetOs_freestanding) {
|
||||||
bc->ODIN_DEFAULT_TO_NIL_ALLOCATOR = !bc->ODIN_DEFAULT_TO_PANIC_ALLOCATOR;
|
bc->ODIN_DEFAULT_TO_NIL_ALLOCATOR = !bc->ODIN_DEFAULT_TO_PANIC_ALLOCATOR;
|
||||||
} else if (is_arch_wasm()) {
|
|
||||||
if (bc->metrics.os == TargetOs_js || bc->metrics.os == TargetOs_wasi) {
|
|
||||||
// TODO(bill): Should these even have a default "heap-like" allocator?
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!bc->ODIN_DEFAULT_TO_NIL_ALLOCATOR && !bc->ODIN_DEFAULT_TO_PANIC_ALLOCATOR) {
|
|
||||||
bc->ODIN_DEFAULT_TO_PANIC_ALLOCATOR = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+35
-1
@@ -1092,7 +1092,13 @@ gb_internal bool cache_load_file_directive(CheckerContext *c, Ast *call, String
|
|||||||
|
|
||||||
BlockingMutex *ignore_mutex = nullptr;
|
BlockingMutex *ignore_mutex = nullptr;
|
||||||
bool ok = determine_path_from_string(ignore_mutex, call, base_dir, original_string, &path);
|
bool ok = determine_path_from_string(ignore_mutex, call, base_dir, original_string, &path);
|
||||||
gb_unused(ok);
|
if (!ok) {
|
||||||
|
if (err_on_not_found) {
|
||||||
|
error(ce->proc, "Failed to `#%.*s` file: %.*s; invalid file or cannot be found", LIT(builtin_name), LIT(original_string));
|
||||||
|
}
|
||||||
|
call->state_flags |= StateFlag_DirectiveWasFalse;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MUTEX_GUARD(&c->info->load_file_mutex);
|
MUTEX_GUARD(&c->info->load_file_mutex);
|
||||||
@@ -5235,6 +5241,34 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
|
|||||||
operand->type = t_untyped_bool;
|
operand->type = t_untyped_bool;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case BuiltinProc_type_is_matrix_row_major:
|
||||||
|
case BuiltinProc_type_is_matrix_column_major:
|
||||||
|
{
|
||||||
|
Operand op = {};
|
||||||
|
Type *bt = check_type(c, ce->args[0]);
|
||||||
|
Type *type = base_type(bt);
|
||||||
|
if (type == nullptr || type == t_invalid) {
|
||||||
|
error(ce->args[0], "Expected a type for '%.*s'", LIT(builtin_name));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (type->kind != Type_Matrix) {
|
||||||
|
gbString s = type_to_string(bt);
|
||||||
|
error(ce->args[0], "Expected a matrix type for '%.*s', got '%s'", LIT(builtin_name), s);
|
||||||
|
gb_string_free(s);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id == BuiltinProc_type_is_matrix_row_major) {
|
||||||
|
operand->value = exact_value_bool(bt->Matrix.is_row_major == true);
|
||||||
|
} else {
|
||||||
|
operand->value = exact_value_bool(bt->Matrix.is_row_major == false);
|
||||||
|
}
|
||||||
|
operand->mode = Addressing_Constant;
|
||||||
|
operand->type = t_untyped_bool;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case BuiltinProc_type_has_field:
|
case BuiltinProc_type_has_field:
|
||||||
{
|
{
|
||||||
Operand op = {};
|
Operand op = {};
|
||||||
|
|||||||
+55
-6
@@ -1179,15 +1179,59 @@ gb_internal void check_assignment(CheckerContext *c, Operand *operand, Type *typ
|
|||||||
LIT(context_name));
|
LIT(context_name));
|
||||||
check_assignment_error_suggestion(c, operand, type);
|
check_assignment_error_suggestion(c, operand, type);
|
||||||
|
|
||||||
|
Type *src = base_type(operand->type);
|
||||||
|
Type *dst = base_type(type);
|
||||||
if (context_name == "procedure argument") {
|
if (context_name == "procedure argument") {
|
||||||
Type *src = base_type(operand->type);
|
|
||||||
Type *dst = base_type(type);
|
|
||||||
if (is_type_slice(src) && are_types_identical(src->Slice.elem, dst)) {
|
if (is_type_slice(src) && are_types_identical(src->Slice.elem, dst)) {
|
||||||
gbString a = expr_to_string(operand->expr);
|
gbString a = expr_to_string(operand->expr);
|
||||||
error_line("\tSuggestion: Did you mean to pass the slice into the variadic parameter with ..%s?\n\n", a);
|
error_line("\tSuggestion: Did you mean to pass the slice into the variadic parameter with ..%s?\n\n", a);
|
||||||
gb_string_free(a);
|
gb_string_free(a);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (src->kind == dst->kind && src->kind == Type_Proc) {
|
||||||
|
Type *x = src;
|
||||||
|
Type *y = dst;
|
||||||
|
bool same_inputs = are_types_identical_internal(x->Proc.params, y->Proc.params, false);
|
||||||
|
bool same_outputs = are_types_identical_internal(x->Proc.results, y->Proc.results, false);
|
||||||
|
if (same_inputs && same_outputs &&
|
||||||
|
x->Proc.calling_convention != y->Proc.calling_convention) {
|
||||||
|
gbString s_expected = type_to_string(y);
|
||||||
|
gbString s_got = type_to_string(x);
|
||||||
|
|
||||||
|
error_line("\tNote: The calling conventions differ between the procedure signature types\n");
|
||||||
|
error_line("\t Expected \"%s\", got \"%s\"\n",
|
||||||
|
proc_calling_convention_strings[y->Proc.calling_convention],
|
||||||
|
proc_calling_convention_strings[x->Proc.calling_convention]);
|
||||||
|
error_line("\t Expected: %s\n", s_expected);
|
||||||
|
error_line("\t Got: %s\n", s_got);
|
||||||
|
gb_string_free(s_got);
|
||||||
|
gb_string_free(s_expected);
|
||||||
|
} else if (same_inputs && !same_outputs) {
|
||||||
|
gbString s_expected = type_to_string(y->Proc.results);
|
||||||
|
gbString s_got = type_to_string(x->Proc.results);
|
||||||
|
error_line("\tNote: The return types differ between the procedure signature types\n");
|
||||||
|
error_line("\t Expected: %s\n", s_expected);
|
||||||
|
error_line("\t Got: %s\n", s_got);
|
||||||
|
gb_string_free(s_got);
|
||||||
|
gb_string_free(s_expected);
|
||||||
|
} else if (!same_inputs && same_outputs) {
|
||||||
|
gbString s_expected = type_to_string(y->Proc.params);
|
||||||
|
gbString s_got = type_to_string(x->Proc.params);
|
||||||
|
error_line("\tNote: The input parameter types differ between the procedure signature types\n");
|
||||||
|
error_line("\t Expected: %s\n", s_expected);
|
||||||
|
error_line("\t Got: %s\n", s_got);
|
||||||
|
gb_string_free(s_got);
|
||||||
|
gb_string_free(s_expected);
|
||||||
|
} else {
|
||||||
|
gbString s_expected = type_to_string(y);
|
||||||
|
gbString s_got = type_to_string(x);
|
||||||
|
error_line("\tNote: The signature type do not match whatsoever\n");
|
||||||
|
error_line("\t Expected: %s\n", s_expected);
|
||||||
|
error_line("\t Got: %s\n", s_got);
|
||||||
|
gb_string_free(s_got);
|
||||||
|
gb_string_free(s_expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1761,7 +1805,7 @@ gb_internal Entity *check_ident(CheckerContext *c, Operand *o, Ast *n, Type *nam
|
|||||||
|
|
||||||
case Entity_ImportName:
|
case Entity_ImportName:
|
||||||
if (!allow_import_name) {
|
if (!allow_import_name) {
|
||||||
error(n, "Use of import '%.*s' not in selector", LIT(name));
|
error(n, "Use of import name '%.*s' not in the form of 'x.y'", LIT(name));
|
||||||
}
|
}
|
||||||
return e;
|
return e;
|
||||||
case Entity_LibraryName:
|
case Entity_LibraryName:
|
||||||
@@ -7757,13 +7801,18 @@ gb_internal bool check_set_index_data(Operand *o, Type *t, bool indirection, i64
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case Type_Matrix:
|
case Type_Matrix:
|
||||||
*max_count = t->Matrix.column_count;
|
|
||||||
if (indirection) {
|
if (indirection) {
|
||||||
o->mode = Addressing_Variable;
|
o->mode = Addressing_Variable;
|
||||||
} else if (o->mode != Addressing_Variable) {
|
} else if (o->mode != Addressing_Variable) {
|
||||||
o->mode = Addressing_Value;
|
o->mode = Addressing_Value;
|
||||||
}
|
}
|
||||||
o->type = alloc_type_array(t->Matrix.elem, t->Matrix.row_count);
|
if (t->Matrix.is_row_major) {
|
||||||
|
*max_count = t->Matrix.row_count;
|
||||||
|
o->type = alloc_type_array(t->Matrix.elem, t->Matrix.column_count);
|
||||||
|
} else {
|
||||||
|
*max_count = t->Matrix.column_count;
|
||||||
|
o->type = alloc_type_array(t->Matrix.elem, t->Matrix.row_count);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case Type_Slice:
|
case Type_Slice:
|
||||||
@@ -10159,7 +10208,7 @@ gb_internal ExprKind check_index_expr(CheckerContext *c, Operand *o, Ast *node,
|
|||||||
o->mode = Addressing_Invalid;
|
o->mode = Addressing_Invalid;
|
||||||
o->expr = node;
|
o->expr = node;
|
||||||
return kind;
|
return kind;
|
||||||
} else if (ok) {
|
} else if (ok && !is_type_matrix(t)) {
|
||||||
ExactValue value = type_and_value_of_expr(ie->expr).value;
|
ExactValue value = type_and_value_of_expr(ie->expr).value;
|
||||||
o->mode = Addressing_Constant;
|
o->mode = Addressing_Constant;
|
||||||
bool success = false;
|
bool success = false;
|
||||||
|
|||||||
+7
-2
@@ -565,7 +565,11 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O
|
|||||||
} else {
|
} else {
|
||||||
error(lhs->expr, "Cannot assign to '%s' which is a procedure parameter", str);
|
error(lhs->expr, "Cannot assign to '%s' which is a procedure parameter", str);
|
||||||
}
|
}
|
||||||
error_line("\tSuggestion: Did you mean to pass '%.*s' by pointer?\n", LIT(e->token.string));
|
if (is_type_pointer(e->type)) {
|
||||||
|
error_line("\tSuggestion: Did you mean to shadow it? '%.*s := %.*s'?\n", LIT(e->token.string), LIT(e->token.string));
|
||||||
|
} else {
|
||||||
|
error_line("\tSuggestion: Did you mean to pass '%.*s' by pointer?\n", LIT(e->token.string));
|
||||||
|
}
|
||||||
show_error_on_line(e->token.pos, token_pos_end(e->token));
|
show_error_on_line(e->token.pos, token_pos_end(e->token));
|
||||||
} else {
|
} else {
|
||||||
ERROR_BLOCK();
|
ERROR_BLOCK();
|
||||||
@@ -1663,6 +1667,7 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
bool is_ptr = type_deref(operand.type);
|
||||||
Type *t = base_type(type_deref(operand.type));
|
Type *t = base_type(type_deref(operand.type));
|
||||||
|
|
||||||
switch (t->kind) {
|
switch (t->kind) {
|
||||||
@@ -1707,7 +1712,7 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case Type_Array:
|
case Type_Array:
|
||||||
is_possibly_addressable = operand.mode == Addressing_Variable;
|
is_possibly_addressable = operand.mode == Addressing_Variable || is_ptr;
|
||||||
array_add(&vals, t->Array.elem);
|
array_add(&vals, t->Array.elem);
|
||||||
array_add(&vals, t_int);
|
array_add(&vals, t_int);
|
||||||
break;
|
break;
|
||||||
|
|||||||
+1
-1
@@ -1595,7 +1595,7 @@ gb_internal bool is_expr_from_a_parameter(CheckerContext *ctx, Ast *expr) {
|
|||||||
return is_expr_from_a_parameter(ctx, lhs);
|
return is_expr_from_a_parameter(ctx, lhs);
|
||||||
} else if (expr->kind == Ast_Ident) {
|
} else if (expr->kind == Ast_Ident) {
|
||||||
Operand x= {};
|
Operand x= {};
|
||||||
Entity *e = check_ident(ctx, &x, expr, nullptr, nullptr, false);
|
Entity *e = check_ident(ctx, &x, expr, nullptr, nullptr, true);
|
||||||
if (e->flags & EntityFlag_Param) {
|
if (e->flags & EntityFlag_Param) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2645,6 +2645,10 @@ gb_internal void generate_minimum_dependency_set(Checker *c, Entity *start) {
|
|||||||
str_lit("memmove"),
|
str_lit("memmove"),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
FORCE_ADD_RUNTIME_ENTITIES(build_context.metrics.arch == TargetArch_arm32,
|
||||||
|
str_lit("aeabi_d2h")
|
||||||
|
);
|
||||||
|
|
||||||
FORCE_ADD_RUNTIME_ENTITIES(is_arch_wasm() && !build_context.tilde_backend,
|
FORCE_ADD_RUNTIME_ENTITIES(is_arch_wasm() && !build_context.tilde_backend,
|
||||||
// // Extended data type internal procedures
|
// // Extended data type internal procedures
|
||||||
// str_lit("umodti3"),
|
// str_lit("umodti3"),
|
||||||
|
|||||||
@@ -256,6 +256,9 @@ BuiltinProc__type_simple_boolean_begin,
|
|||||||
|
|
||||||
BuiltinProc__type_simple_boolean_end,
|
BuiltinProc__type_simple_boolean_end,
|
||||||
|
|
||||||
|
BuiltinProc_type_is_matrix_row_major,
|
||||||
|
BuiltinProc_type_is_matrix_column_major,
|
||||||
|
|
||||||
BuiltinProc_type_has_field,
|
BuiltinProc_type_has_field,
|
||||||
BuiltinProc_type_field_type,
|
BuiltinProc_type_field_type,
|
||||||
|
|
||||||
@@ -567,6 +570,9 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
|
|||||||
{STR_LIT("type_has_nil"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
|
{STR_LIT("type_has_nil"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
|
||||||
{STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
|
{STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
|
||||||
|
|
||||||
|
{STR_LIT("type_is_matrix_row_major"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
|
||||||
|
{STR_LIT("type_is_matrix_column_major"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
|
||||||
|
|
||||||
{STR_LIT("type_has_field"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
|
{STR_LIT("type_has_field"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
|
||||||
{STR_LIT("type_field_type"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
|
{STR_LIT("type_field_type"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
|
||||||
|
|
||||||
|
|||||||
+4
-3
@@ -6261,11 +6261,12 @@ gb_no_inline isize gb_snprintf_va(char *text, isize max_len, char const *fmt, va
|
|||||||
#elif defined(__aarch64__)
|
#elif defined(__aarch64__)
|
||||||
gb_inline u64 gb_rdtsc(void) {
|
gb_inline u64 gb_rdtsc(void) {
|
||||||
int64_t virtual_timer_value;
|
int64_t virtual_timer_value;
|
||||||
asm volatile("mrs %0, cntvct_el0" : "=r"(virtual_timer_value));
|
asm volatile("mrs %0, cntvct_el0" : "=r"(virtual_timer_value));
|
||||||
return virtual_timer_value;
|
return virtual_timer_value;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
#error "gb_rdtsc not supported"
|
#warning "gb_rdtsc not supported"
|
||||||
|
gb_inline u64 gb_rdtsc(void) { return 0; }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(GB_SYSTEM_WINDOWS)
|
#if defined(GB_SYSTEM_WINDOWS)
|
||||||
|
|||||||
@@ -2384,9 +2384,10 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu
|
|||||||
|
|
||||||
lbValue ptr0 = lb_emit_conv(p, lb_build_expr(p, ce->args[0]), t_uintptr);
|
lbValue ptr0 = lb_emit_conv(p, lb_build_expr(p, ce->args[0]), t_uintptr);
|
||||||
lbValue ptr1 = lb_emit_conv(p, lb_build_expr(p, ce->args[1]), t_uintptr);
|
lbValue ptr1 = lb_emit_conv(p, lb_build_expr(p, ce->args[1]), t_uintptr);
|
||||||
|
ptr0 = lb_emit_conv(p, ptr0, t_int);
|
||||||
|
ptr1 = lb_emit_conv(p, ptr1, t_int);
|
||||||
|
|
||||||
lbValue diff = lb_emit_arith(p, Token_Sub, ptr0, ptr1, t_uintptr);
|
lbValue diff = lb_emit_arith(p, Token_Sub, ptr0, ptr1, t_int);
|
||||||
diff = lb_emit_conv(p, diff, t_int);
|
|
||||||
return lb_emit_arith(p, Token_Quo, diff, lb_const_int(p->module, t_int, type_size_of(elem)), t_int);
|
return lb_emit_arith(p, Token_Quo, diff, lb_const_int(p->module, t_int, type_size_of(elem)), t_int);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2903,7 +2904,6 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu
|
|||||||
break;
|
break;
|
||||||
case TargetArch_arm32:
|
case TargetArch_arm32:
|
||||||
{
|
{
|
||||||
// TODO(bill): Check this is correct
|
|
||||||
GB_ASSERT(arg_count <= 7);
|
GB_ASSERT(arg_count <= 7);
|
||||||
|
|
||||||
char asm_string[] = "svc #0";
|
char asm_string[] = "svc #0";
|
||||||
@@ -2911,13 +2911,14 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu
|
|||||||
for (unsigned i = 0; i < arg_count; i++) {
|
for (unsigned i = 0; i < arg_count; i++) {
|
||||||
constraints = gb_string_appendc(constraints, ",{");
|
constraints = gb_string_appendc(constraints, ",{");
|
||||||
static char const *regs[] = {
|
static char const *regs[] = {
|
||||||
"r8",
|
"r7",
|
||||||
"r0",
|
"r0",
|
||||||
"r1",
|
"r1",
|
||||||
"r2",
|
"r2",
|
||||||
"r3",
|
"r3",
|
||||||
"r4",
|
"r4",
|
||||||
"r5",
|
"r5",
|
||||||
|
"r6",
|
||||||
};
|
};
|
||||||
constraints = gb_string_appendc(constraints, regs[i]);
|
constraints = gb_string_appendc(constraints, regs[i]);
|
||||||
constraints = gb_string_appendc(constraints, "}");
|
constraints = gb_string_appendc(constraints, "}");
|
||||||
|
|||||||
@@ -2223,8 +2223,8 @@ gb_internal LLVMAtomicOrdering llvm_atomic_ordering_from_odin(ExactValue const &
|
|||||||
GB_ASSERT(value.kind == ExactValue_Integer);
|
GB_ASSERT(value.kind == ExactValue_Integer);
|
||||||
i64 v = exact_value_to_i64(value);
|
i64 v = exact_value_to_i64(value);
|
||||||
switch (v) {
|
switch (v) {
|
||||||
case OdinAtomicMemoryOrder_relaxed: return LLVMAtomicOrderingUnordered;
|
case OdinAtomicMemoryOrder_relaxed: return LLVMAtomicOrderingMonotonic;
|
||||||
case OdinAtomicMemoryOrder_consume: return LLVMAtomicOrderingMonotonic;
|
case OdinAtomicMemoryOrder_consume: return LLVMAtomicOrderingAcquire;
|
||||||
case OdinAtomicMemoryOrder_acquire: return LLVMAtomicOrderingAcquire;
|
case OdinAtomicMemoryOrder_acquire: return LLVMAtomicOrderingAcquire;
|
||||||
case OdinAtomicMemoryOrder_release: return LLVMAtomicOrderingRelease;
|
case OdinAtomicMemoryOrder_release: return LLVMAtomicOrderingRelease;
|
||||||
case OdinAtomicMemoryOrder_acq_rel: return LLVMAtomicOrderingAcquireRelease;
|
case OdinAtomicMemoryOrder_acq_rel: return LLVMAtomicOrderingAcquireRelease;
|
||||||
|
|||||||
+8
-2
@@ -3499,8 +3499,14 @@ gb_internal Array<Ast *> parse_ident_list(AstFile *f, bool allow_poly_names) {
|
|||||||
gb_internal Ast *parse_type(AstFile *f) {
|
gb_internal Ast *parse_type(AstFile *f) {
|
||||||
Ast *type = parse_type_or_ident(f);
|
Ast *type = parse_type_or_ident(f);
|
||||||
if (type == nullptr) {
|
if (type == nullptr) {
|
||||||
Token token = advance_token(f);
|
Token prev_token = f->curr_token;
|
||||||
syntax_error(token, "Expected a type");
|
Token token = {};
|
||||||
|
if (f->curr_token.kind == Token_OpenBrace) {
|
||||||
|
token = f->curr_token;
|
||||||
|
} else {
|
||||||
|
token = advance_token(f);
|
||||||
|
}
|
||||||
|
syntax_error(token, "Expected a type, got '%.*s'", LIT(prev_token.string));
|
||||||
return ast_bad_expr(f, token, f->curr_token);
|
return ast_bad_expr(f, token, f->curr_token);
|
||||||
} else if (type->kind == Ast_ParenExpr &&
|
} else if (type->kind == Ast_ParenExpr &&
|
||||||
unparen_expr(type) == nullptr) {
|
unparen_expr(type) == nullptr) {
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,18 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: 0.1.0\n"
|
||||||
|
"PO-Revision-Date: 2024-04-13 11:13+0200\n"
|
||||||
|
"Last-Translator: Someone <someone@somewhere.com>\n"
|
||||||
|
"Language-Team: English\n"
|
||||||
|
"Language: en_IE\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
|
msgctxt "Context"
|
||||||
|
msgid "Message1"
|
||||||
|
msgstr "This is message 1 with Context"
|
||||||
|
|
||||||
|
msgid "Message1"
|
||||||
|
msgstr "This is message 1 without Context"
|
||||||
Binary file not shown.
@@ -0,0 +1,17 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: 0.1.0\n"
|
||||||
|
"PO-Revision-Date: 2024-04-13 11:13+0200\n"
|
||||||
|
"Last-Translator: Someone <someone@somewhere.com>\n"
|
||||||
|
"Language-Team: English\n"
|
||||||
|
"Language: it_IT\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=3; plural=(n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2);\n"
|
||||||
|
|
||||||
|
msgid "Message1"
|
||||||
|
msgid_plural "Message1/plural"
|
||||||
|
msgstr[0] "This is message 1"
|
||||||
|
msgstr[1] "This is message 1 - plural A"
|
||||||
|
msgstr[2] "This is message 1 - plural B"
|
||||||
+39
-44
@@ -3,30 +3,15 @@ set COMMON=-no-bounds-check -vet -strict-style
|
|||||||
set COLLECTION=-collection:tests=..
|
set COLLECTION=-collection:tests=..
|
||||||
set PATH_TO_ODIN==..\..\odin
|
set PATH_TO_ODIN==..\..\odin
|
||||||
python3 download_assets.py
|
python3 download_assets.py
|
||||||
echo ---
|
|
||||||
echo Running core:image tests
|
|
||||||
echo ---
|
|
||||||
%PATH_TO_ODIN% run image %COMMON% -out:test_core_image.exe || exit /b
|
|
||||||
|
|
||||||
echo ---
|
echo ---
|
||||||
echo Running core:compress tests
|
echo Running core:compress tests
|
||||||
echo ---
|
echo ---
|
||||||
%PATH_TO_ODIN% run compress %COMMON% -out:test_core_compress.exe || exit /b
|
%PATH_TO_ODIN% run compress %COMMON% -out:test_core_compress.exe || exit /b
|
||||||
|
|
||||||
echo ---
|
echo ---
|
||||||
echo Running core:strings tests
|
echo Running core:container tests
|
||||||
echo ---
|
echo ---
|
||||||
%PATH_TO_ODIN% run strings %COMMON% -out:test_core_strings.exe || exit /b
|
%PATH_TO_ODIN% run container %COMMON% %COLLECTION% -out:test_core_container.exe || exit /b
|
||||||
|
|
||||||
echo ---
|
|
||||||
echo Running core:hash tests
|
|
||||||
echo ---
|
|
||||||
%PATH_TO_ODIN% run hash %COMMON% -o:size -out:test_core_hash.exe || exit /b
|
|
||||||
|
|
||||||
echo ---
|
|
||||||
echo Running core:odin tests
|
|
||||||
echo ---
|
|
||||||
%PATH_TO_ODIN% run odin %COMMON% -o:size -out:test_core_odin.exe || exit /b
|
|
||||||
|
|
||||||
echo ---
|
echo ---
|
||||||
echo Running core:crypto tests
|
echo Running core:crypto tests
|
||||||
@@ -45,9 +30,19 @@ rem %PATH_TO_ODIN% run encoding/hxa %COMMON% %COLLECTION% -out:test_hxa.exe |
|
|||||||
%PATH_TO_ODIN% run encoding/base64 %COMMON% -out:test_base64.exe || exit /b
|
%PATH_TO_ODIN% run encoding/base64 %COMMON% -out:test_base64.exe || exit /b
|
||||||
|
|
||||||
echo ---
|
echo ---
|
||||||
echo Running core:math/noise tests
|
echo Running core:fmt tests
|
||||||
echo ---
|
echo ---
|
||||||
%PATH_TO_ODIN% run math/noise %COMMON% -out:test_noise.exe || exit /b
|
%PATH_TO_ODIN% run fmt %COMMON% %COLLECTION% -out:test_core_fmt.exe || exit /b
|
||||||
|
|
||||||
|
echo ---
|
||||||
|
echo Running core:hash tests
|
||||||
|
echo ---
|
||||||
|
%PATH_TO_ODIN% run hash %COMMON% -o:size -out:test_core_hash.exe || exit /b
|
||||||
|
|
||||||
|
echo ---
|
||||||
|
echo Running core:image tests
|
||||||
|
echo ---
|
||||||
|
%PATH_TO_ODIN% run image %COMMON% -out:test_core_image.exe || exit /b
|
||||||
|
|
||||||
echo ---
|
echo ---
|
||||||
echo Running core:math tests
|
echo Running core:math tests
|
||||||
@@ -59,6 +54,21 @@ echo Running core:math/linalg/glsl tests
|
|||||||
echo ---
|
echo ---
|
||||||
%PATH_TO_ODIN% run math/linalg/glsl %COMMON% %COLLECTION% -out:test_linalg_glsl.exe || exit /b
|
%PATH_TO_ODIN% run math/linalg/glsl %COMMON% %COLLECTION% -out:test_linalg_glsl.exe || exit /b
|
||||||
|
|
||||||
|
echo ---
|
||||||
|
echo Running core:math/noise tests
|
||||||
|
echo ---
|
||||||
|
%PATH_TO_ODIN% run math/noise %COMMON% -out:test_noise.exe || exit /b
|
||||||
|
|
||||||
|
echo ---
|
||||||
|
echo Running core:net
|
||||||
|
echo ---
|
||||||
|
%PATH_TO_ODIN% run net %COMMON% -out:test_core_net.exe || exit /b
|
||||||
|
|
||||||
|
echo ---
|
||||||
|
echo Running core:odin tests
|
||||||
|
echo ---
|
||||||
|
%PATH_TO_ODIN% run odin %COMMON% -o:size -out:test_core_odin.exe || exit /b
|
||||||
|
|
||||||
echo ---
|
echo ---
|
||||||
echo Running core:path/filepath tests
|
echo Running core:path/filepath tests
|
||||||
echo ---
|
echo ---
|
||||||
@@ -69,47 +79,32 @@ echo Running core:reflect tests
|
|||||||
echo ---
|
echo ---
|
||||||
%PATH_TO_ODIN% run reflect %COMMON% %COLLECTION% -out:test_core_reflect.exe || exit /b
|
%PATH_TO_ODIN% run reflect %COMMON% %COLLECTION% -out:test_core_reflect.exe || exit /b
|
||||||
|
|
||||||
|
echo ---
|
||||||
|
echo Running core:runtime tests
|
||||||
|
echo ---
|
||||||
|
%PATH_TO_ODIN% run runtime %COMMON% %COLLECTION% -out:test_core_runtime.exe || exit /b
|
||||||
|
|
||||||
echo ---
|
echo ---
|
||||||
echo Running core:slice tests
|
echo Running core:slice tests
|
||||||
echo ---
|
echo ---
|
||||||
%PATH_TO_ODIN% run slice %COMMON% -out:test_core_slice.exe || exit /b
|
%PATH_TO_ODIN% run slice %COMMON% -out:test_core_slice.exe || exit /b
|
||||||
|
|
||||||
|
echo ---
|
||||||
|
echo Running core:strings tests
|
||||||
|
echo ---
|
||||||
|
%PATH_TO_ODIN% run strings %COMMON% -out:test_core_strings.exe || exit /b
|
||||||
|
|
||||||
echo ---
|
echo ---
|
||||||
echo Running core:text/i18n tests
|
echo Running core:text/i18n tests
|
||||||
echo ---
|
echo ---
|
||||||
%PATH_TO_ODIN% run text\i18n %COMMON% -out:test_core_i18n.exe || exit /b
|
%PATH_TO_ODIN% run text\i18n %COMMON% -out:test_core_i18n.exe || exit /b
|
||||||
|
|
||||||
echo ---
|
|
||||||
echo Running core:net
|
|
||||||
echo ---
|
|
||||||
%PATH_TO_ODIN% run net %COMMON% -out:test_core_net.exe || exit /b
|
|
||||||
|
|
||||||
echo ---
|
|
||||||
echo Running core:slice tests
|
|
||||||
echo ---
|
|
||||||
%PATH_TO_ODIN% run slice %COMMON% -out:test_core_slice.exe || exit /b
|
|
||||||
|
|
||||||
echo ---
|
|
||||||
echo Running core:container tests
|
|
||||||
echo ---
|
|
||||||
%PATH_TO_ODIN% run container %COMMON% %COLLECTION% -out:test_core_container.exe || exit /b
|
|
||||||
|
|
||||||
echo ---
|
echo ---
|
||||||
echo Running core:thread tests
|
echo Running core:thread tests
|
||||||
echo ---
|
echo ---
|
||||||
%PATH_TO_ODIN% run thread %COMMON% %COLLECTION% -out:test_core_thread.exe || exit /b
|
%PATH_TO_ODIN% run thread %COMMON% %COLLECTION% -out:test_core_thread.exe || exit /b
|
||||||
|
|
||||||
echo ---
|
|
||||||
echo Running core:runtime tests
|
|
||||||
echo ---
|
|
||||||
%PATH_TO_ODIN% run runtime %COMMON% %COLLECTION% -out:test_core_runtime.exe || exit /b
|
|
||||||
|
|
||||||
echo ---
|
echo ---
|
||||||
echo Running core:time tests
|
echo Running core:time tests
|
||||||
echo ---
|
echo ---
|
||||||
%PATH_TO_ODIN% run time %COMMON% %COLLECTION% -out:test_core_time.exe || exit /b
|
%PATH_TO_ODIN% run time %COMMON% %COLLECTION% -out:test_core_time.exe || exit /b
|
||||||
|
|
||||||
echo ---
|
|
||||||
echo Running core:fmt tests
|
|
||||||
echo ---
|
|
||||||
%PATH_TO_ODIN% run fmt %COMMON% %COLLECTION% -out:test_core_fmt.exe || exit /b
|
|
||||||
@@ -4,12 +4,12 @@ import "core:container/avl"
|
|||||||
import "core:math/rand"
|
import "core:math/rand"
|
||||||
import "core:slice"
|
import "core:slice"
|
||||||
import "core:testing"
|
import "core:testing"
|
||||||
|
import "core:fmt"
|
||||||
import tc "tests:common"
|
import tc "tests:common"
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_avl :: proc(t: ^testing.T) {
|
test_avl :: proc(t: ^testing.T) {
|
||||||
tc.log(t, "Testing avl")
|
tc.log(t, fmt.tprintf("Testing avl, using random seed %v, add -define:RANDOM_SEED=%v to reuse it.", random_seed, random_seed))
|
||||||
|
|
||||||
// Initialization.
|
// Initialization.
|
||||||
tree: avl.Tree(int)
|
tree: avl.Tree(int)
|
||||||
@@ -21,11 +21,14 @@ test_avl :: proc(t: ^testing.T) {
|
|||||||
iter := avl.iterator(&tree, avl.Direction.Forward)
|
iter := avl.iterator(&tree, avl.Direction.Forward)
|
||||||
tc.expect(t, avl.iterator_get(&iter) == nil, "empty/iterator: first node should be nil")
|
tc.expect(t, avl.iterator_get(&iter) == nil, "empty/iterator: first node should be nil")
|
||||||
|
|
||||||
|
r: rand.Rand
|
||||||
|
rand.init(&r, random_seed)
|
||||||
|
|
||||||
// Test insertion.
|
// Test insertion.
|
||||||
NR_INSERTS :: 32 + 1 // Ensure at least 1 collision.
|
NR_INSERTS :: 32 + 1 // Ensure at least 1 collision.
|
||||||
inserted_map := make(map[int]^avl.Node(int))
|
inserted_map := make(map[int]^avl.Node(int))
|
||||||
for i := 0; i < NR_INSERTS; i += 1 {
|
for i := 0; i < NR_INSERTS; i += 1 {
|
||||||
v := int(rand.uint32() & 0x1f)
|
v := int(rand.uint32(&r) & 0x1f)
|
||||||
existing_node, in_map := inserted_map[v]
|
existing_node, in_map := inserted_map[v]
|
||||||
|
|
||||||
n, ok, _ := avl.find_or_insert(&tree, v)
|
n, ok, _ := avl.find_or_insert(&tree, v)
|
||||||
@@ -38,7 +41,7 @@ test_avl :: proc(t: ^testing.T) {
|
|||||||
}
|
}
|
||||||
nrEntries := len(inserted_map)
|
nrEntries := len(inserted_map)
|
||||||
tc.expect(t, avl.len(&tree) == nrEntries, "insert: len after")
|
tc.expect(t, avl.len(&tree) == nrEntries, "insert: len after")
|
||||||
tree_validate(t, &tree)
|
validate_avl(t, &tree)
|
||||||
|
|
||||||
// Ensure that all entries can be found.
|
// Ensure that all entries can be found.
|
||||||
for k, v in inserted_map {
|
for k, v in inserted_map {
|
||||||
@@ -74,7 +77,7 @@ test_avl :: proc(t: ^testing.T) {
|
|||||||
tc.expect(t, visited == nrEntries, "iterator/backward: visited")
|
tc.expect(t, visited == nrEntries, "iterator/backward: visited")
|
||||||
|
|
||||||
// Test removal.
|
// Test removal.
|
||||||
rand.shuffle(inserted_values[:])
|
rand.shuffle(inserted_values[:], &r)
|
||||||
for v, i in inserted_values {
|
for v, i in inserted_values {
|
||||||
node := avl.find(&tree, v)
|
node := avl.find(&tree, v)
|
||||||
tc.expect(t, node != nil, "remove: find (pre)")
|
tc.expect(t, node != nil, "remove: find (pre)")
|
||||||
@@ -82,7 +85,7 @@ test_avl :: proc(t: ^testing.T) {
|
|||||||
ok := avl.remove(&tree, v)
|
ok := avl.remove(&tree, v)
|
||||||
tc.expect(t, ok, "remove: succeeds")
|
tc.expect(t, ok, "remove: succeeds")
|
||||||
tc.expect(t, nrEntries - (i + 1) == avl.len(&tree), "remove: len (post)")
|
tc.expect(t, nrEntries - (i + 1) == avl.len(&tree), "remove: len (post)")
|
||||||
tree_validate(t, &tree)
|
validate_avl(t, &tree)
|
||||||
|
|
||||||
tc.expect(t, nil == avl.find(&tree, v), "remove: find (post")
|
tc.expect(t, nil == avl.find(&tree, v), "remove: find (post")
|
||||||
}
|
}
|
||||||
@@ -114,7 +117,7 @@ test_avl :: proc(t: ^testing.T) {
|
|||||||
tc.expect(t, ok == (avl.len(&tree) > 0), "iterator/remove: next should return false")
|
tc.expect(t, ok == (avl.len(&tree) > 0), "iterator/remove: next should return false")
|
||||||
tc.expect(t, node == avl.first(&tree), "iterator/remove: next should return first")
|
tc.expect(t, node == avl.first(&tree), "iterator/remove: next should return first")
|
||||||
|
|
||||||
tree_validate(t, &tree)
|
validate_avl(t, &tree)
|
||||||
}
|
}
|
||||||
tc.expect(t, avl.len(&tree) == nrEntries - 1, "iterator/remove: len should drop by 1")
|
tc.expect(t, avl.len(&tree) == nrEntries - 1, "iterator/remove: len should drop by 1")
|
||||||
|
|
||||||
@@ -123,7 +126,7 @@ test_avl :: proc(t: ^testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@(private)
|
@(private)
|
||||||
tree_validate :: proc(t: ^testing.T, tree: ^avl.Tree($Value)) {
|
validate_avl :: proc(t: ^testing.T, tree: ^avl.Tree($Value)) {
|
||||||
tree_check_invariants(t, tree, tree._root, nil)
|
tree_check_invariants(t, tree, tree._root, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ main :: proc() {
|
|||||||
t := testing.T{}
|
t := testing.T{}
|
||||||
|
|
||||||
test_avl(&t)
|
test_avl(&t)
|
||||||
|
test_rbtree(&t)
|
||||||
test_small_array(&t)
|
test_small_array(&t)
|
||||||
|
|
||||||
tc.report(&t)
|
tc.report(&t)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,244 @@
|
|||||||
|
package test_core_container
|
||||||
|
|
||||||
|
import rb "core:container/rbtree"
|
||||||
|
import "core:math/rand"
|
||||||
|
import "core:testing"
|
||||||
|
import "core:fmt"
|
||||||
|
import "base:intrinsics"
|
||||||
|
import "core:mem"
|
||||||
|
import "core:slice"
|
||||||
|
import tc "tests:common"
|
||||||
|
|
||||||
|
RANDOM_SEED :: #config(RANDOM_SEED, 0)
|
||||||
|
random_seed := u64(intrinsics.read_cycle_counter()) when RANDOM_SEED == 0 else u64(RANDOM_SEED)
|
||||||
|
|
||||||
|
test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) {
|
||||||
|
track: mem.Tracking_Allocator
|
||||||
|
mem.tracking_allocator_init(&track, context.allocator)
|
||||||
|
defer mem.tracking_allocator_destroy(&track)
|
||||||
|
context.allocator = mem.tracking_allocator(&track)
|
||||||
|
|
||||||
|
r: rand.Rand
|
||||||
|
rand.init(&r, random_seed)
|
||||||
|
|
||||||
|
tc.log(t, fmt.tprintf("Testing Red-Black Tree($Key=%v,$Value=%v), using random seed %v, add -define:RANDOM_SEED=%v to reuse it.", type_info_of(Key), type_info_of(Value), random_seed, random_seed))
|
||||||
|
tree: rb.Tree(Key, Value)
|
||||||
|
rb.init(&tree)
|
||||||
|
|
||||||
|
tc.expect(t, rb.len(&tree) == 0, "empty: len should be 0")
|
||||||
|
tc.expect(t, rb.first(&tree) == nil, "empty: first should be nil")
|
||||||
|
tc.expect(t, rb.last(&tree) == nil, "empty: last should be nil")
|
||||||
|
iter := rb.iterator(&tree, .Forward)
|
||||||
|
tc.expect(t, rb.iterator_get(&iter) == nil, "empty/iterator: first node should be nil")
|
||||||
|
|
||||||
|
// Test insertion.
|
||||||
|
NR_INSERTS :: 32 + 1 // Ensure at least 1 collision.
|
||||||
|
inserted_map := make(map[Key]^rb.Node(Key, Value))
|
||||||
|
|
||||||
|
min_key := max(Key)
|
||||||
|
max_key := min(Key)
|
||||||
|
|
||||||
|
for i := 0; i < NR_INSERTS; i += 1 {
|
||||||
|
k := Key(rand.uint32(&r)) & 0x1f
|
||||||
|
min_key = min(min_key, k); max_key = max(max_key, k)
|
||||||
|
v := Value(rand.uint32(&r))
|
||||||
|
|
||||||
|
existing_node, in_map := inserted_map[k]
|
||||||
|
n, inserted, _ := rb.find_or_insert(&tree, k, v)
|
||||||
|
tc.expect(t, in_map != inserted, "insert: inserted should match inverse of map lookup")
|
||||||
|
if inserted {
|
||||||
|
inserted_map[k] = n
|
||||||
|
} else {
|
||||||
|
tc.expect(t, existing_node == n, "insert: expecting existing node")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entry_count := len(inserted_map)
|
||||||
|
tc.expect(t, rb.len(&tree) == entry_count, "insert: len after")
|
||||||
|
validate_rbtree(t, &tree)
|
||||||
|
|
||||||
|
first := rb.first(&tree)
|
||||||
|
last := rb.last(&tree)
|
||||||
|
tc.expect(t, first != nil && first.key == min_key, fmt.tprintf("insert: first should be present with key %v", min_key))
|
||||||
|
tc.expect(t, last != nil && last.key == max_key, fmt.tprintf("insert: last should be present with key %v", max_key))
|
||||||
|
|
||||||
|
// Ensure that all entries can be found.
|
||||||
|
for k, v in inserted_map {
|
||||||
|
tc.expect(t, v == rb.find(&tree, k), "Find(): Node")
|
||||||
|
tc.expect(t, k == v.key, "Find(): Node key")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the forward/backward iterators.
|
||||||
|
inserted_keys: [dynamic]Key
|
||||||
|
for k in inserted_map {
|
||||||
|
append(&inserted_keys, k)
|
||||||
|
}
|
||||||
|
slice.sort(inserted_keys[:])
|
||||||
|
|
||||||
|
iter = rb.iterator(&tree, rb.Direction.Forward)
|
||||||
|
visited: int
|
||||||
|
for node in rb.iterator_next(&iter) {
|
||||||
|
k, idx := node.key, visited
|
||||||
|
tc.expect(t, inserted_keys[idx] == k, "iterator/forward: key")
|
||||||
|
tc.expect(t, node == rb.iterator_get(&iter), "iterator/forward: get")
|
||||||
|
visited += 1
|
||||||
|
}
|
||||||
|
tc.expect(t, visited == entry_count, "iterator/forward: visited")
|
||||||
|
|
||||||
|
slice.reverse(inserted_keys[:])
|
||||||
|
iter = rb.iterator(&tree, rb.Direction.Backward)
|
||||||
|
visited = 0
|
||||||
|
for node in rb.iterator_next(&iter) {
|
||||||
|
k, idx := node.key, visited
|
||||||
|
tc.expect(t, inserted_keys[idx] == k, "iterator/backward: key")
|
||||||
|
visited += 1
|
||||||
|
}
|
||||||
|
tc.expect(t, visited == entry_count, "iterator/backward: visited")
|
||||||
|
|
||||||
|
// Test removal (and on_remove callback)
|
||||||
|
rand.shuffle(inserted_keys[:], &r)
|
||||||
|
callback_count := entry_count
|
||||||
|
tree.user_data = &callback_count
|
||||||
|
tree.on_remove = proc(key: Key, value: Value, user_data: rawptr) {
|
||||||
|
(^int)(user_data)^ -= 1
|
||||||
|
}
|
||||||
|
for k, i in inserted_keys {
|
||||||
|
node := rb.find(&tree, k)
|
||||||
|
tc.expect(t, node != nil, "remove: find (pre)")
|
||||||
|
|
||||||
|
ok := rb.remove(&tree, k)
|
||||||
|
tc.expect(t, ok, "remove: succeeds")
|
||||||
|
tc.expect(t, entry_count - (i + 1) == rb.len(&tree), "remove: len (post)")
|
||||||
|
validate_rbtree(t, &tree)
|
||||||
|
|
||||||
|
tc.expect(t, nil == rb.find(&tree, k), "remove: find (post")
|
||||||
|
}
|
||||||
|
tc.expect(t, rb.len(&tree) == 0, "remove: len should be 0")
|
||||||
|
tc.expect(t, callback_count == 0, fmt.tprintf("remove: on_remove should've been called %v times, it was %v", entry_count, callback_count))
|
||||||
|
tc.expect(t, rb.first(&tree) == nil, "remove: first should be nil")
|
||||||
|
tc.expect(t, rb.last(&tree) == nil, "remove: last should be nil")
|
||||||
|
|
||||||
|
// Refill the tree.
|
||||||
|
for k in inserted_keys {
|
||||||
|
rb.find_or_insert(&tree, k, 42)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that removing the node doesn't break the iterator.
|
||||||
|
callback_count = entry_count
|
||||||
|
iter = rb.iterator(&tree, rb.Direction.Forward)
|
||||||
|
if node := rb.iterator_get(&iter); node != nil {
|
||||||
|
k := node.key
|
||||||
|
|
||||||
|
ok := rb.iterator_remove(&iter)
|
||||||
|
tc.expect(t, ok, "iterator/remove: success")
|
||||||
|
|
||||||
|
ok = rb.iterator_remove(&iter)
|
||||||
|
tc.expect(t, !ok, "iterator/remove: redundant removes should fail")
|
||||||
|
|
||||||
|
tc.expect(t, rb.find(&tree, k) == nil, "iterator/remove: node should be gone")
|
||||||
|
tc.expect(t, rb.iterator_get(&iter) == nil, "iterator/remove: get should return nil")
|
||||||
|
|
||||||
|
// Ensure that iterator_next still works.
|
||||||
|
node, ok = rb.iterator_next(&iter)
|
||||||
|
tc.expect(t, ok == (rb.len(&tree) > 0), "iterator/remove: next should return false")
|
||||||
|
tc.expect(t, node == rb.first(&tree), "iterator/remove: next should return first")
|
||||||
|
|
||||||
|
validate_rbtree(t, &tree)
|
||||||
|
}
|
||||||
|
tc.expect(t, rb.len(&tree) == entry_count - 1, "iterator/remove: len should drop by 1")
|
||||||
|
|
||||||
|
rb.destroy(&tree)
|
||||||
|
tc.expect(t, rb.len(&tree) == 0, "destroy: len should be 0")
|
||||||
|
tc.expect(t, callback_count == 0, fmt.tprintf("remove: on_remove should've been called %v times, it was %v", entry_count, callback_count))
|
||||||
|
|
||||||
|
// print_tree_node(tree._root)
|
||||||
|
delete(inserted_map)
|
||||||
|
delete(inserted_keys)
|
||||||
|
tc.expect(t, len(track.allocation_map) == 0, fmt.tprintf("Expected 0 leaks, have %v", len(track.allocation_map)))
|
||||||
|
tc.expect(t, len(track.bad_free_array) == 0, fmt.tprintf("Expected 0 bad frees, have %v", len(track.bad_free_array)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_rbtree :: proc(t: ^testing.T) {
|
||||||
|
test_rbtree_integer(t, u16, u16)
|
||||||
|
}
|
||||||
|
|
||||||
|
print_tree_node :: proc(n: ^$N/rb.Node($Key, $Value), indent := 0) {
|
||||||
|
if n == nil {
|
||||||
|
fmt.println("<empty tree>")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n.right != nil {
|
||||||
|
print_tree_node(n.right, indent + 1)
|
||||||
|
}
|
||||||
|
for _ in 0..<indent {
|
||||||
|
fmt.printf("\t")
|
||||||
|
}
|
||||||
|
if n.color == .Black {
|
||||||
|
fmt.printfln("%v", n.key)
|
||||||
|
} else {
|
||||||
|
fmt.printfln("<%v>", n.key)
|
||||||
|
}
|
||||||
|
if n.left != nil {
|
||||||
|
print_tree_node(n.left, indent + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_rbtree :: proc(t: ^testing.T, tree: ^$T/rb.Tree($Key, $Value)) {
|
||||||
|
verify_rbtree_propery_1(t, tree._root)
|
||||||
|
verify_rbtree_propery_2(t, tree._root)
|
||||||
|
/* Property 3 is implicit */
|
||||||
|
verify_rbtree_propery_4(t, tree._root)
|
||||||
|
verify_rbtree_propery_5(t, tree._root)
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_rbtree_propery_1 :: proc(t: ^testing.T, n: ^$N/rb.Node($Key, $Value)) {
|
||||||
|
tc.expect(t, rb.node_color(n) == .Black || rb.node_color(n) == .Red, "Property #1: Each node is either red or black.")
|
||||||
|
if n == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
verify_rbtree_propery_1(t, n._left)
|
||||||
|
verify_rbtree_propery_1(t, n._right)
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_rbtree_propery_2 :: proc(t: ^testing.T, root: ^$N/rb.Node($Key, $Value)) {
|
||||||
|
tc.expect(t, rb.node_color(root) == .Black, "Property #2: Root node should be black.")
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_rbtree_propery_4 :: proc(t: ^testing.T, n: ^$N/rb.Node($Key, $Value)) {
|
||||||
|
if rb.node_color(n) == .Red {
|
||||||
|
// A red node's left, right and parent should be black
|
||||||
|
all_black := rb.node_color(n._left) == .Black && rb.node_color(n._right) == .Black && rb.node_color(n._parent) == .Black
|
||||||
|
tc.expect(t, all_black, "Property #3: Red node's children + parent must be black.")
|
||||||
|
}
|
||||||
|
if n == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
verify_rbtree_propery_4(t, n._left)
|
||||||
|
verify_rbtree_propery_4(t, n._right)
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_rbtree_propery_5 :: proc(t: ^testing.T, root: ^$N/rb.Node($Key, $Value)) {
|
||||||
|
black_count_path := -1
|
||||||
|
verify_rbtree_propery_5_helper(t, root, 0, &black_count_path)
|
||||||
|
}
|
||||||
|
verify_rbtree_propery_5_helper :: proc(t: ^testing.T, n: ^$N/rb.Node($Key, $Value), black_count: int, path_black_count: ^int) {
|
||||||
|
black_count := black_count
|
||||||
|
|
||||||
|
if rb.node_color(n) == .Black {
|
||||||
|
black_count += 1
|
||||||
|
}
|
||||||
|
if n == nil {
|
||||||
|
if path_black_count^ == -1 {
|
||||||
|
path_black_count^ = black_count
|
||||||
|
} else {
|
||||||
|
tc.expect(t, black_count == path_black_count^, "Property #5: Paths from a node to its leaves contain same black count.")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
verify_rbtree_propery_5_helper(t, n._left, black_count, path_black_count)
|
||||||
|
verify_rbtree_propery_5_helper(t, n._right, black_count, path_black_count)
|
||||||
|
}
|
||||||
|
// Properties 4 and 5 together guarantee that no path in the tree is more than about twice as long as any other path,
|
||||||
|
// which guarantees that it has O(log n) height.
|
||||||
@@ -3,9 +3,7 @@ package test_core_odin_parser
|
|||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import "core:odin/ast"
|
import "core:odin/ast"
|
||||||
import "core:odin/parser"
|
import "core:odin/parser"
|
||||||
import "core:odin/printer"
|
|
||||||
import "core:os"
|
import "core:os"
|
||||||
import "core:strings"
|
|
||||||
import "core:testing"
|
import "core:testing"
|
||||||
|
|
||||||
|
|
||||||
@@ -81,14 +79,4 @@ Foo :: bit_field uint {
|
|||||||
p := parser.default_parser()
|
p := parser.default_parser()
|
||||||
ok := parser.parse_file(&p, &file)
|
ok := parser.parse_file(&p, &file)
|
||||||
expect(t, ok == true, "bad parse")
|
expect(t, ok == true, "bad parse")
|
||||||
|
|
||||||
cfg := printer.default_style
|
|
||||||
cfg.newline_style = .LF
|
|
||||||
print := printer.make_printer(cfg)
|
|
||||||
out := printer.print(&print, &file)
|
|
||||||
|
|
||||||
tsrc := strings.trim_space(file.src)
|
|
||||||
tout := strings.trim_space(out)
|
|
||||||
|
|
||||||
expect(t, tsrc == tout, fmt.tprintf("\n%s\n!=\n%s", tsrc, tout))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,44 +38,98 @@ Test :: struct {
|
|||||||
Test_Suite :: struct {
|
Test_Suite :: struct {
|
||||||
file: string,
|
file: string,
|
||||||
loader: proc(string, i18n.Parse_Options, proc(int) -> int, mem.Allocator) -> (^i18n.Translation, i18n.Error),
|
loader: proc(string, i18n.Parse_Options, proc(int) -> int, mem.Allocator) -> (^i18n.Translation, i18n.Error),
|
||||||
|
plural: proc(int) -> int,
|
||||||
err: i18n.Error,
|
err: i18n.Error,
|
||||||
options: i18n.Parse_Options,
|
options: i18n.Parse_Options,
|
||||||
tests: []Test,
|
tests: []Test,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Custom pluralizer for plur.mo
|
||||||
|
plur_mo_pluralizer :: proc(n: int) -> (slot: int) {
|
||||||
|
switch {
|
||||||
|
case n == 1: return 0
|
||||||
|
case n != 0 && n % 1_000_000 == 0: return 1
|
||||||
|
case: return 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TESTS := []Test_Suite{
|
TESTS := []Test_Suite{
|
||||||
|
{
|
||||||
|
file = "assets/I18N/plur.mo",
|
||||||
|
loader = i18n.parse_mo_file,
|
||||||
|
plural = plur_mo_pluralizer,
|
||||||
|
tests = {
|
||||||
|
// These are in the catalog.
|
||||||
|
{"", "Message1", "This is message 1", 1},
|
||||||
|
{"", "Message1", "This is message 1 - plural A", 1_000_000},
|
||||||
|
{"", "Message1", "This is message 1 - plural B", 42},
|
||||||
|
{"", "Message1/plural", "This is message 1", 1},
|
||||||
|
{"", "Message1/plural", "This is message 1 - plural A", 1_000_000},
|
||||||
|
{"", "Message1/plural", "This is message 1 - plural B", 42},
|
||||||
|
|
||||||
|
// This isn't in the catalog, so should ruturn the key.
|
||||||
|
{"", "Come visit us on Discord!", "Come visit us on Discord!", 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
file = "assets/I18N/mixed_context.mo",
|
||||||
|
loader = i18n.parse_mo_file,
|
||||||
|
plural = nil,
|
||||||
|
tests = {
|
||||||
|
// These are in the catalog.
|
||||||
|
{"", "Message1", "This is message 1 without Context", 1},
|
||||||
|
{"Context", "Message1", "This is message 1 with Context", 1},
|
||||||
|
|
||||||
|
// This isn't in the catalog, so should ruturn the key.
|
||||||
|
{"", "Come visit us on Discord!", "Come visit us on Discord!", 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
file = "assets/I18N/mixed_context.mo",
|
||||||
|
loader = i18n.parse_mo_file,
|
||||||
|
plural = nil,
|
||||||
|
// Message1 exists twice, once within Context, which has been merged into ""
|
||||||
|
err = .Duplicate_Key,
|
||||||
|
options = {merge_sections = true},
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
file = "assets/I18N/nl_NL.mo",
|
file = "assets/I18N/nl_NL.mo",
|
||||||
loader = i18n.parse_mo_file,
|
loader = i18n.parse_mo_file,
|
||||||
|
plural = nil, // Default pluralizer
|
||||||
tests = {
|
tests = {
|
||||||
// These are in the catalog.
|
// These are in the catalog.
|
||||||
{ "", "There are 69,105 leaves here.", "Er zijn hier 69.105 bladeren.", 1 },
|
{"", "There are 69,105 leaves here.", "Er zijn hier 69.105 bladeren.", 1},
|
||||||
{ "", "Hellope, World!", "Hallo, Wereld!", 1 },
|
{"", "Hellope, World!", "Hallo, Wereld!", 1},
|
||||||
{ "", "There is %d leaf.\n", "Er is %d blad.\n", 1 },
|
{"", "There is %d leaf.\n", "Er is %d blad.\n", 1},
|
||||||
{ "", "There are %d leaves.\n", "Er is %d blad.\n", 1 },
|
{"", "There are %d leaves.\n", "Er is %d blad.\n", 1},
|
||||||
{ "", "There is %d leaf.\n", "Er zijn %d bladeren.\n", 42 },
|
{"", "There is %d leaf.\n", "Er zijn %d bladeren.\n", 42},
|
||||||
{ "", "There are %d leaves.\n", "Er zijn %d bladeren.\n", 42 },
|
{"", "There are %d leaves.\n", "Er zijn %d bladeren.\n", 42},
|
||||||
|
|
||||||
// This isn't in the catalog, so should ruturn the key.
|
// This isn't in the catalog, so should ruturn the key.
|
||||||
{ "", "Come visit us on Discord!", "Come visit us on Discord!", 1 },
|
{"", "Come visit us on Discord!", "Come visit us on Discord!", 1},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
// QT Linguist with default loader options.
|
// QT Linguist with default loader options.
|
||||||
{
|
{
|
||||||
file = "assets/I18N/nl_NL-qt-ts.ts",
|
file = "assets/I18N/nl_NL-qt-ts.ts",
|
||||||
loader = i18n.parse_qt_linguist_file,
|
loader = i18n.parse_qt_linguist_file,
|
||||||
|
plural = nil, // Default pluralizer
|
||||||
tests = {
|
tests = {
|
||||||
// These are in the catalog.
|
// These are in the catalog.
|
||||||
{ "Page", "Text for translation", "Tekst om te vertalen", 1},
|
{"Page", "Text for translation", "Tekst om te vertalen", 1},
|
||||||
{ "Page", "Also text to translate", "Ook tekst om te vertalen", 1},
|
{"Page", "Also text to translate", "Ook tekst om te vertalen", 1},
|
||||||
{ "installscript", "99 bottles of beer on the wall", "99 flessen bier op de muur", 1},
|
{"installscript", "99 bottles of beer on the wall", "99 flessen bier op de muur", 1},
|
||||||
{ "apple_count", "%d apple(s)", "%d appel", 1},
|
{"apple_count", "%d apple(s)", "%d appel", 1},
|
||||||
{ "apple_count", "%d apple(s)", "%d appels", 42},
|
{"apple_count", "%d apple(s)", "%d appels", 42},
|
||||||
|
|
||||||
// These aren't in the catalog, so should ruturn the key.
|
// These aren't in the catalog, so should ruturn the key.
|
||||||
{ "", "Come visit us on Discord!", "Come visit us on Discord!", 1 },
|
{"", "Come visit us on Discord!", "Come visit us on Discord!", 1},
|
||||||
{ "Fake_Section", "Come visit us on Discord!", "Come visit us on Discord!", 1 },
|
{"Fake_Section", "Come visit us on Discord!", "Come visit us on Discord!", 1},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -83,21 +137,22 @@ TESTS := []Test_Suite{
|
|||||||
{
|
{
|
||||||
file = "assets/I18N/nl_NL-qt-ts.ts",
|
file = "assets/I18N/nl_NL-qt-ts.ts",
|
||||||
loader = i18n.parse_qt_linguist_file,
|
loader = i18n.parse_qt_linguist_file,
|
||||||
|
plural = nil, // Default pluralizer
|
||||||
options = {merge_sections = true},
|
options = {merge_sections = true},
|
||||||
tests = {
|
tests = {
|
||||||
// All of them are now in section "", lookup with original section should return the key.
|
// All of them are now in section "", lookup with original section should return the key.
|
||||||
{ "", "Text for translation", "Tekst om te vertalen", 1},
|
{"", "Text for translation", "Tekst om te vertalen", 1},
|
||||||
{ "", "Also text to translate", "Ook tekst om te vertalen", 1},
|
{"", "Also text to translate", "Ook tekst om te vertalen", 1},
|
||||||
{ "", "99 bottles of beer on the wall", "99 flessen bier op de muur", 1},
|
{"", "99 bottles of beer on the wall", "99 flessen bier op de muur", 1},
|
||||||
{ "", "%d apple(s)", "%d appel", 1},
|
{"", "%d apple(s)", "%d appel", 1},
|
||||||
{ "", "%d apple(s)", "%d appels", 42},
|
{"", "%d apple(s)", "%d appels", 42},
|
||||||
|
|
||||||
// All of them are now in section "", lookup with original section should return the key.
|
// All of them are now in section "", lookup with original section should return the key.
|
||||||
{ "Page", "Text for translation", "Text for translation", 1},
|
{"Page", "Text for translation", "Text for translation", 1},
|
||||||
{ "Page", "Also text to translate", "Also text to translate", 1},
|
{"Page", "Also text to translate", "Also text to translate", 1},
|
||||||
{ "installscript", "99 bottles of beer on the wall", "99 bottles of beer on the wall", 1},
|
{"installscript", "99 bottles of beer on the wall", "99 bottles of beer on the wall", 1},
|
||||||
{ "apple_count", "%d apple(s)", "%d apple(s)", 1},
|
{"apple_count", "%d apple(s)", "%d apple(s)", 1},
|
||||||
{ "apple_count", "%d apple(s)", "%d apple(s)", 42},
|
{"apple_count", "%d apple(s)", "%d apple(s)", 42},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -105,6 +160,7 @@ TESTS := []Test_Suite{
|
|||||||
{
|
{
|
||||||
file = "assets/I18N/duplicate-key.ts",
|
file = "assets/I18N/duplicate-key.ts",
|
||||||
loader = i18n.parse_qt_linguist_file,
|
loader = i18n.parse_qt_linguist_file,
|
||||||
|
plural = nil, // Default pluralizer
|
||||||
options = {merge_sections = true},
|
options = {merge_sections = true},
|
||||||
err = .Duplicate_Key,
|
err = .Duplicate_Key,
|
||||||
},
|
},
|
||||||
@@ -113,6 +169,7 @@ TESTS := []Test_Suite{
|
|||||||
{
|
{
|
||||||
file = "assets/I18N/duplicate-key.ts",
|
file = "assets/I18N/duplicate-key.ts",
|
||||||
loader = i18n.parse_qt_linguist_file,
|
loader = i18n.parse_qt_linguist_file,
|
||||||
|
plural = nil, // Default pluralizer
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +179,7 @@ tests :: proc(t: ^testing.T) {
|
|||||||
err: i18n.Error
|
err: i18n.Error
|
||||||
|
|
||||||
for suite in TESTS {
|
for suite in TESTS {
|
||||||
cat, err = suite.loader(suite.file, suite.options, nil, context.allocator)
|
cat, err = suite.loader(suite.file, suite.options, suite.plural, context.allocator)
|
||||||
|
|
||||||
msg := fmt.tprintf("Expected loading %v to return %v, got %v", suite.file, suite.err, err)
|
msg := fmt.tprintf("Expected loading %v to return %v, got %v", suite.file, suite.err, err)
|
||||||
expect(t, err == suite.err, msg)
|
expect(t, err == suite.err, msg)
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ main :: proc() {
|
|||||||
test_ordinal_date_roundtrip(&t)
|
test_ordinal_date_roundtrip(&t)
|
||||||
test_component_to_time_roundtrip(&t)
|
test_component_to_time_roundtrip(&t)
|
||||||
test_parse_rfc3339_string(&t)
|
test_parse_rfc3339_string(&t)
|
||||||
|
test_parse_iso8601_string(&t)
|
||||||
|
|
||||||
for _, leak in track.allocation_map {
|
for _, leak in track.allocation_map {
|
||||||
expect(&t, false, fmt.tprintf("%v leaked %m\n", leak.location, leak.size))
|
expect(&t, false, fmt.tprintf("%v leaked %m\n", leak.location, leak.size))
|
||||||
@@ -91,7 +92,46 @@ RFC3339_Test :: struct{
|
|||||||
// These are based on RFC 3339's examples, see https://www.rfc-editor.org/rfc/rfc3339#page-10
|
// These are based on RFC 3339's examples, see https://www.rfc-editor.org/rfc/rfc3339#page-10
|
||||||
rfc3339_tests :: []RFC3339_Test{
|
rfc3339_tests :: []RFC3339_Test{
|
||||||
// This represents 20 minutes and 50.52 seconds after the 23rd hour of April 12th, 1985 in UTC.
|
// This represents 20 minutes and 50.52 seconds after the 23rd hour of April 12th, 1985 in UTC.
|
||||||
{"1985-04-12T23:20:50.52Z", {482196050520000000}, true, 0, 23, false},
|
{"1985-04-12 23:20:50.52Z", {482196050520000000}, true, 0, 23, false},
|
||||||
|
// Same, but lowercase z
|
||||||
|
{"1985-04-12 23:20:50.52z", {482196050520000000}, true, 0, 23, false},
|
||||||
|
|
||||||
|
// This represents 39 minutes and 57 seconds after the 16th hour of December 19th, 1996 with an offset of -08:00 from UTC (Pacific Standard Time).
|
||||||
|
// Note that this is equivalent to 1996-12-20T00:39:57Z in UTC.
|
||||||
|
{"1996-12-19 16:39:57-08:00", {851013597000000000}, false, -480, 25, false},
|
||||||
|
{"1996-12-19 16:39:57-08:00", {851042397000000000}, true, 0, 25, false},
|
||||||
|
{"1996-12-20 00:39:57Z", {851042397000000000}, false, 0, 20, false},
|
||||||
|
|
||||||
|
// This represents the leap second inserted at the end of 1990.
|
||||||
|
// It'll be represented as 1990-12-31 23:59:59 UTC after parsing, and `is_leap` will be set to `true`.
|
||||||
|
{"1990-12-31 23:59:60Z", {662687999000000000}, true, 0, 20, true},
|
||||||
|
|
||||||
|
// This represents the same leap second in Pacific Standard Time, 8 hours behind UTC.
|
||||||
|
{"1990-12-31 15:59:60-08:00", {662687999000000000}, true, 0, 25, true},
|
||||||
|
|
||||||
|
// This represents the same instant of time as noon, January 1, 1937, Netherlands time.
|
||||||
|
// Standard time in the Netherlands was exactly 19 minutes and 32.13 seconds ahead of UTC by law
|
||||||
|
// from 1909-05-01 through 1937-06-30. This time zone cannot be represented exactly using the
|
||||||
|
// HH:MM format, and this timestamp uses the closest representable UTC offset.
|
||||||
|
{"1937-01-01 12:00:27.87+00:20", {-1041335972130000000}, false, 20, 28, false},
|
||||||
|
{"1937-01-01 12:00:27.87+00:20", {-1041337172130000000}, true, 0, 28, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
ISO8601_Test :: struct{
|
||||||
|
iso_8601: string,
|
||||||
|
datetime: time.Time,
|
||||||
|
apply_offset: bool,
|
||||||
|
utc_offset: int,
|
||||||
|
consumed: int,
|
||||||
|
is_leap: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
// These are based on RFC 3339's examples, see https://www.rfc-editor.org/rfc/rfc3339#page-10
|
||||||
|
iso8601_tests :: []ISO8601_Test{
|
||||||
|
// This represents 20 minutes and .003362 seconds after the 23rd hour of April 12th, 1985 in UTC.
|
||||||
|
{"1985-04-12T23:20:50.003362", {482196050003362000}, true, 0, 26, false},
|
||||||
|
{"1985-04-12t23:20:50.003362", {482196050003362000}, true, 0, 26, false},
|
||||||
|
{"1985-04-12 23:20:50.003362", {482196050003362000}, true, 0, 26, false},
|
||||||
|
|
||||||
// This represents 39 minutes and 57 seconds after the 16th hour of December 19th, 1996 with an offset of -08:00 from UTC (Pacific Standard Time).
|
// This represents 39 minutes and 57 seconds after the 16th hour of December 19th, 1996 with an offset of -08:00 from UTC (Pacific Standard Time).
|
||||||
// Note that this is equivalent to 1996-12-20T00:39:57Z in UTC.
|
// Note that this is equivalent to 1996-12-20T00:39:57Z in UTC.
|
||||||
@@ -110,8 +150,8 @@ rfc3339_tests :: []RFC3339_Test{
|
|||||||
// Standard time in the Netherlands was exactly 19 minutes and 32.13 seconds ahead of UTC by law
|
// Standard time in the Netherlands was exactly 19 minutes and 32.13 seconds ahead of UTC by law
|
||||||
// from 1909-05-01 through 1937-06-30. This time zone cannot be represented exactly using the
|
// from 1909-05-01 through 1937-06-30. This time zone cannot be represented exactly using the
|
||||||
// HH:MM format, and this timestamp uses the closest representable UTC offset.
|
// HH:MM format, and this timestamp uses the closest representable UTC offset.
|
||||||
{"1937-01-01T12:00:27.87+00:20", {-1041335972130000000}, false, 20, 28, false},
|
{"1937-01-01 12:00:27.87+00:20", {-1041335972130000000}, false, 20, 28, false},
|
||||||
{"1937-01-01T12:00:27.87+00:20", {-1041337172130000000}, true, 0, 28, false},
|
{"1937-01-01 12:00:27.87+00:20", {-1041337172130000000}, true, 0, 28, false},
|
||||||
}
|
}
|
||||||
|
|
||||||
@test
|
@test
|
||||||
@@ -141,6 +181,33 @@ test_parse_rfc3339_string :: proc(t: ^testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test
|
||||||
|
test_parse_iso8601_string :: proc(t: ^testing.T) {
|
||||||
|
for test in iso8601_tests {
|
||||||
|
is_leap := false
|
||||||
|
if test.apply_offset {
|
||||||
|
res, consumed := time.iso8601_to_time_utc(test.iso_8601, &is_leap)
|
||||||
|
msg := fmt.tprintf("[apply offet] Parsing failed: %v -> %v (nsec: %v). Expected %v consumed, got %v", test.iso_8601, res, res._nsec, test.consumed, consumed)
|
||||||
|
expect(t, test.consumed == consumed, msg)
|
||||||
|
|
||||||
|
if test.consumed == consumed {
|
||||||
|
expect(t, test.datetime == res, fmt.tprintf("Time didn't match. Expected %v (%v), got %v (%v)", test.datetime, test.datetime._nsec, res, res._nsec))
|
||||||
|
expect(t, test.is_leap == is_leap, "Expected a leap second, got none.")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res, offset, consumed := time.iso8601_to_time_and_offset(test.iso_8601)
|
||||||
|
msg := fmt.tprintf("Parsing failed: %v -> %v (nsec: %v), offset: %v. Expected %v consumed, got %v", test.iso_8601, res, res._nsec, offset, test.consumed, consumed)
|
||||||
|
expect(t, test.consumed == consumed, msg)
|
||||||
|
|
||||||
|
if test.consumed == consumed {
|
||||||
|
expect(t, test.datetime == res, fmt.tprintf("Time didn't match. Expected %v (%v), got %v (%v)", test.datetime, test.datetime._nsec, res, res._nsec))
|
||||||
|
expect(t, test.utc_offset == offset, fmt.tprintf("UTC offset didn't match. Expected %v, got %v", test.utc_offset, offset))
|
||||||
|
expect(t, test.is_leap == is_leap, "Expected a leap second, got none.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MONTH_DAYS := []int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
|
MONTH_DAYS := []int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
|
||||||
YEAR_START :: 1900
|
YEAR_START :: 1900
|
||||||
YEAR_END :: 2024
|
YEAR_END :: 2024
|
||||||
|
|||||||
Vendored
+137
-23
@@ -29,6 +29,7 @@ import "core:sort"
|
|||||||
import "core:strings"
|
import "core:strings"
|
||||||
import "core:strconv"
|
import "core:strconv"
|
||||||
import "core:math"
|
import "core:math"
|
||||||
|
import textedit "core:text/edit"
|
||||||
|
|
||||||
COMMAND_LIST_SIZE :: #config(MICROUI_COMMAND_LIST_SIZE, 256 * 1024)
|
COMMAND_LIST_SIZE :: #config(MICROUI_COMMAND_LIST_SIZE, 256 * 1024)
|
||||||
ROOT_LIST_SIZE :: #config(MICROUI_ROOT_LIST_SIZE, 32)
|
ROOT_LIST_SIZE :: #config(MICROUI_ROOT_LIST_SIZE, 32)
|
||||||
@@ -51,6 +52,7 @@ Clip :: enum u32 {
|
|||||||
|
|
||||||
Color_Type :: enum u32 {
|
Color_Type :: enum u32 {
|
||||||
TEXT,
|
TEXT,
|
||||||
|
SELECTION_BG,
|
||||||
BORDER,
|
BORDER,
|
||||||
WINDOW_BG,
|
WINDOW_BG,
|
||||||
TITLE_BG,
|
TITLE_BG,
|
||||||
@@ -111,7 +113,16 @@ Key :: enum u32 {
|
|||||||
CTRL,
|
CTRL,
|
||||||
ALT,
|
ALT,
|
||||||
BACKSPACE,
|
BACKSPACE,
|
||||||
|
DELETE,
|
||||||
RETURN,
|
RETURN,
|
||||||
|
LEFT,
|
||||||
|
RIGHT,
|
||||||
|
HOME,
|
||||||
|
END,
|
||||||
|
A,
|
||||||
|
X,
|
||||||
|
C,
|
||||||
|
V,
|
||||||
}
|
}
|
||||||
Key_Set :: distinct bit_set[Key; u32]
|
Key_Set :: distinct bit_set[Key; u32]
|
||||||
|
|
||||||
@@ -235,6 +246,8 @@ Context :: struct {
|
|||||||
key_down_bits, key_pressed_bits: Key_Set,
|
key_down_bits, key_pressed_bits: Key_Set,
|
||||||
_text_store: [MAX_TEXT_STORE]u8,
|
_text_store: [MAX_TEXT_STORE]u8,
|
||||||
text_input: strings.Builder, // uses `_text_store` as backing store with nil_allocator.
|
text_input: strings.Builder, // uses `_text_store` as backing store with nil_allocator.
|
||||||
|
textbox_state: textedit.State,
|
||||||
|
textbox_offset: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
Stack :: struct($T: typeid, $N: int) {
|
Stack :: struct($T: typeid, $N: int) {
|
||||||
@@ -260,6 +273,7 @@ default_style := Style{
|
|||||||
scrollbar_size = 12, thumb_size = 8,
|
scrollbar_size = 12, thumb_size = 8,
|
||||||
colors = {
|
colors = {
|
||||||
.TEXT = {230, 230, 230, 255},
|
.TEXT = {230, 230, 230, 255},
|
||||||
|
.SELECTION_BG = {90, 90, 90, 255},
|
||||||
.BORDER = {25, 25, 25, 255},
|
.BORDER = {25, 25, 25, 255},
|
||||||
.WINDOW_BG = {50, 50, 50, 255},
|
.WINDOW_BG = {50, 50, 50, 255},
|
||||||
.TITLE_BG = {25, 25, 25, 255},
|
.TITLE_BG = {25, 25, 25, 255},
|
||||||
@@ -305,12 +319,16 @@ default_draw_frame :: proc(ctx: ^Context, rect: Rect, colorid: Color_Type) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init :: proc(ctx: ^Context) {
|
init :: proc(ctx: ^Context, set_clipboard: proc(user_data: rawptr, text: string) -> (ok: bool), get_clipboard: proc(user_data: rawptr) -> (text: string, ok: bool), clipboard_user_data: rawptr) {
|
||||||
ctx^ = {} // zero memory
|
ctx^ = {} // zero memory
|
||||||
ctx.draw_frame = default_draw_frame
|
ctx.draw_frame = default_draw_frame
|
||||||
ctx._style = default_style
|
ctx._style = default_style
|
||||||
ctx.style = &ctx._style
|
ctx.style = &ctx._style
|
||||||
ctx.text_input = strings.builder_from_bytes(ctx._text_store[:])
|
ctx.text_input = strings.builder_from_bytes(ctx._text_store[:])
|
||||||
|
|
||||||
|
ctx.textbox_state.set_clipboard = set_clipboard
|
||||||
|
ctx.textbox_state.get_clipboard = get_clipboard
|
||||||
|
ctx.textbox_state.clipboard_user_data = clipboard_user_data
|
||||||
}
|
}
|
||||||
|
|
||||||
begin :: proc(ctx: ^Context) {
|
begin :: proc(ctx: ^Context) {
|
||||||
@@ -967,23 +985,95 @@ checkbox :: proc(ctx: ^Context, label: string, state: ^bool) -> (res: Result_Set
|
|||||||
textbox_raw :: proc(ctx: ^Context, textbuf: []u8, textlen: ^int, id: Id, r: Rect, opt := Options{}) -> (res: Result_Set) {
|
textbox_raw :: proc(ctx: ^Context, textbuf: []u8, textlen: ^int, id: Id, r: Rect, opt := Options{}) -> (res: Result_Set) {
|
||||||
update_control(ctx, id, r, opt | {.HOLD_FOCUS})
|
update_control(ctx, id, r, opt | {.HOLD_FOCUS})
|
||||||
|
|
||||||
|
font := ctx.style.font
|
||||||
|
|
||||||
if ctx.focus_id == id {
|
if ctx.focus_id == id {
|
||||||
|
/* create a builder backed by the user's buffer */
|
||||||
|
builder := strings.builder_from_bytes(textbuf)
|
||||||
|
non_zero_resize(&builder.buf, textlen^)
|
||||||
|
ctx.textbox_state.builder = &builder
|
||||||
|
if ctx.textbox_state.id != u64(id) {
|
||||||
|
ctx.textbox_state.id = u64(id)
|
||||||
|
ctx.textbox_state.selection = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check selection bounds */
|
||||||
|
if ctx.textbox_state.selection[0] > textlen^ || ctx.textbox_state.selection[1] > textlen^ {
|
||||||
|
ctx.textbox_state.selection = {}
|
||||||
|
}
|
||||||
|
|
||||||
/* handle text input */
|
/* handle text input */
|
||||||
n := min(len(textbuf) - textlen^, strings.builder_len(ctx.text_input))
|
if strings.builder_len(ctx.text_input) > 0 {
|
||||||
if n > 0 {
|
if textedit.input_text(&ctx.textbox_state, strings.to_string(ctx.text_input)) > 0 {
|
||||||
copy(textbuf[textlen^:], strings.to_string(ctx.text_input)[:n])
|
textlen^ = strings.builder_len(builder)
|
||||||
textlen^ += n
|
res += {.CHANGE}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* handle ctrl+a */
|
||||||
|
if .A in ctx.key_pressed_bits && .CTRL in ctx.key_down_bits && .ALT not_in ctx.key_down_bits {
|
||||||
|
ctx.textbox_state.selection = {textlen^, 0}
|
||||||
|
}
|
||||||
|
/* handle ctrl+x */
|
||||||
|
if .X in ctx.key_pressed_bits && .CTRL in ctx.key_down_bits && .ALT not_in ctx.key_down_bits {
|
||||||
|
if textedit.cut(&ctx.textbox_state) {
|
||||||
|
textlen^ = strings.builder_len(builder)
|
||||||
|
res += {.CHANGE}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* handle ctrl+c */
|
||||||
|
if .C in ctx.key_pressed_bits && .CTRL in ctx.key_down_bits && .ALT not_in ctx.key_down_bits {
|
||||||
|
textedit.copy(&ctx.textbox_state)
|
||||||
|
}
|
||||||
|
/* handle ctrl+v */
|
||||||
|
if .V in ctx.key_pressed_bits && .CTRL in ctx.key_down_bits && .ALT not_in ctx.key_down_bits {
|
||||||
|
if textedit.paste(&ctx.textbox_state) {
|
||||||
|
textlen^ = strings.builder_len(builder)
|
||||||
|
res += {.CHANGE}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* handle left/right */
|
||||||
|
if .LEFT in ctx.key_pressed_bits {
|
||||||
|
move: textedit.Translation = .Word_Left if .CTRL in ctx.key_down_bits else .Left
|
||||||
|
if .SHIFT in ctx.key_down_bits {
|
||||||
|
textedit.select_to(&ctx.textbox_state, move)
|
||||||
|
} else {
|
||||||
|
textedit.move_to(&ctx.textbox_state, move)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if .RIGHT in ctx.key_pressed_bits {
|
||||||
|
move: textedit.Translation = .Word_Right if .CTRL in ctx.key_down_bits else .Right
|
||||||
|
if .SHIFT in ctx.key_down_bits {
|
||||||
|
textedit.select_to(&ctx.textbox_state, move)
|
||||||
|
} else {
|
||||||
|
textedit.move_to(&ctx.textbox_state, move)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* handle home/end */
|
||||||
|
if .HOME in ctx.key_pressed_bits {
|
||||||
|
if .SHIFT in ctx.key_down_bits {
|
||||||
|
textedit.select_to(&ctx.textbox_state, .Start)
|
||||||
|
} else {
|
||||||
|
textedit.move_to(&ctx.textbox_state, .Start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if .END in ctx.key_pressed_bits {
|
||||||
|
if .SHIFT in ctx.key_down_bits {
|
||||||
|
textedit.select_to(&ctx.textbox_state, .End)
|
||||||
|
} else {
|
||||||
|
textedit.move_to(&ctx.textbox_state, .End)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* handle backspace/delete */
|
||||||
|
if .BACKSPACE in ctx.key_pressed_bits && textlen^ > 0 {
|
||||||
|
move: textedit.Translation = .Word_Left if .CTRL in ctx.key_down_bits else .Left
|
||||||
|
textedit.delete_to(&ctx.textbox_state, move)
|
||||||
|
textlen^ = strings.builder_len(builder)
|
||||||
res += {.CHANGE}
|
res += {.CHANGE}
|
||||||
}
|
}
|
||||||
/* handle backspace */
|
if .DELETE in ctx.key_pressed_bits && textlen^ > 0 {
|
||||||
if .BACKSPACE in ctx.key_pressed_bits && textlen^ > 0 {
|
move: textedit.Translation = .Word_Right if .CTRL in ctx.key_down_bits else .Right
|
||||||
/* skip utf-8 continuation bytes */
|
textedit.delete_to(&ctx.textbox_state, move)
|
||||||
for textlen^ > 0 {
|
textlen^ = strings.builder_len(builder)
|
||||||
textlen^ -= 1
|
|
||||||
if textbuf[textlen^] & 0xc0 != 0x80 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res += {.CHANGE}
|
res += {.CHANGE}
|
||||||
}
|
}
|
||||||
/* handle return */
|
/* handle return */
|
||||||
@@ -991,6 +1081,25 @@ textbox_raw :: proc(ctx: ^Context, textbuf: []u8, textlen: ^int, id: Id, r: Rect
|
|||||||
set_focus(ctx, 0)
|
set_focus(ctx, 0)
|
||||||
res += {.SUBMIT}
|
res += {.SUBMIT}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* handle click/drag */
|
||||||
|
if .LEFT in ctx.mouse_down_bits {
|
||||||
|
idx := textlen^
|
||||||
|
for i in 0..<textlen^ {
|
||||||
|
/* skip continuation bytes */
|
||||||
|
if textbuf[i] >= 0x80 && textbuf[i] < 0xc0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ctx.mouse_pos.x < r.x + ctx.textbox_offset + ctx.text_width(font, string(textbuf[:i])) {
|
||||||
|
idx = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.textbox_state.selection[0] = idx
|
||||||
|
if .LEFT in ctx.mouse_pressed_bits && .SHIFT not_in ctx.key_down_bits {
|
||||||
|
ctx.textbox_state.selection[1] = idx
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textstr := string(textbuf[:textlen^])
|
textstr := string(textbuf[:textlen^])
|
||||||
@@ -998,16 +1107,21 @@ textbox_raw :: proc(ctx: ^Context, textbuf: []u8, textlen: ^int, id: Id, r: Rect
|
|||||||
/* draw */
|
/* draw */
|
||||||
draw_control_frame(ctx, id, r, .BASE, opt)
|
draw_control_frame(ctx, id, r, .BASE, opt)
|
||||||
if ctx.focus_id == id {
|
if ctx.focus_id == id {
|
||||||
color := ctx.style.colors[.TEXT]
|
text_color := ctx.style.colors[.TEXT]
|
||||||
font := ctx.style.font
|
sel_color := ctx.style.colors[.SELECTION_BG]
|
||||||
textw := ctx.text_width(font, textstr)
|
textw := ctx.text_width(font, textstr)
|
||||||
texth := ctx.text_height(font)
|
texth := ctx.text_height(font)
|
||||||
ofx := r.w - ctx.style.padding - textw - 1
|
headx := ctx.text_width(font, textstr[:ctx.textbox_state.selection[0]])
|
||||||
textx := r.x + min(ofx, ctx.style.padding)
|
tailx := ctx.text_width(font, textstr[:ctx.textbox_state.selection[1]])
|
||||||
texty := r.y + (r.h - texth) / 2
|
ofmin := max(ctx.style.padding - headx, r.w - textw - ctx.style.padding)
|
||||||
|
ofmax := min(r.w - headx - ctx.style.padding, ctx.style.padding)
|
||||||
|
ctx.textbox_offset = clamp(ctx.textbox_offset, ofmin, ofmax)
|
||||||
|
textx := r.x + ctx.textbox_offset
|
||||||
|
texty := r.y + (r.h - texth) / 2
|
||||||
push_clip_rect(ctx, r)
|
push_clip_rect(ctx, r)
|
||||||
draw_text(ctx, font, textstr, Vec2{textx, texty}, color)
|
draw_rect(ctx, Rect{textx + min(headx, tailx), texty, abs(headx - tailx), texth}, sel_color)
|
||||||
draw_rect(ctx, Rect{textx + textw, texty, 1, texth}, color)
|
draw_text(ctx, font, textstr, Vec2{textx, texty}, text_color)
|
||||||
|
draw_rect(ctx, Rect{textx + headx, texty, 1, texth}, text_color)
|
||||||
pop_clip_rect(ctx)
|
pop_clip_rect(ctx)
|
||||||
} else {
|
} else {
|
||||||
draw_control_text(ctx, textstr, r, .TEXT, opt)
|
draw_control_text(ctx, textstr, r, .TEXT, opt)
|
||||||
|
|||||||
Vendored
+45
-13
@@ -13,7 +13,8 @@ function stripNewline(str) {
|
|||||||
return str.replace(/\n/, ' ')
|
return str.replace(/\n/, ' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
const STRING_SIZE = 2*4;
|
const INT_SIZE = 4; // NOTE: set to `8` if the target has 64 bit ints (`wasm64p32` for example).
|
||||||
|
const STRING_SIZE = 2*INT_SIZE;
|
||||||
|
|
||||||
class WasmMemoryInterface {
|
class WasmMemoryInterface {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -69,19 +70,34 @@ class WasmMemoryInterface {
|
|||||||
const hi = this.mem.getInt32 (addr + 4, true);
|
const hi = this.mem.getInt32 (addr + 4, true);
|
||||||
return lo + hi*4294967296;
|
return lo + hi*4294967296;
|
||||||
};
|
};
|
||||||
loadF32(addr) { return this.mem.getFloat32(addr, true); }
|
loadF32(addr) { return this.mem.getFloat32(addr, true); }
|
||||||
loadF64(addr) { return this.mem.getFloat64(addr, true); }
|
loadF64(addr) { return this.mem.getFloat64(addr, true); }
|
||||||
loadInt(addr) { return this.mem.getInt32 (addr, true); }
|
loadInt(addr) {
|
||||||
loadUint(addr) { return this.mem.getUint32 (addr, true); }
|
if (INT_SIZE == 8) {
|
||||||
|
return this.loadI64(addr);
|
||||||
loadPtr(addr) { return this.loadUint(addr); }
|
} else if (INT_SIZE == 4) {
|
||||||
|
return this.loadI32(addr);
|
||||||
|
} else {
|
||||||
|
throw new Error('Unhandled `INT_SIZE`, expected `4` or `8`');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadUint(addr) {
|
||||||
|
if (INT_SIZE == 8) {
|
||||||
|
return this.loadU64(addr);
|
||||||
|
} else if (INT_SIZE == 4) {
|
||||||
|
return this.loadU32(addr);
|
||||||
|
} else {
|
||||||
|
throw new Error('Unhandled `INT_SIZE`, expected `4` or `8`');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadPtr(addr) { return this.loadU32(addr); }
|
||||||
|
|
||||||
loadBytes(ptr, len) {
|
loadBytes(ptr, len) {
|
||||||
return new Uint8Array(this.memory.buffer, ptr, len);
|
return new Uint8Array(this.memory.buffer, ptr, Number(len));
|
||||||
}
|
}
|
||||||
|
|
||||||
loadString(ptr, len) {
|
loadString(ptr, len) {
|
||||||
const bytes = this.loadBytes(ptr, len);
|
const bytes = this.loadBytes(ptr, Number(len));
|
||||||
return new TextDecoder().decode(bytes);
|
return new TextDecoder().decode(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,10 +115,26 @@ class WasmMemoryInterface {
|
|||||||
this.mem.setUint32(addr + 0, value, true);
|
this.mem.setUint32(addr + 0, value, true);
|
||||||
this.mem.setInt32 (addr + 4, Math.floor(value / 4294967296), true);
|
this.mem.setInt32 (addr + 4, Math.floor(value / 4294967296), true);
|
||||||
}
|
}
|
||||||
storeF32(addr, value) { this.mem.setFloat32(addr, value, true); }
|
storeF32(addr, value) { this.mem.setFloat32(addr, value, true); }
|
||||||
storeF64(addr, value) { this.mem.setFloat64(addr, value, true); }
|
storeF64(addr, value) { this.mem.setFloat64(addr, value, true); }
|
||||||
storeInt(addr, value) { this.mem.setInt32 (addr, value, true); }
|
storeInt(addr, value) {
|
||||||
storeUint(addr, value) { this.mem.setUint32 (addr, value, true); }
|
if (INT_SIZE == 8) {
|
||||||
|
this.storeI64(addr, value);
|
||||||
|
} else if (INT_SIZE == 4) {
|
||||||
|
this.storeI32(addr, value);
|
||||||
|
} else {
|
||||||
|
throw new Error('Unhandled `INT_SIZE`, expected `4` or `8`');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
storeUint(addr, value) {
|
||||||
|
if (INT_SIZE == 8) {
|
||||||
|
this.storeU64(addr, value);
|
||||||
|
} else if (INT_SIZE == 4) {
|
||||||
|
this.storeU32(addr, value);
|
||||||
|
} else {
|
||||||
|
throw new Error('Unhandled `INT_SIZE`, expected `4` or `8`');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Returned length might not be the same as `value.length` if non-ascii strings are given.
|
// Returned length might not be the same as `value.length` if non-ascii strings are given.
|
||||||
storeString(addr, value) {
|
storeString(addr, value) {
|
||||||
|
|||||||
Reference in New Issue
Block a user