浏览代码

Merge branch 'master' into extend_win32_api_types

VladPavliuk 8 月之前
父节点
当前提交
470c05a902
共有 100 个文件被更改,包括 1905 次插入4609 次删除
  1. 1 1
      .github/workflows/ci.yml
  2. 1 0
      .github/workflows/nightly.yml
  3. 0 12
      base/runtime/core.odin
  4. 3 15
      base/runtime/core_builtin.odin
  5. 2 0
      base/runtime/core_builtin_soa.odin
  6. 0 12
      base/runtime/print.odin
  7. 19 0
      bin/RAD-LICENSE
  8. 二进制
      bin/radlink.exe
  9. 5 2
      build.bat
  10. 17 10
      build_odin.sh
  11. 0 25
      core/c/frontend/preprocessor/const_expr.odin
  12. 0 1510
      core/c/frontend/preprocessor/preprocess.odin
  13. 0 154
      core/c/frontend/preprocessor/unquote.odin
  14. 0 31
      core/c/frontend/tokenizer/doc.odin
  15. 0 68
      core/c/frontend/tokenizer/hide_set.odin
  16. 0 169
      core/c/frontend/tokenizer/token.odin
  17. 0 667
      core/c/frontend/tokenizer/tokenizer.odin
  18. 0 116
      core/c/frontend/tokenizer/unicode.odin
  19. 2 2
      core/c/libc/README.md
  20. 133 0
      core/c/libc/locale.odin
  21. 2 1
      core/compress/zlib/doc.odin
  22. 7 5
      core/dynlib/example/example.odin
  23. 28 18
      core/dynlib/lib.odin
  24. 7 3
      core/dynlib/lib_js.odin
  25. 22 10
      core/dynlib/lib_unix.odin
  26. 7 3
      core/dynlib/lib_windows.odin
  27. 1 1
      core/encoding/cbor/cbor.odin
  28. 20 5
      core/encoding/cbor/unmarshal.odin
  29. 1 1
      core/encoding/hxa/read.odin
  30. 0 6
      core/encoding/json/marshal.odin
  31. 2 1
      core/encoding/json/tokenizer.odin
  32. 48 24
      core/encoding/json/unmarshal.odin
  33. 1 13
      core/fmt/fmt.odin
  34. 10 2
      core/image/general.odin
  35. 1 129
      core/image/png/helpers.odin
  36. 8 4
      core/io/util.odin
  37. 8 8
      core/log/file_console_logger.odin
  38. 6 6
      core/log/multi_logger.odin
  39. 15 3
      core/math/linalg/general.odin
  40. 16 0
      core/math/linalg/glsl/linalg_glsl.odin
  41. 2 0
      core/math/linalg/glsl/linalg_glsl_math.odin
  42. 1 1
      core/math/math.odin
  43. 6 6
      core/math/math_sincos.odin
  44. 64 39
      core/math/rand/rand.odin
  45. 0 23
      core/mem/allocators.odin
  46. 4 9
      core/mem/mem.odin
  47. 3 7
      core/mem/virtual/virtual_posix.odin
  48. 21 3
      core/odin/parser/file_tags.odin
  49. 3 1
      core/odin/parser/parser.odin
  50. 1 1
      core/odin/tokenizer/tokenizer.odin
  51. 0 584
      core/os/file_windows.odin
  52. 1 1
      core/os/os2/dir_linux.odin
  53. 4 6
      core/os/os2/dir_windows.odin
  54. 3 19
      core/os/os2/file_linux.odin
  55. 3 3
      core/os/os2/file_windows.odin
  56. 1 1
      core/os/os2/heap_linux.odin
  57. 10 1
      core/os/os2/process.odin
  58. 11 36
      core/os/os2/process_linux.odin
  59. 1 1
      core/os/os2/process_windows.odin
  60. 27 11
      core/os/os2/stat_windows.odin
  61. 3 5
      core/os/os_darwin.odin
  62. 2 5
      core/os/os_freebsd.odin
  63. 2 4
      core/os/os_haiku.odin
  64. 3 5
      core/os/os_linux.odin
  65. 2 4
      core/os/os_netbsd.odin
  66. 2 4
      core/os/os_openbsd.odin
  67. 578 3
      core/os/os_windows.odin
  68. 7 0
      core/path/filepath/match.odin
  69. 5 32
      core/path/filepath/path_unix.odin
  70. 2 0
      core/prof/spall/doc.odin
  71. 48 54
      core/reflect/reflect.odin
  72. 0 32
      core/reflect/types.odin
  73. 41 16
      core/slice/slice.odin
  74. 2 1
      core/strconv/strconv.odin
  75. 2 1
      core/strings/strings.odin
  76. 5 15
      core/sync/futex_wasm.odin
  77. 1 1
      core/sync/primitives_atomic.odin
  78. 10 0
      core/sys/darwin/Foundation/NSApplication.odin
  79. 5 0
      core/sys/darwin/Foundation/NSObjectProtocol.odin
  80. 203 0
      core/sys/darwin/Foundation/NSProcessInfo.odin
  81. 2 2
      core/sys/darwin/Foundation/NSTypes.odin
  82. 46 1
      core/sys/info/cpu_intel.odin
  83. 1 1
      core/sys/info/doc.odin
  84. 71 551
      core/sys/info/platform_darwin.odin
  85. 89 37
      core/sys/linux/bits.odin
  86. 25 0
      core/sys/linux/constants.odin
  87. 22 3
      core/sys/linux/sys.odin
  88. 27 0
      core/sys/linux/types.odin
  89. 1 0
      core/sys/posix/arpa_inet.odin
  90. 41 26
      core/sys/posix/dirent.odin
  91. 1 2
      core/sys/posix/dlfcn.odin
  92. 80 2
      core/sys/posix/errno.odin
  93. 3 4
      core/sys/posix/fcntl.odin
  94. 1 2
      core/sys/posix/fnmatch.odin
  95. 1 2
      core/sys/posix/glob.odin
  96. 1 2
      core/sys/posix/grp.odin
  97. 1 0
      core/sys/posix/iconv.odin
  98. 1 0
      core/sys/posix/langinfo.odin
  99. 8 0
      core/sys/posix/libgen.odin
  100. 1 2
      core/sys/posix/limits.odin

+ 1 - 1
.github/workflows/ci.yml

@@ -6,7 +6,7 @@ jobs:
     name: NetBSD Build, Check, and Test
     runs-on: ubuntu-latest
     env:
-      PKGSRC_BRANCH: 2024Q2
+      PKGSRC_BRANCH: 2024Q3
     steps:
     - uses: actions/checkout@v4
     - name: Build, Check, and Test

+ 1 - 0
.github/workflows/nightly.yml

@@ -38,6 +38,7 @@ jobs:
       - name: Upload artifact
         uses: actions/upload-artifact@v4
         with:
+          include-hidden-files: true
           name: windows_artifacts
           path: dist
   build_linux:

+ 0 - 12
base/runtime/core.odin

@@ -171,14 +171,6 @@ Type_Info_Simd_Vector :: struct {
 	elem_size:  int,
 	count:      int,
 }
-Type_Info_Relative_Pointer :: struct {
-	pointer:      ^Type_Info, // ^T
-	base_integer: ^Type_Info,
-}
-Type_Info_Relative_Multi_Pointer :: struct {
-	pointer:      ^Type_Info, // [^]T
-	base_integer: ^Type_Info,
-}
 Type_Info_Matrix :: struct {
 	elem:         ^Type_Info,
 	elem_size:    int,
@@ -241,8 +233,6 @@ Type_Info :: struct {
 		Type_Info_Map,
 		Type_Info_Bit_Set,
 		Type_Info_Simd_Vector,
-		Type_Info_Relative_Pointer,
-		Type_Info_Relative_Multi_Pointer,
 		Type_Info_Matrix,
 		Type_Info_Soa_Pointer,
 		Type_Info_Bit_Field,
@@ -275,8 +265,6 @@ Typeid_Kind :: enum u8 {
 	Map,
 	Bit_Set,
 	Simd_Vector,
-	Relative_Pointer,
-	Relative_Multi_Pointer,
 	Matrix,
 	Soa_Pointer,
 	Bit_Field,

+ 3 - 15
base/runtime/core_builtin.odin

@@ -388,7 +388,7 @@ _make_dynamic_array_len_cap :: proc(array: ^Raw_Dynamic_Array, size_of_elem, ali
 //
 // Note: Prefer using the procedure group `make`.
 @(builtin, require_results)
-make_map :: proc($T: typeid/map[$K]$E, allocator := context.allocator) -> (m: T) {
+make_map :: proc($T: typeid/map[$K]$E, allocator := context.allocator, loc := #caller_location) -> (m: T) {
 	m.allocator = allocator
 	return m
 }
@@ -399,7 +399,7 @@ make_map :: proc($T: typeid/map[$K]$E, allocator := context.allocator) -> (m: T)
 //
 // Note: Prefer using the procedure group `make`.
 @(builtin, require_results)
-make_map_cap :: proc($T: typeid/map[$K]$E, #any_int capacity: int = 1<<MAP_MIN_LOG2_CAPACITY, allocator := context.allocator, loc := #caller_location) -> (m: T, err: Allocator_Error) #optional_allocator_error {
+make_map_cap :: proc($T: typeid/map[$K]$E, #any_int capacity: int, allocator := context.allocator, loc := #caller_location) -> (m: T, err: Allocator_Error) #optional_allocator_error {
 	make_map_expr_error_loc(loc, capacity)
 	context.allocator = allocator
 
@@ -939,19 +939,7 @@ map_upsert :: proc(m: ^$T/map[$K]$V, key: K, value: V, loc := #caller_location)
 
 @builtin
 card :: proc "contextless" (s: $S/bit_set[$E; $U]) -> int {
-	when size_of(S) == 1 {
-		return int(intrinsics.count_ones(transmute(u8)s))
-	} else when size_of(S) == 2 {
-		return int(intrinsics.count_ones(transmute(u16)s))
-	} else when size_of(S) == 4 {
-		return int(intrinsics.count_ones(transmute(u32)s))
-	} else when size_of(S) == 8 {
-		return int(intrinsics.count_ones(transmute(u64)s))
-	} else when size_of(S) == 16 {
-		return int(intrinsics.count_ones(transmute(u128)s))
-	} else {
-		#panic("Unhandled card bit_set size")
-	}
+	return int(intrinsics.count_ones(transmute(intrinsics.type_bit_set_underlying_type(S))s))
 }
 
 

+ 2 - 0
base/runtime/core_builtin_soa.odin

@@ -142,6 +142,7 @@ make_soa_slice :: proc($T: typeid/#soa[]$E, #any_int length: int, allocator := c
 @(builtin, require_results)
 make_soa_dynamic_array :: proc($T: typeid/#soa[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error {
 	context.allocator = allocator
+	array.allocator = allocator
 	reserve_soa(&array, 0, loc) or_return
 	return array, nil
 }
@@ -149,6 +150,7 @@ make_soa_dynamic_array :: proc($T: typeid/#soa[dynamic]$E, allocator := context.
 @(builtin, require_results)
 make_soa_dynamic_array_len :: proc($T: typeid/#soa[dynamic]$E, #any_int length: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error {
 	context.allocator = allocator
+	array.allocator = allocator
 	resize_soa(&array, length, loc) or_return
 	return array, nil
 }

+ 0 - 12
base/runtime/print.odin

@@ -486,18 +486,6 @@ print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) {
 		print_u64(u64(info.count))
 		print_byte(']')
 		print_type(info.elem)
-
-	case Type_Info_Relative_Pointer:
-		print_string("#relative(")
-		print_type(info.base_integer)
-		print_string(") ")
-		print_type(info.pointer)
-
-	case Type_Info_Relative_Multi_Pointer:
-		print_string("#relative(")
-		print_type(info.base_integer)
-		print_string(") ")
-		print_type(info.pointer)
 		
 	case Type_Info_Matrix:
 		print_string("matrix[")

+ 19 - 0
bin/RAD-LICENSE

@@ -0,0 +1,19 @@
+Copyright (c) 2024 Epic Games Tools
+
+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.

二进制
bin/radlink.exe


+ 5 - 2
build.bat

@@ -19,7 +19,11 @@ if "%VSCMD_ARG_TGT_ARCH%" neq "x64" (
 	)
 )
 
-for /f %%i in ('powershell get-date -format "{yyyyMMdd}"') do (
+pushd misc
+cl /nologo get-date.c
+popd
+
+for /f %%i in ('misc\get-date') do (
 	set CURR_DATE_TIME=%%i
 )
 set curr_year=%CURR_DATE_TIME:~0,4%
@@ -58,7 +62,6 @@ set V4=0
 set odin_version_full="%V1%.%V2%.%V3%.%V4%"
 set odin_version_raw="dev-%V1%-%V2%"
 
-
 set compiler_flags= -nologo -Oi -TP -fp:precise -Gm- -MP -FC -EHsc- -GR- -GF
 rem Parse source code as utf-8 even on shift-jis and other codepages
 rem See https://learn.microsoft.com/en-us/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170

+ 17 - 10
build_odin.sh

@@ -25,7 +25,8 @@ error() {
 
 # Brew advises people not to add llvm to their $PATH, so try and use brew to find it.
 if [ -z "$LLVM_CONFIG" ] &&  [ -n "$(command -v brew)" ]; then
-    if   [ -n "$(command -v $(brew --prefix llvm@18)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@18)/bin/llvm-config"
+    if   [ -n "$(command -v $(brew --prefix llvm@19)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@19)/bin/llvm-config"
+    elif [ -n "$(command -v $(brew --prefix llvm@18)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@18)/bin/llvm-config"
     elif [ -n "$(command -v $(brew --prefix llvm@17)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@17)/bin/llvm-config"
     elif [ -n "$(command -v $(brew --prefix llvm@14)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@14)/bin/llvm-config"
     fi
@@ -33,13 +34,15 @@ fi
 
 if [ -z "$LLVM_CONFIG" ]; then
 	# darwin, linux, openbsd
-	if   [ -n "$(command -v llvm-config-18)" ]; then LLVM_CONFIG="llvm-config-18"
+	if   [ -n "$(command -v llvm-config-19)" ]; then LLVM_CONFIG="llvm-config-19"
+	elif [ -n "$(command -v llvm-config-18)" ]; then LLVM_CONFIG="llvm-config-18"
 	elif [ -n "$(command -v llvm-config-17)" ]; then LLVM_CONFIG="llvm-config-17"
 	elif [ -n "$(command -v llvm-config-14)" ]; then LLVM_CONFIG="llvm-config-14"
 	elif [ -n "$(command -v llvm-config-13)" ]; then LLVM_CONFIG="llvm-config-13"
 	elif [ -n "$(command -v llvm-config-12)" ]; then LLVM_CONFIG="llvm-config-12"
 	elif [ -n "$(command -v llvm-config-11)" ]; then LLVM_CONFIG="llvm-config-11"
 	# freebsd
+	elif [ -n "$(command -v llvm-config19)" ]; then  LLVM_CONFIG="llvm-config19"
 	elif [ -n "$(command -v llvm-config18)" ]; then  LLVM_CONFIG="llvm-config18"
 	elif [ -n "$(command -v llvm-config17)" ]; then  LLVM_CONFIG="llvm-config17"
 	elif [ -n "$(command -v llvm-config14)" ]; then  LLVM_CONFIG="llvm-config14"
@@ -66,15 +69,15 @@ LLVM_VERSION_MAJOR="$(echo $LLVM_VERSION | awk -F. '{print $1}')"
 LLVM_VERSION_MINOR="$(echo $LLVM_VERSION | awk -F. '{print $2}')"
 LLVM_VERSION_PATCH="$(echo $LLVM_VERSION | awk -F. '{print $3}')"
 
-if [ $LLVM_VERSION_MAJOR -lt 11 ] || ([ $LLVM_VERSION_MAJOR -gt 14 ] && [ $LLVM_VERSION_MAJOR -lt 17 ]) || [ $LLVM_VERSION_MAJOR -gt 18 ]; then
-	error "Invalid LLVM version $LLVM_VERSION: must be 11, 12, 13, 14, 17 or 18"
+if [ $LLVM_VERSION_MAJOR -lt 11 ] || ([ $LLVM_VERSION_MAJOR -gt 14 ] && [ $LLVM_VERSION_MAJOR -lt 17 ]) || [ $LLVM_VERSION_MAJOR -gt 19 ]; then
+	error "Invalid LLVM version $LLVM_VERSION: must be 11, 12, 13, 14, 17, 18 or 19"
 fi
 
 case "$OS_NAME" in
 Darwin)
 	if [ "$OS_ARCH" = "arm64" ]; then
 		if [ $LLVM_VERSION_MAJOR -lt 13 ]; then
-			error "Invalid LLVM version $LLVM_VERSION: Darwin Arm64 requires LLVM 13, 14, 17 or 18"
+			error "Invalid LLVM version $LLVM_VERSION: Darwin Arm64 requires LLVM 13, 14, 17, 18 or 19"
 		fi
 	fi
 
@@ -152,9 +155,7 @@ build_odin() {
 }
 
 run_demo() {
-	if [ $# -eq 0 ] || [ "$1" = "debug" ]; then
-		./odin run examples/demo -vet -strict-style -- Hellope World
-	fi
+	./odin run examples/demo -vet -strict-style -- Hellope World
 }
 
 if [ $# -eq 0 ]; then
@@ -166,14 +167,20 @@ if [ $# -eq 0 ]; then
 elif [ $# -eq 1 ]; then
 	case $1 in
 	report)
-		[ ! -f "./odin" ] && build_odin debug
+		if [ ! -f "./odin" ]; then
+			build_odin debug
+			run_demo
+		fi
 		./odin report
 		;;
+	debug)
+		build_odin debug
+		run_demo
+		;;
 	*)
 		build_odin $1
 		;;
 	esac
-	run_demo
 else
 	error "Too many arguments!"
 fi

+ 0 - 25
core/c/frontend/preprocessor/const_expr.odin

@@ -1,25 +0,0 @@
-package c_frontend_preprocess
-
-import "core:c/frontend/tokenizer"
-
-const_expr :: proc(rest: ^^Token, tok: ^Token) -> i64 {
-	// TODO(bill): Handle const_expr correctly
-	// This is effectively a mini-parser
-
-	assert(rest != nil)
-	assert(tok != nil)
-	rest^ = tokenizer.new_eof(tok)
-	switch v in tok.val {
-	case i64:
-		return v
-	case f64:
-		return i64(v)
-	case string:
-		return 0
-	case []u16:
-		// TODO
-	case []u32:
-		// TODO
-	}
-	return 0
-}

+ 0 - 1510
core/c/frontend/preprocessor/preprocess.odin

@@ -1,1510 +0,0 @@
-package c_frontend_preprocess
-
-import "../tokenizer"
-
-import "core:strings"
-import "core:strconv"
-import "core:path/filepath"
-import "core:unicode/utf8"
-import "core:unicode/utf16"
-import "core:os"
-import "core:io"
-
-@(private)
-Tokenizer :: tokenizer.Tokenizer
-@(private)
-Token :: tokenizer.Token
-
-Error_Handler :: tokenizer.Error_Handler
-
-Macro_Param :: struct {
-	next: ^Macro_Param,
-	name: string,
-}
-
-Macro_Arg :: struct {
-	next: ^Macro_Arg,
-	name: string,
-	tok: ^Token,
-	is_va_args: bool,
-}
-
-Macro_Kind :: enum u8 {
-	Function_Like,
-	Value_Like,
-}
-
-Macro_Handler :: #type proc(^Preprocessor, ^Token) -> ^Token
-
-Macro :: struct {
-	name: string,
-	kind: Macro_Kind,
-	params: ^Macro_Param,
-	va_args_name: string,
-	body: ^Token,
-	handler: Macro_Handler,
-}
-
-Cond_Incl_State :: enum u8 {
-	In_Then,
-	In_Elif,
-	In_Else,
-}
-
-Cond_Incl :: struct {
-	next: ^Cond_Incl,
-	tok:  ^Token,
-	state:    Cond_Incl_State,
-	included: bool,
-}
-
-Pragma_Handler :: #type proc(^Preprocessor, ^Token)
-
-Preprocessor :: struct {
-	// Lookup tables
-	macros:         map[string]^Macro,
-	pragma_once:    map[string]bool,
-	include_guards: map[string]string,
-	filepath_cache: map[string]string,
-
-	// Include path data
-	include_paths: []string,
-
-	// Counter for __COUNTER__ macro
-	counter: i64,
-
-	// Include information
-	cond_incl: ^Cond_Incl,
-	include_level: int,
-	include_next_index: int,
-
-	wide_char_size: int,
-
-	// Mutable data
-	err:  Error_Handler,
-	warn: Error_Handler,
-	pragma_handler: Pragma_Handler,
-	error_count:   int,
-	warning_count: int,
-}
-
-MAX_INCLUDE_LEVEL :: 1024
-
-error :: proc(cpp: ^Preprocessor, tok: ^Token, msg: string, args: ..any) {
-	if cpp.err != nil {
-		cpp.err(tok.pos, msg, ..args)
-	}
-	cpp.error_count += 1
-}
-
-warn :: proc(cpp: ^Preprocessor, tok: ^Token, msg: string, args: ..any) {
-	if cpp.warn != nil {
-		cpp.warn(tok.pos, msg, ..args)
-	}
-	cpp.warning_count += 1
-}
-
-is_hash :: proc(tok: ^Token) -> bool {
-	return tok.at_bol && tok.lit == "#"
-}
-
-skip_line :: proc(cpp: ^Preprocessor, tok: ^Token) -> ^Token {
-	tok := tok
-	if tok.at_bol {
-		return tok
-	}
-	warn(cpp, tok, "extra token")
-	for tok.at_bol {
-		tok = tok.next
-	}
-	return tok
-}
-
-
-append_token :: proc(a, b: ^Token) -> ^Token {
-	if a.kind == .EOF {
-		return b
-	}
-
-	head: Token
-	curr := &head
-
-	for tok := a; tok.kind != .EOF; tok = tok.next {
-		curr.next = tokenizer.copy_token(tok)
-		curr = curr.next
-	}
-	curr.next = b
-	return head.next
-}
-
-
-is_hex_digit :: proc(x: byte) -> bool {
-	switch x {
-	case '0'..='9', 'a'..='f', 'A'..='F':
-		return true
-	}
-	return false
-}
-from_hex :: proc(x: byte) -> i32 {
-	switch x {
-	case '0'..='9':
-		return i32(x) - '0'
-	case 'a'..='f':
-		return i32(x) - 'a' + 10
-	case 'A'..='F':
-		return i32(x) - 'A' + 10
-	}
-	return 16
-}
-
-
-convert_pp_number :: proc(tok: ^Token) {
-	convert_pp_int :: proc(tok: ^Token) -> bool {
-		p := tok.lit
-		base := 10
-		if len(p) > 2 {
-			if strings.equal_fold(p[:2], "0x") && is_hex_digit(p[2]) {
-				p = p[2:]
-				base = 16
-			} else if strings.equal_fold(p[:2], "0b") && p[2] == '0' || p[2] == '1' {
-				p = p[2:]
-				base = 2
-			}
-		}
-		if base == 10 && p[0] == '0' {
-			base = 8
-		}
-
-
-		tok.val, _ = strconv.parse_i64_of_base(p, base)
-
-		l, u: int
-
-		suf: [3]byte
-		suf_n := 0
-		i := len(p)-1
-		for /**/; i >= 0 && suf_n < len(suf); i -= 1 {
-			switch p[i] {
-			case 'l', 'L':
-				suf[suf_n] = 'l'
-				l += 1
-				suf_n += 1
-			case 'u', 'U':
-				suf[suf_n] = 'u'
-				u += 1
-				suf_n += 1
-			}
-		}
-		if i < len(p) {
-			if !is_hex_digit(p[i]) && p[i] != '.' {
-				return false
-			}
-		}
-		if u > 1 {
-			return false
-		}
-
-		if l > 2 {
-			return false
-		}
-
-		if u == 1 {
-			switch l {
-			case 0: tok.type_hint = .Unsigned_Int
-			case 1: tok.type_hint = .Unsigned_Long
-			case 2: tok.type_hint = .Unsigned_Long_Long
-			}
-		} else {
-			switch l {
-			case 0: tok.type_hint = .Int
-			case 1: tok.type_hint = .Long
-			case 2: tok.type_hint = .Long_Long
-			}
-		}
-		return true
-	}
-
-	if convert_pp_int(tok) {
-		return
-	}
-
-	fval, _ := strconv.parse_f64(tok.lit)
-	tok.val = fval
-
-	end := tok.lit[len(tok.lit)-1]
-	switch end {
-	case 'f', 'F':
-		tok.type_hint = .Float
-	case 'l', 'L':
-		tok.type_hint = .Long_Double
-	case:
-		tok.type_hint = .Double
-	}
-
-}
-
-convert_pp_char :: proc(tok: ^Token) {
-	assert(len(tok.lit) >= 2)
-	r, _, _, _ := unquote_char(tok.lit, tok.lit[0])
-	tok.val = i64(r)
-
-	tok.type_hint = .Int
-	switch tok.prefix {
-	case "u": tok.type_hint = .UTF_16
-	case "U": tok.type_hint = .UTF_32
-	case "L": tok.type_hint = .UTF_Wide
-	}
-}
-
-wide_char_size :: proc(cpp: ^Preprocessor) -> int {
-	char_size := 4
-	if cpp.wide_char_size > 0 {
-		char_size = clamp(cpp.wide_char_size, 1, 4)
-		assert(char_size & (char_size-1) == 0)
-	}
-	return char_size
-}
-
-convert_pp_string :: proc(cpp: ^Preprocessor, tok: ^Token) {
-	assert(len(tok.lit) >= 2)
-	str, _, _ := unquote_string(tok.lit)
-	tok.val = str
-
-	char_size := 1
-
-	switch tok.prefix {
-	case "u8":
-		tok.type_hint = .UTF_8
-		char_size = 1
-	case "u":
-		tok.type_hint = .UTF_16
-		char_size = 2
-	case "U":
-		tok.type_hint = .UTF_32
-		char_size = 4
-	case "L":
-		tok.type_hint = .UTF_Wide
-		char_size = wide_char_size(cpp)
-	}
-
-	switch char_size {
-	case 2:
-		n: int
-		buf := make([]u16, len(str))
-		for c in str {
-			ch := c
-			if ch < 0x10000 {
-				buf[n] = u16(ch)
-				n += 1
-			} else {
-				ch -= 0x10000
-				buf[n+0] = 0xd800 + u16((ch >> 10) & 0x3ff)
-				buf[n+1] = 0xdc00 + u16(ch & 0x3ff)
-				n += 2
-			}
-		}
-		tok.val = buf[:n]
-	case 4:
-		n: int
-		buf := make([]u32, len(str))
-		for ch in str {
-			buf[n] = u32(ch)
-			n += 1
-		}
-		tok.val = buf[:n]
-	}
-
-}
-
-convert_pp_token :: proc(cpp: ^Preprocessor, t: ^Token, is_keyword: tokenizer.Is_Keyword_Proc) {
-	switch {
-	case t.kind == .Char:
-		convert_pp_char(t)
-	case t.kind == .String:
-		convert_pp_string(cpp, t)
-	case is_keyword != nil && is_keyword(t):
-		t.kind = .Keyword
-	case t.kind == .PP_Number:
-		convert_pp_number(t)
-	}
-}
-convert_pp_tokens :: proc(cpp: ^Preprocessor, tok: ^Token, is_keyword: tokenizer.Is_Keyword_Proc) {
-	for t := tok; t != nil && t.kind != .EOF; t = t.next {
-		convert_pp_token(cpp, tok, is_keyword)
-	}
-}
-
-join_adjacent_string_literals :: proc(cpp: ^Preprocessor, initial_tok: ^Token) {
-	for tok1 := initial_tok; tok1.kind != .EOF; /**/ {
-		if tok1.kind != .String || tok1.next.kind != .String {
-			tok1 = tok1.next
-			continue
-		}
-
-		type_hint := tokenizer.Token_Type_Hint.None
-		char_size := 1
-
-		start := tok1
-		for t := tok1; t != nil && t.kind == .String; t = t.next {
-			if t.val == nil {
-				convert_pp_string(cpp, t)
-			}
-			tok1 = t.next
-			if type_hint != t.type_hint {
-				if t.type_hint != .None && type_hint != .None {
-					error(cpp, t, "unsupported non-standard concatenation of string literals of different types")
-				}
-				prev_char_size := char_size
-
-				#partial switch type_hint {
-				case .UTF_8:    char_size = max(char_size, 1)
-				case .UTF_16:   char_size = max(char_size, 2)
-				case .UTF_32:   char_size = max(char_size, 4)
-				case .UTF_Wide: char_size = max(char_size, wide_char_size(cpp))
-				}
-
-				if type_hint == .None || prev_char_size < char_size {
-					type_hint = t.type_hint
-				}
-			}
-		}
-
-		// NOTE(bill): Verbose logic in order to correctly concantenate strings, even if they different in type
-		max_len := 0
-		switch char_size {
-		case 1:
-			for t := start; t != nil && t.kind == .String; t = t.next {
-				#partial switch v in t.val {
-				case string: max_len += len(v)
-				case []u16:  max_len += 2*len(v)
-				case []u32:  max_len += 4*len(v)
-				}
-			}
-			n := 0
-			buf := make([]byte, max_len)
-			for t := start; t != nil && t.kind == .String; t = t.next {
-				#partial switch v in t.val {
-				case string:
-					n += copy(buf[n:], v)
-				case []u16:
-					for i := 0; i < len(v); /**/ {
-						c1 := v[i]
-						r: rune
-						if !utf16.is_surrogate(rune(c1)) {
-							r = rune(c1)
-							i += 1
-						} else if i+1 == len(v) {
-							r = utf16.REPLACEMENT_CHAR
-							i += 1
-						} else {
-							c2 := v[i+1]
-							i += 2
-							r = utf16.decode_surrogate_pair(rune(c1), rune(c2))
-						}
-
-						b, w := utf8.encode_rune(r)
-						n += copy(buf[n:], b[:w])
-					}
-				case []u32:
-					for r in v {
-						b, w := utf8.encode_rune(rune(r))
-						n += copy(buf[n:], b[:w])
-					}
-				}
-			}
-
-			new_tok := tokenizer.copy_token(start)
-			new_tok.lit = ""
-			new_tok.val = string(buf[:n])
-			new_tok.next = tok1
-			new_tok.type_hint = type_hint
-			start^ = new_tok^
-		case 2:
-			for t := start; t != nil && t.kind == .String; t = t.next {
-				#partial switch v in t.val {
-				case string: max_len += len(v)
-				case []u16:  max_len += len(v)
-				case []u32:  max_len += 2*len(v)
-				}
-			}
-			n := 0
-			buf := make([]u16, max_len)
-			for t := start; t != nil && t.kind == .String; t = t.next {
-				#partial switch v in t.val {
-				case string:
-					for r in v {
-						if r >= 0x10000 {
-							c1, c2 := utf16.encode_surrogate_pair(r)
-							buf[n+0] = u16(c1)
-							buf[n+1] = u16(c2)
-							n += 2
-						} else {
-							buf[n] = u16(r)
-							n += 1
-						}
-					}
-				case []u16:
-					n += copy(buf[n:], v)
-				case []u32:
-					for r in v {
-						if r >= 0x10000 {
-							c1, c2 := utf16.encode_surrogate_pair(rune(r))
-							buf[n+0] = u16(c1)
-							buf[n+1] = u16(c2)
-							n += 2
-						} else {
-							buf[n] = u16(r)
-							n += 1
-						}
-					}
-				}
-			}
-
-			new_tok := tokenizer.copy_token(start)
-			new_tok.lit = ""
-			new_tok.val = buf[:n]
-			new_tok.next = tok1
-			new_tok.type_hint = type_hint
-			start^ = new_tok^
-		case 4:
-			for t := start; t != nil && t.kind == .String; t = t.next {
-				#partial switch v in t.val {
-				case string: max_len += len(v)
-				case []u16:  max_len += len(v)
-				case []u32:  max_len += len(v)
-				}
-			}
-			n := 0
-			buf := make([]u32, max_len)
-			for t := start; t != nil && t.kind == .String; t = t.next {
-				#partial switch v in t.val {
-				case string:
-					for r in v {
-						buf[n] = u32(r)
-						n += 1
-					}
-				case []u16:
-					for i := 0; i < len(v); /**/ {
-						c1 := v[i]
-						if !utf16.is_surrogate(rune(c1)) {
-							buf[n] = u32(c1)
-							n += 1
-							i += 1
-						} else if i+1 == len(v) {
-							buf[n] = utf16.REPLACEMENT_CHAR
-							n += 1
-							i += 1
-						} else {
-							c2 := v[i+1]
-							i += 2
-							r := utf16.decode_surrogate_pair(rune(c1), rune(c2))
-							buf[n] = u32(r)
-							n += 1
-						}
-					}
-				case []u32:
-					n += copy(buf[n:], v)
-				}
-			}
-
-			new_tok := tokenizer.copy_token(start)
-			new_tok.lit = ""
-			new_tok.val = buf[:n]
-			new_tok.next = tok1
-			new_tok.type_hint = type_hint
-			start^ = new_tok^
-		}
-	}
-}
-
-
-quote_string :: proc(s: string) -> []byte {
-	b := strings.builder_make(0, len(s)+2)
-	io.write_quoted_string(strings.to_writer(&b), s, '"')
-	return b.buf[:]
-}
-
-
-_init_tokenizer_from_preprocessor :: proc(t: ^Tokenizer, cpp: ^Preprocessor) -> ^Tokenizer {
-	t.warn = cpp.warn
-	t.err = cpp.err
-	return t
-}
-
-new_string_token :: proc(cpp: ^Preprocessor, str: string, tok: ^Token) -> ^Token {
-	assert(tok != nil)
-	assert(str != "")
-	t := _init_tokenizer_from_preprocessor(&Tokenizer{}, cpp)
-	src := quote_string(str)
-	return tokenizer.inline_tokenize(t, tok, src)
-}
-
-stringize :: proc(cpp: ^Preprocessor, hash, arg: ^Token) -> ^Token {
-	s := join_tokens(arg, nil)
-	return new_string_token(cpp, s, hash)
-}
-
-
-new_number_token :: proc(cpp: ^Preprocessor, i: i64, tok: ^Token) -> ^Token {
-	t := _init_tokenizer_from_preprocessor(&Tokenizer{}, cpp)
-	buf: [32]byte
-	n := len(strconv.append_int(buf[:], i, 10))
-	src := make([]byte, n)
-	copy(src, buf[:n])
-	return tokenizer.inline_tokenize(t, tok, src)
-}
-
-
-find_macro :: proc(cpp: ^Preprocessor, tok: ^Token) -> ^Macro {
-	if tok.kind != .Ident {
-		return nil
-	}
-	return cpp.macros[tok.lit]
-}
-
-add_macro :: proc(cpp: ^Preprocessor, name: string, kind: Macro_Kind, body: ^Token) -> ^Macro {
-	m := new(Macro)
-	m.name = name
-	m.kind = kind
-	m.body = body
-	cpp.macros[name] = m
-	return m
-}
-
-
-undef_macro :: proc(cpp: ^Preprocessor, name: string) {
-	delete_key(&cpp.macros, name)
-}
-
-add_builtin :: proc(cpp: ^Preprocessor, name: string, handler: Macro_Handler) -> ^Macro {
-	m := add_macro(cpp, name, .Value_Like, nil)
-	m.handler = handler
-	return m
-}
-
-
-skip :: proc(cpp: ^Preprocessor, tok: ^Token, op: string) -> ^Token {
-	if tok.lit != op {
-		error(cpp, tok, "expected '%q'", op)
-	}
-	return tok.next
-}
-
-consume :: proc(rest: ^^Token, tok: ^Token, lit: string) -> bool {
-	if tok.lit == lit {
-		rest^ = tok.next
-		return true
-	}
-	rest^ = tok
-	return false
-}
-
-read_macro_params :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token) -> (param: ^Macro_Param, va_args_name: string) {
-	head: Macro_Param
-	curr := &head
-
-	tok := tok
-	for tok.lit != ")" && tok.kind != .EOF {
-		if curr != &head {
-			tok = skip(cpp, tok, ",")
-		}
-
-		if tok.lit == "..." {
-			va_args_name = "__VA_ARGS__"
-			rest^ = skip(cpp, tok.next, ")")
-			param = head.next
-			return
-		}
-
-		if tok.kind != .Ident {
-			error(cpp, tok, "expected an identifier")
-		}
-
-		if tok.next.lit == "..." {
-			va_args_name = tok.lit
-			rest^ = skip(cpp, tok.next.next, ")")
-			param = head.next
-			return
-		}
-
-		m := new(Macro_Param)
-		m.name = tok.lit
-		curr.next = m
-		curr = curr.next
-		tok = tok.next
-	}
-
-
-	rest^ = tok.next
-	param = head.next
-	return
-}
-
-copy_line :: proc(rest: ^^Token, tok: ^Token) -> ^Token {
-	head: Token
-	curr := &head
-
-	tok := tok
-	for ; !tok.at_bol; tok = tok.next {
-		curr.next = tokenizer.copy_token(tok)
-		curr = curr.next
-	}
-	curr.next = tokenizer.new_eof(tok)
-	rest^ = tok
-	return head.next
-}
-
-read_macro_definition :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token) {
-	tok := tok
-	if tok.kind != .Ident {
-		error(cpp, tok, "macro name must be an identifier")
-	}
-	name := tok.lit
-	tok = tok.next
-
-	if !tok.has_space && tok.lit == "(" {
-		params, va_args_name := read_macro_params(cpp, &tok, tok.next)
-
-		m := add_macro(cpp, name, .Function_Like, copy_line(rest, tok))
-		m.params = params
-		m.va_args_name = va_args_name
-	} else {
-		add_macro(cpp, name, .Value_Like, copy_line(rest, tok))
-	}
-}
-
-
-join_tokens :: proc(tok, end: ^Token) -> string {
-	n := 1
-	for t := tok; t != end && t.kind != .EOF; t = t.next {
-		if t != tok && t.has_space {
-			n += 1
-		}
-		n += len(t.lit)
-	}
-
-	buf := make([]byte, n)
-
-	pos := 0
-	for t := tok; t != end && t.kind != .EOF; t = t.next {
-		if t != tok && t.has_space {
-			buf[pos] = ' '
-			pos += 1
-		}
-		copy(buf[pos:], t.lit)
-		pos += len(t.lit)
-	}
-
-	return string(buf[:pos])
-}
-
-read_include_filename :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token) -> (filename: string, is_quote: bool) {
-	tok := tok
-
-	if tok.kind == .String {
-		rest^ = skip_line(cpp, tok.next)
-		filename = tok.lit[1:len(tok.lit)-1]
-		is_quote = true
-		return
-	}
-
-	if tok.lit == "<" {
-		start := tok
-		for ; tok.kind != .EOF; tok = tok.next {
-			if tok.at_bol || tok.kind == .EOF {
-				error(cpp, tok, "expected '>'")
-			}
-			is_quote = false
-			if tok.lit == ">" {
-				break
-			}
-		}
-		rest^ = skip_line(cpp, tok.next)
-		filename = join_tokens(start.next, tok)
-		return
-	}
-
-	if tok.kind == .Ident {
-		tok2 := preprocess_internal(cpp, copy_line(rest, tok))
-		return read_include_filename(cpp, &tok2, tok2)
-	}
-
-	error(cpp, tok, "expected a filename")
-	return
-}
-
-skip_cond_incl :: proc(tok: ^Token) -> ^Token {
-	next_skip :: proc(tok: ^Token) -> ^Token {
-		tok := tok
-		for tok.kind != .EOF {
-			if is_hash(tok) {
-				switch tok.next.lit {
-				case "if", "ifdef", "ifndef":
-					tok = next_skip(tok.next.next)
-					continue
-
-				case "endif":
-					return tok.next.next
-				}
-			}
-			tok = tok.next
-		}
-		return tok
-	}
-
-	tok := tok
-
-	loop: for tok.kind != .EOF {
-		if is_hash(tok) {
-			switch tok.next.lit {
-			case "if", "ifdef", "ifndef":
-				tok = next_skip(tok.next.next)
-				continue loop
-
-			case "elif", "else", "endif":
-				break loop
-			}
-		}
-
-		tok = tok.next
-	}
-	return tok
-}
-
-check_for_include_guard :: proc(tok: ^Token) -> (guard: string, ok: bool) {
-	if !is_hash(tok) || tok.next.lit != "ifndef" {
-		return
-	}
-	tok := tok
-	tok = tok.next.next
-
-	if tok.kind != .Ident {
-		return
-	}
-
-	m := tok.lit
-	tok = tok.next
-
-	if !is_hash(tok) || tok.next.lit != "define" || tok.next.lit != "macro" {
-		return
-	}
-
-	for tok.kind != .EOF {
-		if !is_hash(tok) {
-			tok = tok.next
-			continue
-		}
-
-		if tok.next.lit == "endif" && tok.next.next.kind == .EOF {
-			return m, true
-		}
-
-		switch tok.lit {
-		case "if", "ifdef", "ifndef":
-			tok = skip_cond_incl(tok.next)
-		case:
-			tok = tok.next
-		}
-	}
-	return
-}
-
-include_file :: proc(cpp: ^Preprocessor, tok: ^Token, path: string, filename_tok: ^Token) -> ^Token {
-	if cpp.pragma_once[path] {
-		return tok
-	}
-
-	guard_name, guard_name_found := cpp.include_guards[path]
-	if guard_name_found && cpp.macros[guard_name] != nil {
-		return tok
-	}
-
-	if !os.exists(path) {
-		error(cpp, filename_tok, "%s: cannot open file", path)
-		return tok
-	}
-
-	cpp.include_level += 1
-	if cpp.include_level > MAX_INCLUDE_LEVEL {
-		error(cpp, tok, "exceeded maximum nest amount: %d", MAX_INCLUDE_LEVEL)
-		return tok
-	}
-
-	t := _init_tokenizer_from_preprocessor(&Tokenizer{}, cpp)
-	tok2 := tokenizer.tokenize_file(t, path, /*file.id*/1)
-	if tok2 == nil {
-		error(cpp, filename_tok, "%s: cannot open file", path)
-	}
-	cpp.include_level -= 1
-
-	guard_name, guard_name_found = check_for_include_guard(tok2)
-	if guard_name_found {
-		cpp.include_guards[path] = guard_name
-	}
-
-	return append_token(tok2, tok)
-}
-
-find_arg :: proc(args: ^Macro_Arg, tok: ^Token) -> ^Macro_Arg {
-	for ap := args; ap != nil; ap = ap.next {
-		if tok.lit == ap.name {
-			return ap
-		}
-	}
-	return nil
-}
-
-paste :: proc(cpp: ^Preprocessor, lhs, rhs: ^Token) -> ^Token {
-	buf := strings.concatenate({lhs.lit, rhs.lit})
-	t := _init_tokenizer_from_preprocessor(&Tokenizer{}, cpp)
-	tok := tokenizer.inline_tokenize(t, lhs, transmute([]byte)buf)
-	if tok.next.kind != .EOF {
-		error(cpp, lhs, "pasting forms '%s', an invalid token", buf)
-	}
-	return tok
-}
-
-has_varargs :: proc(args: ^Macro_Arg) -> bool {
-	for ap := args; ap != nil; ap = ap.next {
-		if ap.name == "__VA_ARGS__" {
-			return ap.tok.kind != .EOF
-		}
-	}
-	return false
-}
-
-substitute_token :: proc(cpp: ^Preprocessor, tok: ^Token, args: ^Macro_Arg) -> ^Token {
-	head: Token
-	curr := &head
-	tok := tok
-	for tok.kind != .EOF {
-		if tok.lit == "#" {
-			arg := find_arg(args, tok.next)
-			if arg == nil {
-				error(cpp, tok.next, "'#' is not followed by a macro parameter")
-			}
-			arg_tok := arg.tok if arg != nil else tok.next
-			curr.next = stringize(cpp, tok, arg_tok)
-			curr = curr.next
-			tok = tok.next.next
-			continue
-		}
-
-		if tok.lit == "," && tok.next.lit == "##" {
-			if arg := find_arg(args, tok.next.next); arg != nil && arg.is_va_args {
-				if arg.tok.kind == .EOF {
-					tok = tok.next.next.next
-				} else {
-					curr.next = tokenizer.copy_token(tok)
-					curr = curr.next
-					tok = tok.next.next
-				}
-				continue
-			}
-		}
-
-		if tok.lit == "##" {
-			if curr == &head {
-				error(cpp, tok, "'##' cannot appear at start of macro expansion")
-			}
-			if tok.next.kind == .EOF {
-				error(cpp, tok, "'##' cannot appear at end of macro expansion")
-			}
-
-			if arg := find_arg(args, tok.next); arg != nil {
-				if arg.tok.kind != .EOF {
-					curr^ = paste(cpp, curr, arg.tok)^
-					for t := arg.tok.next; t.kind != .EOF; t = t.next {
-						curr.next = tokenizer.copy_token(t)
-						curr = curr.next
-					}
-				}
-				tok = tok.next.next
-				continue
-			}
-
-			curr^ = paste(cpp, curr, tok.next)^
-			tok = tok.next.next
-			continue
-		}
-
-		arg := find_arg(args, tok)
-
-		if arg != nil && tok.next.lit == "##" {
-			rhs := tok.next.next
-
-			if arg.tok.kind == .EOF {
-				args2 := find_arg(args, rhs)
-				if args2 != nil {
-					for t := args.tok; t.kind != .EOF; t = t.next {
-						curr.next = tokenizer.copy_token(t)
-						curr = curr.next
-					}
-				} else {
-					curr.next = tokenizer.copy_token(rhs)
-					curr = curr.next
-				}
-				tok = rhs.next
-				continue
-			}
-
-			for t := arg.tok; t.kind != .EOF; t = t.next {
-				curr.next = tokenizer.copy_token(t)
-				curr = curr.next
-			}
-			tok = tok.next
-			continue
-		}
-
-		if tok.lit == "__VA_OPT__" && tok.next.lit == "(" {
-			opt_arg := read_macro_arg_one(cpp, &tok, tok.next.next, true)
-			if has_varargs(args) {
-				for t := opt_arg.tok; t.kind != .EOF; t = t.next {
-					curr.next = t
-					curr = curr.next
-				}
-			}
-			tok = skip(cpp, tok, ")")
-			continue
-		}
-
-		if arg != nil {
-			t := preprocess_internal(cpp, arg.tok)
-			t.at_bol = tok.at_bol
-			t.has_space = tok.has_space
-			for ; t.kind != .EOF; t = t.next {
-				curr.next = tokenizer.copy_token(t)
-				curr = curr.next
-			}
-			tok = tok.next
-			continue
-		}
-
-		curr.next = tokenizer.copy_token(tok)
-		curr = curr.next
-		tok = tok.next
-		continue
-	}
-
-	curr.next = tok
-	return head.next
-}
-
-read_macro_arg_one :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token, read_rest: bool) -> ^Macro_Arg {
-	tok := tok
-	head: Token
-	curr := &head
-	level := 0
-	for {
-		if level == 0 && tok.lit == ")" {
-			break
-		}
-		if level == 0 && !read_rest && tok.lit == "," {
-			break
-		}
-
-		if tok.kind == .EOF {
-			error(cpp, tok, "premature end of input")
-		}
-
-		switch tok.lit {
-		case "(": level += 1
-		case ")": level -= 1
-		}
-
-		curr.next = tokenizer.copy_token(tok)
-		curr = curr.next
-		tok = tok.next
-	}
-	curr.next = tokenizer.new_eof(tok)
-
-	arg := new(Macro_Arg)
-	arg.tok = head.next
-	rest^ = tok
-	return arg
-}
-
-read_macro_args :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token, params: ^Macro_Param, va_args_name: string) -> ^Macro_Arg {
-	tok := tok
-	start := tok
-	tok = tok.next.next
-
-	head: Macro_Arg
-	curr := &head
-
-	pp := params
-	for ; pp != nil; pp = pp.next {
-		if curr != &head {
-			tok = skip(cpp, tok, ",")
-		}
-		curr.next = read_macro_arg_one(cpp, &tok, tok, false)
-		curr = curr.next
-		curr.name = pp.name
-	}
-
-	if va_args_name != "" {
-		arg: ^Macro_Arg
-		if tok.lit == ")" {
-			arg = new(Macro_Arg)
-			arg.tok = tokenizer.new_eof(tok)
-		} else {
-			if pp != params {
-				tok = skip(cpp, tok, ",")
-			}
-			arg = read_macro_arg_one(cpp, &tok, tok, true)
-		}
-		arg.name = va_args_name
-		arg.is_va_args = true
-		curr.next = arg
-		curr = curr.next
-	} else if pp != nil {
-		error(cpp, start, "too many arguments")
-	}
-
-	skip(cpp, tok, ")")
-	rest^ = tok
-	return head.next
-}
-
-expand_macro :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token) -> bool {
-	if tokenizer.hide_set_contains(tok.hide_set, tok.lit) {
-		return false
-	}
-	tok := tok
-	m := find_macro(cpp, tok)
-	if m == nil {
-		return false
-	}
-
-	if m.handler != nil {
-		rest^ = m.handler(cpp, tok)
-		rest^.next = tok.next
-		return true
-	}
-
-	if m.kind == .Value_Like {
-		hs := tokenizer.hide_set_union(tok.hide_set, tokenizer.new_hide_set(m.name))
-		body := tokenizer.add_hide_set(m.body, hs)
-		for t := body; t.kind != .EOF; t = t.next {
-			t.origin = tok
-		}
-		rest^ = append_token(body, tok.next)
-		rest^.at_bol = tok.at_bol
-		rest^.has_space = tok.has_space
-		return true
-	}
-
-	if tok.next.lit != "(" {
-		return false
-	}
-
-	macro_token := tok
-	args := read_macro_args(cpp, &tok, tok, m.params, m.va_args_name)
-	close_paren := tok
-
-	hs := tokenizer.hide_set_intersection(macro_token.hide_set, close_paren.hide_set)
-	hs = tokenizer.hide_set_union(hs, tokenizer.new_hide_set(m.name))
-
-	body := substitute_token(cpp, m.body, args)
-	body = tokenizer.add_hide_set(body, hs)
-	for t := body; t.kind != .EOF; t = t.next {
-		t.origin = macro_token
-	}
-	rest^ = append_token(body, tok.next)
-	rest^.at_bol = macro_token.at_bol
-	rest^.has_space = macro_token.has_space
-	return true
-}
-
-search_include_next :: proc(cpp: ^Preprocessor, filename: string) -> (path: string, ok: bool) {
-	for ; cpp.include_next_index < len(cpp.include_paths); cpp.include_next_index += 1 {
-		tpath := filepath.join({cpp.include_paths[cpp.include_next_index], filename}, allocator=context.temp_allocator)
-		if os.exists(tpath) {
-			return strings.clone(tpath), true
-		}
-	}
-	return
-}
-
-search_include_paths :: proc(cpp: ^Preprocessor, filename: string) -> (path: string, ok: bool) {
-	if filepath.is_abs(filename) {
-		return filename, true
-	}
-
-	if path, ok = cpp.filepath_cache[filename]; ok {
-		return
-	}
-
-	for include_path in cpp.include_paths {
-		tpath := filepath.join({include_path, filename}, allocator=context.temp_allocator)
-		if os.exists(tpath) {
-			path, ok = strings.clone(tpath), true
-			cpp.filepath_cache[filename] = path
-			return
-		}
-	}
-
-	return
-}
-
-read_const_expr :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token) -> ^Token {
-	tok := tok
-	tok = copy_line(rest, tok)
-	head: Token
-	curr := &head
-	for tok.kind != .EOF {
-		if tok.lit == "defined" {
-			start := tok
-			has_paren := consume(&tok, tok.next, "(")
-			if tok.kind != .Ident {
-				error(cpp, start, "macro name must be an identifier")
-			}
-			m := find_macro(cpp, tok)
-			tok = tok.next
-
-			if has_paren {
-				tok = skip(cpp, tok, ")")
-			}
-
-			curr.next = new_number_token(cpp, 1 if m != nil else 0, start)
-			curr = curr.next
-			continue
-		}
-
-		curr.next = tok
-		curr = curr.next
-		tok = tok.next
-	}
-
-	curr.next = tok
-	return head.next
-}
-
-eval_const_expr :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token) -> (val: i64) {
-	tok := tok
-	start := tok
-	expr := read_const_expr(cpp, rest, tok.next)
-	expr = preprocess_internal(cpp, expr)
-
-	if expr.kind == .EOF {
-		error(cpp, start, "no expression")
-	}
-
-	for t := expr; t.kind != .EOF; t = t.next {
-		if t.kind == .Ident {
-			next := t.next
-			t^ = new_number_token(cpp, 0, t)^
-			t.next = next
-		}
-	}
-
-	val = 1
-	convert_pp_tokens(cpp, expr, tokenizer.default_is_keyword)
-
-	rest2: ^Token
-	val = const_expr(&rest2, expr)
-	if rest2 != nil && rest2.kind != .EOF {
-		error(cpp, rest2, "extra token")
-	}
-	return
-}
-
-push_cond_incl :: proc(cpp: ^Preprocessor, tok: ^Token, included: bool) -> ^Cond_Incl {
-	ci := new(Cond_Incl)
-	ci.next = cpp.cond_incl
-	ci.state = .In_Then
-	ci.tok = tok
-	ci.included = included
-	cpp.cond_incl = ci
-	return ci
-}
-
-read_line_marker:: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token) {
-	tok := tok
-	start := tok
-	tok = preprocess(cpp, copy_line(rest, tok))
-	if tok.kind != .Number {
-		error(cpp, tok, "invalid line marker")
-	}
-	ival, _ := tok.val.(i64)
-	start.file.line_delta = int(ival - i64(start.pos.line))
-	tok = tok.next
-	if tok.kind == .EOF {
-		return
-	}
-
-	if tok.kind != .String {
-		error(cpp, tok, "filename expected")
-	}
-	start.file.display_name = tok.lit
-}
-
-preprocess_internal :: proc(cpp: ^Preprocessor, tok: ^Token) -> ^Token {
-	head: Token
-	curr := &head
-
-	tok := tok
-	for tok != nil && tok.kind != .EOF {
-		if expand_macro(cpp, &tok, tok) {
-			continue
-		}
-
-		if !is_hash(tok) {
-			if tok.file != nil {
-				tok.line_delta = tok.file.line_delta
-			}
-			curr.next = tok
-			curr = curr.next
-			tok = tok.next
-			continue
-		}
-
-		start := tok
-		tok = tok.next
-
-		switch tok.lit {
-		case "include":
-			filename, is_quote := read_include_filename(cpp, &tok, tok.next)
-			is_absolute := filepath.is_abs(filename)
-			if is_absolute {
-				tok = include_file(cpp, tok, filename, start.next.next)
-				continue
-			}
-
-			if is_quote {
-				dir := ""
-				if start.file != nil {
-					dir = filepath.dir(start.file.name)
-				}
-				path := filepath.join({dir, filename})
-				if os.exists(path) {
-					tok = include_file(cpp, tok, path, start.next.next)
-					continue
-				}
-			}
-
-			path, ok := search_include_paths(cpp, filename)
-			if !ok {
-				path = filename
-			}
-			tok = include_file(cpp, tok, path, start.next.next)
-			continue
-
-		case "include_next":
-			filename, _ := read_include_filename(cpp, &tok, tok.next)
-			path, ok := search_include_next(cpp, filename)
-			if !ok {
-				path = filename
-			}
-			tok = include_file(cpp, tok, path, start.next.next)
-			continue
-
-		case "define":
-			read_macro_definition(cpp, &tok, tok.next)
-			continue
-
-		case "undef":
-			tok = tok.next
-			if tok.kind != .Ident {
-				error(cpp, tok, "macro name must be an identifier")
-			}
-			undef_macro(cpp, tok.lit)
-			tok = skip_line(cpp, tok.next)
-			continue
-
-		case "if":
-			val := eval_const_expr(cpp, &tok, tok)
-			push_cond_incl(cpp, start, val != 0)
-			if val == 0 {
-				tok = skip_cond_incl(tok)
-			}
-			continue
-
-		case "ifdef":
-			defined := find_macro(cpp, tok.next)
-			push_cond_incl(cpp, tok, defined != nil)
-			tok = skip_line(cpp, tok.next.next)
-			if defined == nil {
-				tok = skip_cond_incl(tok)
-			}
-			continue
-
-		case "ifndef":
-			defined := find_macro(cpp, tok.next)
-			push_cond_incl(cpp, tok, defined != nil)
-			tok = skip_line(cpp, tok.next.next)
-			if !(defined == nil) {
-				tok = skip_cond_incl(tok)
-			}
-			continue
-
-		case "elif":
-			if cpp.cond_incl == nil || cpp.cond_incl.state == .In_Else {
-				error(cpp, start, "stray #elif")
-			}
-			if cpp.cond_incl != nil {
-				cpp.cond_incl.state = .In_Elif
-			}
-
-			if (cpp.cond_incl != nil && !cpp.cond_incl.included) && eval_const_expr(cpp, &tok, tok) != 0 {
-				cpp.cond_incl.included = true
-			} else {
-				tok = skip_cond_incl(tok)
-			}
-			continue
-
-		case "else":
-			if cpp.cond_incl == nil || cpp.cond_incl.state == .In_Else {
-				error(cpp, start, "stray #else")
-			}
-			if cpp.cond_incl != nil {
-				cpp.cond_incl.state = .In_Else
-			}
-			tok = skip_line(cpp, tok.next)
-
-			if cpp.cond_incl != nil {
-				tok = skip_cond_incl(tok)
-			}
-			continue
-
-		case "endif":
-			if cpp.cond_incl == nil {
-				error(cpp, start, "stray #endif")
-			} else {
-				cpp.cond_incl = cpp.cond_incl.next
-			}
-			tok = skip_line(cpp, tok.next)
-			continue
-
-		case "line":
-			read_line_marker(cpp, &tok, tok.next)
-			continue
-
-		case "pragma":
-			if tok.next.lit == "once" {
-				cpp.pragma_once[tok.pos.file] = true
-				tok = skip_line(cpp, tok.next.next)
-				continue
-			}
-
-			pragma_tok, pragma_end := tok, tok
-
-			for tok != nil && tok.kind != .EOF {
-				pragma_end = tok
-				tok = tok.next
-				if tok.at_bol {
-					break
-				}
-			}
-			pragma_end.next = tokenizer.new_eof(tok)
-			if cpp.pragma_handler != nil {
-				cpp.pragma_handler(cpp, pragma_tok.next)
-				continue
-			}
-
-			continue
-
-		case "error":
-			error(cpp, tok, "error")
-		}
-
-		if tok.kind == .PP_Number {
-			read_line_marker(cpp, &tok, tok)
-			continue
-		}
-
-		if !tok.at_bol {
-			error(cpp, tok, "invalid preprocessor directive")
-		}
-	}
-
-	curr.next = tok
-	return head.next
-}
-
-
-preprocess :: proc(cpp: ^Preprocessor, tok: ^Token) -> ^Token {
-	tok := tok
-	tok = preprocess_internal(cpp, tok)
-	if cpp.cond_incl != nil {
-		error(cpp, tok, "unterminated conditional directive")
-	}
-	convert_pp_tokens(cpp, tok, tokenizer.default_is_keyword)
-	join_adjacent_string_literals(cpp, tok)
-	for t := tok; t != nil; t = t.next {
-		t.pos.line += t.line_delta
-	}
-	return tok
-}
-
-
-define_macro :: proc(cpp: ^Preprocessor, name, def: string) {
-	src := transmute([]byte)def
-
-	file := new(tokenizer.File)
-	file.id = -1
-	file.src = src
-	file.name = "<built-in>"
-	file.display_name = file.name
-
-
-	t := _init_tokenizer_from_preprocessor(&Tokenizer{}, cpp)
-	tok := tokenizer.tokenize(t, file)
-	add_macro(cpp, name, .Value_Like, tok)
-}
-
-
-file_macro :: proc(cpp: ^Preprocessor, tok: ^Token) -> ^Token {
-	tok := tok
-	for tok.origin != nil {
-		tok = tok.origin
-	}
-	i := i64(tok.pos.line + tok.file.line_delta)
-	return new_number_token(cpp, i, tok)
-}
-line_macro :: proc(cpp: ^Preprocessor, tok: ^Token) -> ^Token {
-	tok := tok
-	for tok.origin != nil {
-		tok = tok.origin
-	}
-	return new_string_token(cpp, tok.file.display_name, tok)
-}
-counter_macro :: proc(cpp: ^Preprocessor, tok: ^Token) -> ^Token {
-	i := cpp.counter
-	cpp.counter += 1
-	return new_number_token(cpp, i, tok)
-}
-
-init_default_macros :: proc(cpp: ^Preprocessor) {
-	define_macro(cpp, "__C99_MACRO_WITH_VA_ARGS", "1")
-	define_macro(cpp, "__alignof__", "_Alignof")
-	define_macro(cpp, "__const__", "const")
-	define_macro(cpp, "__inline__", "inline")
-	define_macro(cpp, "__signed__", "signed")
-	define_macro(cpp, "__typeof__", "typeof")
-	define_macro(cpp, "__volatile__", "volatile")
-
-	add_builtin(cpp, "__FILE__", file_macro)
-	add_builtin(cpp, "__LINE__", line_macro)
-	add_builtin(cpp, "__COUNTER__", counter_macro)
-}
-
-init_lookup_tables :: proc(cpp: ^Preprocessor, allocator := context.allocator) {
-	context.allocator = allocator
-	reserve(&cpp.macros,         max(16, cap(cpp.macros)))
-	reserve(&cpp.pragma_once,    max(16, cap(cpp.pragma_once)))
-	reserve(&cpp.include_guards, max(16, cap(cpp.include_guards)))
-	reserve(&cpp.filepath_cache, max(16, cap(cpp.filepath_cache)))
-}
-
-
-init_defaults :: proc(cpp: ^Preprocessor, lookup_tables_allocator := context.allocator) {
-	if cpp.warn == nil {
-		cpp.warn = tokenizer.default_warn_handler
-	}
-	if cpp.err == nil {
-		cpp.err = tokenizer.default_error_handler
-	}
-	init_lookup_tables(cpp, lookup_tables_allocator)
-	init_default_macros(cpp)
-}

+ 0 - 154
core/c/frontend/preprocessor/unquote.odin

@@ -1,154 +0,0 @@
-package c_frontend_preprocess
-
-import "core:unicode/utf8"
-
-unquote_char :: proc(str: string, quote: byte) -> (r: rune, multiple_bytes: bool, tail_string: string, success: bool) {
-	hex_to_int :: proc(c: byte) -> int {
-		switch c {
-		case '0'..='9': return int(c-'0')
-		case 'a'..='f': return int(c-'a')+10
-		case 'A'..='F': return int(c-'A')+10
-		}
-		return -1
-	}
-	w: int
-
-	if str[0] == quote && quote == '"' {
-		return
-	} else if str[0] >= 0x80 {
-		r, w = utf8.decode_rune_in_string(str)
-		return r, true, str[w:], true
-	} else if str[0] != '\\' {
-		return rune(str[0]), false, str[1:], true
-	}
-
-	if len(str) <= 1 {
-		return
-	}
-	s := str
-	c := s[1]
-	s = s[2:]
-
-	switch c {
-	case: r = rune(c)
-
-	case 'a':  r = '\a'
-	case 'b':  r = '\b'
-	case 'e':  r = '\e'
-	case 'f':  r = '\f'
-	case 'n':  r = '\n'
-	case 'r':  r = '\r'
-	case 't':  r = '\t'
-	case 'v':  r = '\v'
-	case '\\': r = '\\'
-
-	case '"':  r = '"'
-	case '\'': r = '\''
-
-	case '0'..='7':
-		v := int(c-'0')
-		if len(s) < 2 {
-			return
-		}
-		for i in 0..<len(s) {
-			d := int(s[i]-'0')
-			if d < 0 || d > 7 {
-				return
-			}
-			v = (v<<3) | d
-		}
-		s = s[2:]
-		if v > 0xff {
-			return
-		}
-		r = rune(v)
-
-	case 'x', 'u', 'U':
-		count: int
-		switch c {
-		case 'x': count = 2
-		case 'u': count = 4
-		case 'U': count = 8
-		}
-
-		if len(s) < count {
-			return
-		}
-
-		for i in 0..<count {
-			d := hex_to_int(s[i])
-			if d < 0 {
-				return
-			}
-			r = (r<<4) | rune(d)
-		}
-		s = s[count:]
-		if c == 'x' {
-			break
-		}
-		if r > utf8.MAX_RUNE {
-			return
-		}
-		multiple_bytes = true
-	}
-
-	success = true
-	tail_string = s
-	return
-}
-
-unquote_string :: proc(lit: string, allocator := context.allocator) -> (res: string, allocated, success: bool) {
-	contains_rune :: proc(s: string, r: rune) -> int {
-		for c, offset in s {
-			if c == r {
-				return offset
-			}
-		}
-		return -1
-	}
-
-	assert(len(lit) >= 2)
-
-	s := lit
-	quote := '"'
-
-	if s == `""` {
-		return "", false, true
-	}
-
-	if contains_rune(s, '\n') >= 0 {
-		return s, false, false
-	}
-
-	if contains_rune(s, '\\') < 0 && contains_rune(s, quote) < 0 {
-		if quote == '"' {
-			return s, false, true
-		}
-	}
-	s = s[1:len(s)-1]
-
-
-	buf_len := 3*len(s) / 2
-	buf := make([]byte, buf_len, allocator)
-	offset := 0
-	for len(s) > 0 {
-		r, multiple_bytes, tail_string, ok := unquote_char(s, byte(quote))
-		if !ok {
-			delete(buf)
-			return s, false, false
-		}
-		s = tail_string
-		if r < 0x80 || !multiple_bytes {
-			buf[offset] = byte(r)
-			offset += 1
-		} else {
-			b, w := utf8.encode_rune(r)
-			copy(buf[offset:], b[:w])
-			offset += w
-		}
-	}
-
-	new_string := string(buf[:offset])
-
-	return new_string, true, true
-}

+ 0 - 31
core/c/frontend/tokenizer/doc.odin

@@ -1,31 +0,0 @@
-/*
-Example:
-	package demo
-
-	import tokenizer "core:c/frontend/tokenizer"
-	import preprocessor "core:c/frontend/preprocessor"
-	import "core:fmt"
-
-	main :: proc() {
-		t := &tokenizer.Tokenizer{};
-		tokenizer.init_defaults(t);
-
-		cpp := &preprocessor.Preprocessor{};
-		cpp.warn, cpp.err = t.warn, t.err;
-		preprocessor.init_lookup_tables(cpp);
-		preprocessor.init_default_macros(cpp);
-		cpp.include_paths = {"my/path/to/include"};
-
-		tok := tokenizer.tokenize_file(t, "the/source/file.c", 1);
-
-		tok = preprocessor.preprocess(cpp, tok);
-		if tok != nil {
-			for t := tok; t.kind != .EOF; t = t.next {
-				fmt.println(t.lit);
-			}
-		}
-
-		fmt.println("[Done]");
-	}
-*/
-package c_frontend_tokenizer

+ 0 - 68
core/c/frontend/tokenizer/hide_set.odin

@@ -1,68 +0,0 @@
-package c_frontend_tokenizer
-
-// NOTE(bill): This is a really dumb approach for a hide set,
-// but it's really simple and probably fast enough in practice
-
-
-Hide_Set :: struct {
-	next: ^Hide_Set,
-	name: string,
-}
-
-
-new_hide_set :: proc(name: string) -> ^Hide_Set {
-	hs := new(Hide_Set)
-	hs.name = name
-	return hs
-}
-
-hide_set_contains :: proc(hs: ^Hide_Set, name: string) -> bool {
-	for h := hs; h != nil; h = h.next {
-		if h.name == name {
-			return true
-		}
-	}
-	return false
-}
-
-
-hide_set_union :: proc(a, b: ^Hide_Set) -> ^Hide_Set {
-	head: Hide_Set
-	curr := &head
-
-	for h := a; h != nil; h = h.next {
-		curr.next = new_hide_set(h.name)
-		curr = curr.next
-	}
-	curr.next = b
-	return head.next
-}
-
-
-hide_set_intersection :: proc(a, b: ^Hide_Set) -> ^Hide_Set {
-	head: Hide_Set
-	curr := &head
-
-	for h := a; h != nil; h = h.next {
-		if hide_set_contains(b, h.name) {
-			curr.next = new_hide_set(h.name)
-			curr = curr.next
-		}
-	}
-	return head.next
-}
-
-
-add_hide_set :: proc(tok: ^Token, hs: ^Hide_Set) -> ^Token {
-	head: Token
-	curr := &head
-
-	tok := tok
-	for ; tok != nil; tok = tok.next {
-		t := copy_token(tok)
-		t.hide_set = hide_set_union(t.hide_set, hs)
-		curr.next = t
-		curr = curr.next
-	}
-	return head.next
-}

+ 0 - 169
core/c/frontend/tokenizer/token.odin

@@ -1,169 +0,0 @@
-package c_frontend_tokenizer
-
-
-Pos :: struct {
-	file:   string,
-	line:   int,
-	column: int,
-	offset: int,
-}
-
-Token_Kind :: enum {
-	Invalid,
-	Ident,
-	Punct,
-	Keyword,
-	Char,
-	String,
-	Number,
-	PP_Number,
-	Comment,
-	EOF,
-}
-
-File :: struct {
-	name: string,
-	id:   int,
-	src:  []byte,
-
-	display_name: string,
-	line_delta:   int,
-}
-
-
-Token_Type_Hint :: enum u8 {
-	None,
-
-	Int,
-	Long,
-	Long_Long,
-
-	Unsigned_Int,
-	Unsigned_Long,
-	Unsigned_Long_Long,
-
-	Float,
-	Double,
-	Long_Double,
-
-	UTF_8,
-	UTF_16,
-	UTF_32,
-	UTF_Wide,
-}
-
-Token_Value :: union {
-	i64,
-	f64,
-	string,
-	[]u16,
-	[]u32,
-}
-
-Token :: struct {
-	kind: Token_Kind,
-	next: ^Token,
-	lit: string,
-
-	pos:   Pos,
-	file:  ^File,
-	line_delta: int,
-	at_bol:     bool,
-	has_space:  bool,
-
-	type_hint: Token_Type_Hint,
-	val: Token_Value,
-	prefix: string,
-
-	// Preprocessor values
-	hide_set: ^Hide_Set,
-	origin:   ^Token,
-}
-
-Is_Keyword_Proc :: #type proc(tok: ^Token) -> bool
-
-copy_token :: proc(tok: ^Token) -> ^Token {
-	t, _ := new_clone(tok^)
-	t.next = nil
-	return t
-}
-
-new_eof :: proc(tok: ^Token) -> ^Token {
-	t, _ := new_clone(tok^)
-	t.kind = .EOF
-	t.lit = ""
-	return t
-}
-
-default_is_keyword :: proc(tok: ^Token) -> bool {
-	if tok.kind == .Keyword {
-		return true
-	}
-	if len(tok.lit) > 0 {
-		return default_keyword_set[tok.lit]
-	}
-	return false
-}
-
-
-token_name := [Token_Kind]string {
-	.Invalid   = "invalid",
-	.Ident     = "ident",
-	.Punct     = "punct",
-	.Keyword   = "keyword",
-	.Char      = "char",
-	.String    = "string",
-	.Number    = "number",
-	.PP_Number = "preprocessor number",
-	.Comment   = "comment",
-	.EOF       = "eof",
-}
-
-default_keyword_set := map[string]bool{
-	"auto"          = true,
-	"break"         = true,
-	"case"          = true,
-	"char"          = true,
-	"const"         = true,
-	"continue"      = true,
-	"default"       = true,
-	"do"            = true,
-	"double"        = true,
-	"else"          = true,
-	"enum"          = true,
-	"extern"        = true,
-	"float"         = true,
-	"for"           = true,
-	"goto"          = true,
-	"if"            = true,
-	"int"           = true,
-	"long"          = true,
-	"register"      = true,
-	"restrict"      = true,
-	"return"        = true,
-	"short"         = true,
-	"signed"        = true,
-	"sizeof"        = true,
-	"static"        = true,
-	"struct"        = true,
-	"switch"        = true,
-	"typedef"       = true,
-	"union"         = true,
-	"unsigned"      = true,
-	"void"          = true,
-	"volatile"      = true,
-	"while"         = true,
-	"_Alignas"      = true,
-	"_Alignof"      = true,
-	"_Atomic"       = true,
-	"_Bool"         = true,
-	"_Generic"      = true,
-	"_Noreturn"     = true,
-	"_Thread_local" = true,
-	"__restrict"    = true,
-	"typeof"        = true,
-	"asm"           = true,
-	"__restrict__"  = true,
-	"__thread"      = true,
-	"__attribute__" = true,
-}

+ 0 - 667
core/c/frontend/tokenizer/tokenizer.odin

@@ -1,667 +0,0 @@
-package c_frontend_tokenizer
-
-import "core:fmt"
-import "core:os"
-import "core:strings"
-import "core:unicode/utf8"
-
-
-Error_Handler :: #type proc(pos: Pos, fmt: string, args: ..any)
-
-
-Tokenizer :: struct {
-	// Immutable data
-	path: string,
-	src:  []byte,
-
-
-	// Tokenizing state
-	ch:          rune,
-	offset:      int,
-	read_offset: int,
-	line_offset: int,
-	line_count:  int,
-
-	// Extra information for tokens
-	at_bol:    bool,
-	has_space: bool,
-
-	// Mutable data
-	err:  Error_Handler,
-	warn: Error_Handler,
-	error_count:   int,
-	warning_count: int,
-}
-
-init_defaults :: proc(t: ^Tokenizer, err: Error_Handler = default_error_handler, warn: Error_Handler = default_warn_handler) {
-	t.err = err
-	t.warn = warn
-}
-
-
-@(private)
-offset_to_pos :: proc(t: ^Tokenizer, offset: int) -> (pos: Pos) {
-	pos.file = t.path
-	pos.offset = offset
-	pos.line = t.line_count
-	pos.column = offset - t.line_offset + 1
-	return
-}
-
-default_error_handler :: proc(pos: Pos, msg: string, args: ..any) {
-	fmt.eprintf("%s(%d:%d) ", pos.file, pos.line, pos.column)
-	fmt.eprintf(msg, ..args)
-	fmt.eprintf("\n")
-}
-
-default_warn_handler :: proc(pos: Pos, msg: string, args: ..any) {
-	fmt.eprintf("%s(%d:%d) warning: ", pos.file, pos.line, pos.column)
-	fmt.eprintf(msg, ..args)
-	fmt.eprintf("\n")
-}
-
-error_offset :: proc(t: ^Tokenizer, offset: int, msg: string, args: ..any) {
-	pos := offset_to_pos(t, offset)
-	if t.err != nil {
-		t.err(pos, msg, ..args)
-	}
-	t.error_count += 1
-}
-
-warn_offset :: proc(t: ^Tokenizer, offset: int, msg: string, args: ..any) {
-	pos := offset_to_pos(t, offset)
-	if t.warn != nil {
-		t.warn(pos, msg, ..args)
-	}
-	t.warning_count += 1
-}
-
-error :: proc(t: ^Tokenizer, tok: ^Token, msg: string, args: ..any) {
-	pos := tok.pos
-	if t.err != nil {
-		t.err(pos, msg, ..args)
-	}
-	t.error_count += 1
-}
-
-warn :: proc(t: ^Tokenizer, tok: ^Token, msg: string, args: ..any) {
-	pos := tok.pos
-	if t.warn != nil {
-		t.warn(pos, msg, ..args)
-	}
-	t.warning_count += 1
-}
-
-
-advance_rune :: proc(t: ^Tokenizer) {
-	if t.read_offset < len(t.src) {
-		t.offset = t.read_offset
-		if t.ch == '\n' {
-			t.at_bol = true
-			t.line_offset = t.offset
-			t.line_count += 1
-		}
-		r, w := rune(t.src[t.read_offset]), 1
-		switch {
-		case r == 0:
-			error_offset(t, t.offset, "illegal character NUL")
-		case r >= utf8.RUNE_SELF:
-			r, w = utf8.decode_rune(t.src[t.read_offset:])
-			if r == utf8.RUNE_ERROR && w == 1 {
-				error_offset(t, t.offset, "illegal UTF-8 encoding")
-			} else if r == utf8.RUNE_BOM && t.offset > 0 {
-				error_offset(t, t.offset, "illegal byte order mark")
-			}
-		}
-		t.read_offset += w
-		t.ch = r
-	} else {
-		t.offset = len(t.src)
-		if t.ch == '\n' {
-			t.at_bol = true
-			t.line_offset = t.offset
-			t.line_count += 1
-		}
-		t.ch = -1
-	}
-}
-
-advance_rune_n :: proc(t: ^Tokenizer, n: int) {
-	for _ in 0..<n {
-		advance_rune(t)
-	}
-}
-
-is_digit :: proc(r: rune) -> bool {
-	return '0' <= r && r <= '9'
-}
-
-skip_whitespace :: proc(t: ^Tokenizer) {
-	for {
-		switch t.ch {
-		case ' ', '\t', '\r', '\v', '\f', '\n':
-			t.has_space = true
-			advance_rune(t)
-		case:
-			return
-		}
-	}
-}
-
-scan_comment :: proc(t: ^Tokenizer) -> string {
-	offset := t.offset-1
-	next := -1
-	general: {
-		if t.ch == '/'{ // line comments
-			advance_rune(t)
-			for t.ch != '\n' && t.ch >= 0 {
-				advance_rune(t)
-			}
-
-			next = t.offset
-			if t.ch == '\n' {
-				next += 1
-			}
-			break general
-		}
-
-		/* style comment */
-		advance_rune(t)
-		for t.ch >= 0 {
-			ch := t.ch
-			advance_rune(t)
-			if ch == '*' && t.ch == '/' {
-				advance_rune(t)
-				next = t.offset
-				break general
-			}
-		}
-
-		error_offset(t, offset, "comment not terminated")
-	}
-
-	lit := t.src[offset : t.offset]
-
-	// NOTE(bill): Strip CR for line comments
-	for len(lit) > 2 && lit[1] == '/' && lit[len(lit)-1] == '\r' {
-		lit = lit[:len(lit)-1]
-	}
-
-
-	return string(lit)
-}
-
-scan_identifier :: proc(t: ^Tokenizer) -> string {
-	offset := t.offset
-
-	for is_ident1(t.ch) {
-		advance_rune(t)
-	}
-
-	return string(t.src[offset : t.offset])
-}
-
-scan_string :: proc(t: ^Tokenizer) -> string {
-	offset := t.offset-1
-
-	for {
-		ch := t.ch
-		if ch == '\n' || ch < 0 {
-			error_offset(t, offset, "string literal was not terminated")
-			break
-		}
-		advance_rune(t)
-		if ch == '"' {
-			break
-		}
-		if ch == '\\' {
-			scan_escape(t)
-		}
-	}
-
-	return string(t.src[offset : t.offset])
-}
-
-digit_val :: proc(r: rune) -> int {
-	switch r {
-	case '0'..='9':
-		return int(r-'0')
-	case 'A'..='F':
-		return int(r-'A' + 10)
-	case 'a'..='f':
-		return int(r-'a' + 10)
-	}
-	return 16
-}
-
-scan_escape :: proc(t: ^Tokenizer) -> bool {
-	offset := t.offset
-
-	esc := t.ch
-	n: int
-	base, max: u32
-	switch esc {
-	case 'a', 'b', 'e', 'f', 'n', 't', 'v', 'r', '\\', '\'', '"':
-		advance_rune(t)
-		return true
-
-	case '0'..='7':
-		for digit_val(t.ch) < 8 {
-			advance_rune(t)
-		}
-		return true
-	case 'x':
-		advance_rune(t)
-		for digit_val(t.ch) < 16 {
-			advance_rune(t)
-		}
-		return true
-	case 'u':
-		advance_rune(t)
-		n, base, max = 4, 16, utf8.MAX_RUNE
-	case 'U':
-		advance_rune(t)
-		n, base, max = 8, 16, utf8.MAX_RUNE
-	case:
-		if t.ch < 0 {
-			error_offset(t, offset, "escape sequence was not terminated")
-		} else {
-			break
-		}
-		return false
-	}
-
-	x: u32
-	main_loop: for n > 0 {
-		d := u32(digit_val(t.ch))
-		if d >= base {
-			if t.ch == '"' || t.ch == '\'' {
-				break main_loop
-			}
-			if t.ch < 0 {
-				error_offset(t, t.offset, "escape sequence was not terminated")
-			} else {
-				error_offset(t, t.offset, "illegal character '%r' : %d in escape sequence", t.ch, t.ch)
-			}
-			return false
-		}
-
-		x = x*base + d
-		advance_rune(t)
-		n -= 1
-	}
-
-	if x > max || 0xd800 <= x && x <= 0xe000 {
-		error_offset(t, offset, "escape sequence is an invalid Unicode code point")
-		return false
-	}
-	return true
-}
-
-scan_rune :: proc(t: ^Tokenizer) -> string {
-	offset := t.offset-1
-	valid := true
-	n := 0
-	for {
-		ch := t.ch
-		if ch == '\n' || ch < 0 {
-			if valid {
-				error_offset(t, offset, "rune literal not terminated")
-				valid = false
-			}
-			break
-		}
-		advance_rune(t)
-		if ch == '\'' {
-			break
-		}
-		n += 1
-		if ch == '\\' {
-			if !scan_escape(t)  {
-				valid = false
-			}
-		}
-	}
-
-	if valid && n != 1 {
-		error_offset(t, offset, "illegal rune literal")
-	}
-
-	return string(t.src[offset : t.offset])
-}
-
-scan_number :: proc(t: ^Tokenizer, seen_decimal_point: bool) -> (Token_Kind, string) {
-	scan_mantissa :: proc(t: ^Tokenizer, base: int) {
-		for digit_val(t.ch) < base {
-			advance_rune(t)
-		}
-	}
-	scan_exponent :: proc(t: ^Tokenizer) {
-		if t.ch == 'e' || t.ch == 'E' || t.ch == 'p' || t.ch == 'P' {
-			advance_rune(t)
-			if t.ch == '-' || t.ch == '+' {
-				advance_rune(t)
-			}
-			if digit_val(t.ch) < 10 {
-				scan_mantissa(t, 10)
-			} else {
-				error_offset(t, t.offset, "illegal floating-point exponent")
-			}
-		}
-	}
-	scan_fraction :: proc(t: ^Tokenizer) -> (early_exit: bool) {
-		if t.ch == '.' && peek(t) == '.' {
-			return true
-		}
-		if t.ch == '.' {
-			advance_rune(t)
-			scan_mantissa(t, 10)
-		}
-		return false
-	}
-
-	check_end := true
-
-
-	offset := t.offset
-	seen_point := seen_decimal_point
-
-	if seen_point {
-		offset -= 1
-		scan_mantissa(t, 10)
-		scan_exponent(t)
-	} else {
-		if t.ch == '0' {
-			int_base :: proc(t: ^Tokenizer, base: int, msg: string) {
-				prev := t.offset
-				advance_rune(t)
-				scan_mantissa(t, base)
-				if t.offset - prev <= 1 {
-					error_offset(t, t.offset, msg)
-				}
-			}
-
-			advance_rune(t)
-			switch t.ch {
-			case 'b', 'B':
-				int_base(t, 2, "illegal binary integer")
-			case 'x', 'X':
-				int_base(t, 16, "illegal hexadecimal integer")
-			case:
-				seen_point = false
-				scan_mantissa(t, 10)
-				if t.ch == '.' {
-					seen_point = true
-					if scan_fraction(t) {
-						check_end = false
-					}
-				}
-				if check_end {
-					scan_exponent(t)
-					check_end = false
-				}
-			}
-		}
-	}
-
-	if check_end {
-		scan_mantissa(t, 10)
-
-		if !scan_fraction(t) {
-			scan_exponent(t)
-		}
-	}
-
-	return .Number, string(t.src[offset : t.offset])
-}
-
-scan_punct :: proc(t: ^Tokenizer, ch: rune) -> (kind: Token_Kind) {
-	kind = .Punct
-	switch ch {
-	case:
-		kind = .Invalid
-
-	case '<', '>':
-		if t.ch == ch {
-			advance_rune(t)
-		}
-		if t.ch == '=' {
-			advance_rune(t)
-		}
-	case '!', '+', '-', '*', '/', '%', '^', '=':
-		if t.ch == '=' {
-			advance_rune(t)
-		}
-	case '#':
-		if t.ch == '#' {
-			advance_rune(t)
-		}
-	case '&':
-		if t.ch == '=' || t.ch == '&' {
-			advance_rune(t)
-		}
-	case '|':
-		if t.ch == '=' || t.ch == '|' {
-			advance_rune(t)
-		}
-	case '(', ')', '[', ']', '{', '}':
-		// okay
-	case '~', ',', ':', ';', '?':
-		// okay
-	case '`':
-		// okay
-	case '.':
-		if t.ch == '.' && peek(t) == '.' {
-			advance_rune(t)
-			advance_rune(t) // consume last '.'
-		}
-	}
-	return
-}
-
-peek :: proc(t: ^Tokenizer) -> byte {
-	if t.read_offset < len(t.src) {
-		return t.src[t.read_offset]
-	}
-	return 0
-}
-peek_str :: proc(t: ^Tokenizer, str: string) -> bool {
-	if t.read_offset < len(t.src) {
-		return strings.has_prefix(string(t.src[t.offset:]), str)
-	}
-	return false
-}
-
-scan_literal_prefix :: proc(t: ^Tokenizer, str: string, prefix: ^string) -> bool {
-	if peek_str(t, str) {
-		offset := t.offset
-		for _ in str {
-			advance_rune(t)
-		}
-		prefix^ = string(t.src[offset:][:len(str)-1])
-		return true
-	}
-	return false
-}
-
-
-allow_next_to_be_newline :: proc(t: ^Tokenizer) -> bool {
-	if t.ch == '\n' {
-		advance_rune(t)
-		return true
-	} else if t.ch == '\r' && peek(t) == '\n' { // allow for MS-DOS style line endings
-		advance_rune(t) // \r
-		advance_rune(t) // \n
-		return true
-	}
-	return false
-}
-
-scan :: proc(t: ^Tokenizer, f: ^File) -> ^Token {
-	skip_whitespace(t)
-
-	offset := t.offset
-
-	kind: Token_Kind
-	lit: string
-	prefix: string
-
-	switch ch := t.ch; {
-	case scan_literal_prefix(t, `u8"`, &prefix):
-		kind = .String
-		lit = scan_string(t)
-	case scan_literal_prefix(t, `u"`, &prefix):
-		kind = .String
-		lit = scan_string(t)
-	case scan_literal_prefix(t, `L"`, &prefix):
-		kind = .String
-		lit = scan_string(t)
-	case scan_literal_prefix(t, `U"`, &prefix):
-		kind = .String
-		lit = scan_string(t)
-	case scan_literal_prefix(t, `u'`, &prefix):
-		kind = .Char
-		lit = scan_rune(t)
-	case scan_literal_prefix(t, `L'`, &prefix):
-		kind = .Char
-		lit = scan_rune(t)
-	case scan_literal_prefix(t, `U'`, &prefix):
-		kind = .Char
-		lit = scan_rune(t)
-
-	case is_ident0(ch):
-		lit = scan_identifier(t)
-		kind = .Ident
-	case '0' <= ch && ch <= '9':
-		kind, lit = scan_number(t, false)
-	case:
-		advance_rune(t)
-		switch ch {
-		case -1:
-			kind = .EOF
-		case '\\':
-			kind = .Punct
-			if allow_next_to_be_newline(t) {
-				t.at_bol = true
-				t.has_space = false
-				return scan(t, f)
-			}
-
-		case '.':
-			if is_digit(t.ch) {
-				kind, lit = scan_number(t, true)
-			} else {
-				kind = scan_punct(t, ch)
-			}
-		case '"':
-			kind = .String
-			lit = scan_string(t)
-		case '\'':
-			kind = .Char
-			lit = scan_rune(t)
-		case '/':
-			if t.ch == '/' || t.ch == '*' {
-				kind = .Comment
-				lit = scan_comment(t)
-				t.has_space = true
-				break
-			}
-			fallthrough
-		case:
-			kind = scan_punct(t, ch)
-			if kind == .Invalid && ch != utf8.RUNE_BOM {
-				error_offset(t, t.offset, "illegal character '%r': %d", ch, ch)
-			}
-		}
-	}
-
-	if lit == "" {
-		lit = string(t.src[offset : t.offset])
-	}
-
-	if kind == .Comment {
-		return scan(t, f)
-	}
-
-	tok := new(Token)
-	tok.kind = kind
-	tok.lit = lit
-	tok.pos = offset_to_pos(t, offset)
-	tok.file = f
-	tok.prefix = prefix
-	tok.at_bol = t.at_bol
-	tok.has_space = t.has_space
-
-	t.at_bol, t.has_space = false, false
-
-	return tok
-}
-
-tokenize :: proc(t: ^Tokenizer, f: ^File) -> ^Token {
-	setup_tokenizer: {
-		t.src = f.src
-		t.ch = ' '
-		t.offset = 0
-		t.read_offset = 0
-		t.line_offset = 0
-		t.line_count = len(t.src) > 0 ? 1 : 0
-		t.error_count = 0
-		t.path = f.name
-
-
-		advance_rune(t)
-		if t.ch == utf8.RUNE_BOM {
-			advance_rune(t)
-		}
-	}
-
-
-	t.at_bol = true
-	t.has_space = false
-
-	head: Token
-	curr := &head
-	for {
-		tok := scan(t, f)
-		if tok == nil {
-			break
-		}
-		curr.next = tok
-		curr = curr.next
-		if tok.kind == .EOF {
-			break
-		}
-	}
-
-	return head.next
-}
-
-add_new_file :: proc(t: ^Tokenizer, name: string, src: []byte, id: int) -> ^File {
-	file := new(File)
-	file.id = id
-	file.src = src
-	file.name = name
-	file.display_name = name
-	return file
-}
-
-tokenize_file :: proc(t: ^Tokenizer, path: string, id: int, loc := #caller_location) -> ^Token {
-	src, ok := os.read_entire_file(path)
-	if !ok {
-		return nil
-	}
-	return tokenize(t, add_new_file(t, path, src, id))
-}
-
-
-inline_tokenize :: proc(t: ^Tokenizer, tok: ^Token, src: []byte) -> ^Token {
-	file := new(File)
-	file.src = src
-	if tok.file != nil {
-		file.id = tok.file.id
-		file.name = tok.file.name
-		file.display_name = tok.file.name
-	}
-
-	return tokenize(t, file)
-}

+ 0 - 116
core/c/frontend/tokenizer/unicode.odin

@@ -1,116 +0,0 @@
-package c_frontend_tokenizer
-
-
-in_range :: proc(range: []rune, c: rune) -> bool #no_bounds_check {
-	for i := 0; range[i] != -1; i += 2 {
-		if range[i] <= c && c <= range[i+1] {
-			return true
-		}
-	}
-	return false
-}
-
-
-// [https://www.sigbus.info/n1570#D] C11 allows ASCII and some multibyte characters in certan Unicode ranges to be used in an identifier.
-//
-// is_ident0 returns true if a given character is acceptable as the first character of an identifier.
-is_ident0 :: proc(c: rune) -> bool {
-	return in_range(_range_ident0, c)
-}
-// is_ident0 returns true if a given character is acceptable as a non-first character of an identifier.
-is_ident1 :: proc(c: rune) -> bool {
-	return is_ident0(c) || in_range(_range_ident1, c)
-}
-
-// Returns the number of columns needed to display a given character in a fixed-width font.
-// Based on https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
-char_width :: proc(c: rune) -> int {
-	switch {
-	case in_range(_range_width0, c):
-		return 0
-	case in_range(_range_width2, c):
-		return 2
-	}
-	return 1
-}
-
-display_width :: proc(str: string) -> (w: int) {
-	for c in str {
-		w += char_width(c)
-	}
-	return
-}
-
-
-
-_range_ident0 := []rune{
-	'_', '_', 'a', 'z', 'A', 'Z', '$', '$',
-	0x00A8, 0x00A8, 0x00AA, 0x00AA, 0x00AD, 0x00AD, 0x00AF, 0x00AF,
-	0x00B2, 0x00B5, 0x00B7, 0x00BA, 0x00BC, 0x00BE, 0x00C0, 0x00D6,
-	0x00D8, 0x00F6, 0x00F8, 0x00FF, 0x0100, 0x02FF, 0x0370, 0x167F,
-	0x1681, 0x180D, 0x180F, 0x1DBF, 0x1E00, 0x1FFF, 0x200B, 0x200D,
-	0x202A, 0x202E, 0x203F, 0x2040, 0x2054, 0x2054, 0x2060, 0x206F,
-	0x2070, 0x20CF, 0x2100, 0x218F, 0x2460, 0x24FF, 0x2776, 0x2793,
-	0x2C00, 0x2DFF, 0x2E80, 0x2FFF, 0x3004, 0x3007, 0x3021, 0x302F,
-	0x3031, 0x303F, 0x3040, 0xD7FF, 0xF900, 0xFD3D, 0xFD40, 0xFDCF,
-	0xFDF0, 0xFE1F, 0xFE30, 0xFE44, 0xFE47, 0xFFFD,
-	0x10000, 0x1FFFD, 0x20000, 0x2FFFD, 0x30000, 0x3FFFD, 0x40000, 0x4FFFD,
-	0x50000, 0x5FFFD, 0x60000, 0x6FFFD, 0x70000, 0x7FFFD, 0x80000, 0x8FFFD,
-	0x90000, 0x9FFFD, 0xA0000, 0xAFFFD, 0xB0000, 0xBFFFD, 0xC0000, 0xCFFFD,
-	0xD0000, 0xDFFFD, 0xE0000, 0xEFFFD,
-	-1,
-}
-
-_range_ident1 := []rune{
-	'0', '9', '$', '$', 0x0300, 0x036F, 0x1DC0, 0x1DFF, 0x20D0, 0x20FF, 0xFE20, 0xFE2F,
-	-1,
-}
-
-
-_range_width0 := []rune{
-	0x0000, 0x001F, 0x007f, 0x00a0, 0x0300, 0x036F, 0x0483, 0x0486,
-	0x0488, 0x0489, 0x0591, 0x05BD, 0x05BF, 0x05BF, 0x05C1, 0x05C2,
-	0x05C4, 0x05C5, 0x05C7, 0x05C7, 0x0600, 0x0603, 0x0610, 0x0615,
-	0x064B, 0x065E, 0x0670, 0x0670, 0x06D6, 0x06E4, 0x06E7, 0x06E8,
-	0x06EA, 0x06ED, 0x070F, 0x070F, 0x0711, 0x0711, 0x0730, 0x074A,
-	0x07A6, 0x07B0, 0x07EB, 0x07F3, 0x0901, 0x0902, 0x093C, 0x093C,
-	0x0941, 0x0948, 0x094D, 0x094D, 0x0951, 0x0954, 0x0962, 0x0963,
-	0x0981, 0x0981, 0x09BC, 0x09BC, 0x09C1, 0x09C4, 0x09CD, 0x09CD,
-	0x09E2, 0x09E3, 0x0A01, 0x0A02, 0x0A3C, 0x0A3C, 0x0A41, 0x0A42,
-	0x0A47, 0x0A48, 0x0A4B, 0x0A4D, 0x0A70, 0x0A71, 0x0A81, 0x0A82,
-	0x0ABC, 0x0ABC, 0x0AC1, 0x0AC5, 0x0AC7, 0x0AC8, 0x0ACD, 0x0ACD,
-	0x0AE2, 0x0AE3, 0x0B01, 0x0B01, 0x0B3C, 0x0B3C, 0x0B3F, 0x0B3F,
-	0x0B41, 0x0B43, 0x0B4D, 0x0B4D, 0x0B56, 0x0B56, 0x0B82, 0x0B82,
-	0x0BC0, 0x0BC0, 0x0BCD, 0x0BCD, 0x0C3E, 0x0C40, 0x0C46, 0x0C48,
-	0x0C4A, 0x0C4D, 0x0C55, 0x0C56, 0x0CBC, 0x0CBC, 0x0CBF, 0x0CBF,
-	0x0CC6, 0x0CC6, 0x0CCC, 0x0CCD, 0x0CE2, 0x0CE3, 0x0D41, 0x0D43,
-	0x0D4D, 0x0D4D, 0x0DCA, 0x0DCA, 0x0DD2, 0x0DD4, 0x0DD6, 0x0DD6,
-	0x0E31, 0x0E31, 0x0E34, 0x0E3A, 0x0E47, 0x0E4E, 0x0EB1, 0x0EB1,
-	0x0EB4, 0x0EB9, 0x0EBB, 0x0EBC, 0x0EC8, 0x0ECD, 0x0F18, 0x0F19,
-	0x0F35, 0x0F35, 0x0F37, 0x0F37, 0x0F39, 0x0F39, 0x0F71, 0x0F7E,
-	0x0F80, 0x0F84, 0x0F86, 0x0F87, 0x0F90, 0x0F97, 0x0F99, 0x0FBC,
-	0x0FC6, 0x0FC6, 0x102D, 0x1030, 0x1032, 0x1032, 0x1036, 0x1037,
-	0x1039, 0x1039, 0x1058, 0x1059, 0x1160, 0x11FF, 0x135F, 0x135F,
-	0x1712, 0x1714, 0x1732, 0x1734, 0x1752, 0x1753, 0x1772, 0x1773,
-	0x17B4, 0x17B5, 0x17B7, 0x17BD, 0x17C6, 0x17C6, 0x17C9, 0x17D3,
-	0x17DD, 0x17DD, 0x180B, 0x180D, 0x18A9, 0x18A9, 0x1920, 0x1922,
-	0x1927, 0x1928, 0x1932, 0x1932, 0x1939, 0x193B, 0x1A17, 0x1A18,
-	0x1B00, 0x1B03, 0x1B34, 0x1B34, 0x1B36, 0x1B3A, 0x1B3C, 0x1B3C,
-	0x1B42, 0x1B42, 0x1B6B, 0x1B73, 0x1DC0, 0x1DCA, 0x1DFE, 0x1DFF,
-	0x200B, 0x200F, 0x202A, 0x202E, 0x2060, 0x2063, 0x206A, 0x206F,
-	0x20D0, 0x20EF, 0x302A, 0x302F, 0x3099, 0x309A, 0xA806, 0xA806,
-	0xA80B, 0xA80B, 0xA825, 0xA826, 0xFB1E, 0xFB1E, 0xFE00, 0xFE0F,
-	0xFE20, 0xFE23, 0xFEFF, 0xFEFF, 0xFFF9, 0xFFFB, 0x10A01, 0x10A03,
-	0x10A05, 0x10A06, 0x10A0C, 0x10A0F, 0x10A38, 0x10A3A, 0x10A3F, 0x10A3F,
-	0x1D167, 0x1D169, 0x1D173, 0x1D182, 0x1D185, 0x1D18B, 0x1D1AA, 0x1D1AD,
-	0x1D242, 0x1D244, 0xE0001, 0xE0001, 0xE0020, 0xE007F, 0xE0100, 0xE01EF,
-	-1,
-}
-
-_range_width2 := []rune{
-	0x1100, 0x115F, 0x2329, 0x2329, 0x232A, 0x232A, 0x2E80, 0x303E,
-	0x3040, 0xA4CF, 0xAC00, 0xD7A3, 0xF900, 0xFAFF, 0xFE10, 0xFE19,
-	0xFE30, 0xFE6F, 0xFF00, 0xFF60, 0xFFE0, 0xFFE6, 0x1F000, 0x1F644,
-	0x20000, 0x2FFFD, 0x30000, 0x3FFFD,
-	-1,
-}

+ 2 - 2
core/c/libc/README.md

@@ -14,7 +14,7 @@ The following is a mostly-complete projection of the C11 standard library as def
 | `<inttypes.h>`    | Fully projected                                    |
 | `<iso646.h>`      | Not applicable, use Odin's operators               |
 | `<limits.h>`      | Not projected                                      |
-| `<locale.h>`      | Not projected                                      |
+| `<locale.h>`      | Fully projected                                    |
 | `<math.h>`        | Mostly projected, see [limitations](#Limitations)  |
 | `<setjmp.h>`      | Fully projected                                    |
 | `<signal.h>`      | Fully projected                                    |
@@ -70,4 +70,4 @@ with the following copyright.
 
 ```
 Copyright 2021 Dale Weiler <[email protected]>.
-```
+```

+ 133 - 0
core/c/libc/locale.odin

@@ -0,0 +1,133 @@
+package libc
+
+import "core:c"
+
+when ODIN_OS == .Windows {
+	foreign import libc "system:libucrt.lib"
+} else when ODIN_OS == .Darwin {
+	foreign import libc "system:System.framework"
+} else {
+	foreign import libc "system:c"
+}
+
+// locale.h - category macros
+
+foreign libc {
+	/*
+	Sets the components of an object with the type lconv with the values appropriate for the
+	formatting of numeric quantities (monetary and otherwise) according to the rules of the current
+	locale.
+
+	Returns: a pointer to the lconv structure, might be invalidated by subsequent calls to localeconv() and setlocale()
+
+	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/localeconv.html ]]
+	*/
+	localeconv :: proc() -> ^lconv ---
+
+	/*
+	Selects the appropriate piece of the global locale, as specified by the category and locale arguments,
+	and can be used to change or query the entire global locale or portions thereof.
+
+	Returns: the current locale if `locale` is `nil`, the set locale otherwise
+
+	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setlocale.html ]]
+	*/
+	@(link_name=LSETLOCALE)
+	setlocale :: proc(category: Locale_Category, locale: cstring) -> cstring ---
+}
+
+Locale_Category :: enum c.int {
+	ALL      = LC_ALL,
+	COLLATE  = LC_COLLATE,
+	CTYPE    = LC_CTYPE,
+	MESSAGES = LC_MESSAGES,
+	MONETARY = LC_MONETARY,
+	NUMERIC  = LC_NUMERIC,
+	TIME     = LC_TIME,
+}
+
+when ODIN_OS == .NetBSD {
+	@(private) LSETLOCALE :: "__setlocale50"
+} else {
+	@(private) LSETLOCALE :: "setlocale"
+}
+
+when ODIN_OS == .Windows {
+	lconv :: struct {
+		decimal_point:        cstring,
+		thousand_sep:         cstring,
+		grouping:             cstring,
+		int_curr_symbol:      cstring,
+		currency_symbol:      cstring,
+		mon_decimal_points:   cstring,
+		mon_thousands_sep:    cstring,
+		mon_grouping:         cstring,
+		positive_sign:        cstring,
+		negative_sign:        cstring,
+		int_frac_digits:      c.char,
+		frac_digits:          c.char,
+		p_cs_precedes:        c.char,
+		p_sep_by_space:       c.char,
+		n_cs_precedes:        c.char,
+		n_sep_by_space:       c.char,
+		p_sign_posn:          c.char,
+		n_sign_posn:          c.char,
+		_W_decimal_point:     [^]u16 `fmt:"s,0"`,
+		_W_thousands_sep:     [^]u16 `fmt:"s,0"`,
+		_W_int_curr_symbol:   [^]u16 `fmt:"s,0"`,
+		_W_currency_symbol:   [^]u16 `fmt:"s,0"`,
+		_W_mon_decimal_point: [^]u16 `fmt:"s,0"`,
+		_W_mon_thousands_sep: [^]u16 `fmt:"s,0"`,
+		_W_positive_sign:     [^]u16 `fmt:"s,0"`,
+		_W_negative_sign:     [^]u16 `fmt:"s,0"`,
+	}
+} else {
+	lconv :: struct {
+		decimal_point:       cstring,
+		thousand_sep:        cstring,
+		grouping:            cstring,
+		int_curr_symbol:     cstring,
+		currency_symbol:     cstring,
+		mon_decimal_points:  cstring,
+		mon_thousands_sep:   cstring,
+		mon_grouping:        cstring,
+		positive_sign:       cstring,
+		negative_sign:       cstring,
+		int_frac_digits:     c.char,
+		frac_digits:         c.char,
+		p_cs_precedes:       c.char,
+		p_sep_by_space:      c.char,
+		n_cs_precedes:       c.char,
+		n_sep_by_space:      c.char,
+		p_sign_posn:         c.char,
+		n_sign_posn:         c.char,
+		_int_p_cs_precedes:  c.char,
+		_int_n_cs_precedes:  c.char,
+		_int_p_sep_by_space: c.char,
+		_int_n_sep_by_space: c.char,
+		_int_p_sign_posn:    c.char,
+		_int_n_sign_posn:    c.char,
+	}
+}
+
+when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD  || ODIN_OS == .OpenBSD || ODIN_OS == .Windows {
+
+	LC_ALL      :: 0
+	LC_COLLATE  :: 1
+	LC_CTYPE    :: 2
+	LC_MESSAGES :: 6
+	LC_MONETARY :: 3
+	LC_NUMERIC  :: 4
+	LC_TIME     :: 5
+
+} else when ODIN_OS == .Linux {
+
+	LC_CTYPE    :: 0
+	LC_NUMERIC  :: 1
+	LC_TIME     :: 2
+	LC_COLLATE  :: 3
+	LC_MONETARY :: 4
+	LC_MESSAGES :: 5
+	LC_ALL      :: 6
+
+}

+ 2 - 1
core/compress/zlib/doc.odin

@@ -13,6 +13,7 @@ Example:
 	package main
 
 	import "core:bytes"
+	import "core:compress/zlib"
 	import "core:fmt"
 
 	main :: proc() {
@@ -36,7 +37,7 @@ Example:
 		buf: bytes.Buffer
 
 		// We can pass ", true" to inflate a raw DEFLATE stream instead of a ZLIB wrapped one.
-		err := inflate(input=ODIN_DEMO, buf=&buf, expected_output_size=OUTPUT_SIZE)
+		err := zlib.inflate(input=ODIN_DEMO, buf=&buf, expected_output_size=OUTPUT_SIZE)
 		defer bytes.buffer_destroy(&buf)
 
 		if err != nil {

+ 7 - 5
core/dynlib/example/example.odin

@@ -21,12 +21,14 @@ Symbols :: struct {
 main :: proc() {
 	sym: Symbols
 
+	LIB_PATH :: "lib." + dynlib.LIBRARY_FILE_EXTENSION
+
 	// Load symbols from `lib.dll` into Symbols struct.
 	// Each struct field is prefixed with `foo_` before lookup in the DLL's symbol table.
 	// The library's Handle (to unload) will be stored in `sym._my_lib_handle`. This way you can load multiple DLLs in one struct.
-	count, ok := dynlib.initialize_symbols(&sym, "lib.dll", "foo_", "_my_lib_handle")
+	count, ok := dynlib.initialize_symbols(&sym, LIB_PATH, "foo_", "_my_lib_handle")
 	defer dynlib.unload_library(sym._my_lib_handle)
-	fmt.printf("(Initial DLL Load) ok: %v. %v symbols loaded from lib.dll (%p).\n", ok, count, sym._my_lib_handle)
+	fmt.printf("(Initial DLL Load) ok: %v. %v symbols loaded from " + LIB_PATH + " (%p).\n", ok, count, sym._my_lib_handle)
 
 	if count > 0 {
 		fmt.println("42 + 42 =", sym.add(42, 42))
@@ -34,12 +36,12 @@ main :: proc() {
 		fmt.println("hellope =", sym.hellope^)
 	}
 
-	count, ok = dynlib.initialize_symbols(&sym, "lib.dll", "foo_", "_my_lib_handle")
-	fmt.printf("(DLL Reload) ok: %v. %v symbols loaded from lib.dll (%p).\n", ok, count, sym._my_lib_handle)
+	count, ok = dynlib.initialize_symbols(&sym, LIB_PATH, "foo_", "_my_lib_handle")
+	fmt.printf("(DLL Reload) ok: %v. %v symbols loaded from " + LIB_PATH + " (%p).\n", ok, count, sym._my_lib_handle)
 
 	if count > 0 {
 		fmt.println("42 + 42 =", sym.add(42, 42))
 		fmt.println("84 - 13 =", sym.sub(84, 13))
 		fmt.println("hellope =", sym.hellope^)
 	}
-}
+}

+ 28 - 18
core/dynlib/lib.odin

@@ -12,6 +12,11 @@ A handle to a dynamically loaded library.
 */
 Library :: distinct rawptr
 
+/*
+The file extension for dynamic libraries on the target OS.
+*/
+LIBRARY_FILE_EXTENSION :: _LIBRARY_FILE_EXTENSION
+
 /*
 Loads a dynamic library from the filesystem. The paramater `global_symbols` makes the symbols in the loaded
 library available to resolve references in subsequently loaded libraries.
@@ -37,8 +42,8 @@ Example:
 		fmt.println("The library %q was successfully loaded", LIBRARY_PATH)
 	}
 */
-load_library :: proc(path: string, global_symbols := false) -> (library: Library, did_load: bool) {
-	return _load_library(path, global_symbols)
+load_library :: proc(path: string, global_symbols := false, allocator := context.temp_allocator) -> (library: Library, did_load: bool) {
+	return _load_library(path, global_symbols, allocator)
 }
 
 /*
@@ -98,8 +103,8 @@ Example:
 		}
 	}
 */
-symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) #optional_ok {
-	return _symbol_address(library, symbol)
+symbol_address :: proc(library: Library, symbol: string, allocator := context.temp_allocator) -> (ptr: rawptr, found: bool) #optional_ok {
+	return _symbol_address(library, symbol, allocator)
 }
 
 /*
@@ -123,31 +128,36 @@ initialize_symbols :: proc(
 ) -> (count: int = -1, ok: bool = false) where intrinsics.type_is_struct(T) {
 	assert(symbol_table != nil)
 
-	handle := load_library(library_path) or_return
-
-	// Buffer to concatenate the prefix + symbol name.
-	prefixed_symbol_buf: [2048]u8 = ---
-
-	count = 0
+	// First, (re)load the library.
+	handle: Library
 	for field in reflect.struct_fields_zipped(T) {
-		// Calculate address of struct member
-		field_ptr := rawptr(uintptr(symbol_table) + field.offset)
-
-		// If we've come across the struct member for the handle, store it and continue scanning for other symbols.
 		if field.name == handle_field_name {
+			field_ptr := rawptr(uintptr(symbol_table) + field.offset)
+
 			// We appear to be hot reloading. Unload previous incarnation of the library.
 			if old_handle := (^Library)(field_ptr)^; old_handle != nil {
 				unload_library(old_handle) or_return
 			}
+
+			handle = load_library(library_path) or_return
 			(^Library)(field_ptr)^ = handle
-			continue
+			break
 		}
+	}
+
+	// Buffer to concatenate the prefix + symbol name.
+	prefixed_symbol_buf: [2048]u8 = ---
 
-		// We're not the library handle, so the field needs to be a pointer type, be it a procedure pointer or an exported global.
-		if !(reflect.is_procedure(field.type) || reflect.is_pointer(field.type)) {
+	count = 0
+	for field in reflect.struct_fields_zipped(T) {
+		// If we're not the library handle, the field needs to be a pointer type, be it a procedure pointer or an exported global.
+		if field.name == handle_field_name || !(reflect.is_procedure(field.type) || reflect.is_pointer(field.type)) {
 			continue
 		}
 
+		// Calculate address of struct member
+		field_ptr := rawptr(uintptr(symbol_table) + field.offset)
+
 		// Let's look up or construct the symbol name to find in the library
 		prefixed_name: string
 
@@ -174,4 +184,4 @@ initialize_symbols :: proc(
 // Returns an error message for the last failed procedure call.
 last_error :: proc() -> string {
 	return _last_error()
-}
+}

+ 7 - 3
core/dynlib/lib_js.odin

@@ -2,7 +2,11 @@
 #+private
 package dynlib
 
-_load_library :: proc(path: string, global_symbols := false) -> (Library, bool) {
+import "base:runtime"
+
+_LIBRARY_FILE_EXTENSION :: ""
+
+_load_library :: proc(path: string, global_symbols: bool, allocator: runtime.Allocator) -> (Library, bool) {
 	return nil, false
 }
 
@@ -10,10 +14,10 @@ _unload_library :: proc(library: Library) -> bool {
 	return false
 }
 
-_symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) {
+_symbol_address :: proc(library: Library, symbol: string, allocator: runtime.Allocator) -> (ptr: rawptr, found: bool) {
 	return nil, false
 }
 
 _last_error :: proc() -> string {
 	return ""
-}
+}

+ 22 - 10
core/dynlib/lib_unix.odin

@@ -2,28 +2,40 @@
 #+private
 package dynlib
 
-import "core:os"
+import "base:runtime"
 
-_load_library :: proc(path: string, global_symbols := false) -> (Library, bool) {
-	flags := os.RTLD_NOW
+import "core:strings"
+import "core:sys/posix"
+
+_LIBRARY_FILE_EXTENSION :: "dylib" when ODIN_OS == .Darwin else "so"
+
+_load_library :: proc(path: string, global_symbols: bool, allocator: runtime.Allocator) -> (Library, bool) {
+	flags := posix.RTLD_Flags{.NOW}
 	if global_symbols {
-		flags |= os.RTLD_GLOBAL
+		flags += {.GLOBAL}
 	}
-	lib := os.dlopen(path, flags)
+
+	cpath := strings.clone_to_cstring(path, allocator)
+	defer delete(cpath, allocator)
+
+	lib := posix.dlopen(cpath, flags)
 	return Library(lib), lib != nil
 }
 
 _unload_library :: proc(library: Library) -> bool {
-	return os.dlclose(rawptr(library))
+	return posix.dlclose(posix.Symbol_Table(library)) == 0
 }
 
-_symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) {
-	ptr = os.dlsym(rawptr(library), symbol)
+_symbol_address :: proc(library: Library, symbol: string, allocator: runtime.Allocator) -> (ptr: rawptr, found: bool) {
+	csymbol := strings.clone_to_cstring(symbol, allocator)
+	defer delete(csymbol, allocator)
+
+	ptr   = posix.dlsym(posix.Symbol_Table(library), csymbol)
 	found = ptr != nil
 	return
 }
 
 _last_error :: proc() -> string {
-	err := os.dlerror()
+	err := string(posix.dlerror())
 	return "unknown" if err == "" else err
-}
+}

+ 7 - 3
core/dynlib/lib_windows.odin

@@ -2,11 +2,15 @@
 #+private
 package dynlib
 
+import "base:runtime"
+
 import win32 "core:sys/windows"
 import "core:strings"
 import "core:reflect"
 
-_load_library :: proc(path: string, global_symbols := false, allocator := context.temp_allocator) -> (Library, bool) {
+_LIBRARY_FILE_EXTENSION :: "dll"
+
+_load_library :: proc(path: string, global_symbols: bool, allocator: runtime.Allocator) -> (Library, bool) {
 	// NOTE(bill): 'global_symbols' is here only for consistency with POSIX which has RTLD_GLOBAL
 	wide_path := win32.utf8_to_wstring(path, allocator)
 	defer free(wide_path, allocator)
@@ -19,7 +23,7 @@ _unload_library :: proc(library: Library) -> bool {
 	return bool(ok)
 }
 
-_symbol_address :: proc(library: Library, symbol: string, allocator := context.temp_allocator) -> (ptr: rawptr, found: bool) {
+_symbol_address :: proc(library: Library, symbol: string, allocator: runtime.Allocator) -> (ptr: rawptr, found: bool) {
 	c_str := strings.clone_to_cstring(symbol, allocator)
 	defer delete(c_str, allocator)
 	ptr = win32.GetProcAddress(cast(win32.HMODULE)library, c_str)
@@ -31,4 +35,4 @@ _last_error :: proc() -> string {
 	err := win32.System_Error(win32.GetLastError())
 	err_msg := reflect.enum_string(err)
 	return "unknown" if err_msg == "" else err_msg
-}
+}

+ 1 - 1
core/encoding/cbor/cbor.odin

@@ -563,7 +563,7 @@ to_json :: proc(val: Value, allocator := context.allocator) -> (json.Value, mem.
 					case: return false
 					}
 				}
-				return false
+				return true
 			}
 
 			if keys_all_strings(v) {

+ 20 - 5
core/encoding/cbor/unmarshal.odin

@@ -442,9 +442,6 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 		loc := #caller_location,
 	) -> (out_of_space: bool, err: Unmarshal_Error) {
 		for idx: uintptr = 0; length == -1 || idx < uintptr(length); idx += 1 {
-			elem_ptr := rawptr(uintptr(da.data) + idx*uintptr(elemt.size))
-			elem     := any{elem_ptr, elemt.id}
-
 			hdr := _decode_header(d.reader) or_return
 			
 			// Double size if out of capacity.
@@ -459,6 +456,10 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 				if !ok { return false, .Out_Of_Memory }
 			}
 			
+			// Set ptr after potential resizes to avoid invalidation.
+			elem_ptr := rawptr(uintptr(da.data) + idx*uintptr(elemt.size))
+			elem     := any{elem_ptr, elemt.id}
+
 			err = _unmarshal_value(d, elem, hdr, allocator=allocator, loc=loc)
 			if length == -1 && err == .Break { break }
 			if err != nil { return }
@@ -509,7 +510,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 		raw           := (^mem.Raw_Dynamic_Array)(v.data)
 		raw.data       = raw_data(data) 
 		raw.len        = 0
-		raw.cap        = length
+		raw.cap        = scap
 		raw.allocator  = context.allocator
 
 		_ = assign_array(d, raw, t.elem, length) or_return
@@ -627,7 +628,8 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header,
 		unknown := length == -1
 		fields := reflect.struct_fields_zipped(ti.id)
 
-		for idx := 0; idx < len(fields) && (unknown || idx < length); idx += 1 {
+		idx := 0
+		for ; idx < len(fields) && (unknown || idx < length); idx += 1 {
 			// Decode key, keys can only be strings.
 			key: string
 			if keyv, kerr := decode_key(d, v, context.temp_allocator); unknown && kerr == .Break {
@@ -662,6 +664,8 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header,
 				
 				// Skips unused map entries.
 				if use_field_idx < 0 {
+					val := err_conv(_decode_from_decoder(d, allocator=context.temp_allocator)) or_return
+					destroy(val, context.temp_allocator)
 					continue
 				}
 			}
@@ -672,6 +676,17 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header,
 			fany  := any{ptr, field.type.id}
 			_unmarshal_value(d, fany, _decode_header(r) or_return) or_return
 		}
+
+		// If there are fields left in the map that did not get decoded into the struct, decode and discard them.
+		if !unknown {
+			for _ in idx..<length {
+				key := err_conv(_decode_from_decoder(d, allocator=context.temp_allocator)) or_return
+				destroy(key, context.temp_allocator)
+				val := err_conv(_decode_from_decoder(d, allocator=context.temp_allocator)) or_return
+				destroy(val, context.temp_allocator)
+			}
+		}
+
 		return
 
 	case reflect.Type_Info_Map:

+ 1 - 1
core/encoding/hxa/read.odin

@@ -117,7 +117,7 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
 			layer.name = read_name(r) or_return
 			layer.components = read_value(r, u8) or_return
 			type := read_value(r, Layer_Data_Type) or_return
-			if type > max(type) {
+			if type > max(Layer_Data_Type) {
 				if r.print_error {
 					fmt.eprintf("HxA Error: file '%s' has layer data type %d. Maximum value is %d\n",
 								r.filename, u8(type), u8(max(Layer_Data_Type)))

+ 0 - 6
core/encoding/json/marshal.odin

@@ -192,12 +192,6 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
 
 	case runtime.Type_Info_Simd_Vector:
 		return .Unsupported_Type
-
-	case runtime.Type_Info_Relative_Pointer:
-		return .Unsupported_Type
-
-	case runtime.Type_Info_Relative_Multi_Pointer:
-		return .Unsupported_Type
 		
 	case runtime.Type_Info_Matrix:
 		return .Unsupported_Type

+ 2 - 1
core/encoding/json/tokenizer.odin

@@ -259,6 +259,7 @@ get_token :: proc(t: ^Tokenizer) -> (token: Token, err: Error) {
 			skip_digits(t)
 		}
 		if t.r == 'e' || t.r == 'E' {
+			token.kind = .Float
 			switch r := next_rune(t); r {
 			case '+', '-':
 				next_rune(t)
@@ -485,7 +486,7 @@ is_valid_string_literal :: proc(str: string, spec: Specification) -> bool {
 	case '"':
 		// okay
 	case '\'':
-		if spec != .JSON {
+		if spec == .JSON {
 			return false
 		}
 		// okay

+ 48 - 24
core/encoding/json/unmarshal.odin

@@ -172,20 +172,33 @@ assign_float :: proc(val: any, f: $T) -> bool {
 
 
 @(private)
-unmarshal_string_token :: proc(p: ^Parser, val: any, str: string, ti: ^reflect.Type_Info) -> bool {
+unmarshal_string_token :: proc(p: ^Parser, val: any, str: string, ti: ^reflect.Type_Info) -> (ok: bool, err: Error) {
 	val := val
 	switch &dst in val {
 	case string:
 		dst = str
-		return true
+		return true, nil
 	case cstring:  
 		if str == "" {
-			dst = strings.clone_to_cstring("", p.allocator)
+			a_err: runtime.Allocator_Error
+			dst, a_err = strings.clone_to_cstring("", p.allocator)
+			#partial switch a_err {
+			case nil:
+				// okay
+			case .Out_Of_Memory:
+				err = .Out_Of_Memory
+			case:
+				err = .Invalid_Allocator
+			}
+			if err != nil {
+				return
+			}
 		} else {
 			// NOTE: This is valid because 'clone_string' appends a NUL terminator
 			dst = cstring(raw_data(str)) 
 		}
-		return true
+		ok = true
+		return
 	}
 	
 	#partial switch variant in ti.variant {
@@ -193,31 +206,37 @@ unmarshal_string_token :: proc(p: ^Parser, val: any, str: string, ti: ^reflect.T
 		for name, i in variant.names {
 			if name == str {
 				assign_int(val, variant.values[i])
-				return true
+				return true, nil
 			}
 		}
 		// TODO(bill): should this be an error or not?
-		return true
+		return true, nil
 		
 	case reflect.Type_Info_Integer:
-		i := strconv.parse_i128(str) or_return
+		i, pok := strconv.parse_i128(str)
+		if !pok {
+			return false, nil
+		}
 		if assign_int(val, i) {
-			return true
+			return true, nil
 		}
 		if assign_float(val, i) {
-			return true
+			return true, nil
 		}
 	case reflect.Type_Info_Float:
-		f := strconv.parse_f64(str) or_return
+		f, pok := strconv.parse_f64(str)
+		if !pok {
+			return false, nil
+		}
 		if assign_int(val, f) {
-			return true
+			return true, nil
 		}
 		if assign_float(val, f) {
-			return true
+			return true, nil
 		}
 	}
 	
-	return false
+	return false, nil
 }
 
 
@@ -304,7 +323,7 @@ unmarshal_value :: proc(p: ^Parser, v: any) -> (err: Unmarshal_Error) {
 	case .Ident:
 		advance_token(p)
 		if p.spec == .MJSON {
-			if unmarshal_string_token(p, any{v.data, ti.id}, token.text, ti) {
+			if unmarshal_string_token(p, any{v.data, ti.id}, token.text, ti) or_return {
 				return nil
 			}
 		}
@@ -312,13 +331,18 @@ unmarshal_value :: proc(p: ^Parser, v: any) -> (err: Unmarshal_Error) {
 		
 	case .String:
 		advance_token(p)
-		str := unquote_string(token, p.spec, p.allocator) or_return
-		if unmarshal_string_token(p, any{v.data, ti.id}, str, ti) {
-			return nil
+		str  := unquote_string(token, p.spec, p.allocator) or_return
+		dest := any{v.data, ti.id}
+		if !(unmarshal_string_token(p, dest, str, ti) or_return) {
+			delete(str, p.allocator)
+			return UNSUPPORTED_TYPE
 		}
-		delete(str, p.allocator)
-		return UNSUPPORTED_TYPE
 
+		switch destv in dest {
+		case string, cstring:
+		case: delete(str, p.allocator)
+		}
+		return nil
 
 	case .Open_Brace:
 		return unmarshal_object(p, v, .Close_Brace)
@@ -393,15 +417,15 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
 		if .raw_union in t.flags {
 			return UNSUPPORTED_TYPE
 		}
-	
+
+		fields := reflect.struct_fields_zipped(ti.id)
+		
 		struct_loop: for p.curr_token.kind != end_token {
-			key, _ := parse_object_key(p, p.allocator)
+			key := parse_object_key(p, p.allocator) or_return
 			defer delete(key, p.allocator)
 			
 			unmarshal_expect_token(p, .Colon)						
 			
-			fields := reflect.struct_fields_zipped(ti.id)
-
 			field_test :: #force_inline proc "contextless" (field_used: [^]byte, offset: uintptr) -> bool {
 				prev_set := field_used[offset/8] & byte(offset&7) != 0
 				field_used[offset/8] |= byte(offset&7)
@@ -409,7 +433,7 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
 			}
 
 			field_used_bytes := (reflect.size_of_typeid(ti.id)+7)/8
-			field_used := intrinsics.alloca(field_used_bytes, 1)
+			field_used := intrinsics.alloca(field_used_bytes + 1, 1) // + 1 to not overflow on size_of 0 types.
 			intrinsics.mem_zero(field_used, field_used_bytes)
 
 			use_field_idx := -1

+ 1 - 13
core/fmt/fmt.odin

@@ -1531,7 +1531,7 @@ fmt_pointer :: proc(fi: ^Info, p: rawptr, verb: rune) {
 	case 'o': _fmt_int(fi, u,  8, false, 8*size_of(rawptr), __DIGITS_UPPER)
 	case 'i', 'd': _fmt_int(fi, u, 10, false, 8*size_of(rawptr), __DIGITS_UPPER)
 	case 'z': _fmt_int(fi, u, 12, false, 8*size_of(rawptr), __DIGITS_UPPER)
-	case 'x': _fmt_int(fi, u, 16, false, 8*size_of(rawptr), __DIGITS_UPPER)
+	case 'x': _fmt_int(fi, u, 16, false, 8*size_of(rawptr), __DIGITS_LOWER)
 	case 'X': _fmt_int(fi, u, 16, false, 8*size_of(rawptr), __DIGITS_UPPER)
 
 	case:
@@ -3002,18 +3002,6 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
 	case runtime.Type_Info_Bit_Set:
 		fmt_bit_set(fi, v, verb = verb)
 
-	case runtime.Type_Info_Relative_Pointer:
-		ptr := reflect.relative_pointer_to_absolute_raw(v.data, info.base_integer.id)
-		absolute_ptr := any{ptr, info.pointer.id}
-
-		fmt_value(fi, absolute_ptr, verb)
-
-	case runtime.Type_Info_Relative_Multi_Pointer:
-		ptr := reflect.relative_pointer_to_absolute_raw(v.data, info.base_integer.id)
-		absolute_ptr := any{ptr, info.pointer.id}
-
-		fmt_value(fi, absolute_ptr, verb)
-
 	case runtime.Type_Info_Matrix:
 		fmt_matrix(fi, v, verb, info)
 

+ 10 - 2
core/image/general.odin

@@ -23,7 +23,15 @@ register :: proc(kind: Which_File_Type, loader: Loader_Proc, destroyer: Destroy_
 load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
 	loader := _internal_loaders[which(data)]
 	if loader == nil {
-		return nil, .Unsupported_Format
+
+		// Check if there is at least one loader, otherwise panic to let the user know about misuse.
+		for a_loader in _internal_loaders {
+			if a_loader != nil {
+				return nil, .Unsupported_Format
+			}
+		}
+
+		panic("image.load called when no image loaders are registered. Register a loader by first importing a subpackage (eg: `import \"core:image/png\"`), or with image.register")
 	}
 	return loader(data, options, allocator)
 }
@@ -185,7 +193,7 @@ which_bytes :: proc(data: []byte) -> Which_File_Type {
 		return .HDR
 	case s[:4] == "\x38\x42\x50\x53":
 		return .PSD
-	case s[:4] != "\x53\x80\xF6\x34" && s[88:92] == "PICT":
+	case s[:4] == "\x53\x80\xF6\x34" && s[88:92] == "PICT":
 		return .PIC
 	case s[:4] == "\x69\x63\x6e\x73":
 		return .ICNS

+ 1 - 129
core/image/png/helpers.odin

@@ -396,132 +396,4 @@ exif :: proc(c: image.PNG_Chunk) -> (res: Exif, ok: bool) {
 	General helper functions
 */
 
-compute_buffer_size :: image.compute_buffer_size
-
-/*
-	PNG save helpers
-*/
-
-when false {
-
-	make_chunk :: proc(c: any, t: Chunk_Type) -> (res: Chunk) {
-
-		data: []u8
-		if v, ok := c.([]u8); ok {
-			data = v
-		} else {
-			data = mem.any_to_bytes(c)
-		}
-
-		res.header.length = u32be(len(data))
-		res.header.type   = t
-		res.data   = data
-
-		// CRC the type
-		crc    := hash.crc32(mem.any_to_bytes(res.header.type))
-		// Extend the CRC with the data
-		res.crc = u32be(hash.crc32(data, crc))
-		return
-	}
-
-	write_chunk :: proc(fd: os.Handle, chunk: Chunk) {
-		c := chunk
-		// Write length + type
-		os.write_ptr(fd, &c.header, 8)
-		// Write data
-		os.write_ptr(fd, mem.raw_data(c.data), int(c.header.length))
-		// Write CRC32
-		os.write_ptr(fd, &c.crc, 4)
-	}
-
-	write_image_as_png :: proc(filename: string, image: Image) -> (err: Error) {
-		profiler.timed_proc()
-		using image
-		using os
-		flags: int = O_WRONLY|O_CREATE|O_TRUNC
-
-		if len(image.pixels) == 0 || len(image.pixels) < image.width * image.height * int(image.channels) {
-			return .Invalid_Image_Dimensions
-		}
-
-		mode: int = 0
-		when ODIN_OS == .Linux || ODIN_OS == .Darwin {
-			// NOTE(justasd): 644 (owner read, write; group read; others read)
-			mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH
-		}
-
-		fd, fderr := open(filename, flags, mode)
-		if fderr != nil {
-			return .Cannot_Open_File
-		}
-		defer close(fd)
-
-		magic := Signature
-
-		write_ptr(fd, &magic, 8)
-
-		ihdr := IHDR{
-			width              = u32be(width),
-			height             = u32be(height),
-			bit_depth          = depth,
-			compression_method = 0,
-			filter_method      = 0,
-			interlace_method   = .None,
-		}
-
-		switch channels {
-		case 1: ihdr.color_type = Color_Type{}
-		case 2: ihdr.color_type = Color_Type{.Alpha}
-		case 3: ihdr.color_type = Color_Type{.Color}
-		case 4: ihdr.color_type = Color_Type{.Color, .Alpha}
-		case:// Unhandled
-			return .Unknown_Color_Type
-		}
-		h := make_chunk(ihdr, .IHDR)
-		write_chunk(fd, h)
-
-		bytes_needed := width * height * int(channels) + height
-		filter_bytes := mem.make_dynamic_array_len_cap([dynamic]u8, bytes_needed, bytes_needed, context.allocator)
-		defer delete(filter_bytes)
-
-		i := 0; j := 0
-		// Add a filter byte 0 per pixel row
-		for y := 0; y < height; y += 1 {
-			filter_bytes[j] = 0; j += 1
-			for x := 0; x < width; x += 1 {
-				for z := 0; z < channels; z += 1 {
-					filter_bytes[j+z] = image.pixels[i+z]
-				}
-				i += channels; j += channels
-			}
-		}
-		assert(j == bytes_needed)
-
-		a: []u8 = filter_bytes[:]
-
-		out_buf: ^[dynamic]u8
-		defer free(out_buf)
-
-		ctx := zlib.ZLIB_Context{
-			in_buf  = &a,
-			out_buf = out_buf,
-		}
-		err = zlib.write_zlib_stream_from_memory(&ctx)
-
-		b: []u8
-		if err == nil {
-			b = ctx.out_buf[:]
-		} else {
-			return err
-		}
-
-		idat := make_chunk(b, .IDAT)
-
-		write_chunk(fd, idat)
-
-		iend := make_chunk([]u8{}, .IEND)
-		write_chunk(fd, iend)
-
-		return nil
-	}
-}
+compute_buffer_size :: image.compute_buffer_size

+ 8 - 4
core/io/util.odin

@@ -132,9 +132,13 @@ write_encoded_rune :: proc(w: Writer, r: rune, write_quote := true, n_written: ^
 			buf: [2]byte
 			s := strconv.append_bits(buf[:], u64(r), 16, true, 64, strconv.digits, nil)
 			switch len(s) {
-			case 0: write_string(w, "00", &n) or_return
-			case 1: write_byte(w, '0',    &n) or_return
-			case 2: write_string(w, s,    &n) or_return
+			case 0: 
+				write_string(w, "00", &n) or_return
+			case 1: 
+				write_byte(w, '0',    &n) or_return
+				fallthrough
+			case 2: 
+				write_string(w, s,    &n) or_return
 			}
 		} else {
 			write_rune(w, r, &n) or_return
@@ -225,7 +229,7 @@ write_escaped_rune :: proc(w: Writer, r: rune, quote: byte, html_safe := false,
 			} else {
 				write_byte(w, '\\', &n) or_return
 				write_byte(w, 'U', &n)  or_return
-				for s := 24; s >= 0; s -= 4 {
+				for s := 28; s >= 0; s -= 4 {
 					write_byte(w, DIGITS_LOWER[c>>uint(s) & 0xf], &n) or_return
 				}
 			}

+ 8 - 8
core/log/file_console_logger.odin

@@ -37,30 +37,30 @@ File_Console_Logger_Data :: struct {
 	ident: string,
 }
 
-create_file_logger :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "") -> Logger {
-	data := new(File_Console_Logger_Data)
+create_file_logger :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "", allocator := context.allocator) -> Logger {
+	data := new(File_Console_Logger_Data, allocator)
 	data.file_handle = h
 	data.ident = ident
 	return Logger{file_console_logger_proc, data, lowest, opt}
 }
 
-destroy_file_logger :: proc(log: Logger) {
+destroy_file_logger :: proc(log: Logger, allocator := context.allocator) {
 	data := cast(^File_Console_Logger_Data)log.data
 	if data.file_handle != os.INVALID_HANDLE {
 		os.close(data.file_handle)
 	}
-	free(data)
+	free(data, allocator)
 }
 
-create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logger_Opts, ident := "") -> Logger {
-	data := new(File_Console_Logger_Data)
+create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logger_Opts, ident := "", allocator := context.allocator) -> Logger {
+	data := new(File_Console_Logger_Data, allocator)
 	data.file_handle = os.INVALID_HANDLE
 	data.ident = ident
 	return Logger{file_console_logger_proc, data, lowest, opt}
 }
 
-destroy_console_logger :: proc(log: Logger) {
-	free(log.data)
+destroy_console_logger :: proc(log: Logger, allocator := context.allocator) {
+	free(log.data, allocator)
 }
 
 file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {

+ 6 - 6
core/log/multi_logger.odin

@@ -5,17 +5,17 @@ Multi_Logger_Data :: struct {
 	loggers: []Logger,
 }
 
-create_multi_logger :: proc(logs: ..Logger) -> Logger {
-	data := new(Multi_Logger_Data)
-	data.loggers = make([]Logger, len(logs))
+create_multi_logger :: proc(logs: ..Logger, allocator := context.allocator) -> Logger {
+	data := new(Multi_Logger_Data, allocator)
+	data.loggers = make([]Logger, len(logs), allocator)
 	copy(data.loggers, logs)
 	return Logger{multi_logger_proc, data, Level.Debug, nil}
 }
 
-destroy_multi_logger :: proc(log: Logger) {
+destroy_multi_logger :: proc(log: Logger, allocator := context.allocator) {
 	data := (^Multi_Logger_Data)(log.data)
-	delete(data.loggers)
-	free(data)
+	delete(data.loggers, allocator)
+	free(data, allocator)
 }
 
 multi_logger_proc :: proc(logger_data: rawptr, level: Level, text: string,

+ 15 - 3
core/math/linalg/general.odin

@@ -53,15 +53,15 @@ vector_dot :: proc "contextless" (a, b: $T/[$N]$E) -> (c: E) where IS_NUMERIC(E)
 }
 @(require_results)
 quaternion64_dot :: proc "contextless" (a, b: $T/quaternion64) -> (c: f16) {
-	return a.w*a.w + a.x*b.x + a.y*b.y + a.z*b.z
+	return a.w*b.w + a.x*b.x + a.y*b.y + a.z*b.z
 }
 @(require_results)
 quaternion128_dot :: proc "contextless" (a, b: $T/quaternion128) -> (c: f32) {
-	return a.w*a.w + a.x*b.x + a.y*b.y + a.z*b.z
+	return a.w*b.w + a.x*b.x + a.y*b.y + a.z*b.z
 }
 @(require_results)
 quaternion256_dot :: proc "contextless" (a, b: $T/quaternion256) -> (c: f64) {
-	return a.w*a.w + a.x*b.x + a.y*b.y + a.z*b.z
+	return a.w*b.w + a.x*b.x + a.y*b.y + a.z*b.z
 }
 
 dot :: proc{scalar_dot, vector_dot, quaternion64_dot, quaternion128_dot, quaternion256_dot}
@@ -167,6 +167,18 @@ vector_triple_product :: proc "contextless" (a, b, c: $T/[$N]$E) -> T where IS_N
 length :: proc{vector_length, quaternion_length}
 length2 :: proc{vector_length2, quaternion_length2}
 
+
+@(require_results)
+clamp_length :: proc "contextless" (v: $T/[$N]$E, a: E) -> T where IS_FLOAT(E) {
+	if a <= 0 {
+		return 0
+	}
+	
+	m2 := length2(v)
+	return v if (m2 <= a*a) else (v / sqrt(m2) * a) // returns original when m2 is 0
+}
+
+
 @(require_results)
 projection :: proc "contextless" (x, normal: $T/[$N]$E) -> T where IS_NUMERIC(E) {
 	return dot(x, normal) / dot(normal, normal) * normal

+ 16 - 0
core/math/linalg/glsl/linalg_glsl.odin

@@ -473,6 +473,22 @@ floor :: proc{
 @(require_results) floor_dvec3 :: proc "c" (x: dvec3) -> dvec3 { return {floor(x.x), floor(x.y), floor(x.z)} }
 @(require_results) floor_dvec4 :: proc "c" (x: dvec4) -> dvec4 { return {floor(x.x), floor(x.y), floor(x.z), floor(x.w)} }
 
+trunc :: proc{
+	trunc_f32,
+	trunc_f64,
+	trunc_vec2,
+	trunc_vec3,
+	trunc_vec4,
+	trunc_dvec2,
+	trunc_dvec3,
+	trunc_dvec4,
+}
+@(require_results) trunc_vec2 :: proc "c" (x: vec2) -> vec2 { return {trunc(x.x), trunc(x.y)} }
+@(require_results) trunc_vec3 :: proc "c" (x: vec3) -> vec3 { return {trunc(x.x), trunc(x.y), trunc(x.z)} }
+@(require_results) trunc_vec4 :: proc "c" (x: vec4) -> vec4 { return {trunc(x.x), trunc(x.y), trunc(x.z), trunc(x.w)} }
+@(require_results) trunc_dvec2 :: proc "c" (x: dvec2) -> dvec2 { return {trunc(x.x), trunc(x.y)} }
+@(require_results) trunc_dvec3 :: proc "c" (x: dvec3) -> dvec3 { return {trunc(x.x), trunc(x.y), trunc(x.z)} }
+@(require_results) trunc_dvec4 :: proc "c" (x: dvec4) -> dvec4 { return {trunc(x.x), trunc(x.y), trunc(x.z), trunc(x.w)} }
 
 
 round :: proc{

+ 2 - 0
core/math/linalg/glsl/linalg_glsl_math.odin

@@ -23,6 +23,7 @@ import "core:math"
 @(require_results) exp2_f32        :: proc "c" (x: f32) -> f32 { return math.pow(f32(2), x) }
 @(require_results) sign_f32        :: proc "c" (x: f32) -> f32 { return math.sign(x) }
 @(require_results) floor_f32       :: proc "c" (x: f32) -> f32 { return math.floor(x) }
+@(require_results) trunc_f32       :: proc "c" (x: f32) -> f32 { return math.trunc(x) }
 @(require_results) round_f32       :: proc "c" (x: f32) -> f32 { return math.round(x) }
 @(require_results) ceil_f32        :: proc "c" (x: f32) -> f32 { return math.ceil(x) }
 @(require_results) mod_f32         :: proc "c" (x, y: f32) -> f32 { return math.mod(x, y) }
@@ -55,6 +56,7 @@ fract_f32 :: proc "c" (x: f32) -> f32 {
 @(require_results) exp2_f64        :: proc "c" (x: f64) -> f64 { return math.pow(f64(2), x) }
 @(require_results) sign_f64        :: proc "c" (x: f64) -> f64 { return math.sign(x) }
 @(require_results) floor_f64       :: proc "c" (x: f64) -> f64 { return math.floor(x) }
+@(require_results) trunc_f64       :: proc "c" (x: f64) -> f64 { return math.trunc(x) }
 @(require_results) round_f64       :: proc "c" (x: f64) -> f64 { return math.round(x) }
 @(require_results) ceil_f64        :: proc "c" (x: f64) -> f64 { return math.ceil(x) }
 @(require_results) mod_f64         :: proc "c" (x, y: f64) -> f64 { return math.mod(x, y) }

+ 1 - 1
core/math/math.odin

@@ -1271,7 +1271,7 @@ binomial :: proc "contextless" (n, k: int) -> int {
 	}
 
 	b := n
-	for i in 2..<k {
+	for i in 2..=k {
 		b = (b * (n+1-i))/i
 	}
 	return b

+ 6 - 6
core/math/math_sincos.odin

@@ -189,12 +189,12 @@ sincos_f64 :: proc "contextless" (x: f64) -> (sin, cos: f64) #no_bounds_check {
 // sin coefficients
 @(private="file")
 _sin := [?]f64{
-	 0h3de5d8fd1fd19ccd, //  1.58962301576546568060e-10
-	 0hbe5ae5e5a9291f5d, // -2.50507477628578072866e-8
-	 0h3ec71de3567d48a1, //  2.75573136213857245213e-6
-	 0hbf2a01a019bfdf03, // -1.98412698295895385996e-4
-	 0h3f8111111110f7d0, //  8.33333333332211858878e-3
-	 0hbfc5555555555548, // -1.66666666666666307295e-1
+	0h3de5d8fd1fd19ccd, //  1.58962301576546568060e-10
+	0hbe5ae5e5a9291f5d, // -2.50507477628578072866e-8
+	0h3ec71de3567d48a1, //  2.75573136213857245213e-6
+	0hbf2a01a019bfdf03, // -1.98412698295895385996e-4
+	0h3f8111111110f7d0, //  8.33333333332211858878e-3
+	0hbfc5555555555548, // -1.66666666666666307295e-1
 }
 
 // cos coefficients

+ 64 - 39
core/math/rand/rand.odin

@@ -29,30 +29,6 @@ Reset the seed used by the context.random_generator.
 Inputs:
 - seed: The seed value
 
-Example:
-	import "core:math/rand"
-	import "core:fmt"
-
-	set_global_seed_example :: proc() {
-		rand.set_global_seed(1)
-		fmt.println(rand.uint64())
-	}
-
-Possible Output:
-
-	10
-*/
-@(deprecated="Prefer `rand.reset`")
-set_global_seed :: proc(seed: u64) {
-	runtime.random_generator_reset_u64(context.random_generator, seed)
-}
-
-/*
-Reset the seed used by the context.random_generator.
-
-Inputs:
-- seed: The seed value
-
 Example:
 	import "core:math/rand"
 	import "core:fmt"
@@ -670,20 +646,69 @@ choice :: proc(array: $T/[]$E, gen := context.random_generator) -> (res: E) {
 
 
 @(require_results)
-choice_enum :: proc($T: typeid, gen := context.random_generator) -> T
-	where
-		intrinsics.type_is_enum(T),
-		size_of(T) <= 8,
-		len(T) == cap(T) /* Only allow contiguous enum types */ \
-{
-	when intrinsics.type_is_unsigned(intrinsics.type_core_type(T)) &&
-	     u64(max(T)) > u64(max(i64)) {
-		i := uint64(gen) % u64(len(T))
-		i += u64(min(T))
-		return T(i)
+choice_enum :: proc($T: typeid, gen := context.random_generator) -> T where intrinsics.type_is_enum(T) {
+	when size_of(T) <= 8 && len(T) == cap(T) {
+		when intrinsics.type_is_unsigned(intrinsics.type_core_type(T)) &&
+			 u64(max(T)) > u64(max(i64)) {
+			i := uint64(gen) % u64(len(T))
+			i += u64(min(T))
+			return T(i)
+		} else {
+			i := int63_max(i64(len(T)), gen)
+			i += i64(min(T))
+			return T(i)
+		}
 	} else {
-		i := int63_max(i64(len(T)), gen)
-		i += i64(min(T))
-		return T(i)
+		values := runtime.type_info_base(type_info_of(T)).variant.(runtime.Type_Info_Enum).values
+		return T(choice(values))
+	}
+}
+
+/*
+Returns a random *set* bit from the provided `bit_set`.
+
+Inputs:
+- set: The `bit_set` to choose a random set bit from
+
+Returns:
+- res: The randomly selected bit, or the zero value if `ok` is `false`
+- ok:  Whether the bit_set was not empty and thus `res` is actually a random set bit
+
+Example:
+	import "core:math/rand"
+	import "core:fmt"
+
+	choice_bit_set_example :: proc() {
+		Flags :: enum {
+			A,
+			B = 10,
+			C,
+		}
+
+		fmt.println(rand.choice_bit_set(bit_set[Flags]{}))
+		fmt.println(rand.choice_bit_set(bit_set[Flags]{.B}))
+		fmt.println(rand.choice_bit_set(bit_set[Flags]{.B, .C}))
+		fmt.println(rand.choice_bit_set(bit_set[0..<15]{5, 1, 4}))
+	}
+
+Possible Output:
+	A false
+	B true
+	C true
+	5 true
+*/
+@(require_results)
+choice_bit_set :: proc(set: $T/bit_set[$E], gen := context.random_generator) -> (res: E, ok: bool) {
+	total_set := card(set)
+	if total_set == 0 {
+		return {}, false
 	}
-}
+
+	core_set := transmute(intrinsics.type_bit_set_underlying_type(T))set
+
+	for target := int_max(total_set, gen); target > 0; target -= 1 {
+		core_set &= core_set - 1
+	}
+
+	return E(intrinsics.count_trailing_zeros(core_set)), true
+}

+ 0 - 23
core/mem/allocators.odin

@@ -140,14 +140,6 @@ arena_init :: proc(a: ^Arena, data: []byte) {
 	a.temp_count = 0
 }
 
-@(deprecated="prefer 'mem.arena_init'")
-init_arena :: proc(a: ^Arena, data: []byte) {
-	a.data       = data
-	a.offset     = 0
-	a.peak_used  = 0
-	a.temp_count = 0
-}
-
 /*
 Allocate memory from an arena.
 
@@ -786,14 +778,6 @@ stack_init :: proc(s: ^Stack, data: []byte) {
 	s.peak_used   = 0
 }
 
-@(deprecated="prefer 'mem.stack_init'")
-init_stack :: proc(s: ^Stack, data: []byte) {
-	s.data        = data
-	s.prev_offset = 0
-	s.curr_offset = 0
-	s.peak_used   = 0
-}
-
 /*
 Allocate memory from stack.
 
@@ -1162,13 +1146,6 @@ small_stack_init :: proc(s: ^Small_Stack, data: []byte) {
 	s.peak_used = 0
 }
 
-@(deprecated="prefer 'small_stack_init'")
-init_small_stack :: proc(s: ^Small_Stack, data: []byte) {
-	s.data      = data
-	s.offset    = 0
-	s.peak_used = 0
-}
-
 /*
 Small stack allocator.
 

+ 4 - 9
core/mem/mem.odin

@@ -461,10 +461,12 @@ Check if a pointer is aligned.
 
 This procedure checks whether a pointer `x` is aligned to a boundary specified
 by `align`, and returns `true` if the pointer is aligned, and false otherwise.
+
+The specified alignment must be a power of 2.
 */
 is_aligned :: proc "contextless" (x: rawptr, align: int) -> bool {
 	p := uintptr(x)
-	return (p & (1<<uintptr(align) - 1)) == 0
+	return (p & (uintptr(align) - 1)) == 0
 }
 
 /*
@@ -683,11 +685,4 @@ calc_padding_with_header :: proc "contextless" (ptr: uintptr, align: uintptr, he
 		}
 	}
 	return int(padding)
-}
-
-@(require_results, deprecated="prefer 'slice.clone'")
-clone_slice :: proc(slice: $T/[]$E, allocator := context.allocator, loc := #caller_location) -> (new_slice: T) {
-	new_slice, _ = make(T, len(slice), allocator, loc)
-	runtime.copy(new_slice, slice)
-	return new_slice
-}
+}

+ 3 - 7
core/mem/virtual/virtual_posix.odin

@@ -6,17 +6,13 @@ import "core:sys/posix"
 
 // Define non-posix needed flags:
 when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD {
-	MAP_ANONYMOUS :: 0x1000 /* allocated from memory, swap space */
-
-	MADV_FREE     :: 5      /* pages unneeded, discard contents */
+	MADV_FREE :: 5      /* pages unneeded, discard contents */
 } else when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
-	MAP_ANONYMOUS :: 0x1000
-
-	MADV_FREE     :: 6
+	MADV_FREE :: 6
 }
 
 _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
-	flags  := posix.Map_Flags{ .PRIVATE } + transmute(posix.Map_Flags)i32(MAP_ANONYMOUS)
+	flags  := posix.Map_Flags{ .ANONYMOUS, .PRIVATE }
 	result := posix.mmap(nil, size, {}, flags)
 	if result == posix.MAP_FAILED {
 		return nil, .Out_Of_Memory

+ 21 - 3
core/odin/parser/file_tags.odin

@@ -17,6 +17,9 @@ Build_Kind :: struct {
 	arch: runtime.Odin_Arch_Types,
 }
 
+// empty build kind acts as a marker for separating multiple lines with build tags
+BUILD_KIND_NEWLINE_MARKER :: Build_Kind{}
+
 File_Tags :: struct {
 	build_project_name: [][]string,
 	build:              []Build_Kind,
@@ -147,6 +150,11 @@ parse_file_tags :: proc(file: ast.File, allocator := context.allocator) -> (tags
 					append(build_project_names, build_project_name_strings[index_start:])
 				}
 			case "build":
+
+				if len(build_kinds) > 0 {
+					append(build_kinds, BUILD_KIND_NEWLINE_MARKER)
+				}
+
 				kinds_loop: for {
 					os_positive: runtime.Odin_OS_Types
 					os_negative: runtime.Odin_OS_Types
@@ -248,10 +256,20 @@ match_build_tags :: proc(file_tags: File_Tags, target: Build_Target) -> bool {
 		project_name_correct ||= group_correct
 	}
 
-	os_and_arch_correct := len(file_tags.build) == 0
+	os_and_arch_correct := true
 
-	for kind in file_tags.build {
-		os_and_arch_correct ||= target.os in kind.os && target.arch in kind.arch
+	if len(file_tags.build) > 0 {
+		os_and_arch_correct_line := false
+
+		for kind in file_tags.build {
+			if kind == BUILD_KIND_NEWLINE_MARKER {
+				os_and_arch_correct &&= os_and_arch_correct_line
+				os_and_arch_correct_line = false
+			} else {
+				os_and_arch_correct_line ||= target.os in kind.os && target.arch in kind.arch
+			}
+		}
+		os_and_arch_correct &&= os_and_arch_correct_line
 	}
 
 	return !file_tags.ignore && project_name_correct && os_and_arch_correct

+ 3 - 1
core/odin/parser/parser.odin

@@ -3696,6 +3696,8 @@ parse_value_decl :: proc(p: ^Parser, names: []^ast.Expr, docs: ^ast.Comment_Grou
 		}
 	}
 
+	end := p.prev_tok
+
 	if p.expr_level >= 0 {
 		end: ^ast.Expr
 		if !is_mutable && len(values) > 0 {
@@ -3715,7 +3717,7 @@ parse_value_decl :: proc(p: ^Parser, names: []^ast.Expr, docs: ^ast.Comment_Grou
 		}
 	}
 
-	decl := ast.new(ast.Value_Decl, names[0].pos, end_pos(p.prev_tok))
+	decl := ast.new(ast.Value_Decl, names[0].pos, end_pos(end))
 	decl.docs = docs
 	decl.names = names
 	decl.type = type

+ 1 - 1
core/odin/tokenizer/tokenizer.odin

@@ -331,7 +331,7 @@ scan_escape :: proc(t: ^Tokenizer) -> bool {
 		n -= 1
 	}
 
-	if x > max || 0xd800 <= x && x <= 0xe000 {
+	if x > max || 0xd800 <= x && x <= 0xdfff {
 		error(t, offset, "escape sequence is an invalid Unicode code point")
 		return false
 	}

+ 0 - 584
core/os/file_windows.odin

@@ -1,584 +0,0 @@
-package os
-
-import win32 "core:sys/windows"
-import "base:intrinsics"
-import "base:runtime"
-import "core:unicode/utf16"
-
-@(require_results)
-is_path_separator :: proc(c: byte) -> bool {
-	return c == '/' || c == '\\'
-}
-
-@(require_results)
-open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Error) {
-	if len(path) == 0 {
-		return INVALID_HANDLE, General_Error.Not_Exist
-	}
-
-	access: u32
-	switch mode & (O_RDONLY|O_WRONLY|O_RDWR) {
-	case O_RDONLY: access = win32.FILE_GENERIC_READ
-	case O_WRONLY: access = win32.FILE_GENERIC_WRITE
-	case O_RDWR:   access = win32.FILE_GENERIC_READ | win32.FILE_GENERIC_WRITE
-	}
-
-	if mode&O_CREATE != 0 {
-		access |= win32.FILE_GENERIC_WRITE
-	}
-	if mode&O_APPEND != 0 {
-		access &~= win32.FILE_GENERIC_WRITE
-		access |=  win32.FILE_APPEND_DATA
-	}
-
-	share_mode := win32.FILE_SHARE_READ|win32.FILE_SHARE_WRITE
-	sa: ^win32.SECURITY_ATTRIBUTES = nil
-	sa_inherit := win32.SECURITY_ATTRIBUTES{nLength = size_of(win32.SECURITY_ATTRIBUTES), bInheritHandle = true}
-	if mode&O_CLOEXEC == 0 {
-		sa = &sa_inherit
-	}
-
-	create_mode: u32
-	switch {
-	case mode&(O_CREATE|O_EXCL) == (O_CREATE | O_EXCL):
-		create_mode = win32.CREATE_NEW
-	case mode&(O_CREATE|O_TRUNC) == (O_CREATE | O_TRUNC):
-		create_mode = win32.CREATE_ALWAYS
-	case mode&O_CREATE == O_CREATE:
-		create_mode = win32.OPEN_ALWAYS
-	case mode&O_TRUNC == O_TRUNC:
-		create_mode = win32.TRUNCATE_EXISTING
-	case:
-		create_mode = win32.OPEN_EXISTING
-	}
-	wide_path := win32.utf8_to_wstring(path)
-	handle := Handle(win32.CreateFileW(wide_path, access, share_mode, sa, create_mode, win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS, nil))
-	if handle != INVALID_HANDLE {
-		return handle, nil
-	}
-
-	return INVALID_HANDLE, get_last_error()
-}
-
-close :: proc(fd: Handle) -> Error {
-	if !win32.CloseHandle(win32.HANDLE(fd)) {
-		return get_last_error()
-	}
-	return nil
-}
-
-flush :: proc(fd: Handle) -> (err: Error) {
-	if !win32.FlushFileBuffers(win32.HANDLE(fd)) {
-		err = get_last_error()
-	}
-	return
-}
-
-
-
-write :: proc(fd: Handle, data: []byte) -> (int, Error) {
-	if len(data) == 0 {
-		return 0, nil
-	}
-
-	single_write_length: win32.DWORD
-	total_write: i64
-	length := i64(len(data))
-
-	for total_write < length {
-		remaining := length - total_write
-		to_write := win32.DWORD(min(i32(remaining), MAX_RW))
-
-		e := win32.WriteFile(win32.HANDLE(fd), &data[total_write], to_write, &single_write_length, nil)
-		if single_write_length <= 0 || !e {
-			return int(total_write), get_last_error()
-		}
-		total_write += i64(single_write_length)
-	}
-	return int(total_write), nil
-}
-
-@(private="file", require_results)
-read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) {
-	if len(b) == 0 {
-		return 0, nil
-	}
-	
-	BUF_SIZE :: 386
-	buf16: [BUF_SIZE]u16
-	buf8: [4*BUF_SIZE]u8
-
-	for n < len(b) && err == nil {
-		min_read := max(len(b)/4, 1 if len(b) > 0 else 0)
-		max_read := u32(min(BUF_SIZE, min_read))
-		if max_read == 0 {
-			break
-		}
-		
-		single_read_length: u32
-		ok := win32.ReadConsoleW(handle, &buf16[0], max_read, &single_read_length, nil)
-		if !ok {
-			err = get_last_error()
-		}
-
-		buf8_len := utf16.decode_to_utf8(buf8[:], buf16[:single_read_length])
-		src := buf8[:buf8_len]
-
-		ctrl_z := false
-		for i := 0; i < len(src) && n < len(b); i += 1 {
-			x := src[i]
-			if x == 0x1a { // ctrl-z
-				ctrl_z = true
-				break
-			}
-			b[n] = x
-			n += 1
-		}
-		if ctrl_z || single_read_length < max_read {
-			break
-		}
-
-		// NOTE(bill): if the last two values were a newline, then it is expected that
-		// this is the end of the input
-		if n >= 2 && single_read_length == max_read && string(b[n-2:n]) == "\r\n" {
-			break
-		}
-
-	}
-	
-	return
-}
-
-read :: proc(fd: Handle, data: []byte) -> (total_read: int, err: Error) {
-	if len(data) == 0 {
-		return 0, nil
-	}
-	
-	handle := win32.HANDLE(fd)
-	
-	m: u32
-	is_console := win32.GetConsoleMode(handle, &m)
-	length := len(data)
-
-	// NOTE(Jeroen): `length` can't be casted to win32.DWORD here because it'll overflow if > 4 GiB and return 0 if exactly that.
-	to_read := min(i64(length), MAX_RW)
-
-	if is_console {
-		total_read, err = read_console(handle, data[total_read:][:to_read])
-		if err != nil {
-			return total_read, err
-		}
-	} else {
-		// NOTE(Jeroen): So we cast it here *after* we've ensured that `to_read` is at most MAX_RW (1 GiB)
-		bytes_read: win32.DWORD
-		if e := win32.ReadFile(handle, &data[total_read], win32.DWORD(to_read), &bytes_read, nil); e {
-			// Successful read can mean two things, including EOF, see:
-			// https://learn.microsoft.com/en-us/windows/win32/fileio/testing-for-the-end-of-a-file
-			if bytes_read == 0 {
-				return 0, .EOF
-			} else {
-				return int(bytes_read), nil
-			}
-		} else {
-			return 0, get_last_error()
-		}
-	}
-	return total_read, nil
-}
-
-seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) {
-	w: u32
-	switch whence {
-	case 0: w = win32.FILE_BEGIN
-	case 1: w = win32.FILE_CURRENT
-	case 2: w = win32.FILE_END
-	case:
-		return 0, .Invalid_Whence
-	}
-	hi := i32(offset>>32)
-	lo := i32(offset)
-	ft := win32.GetFileType(win32.HANDLE(fd))
-	if ft == win32.FILE_TYPE_PIPE {
-		return 0, .File_Is_Pipe
-	}
-
-	dw_ptr := win32.SetFilePointer(win32.HANDLE(fd), lo, &hi, w)
-	if dw_ptr == win32.INVALID_SET_FILE_POINTER {
-		err := get_last_error()
-		return 0, err
-	}
-	return i64(hi)<<32 + i64(dw_ptr), nil
-}
-
-@(require_results)
-file_size :: proc(fd: Handle) -> (i64, Error) {
-	length: win32.LARGE_INTEGER
-	err: Error
-	if !win32.GetFileSizeEx(win32.HANDLE(fd), &length) {
-		err = get_last_error()
-	}
-	return i64(length), err
-}
-
-
-@(private)
-MAX_RW :: 1<<30
-
-@(private)
-pread :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
-	curr_off := seek(fd, 0, 1) or_return
-	defer seek(fd, curr_off, 0)
-
-	buf := data
-	if len(buf) > MAX_RW {
-		buf = buf[:MAX_RW]
-	}
-
-	o := win32.OVERLAPPED{
-		OffsetHigh = u32(offset>>32),
-		Offset = u32(offset),
-	}
-
-	// TODO(bill): Determine the correct behaviour for consoles
-
-	h := win32.HANDLE(fd)
-	done: win32.DWORD
-	e: Error
-	if !win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o) {
-		e = get_last_error()
-		done = 0
-	}
-	return int(done), e
-}
-@(private)
-pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
-	curr_off := seek(fd, 0, 1) or_return
-	defer seek(fd, curr_off, 0)
-
-	buf := data
-	if len(buf) > MAX_RW {
-		buf = buf[:MAX_RW]
-	}
-
-	o := win32.OVERLAPPED{
-		OffsetHigh = u32(offset>>32),
-		Offset = u32(offset),
-	}
-
-	h := win32.HANDLE(fd)
-	done: win32.DWORD
-	e: Error
-	if !win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o) {
-		e = get_last_error()
-		done = 0
-	}
-	return int(done), e
-}
-
-/*
-read_at returns n: 0, err: 0 on EOF
-*/
-read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
-	if offset < 0 {
-		return 0, .Invalid_Offset
-	}
-
-	b, offset := data, offset
-	for len(b) > 0 {
-		m, e := pread(fd, b, offset)
-		if e == ERROR_EOF {
-			err = nil
-			break
-		}
-		if e != nil {
-			err = e
-			break
-		}
-		n += m
-		b = b[m:]
-		offset += i64(m)
-	}
-	return
-}
-
-write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
-	if offset < 0 {
-		return 0, .Invalid_Offset
-	}
-
-	b, offset := data, offset
-	for len(b) > 0 {
-		m := pwrite(fd, b, offset) or_return
-		n += m
-		b = b[m:]
-		offset += i64(m)
-	}
-	return
-}
-
-
-
-// NOTE(bill): Uses startup to initialize it
-stdin  := get_std_handle(uint(win32.STD_INPUT_HANDLE))
-stdout := get_std_handle(uint(win32.STD_OUTPUT_HANDLE))
-stderr := get_std_handle(uint(win32.STD_ERROR_HANDLE))
-
-
-@(require_results)
-get_std_handle :: proc "contextless" (h: uint) -> Handle {
-	fd := win32.GetStdHandle(win32.DWORD(h))
-	return Handle(fd)
-}
-
-
-exists :: proc(path: string) -> bool {
-	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
-	wpath := win32.utf8_to_wstring(path, context.temp_allocator)
-	attribs := win32.GetFileAttributesW(wpath)
-
-	return attribs != win32.INVALID_FILE_ATTRIBUTES
-}
-
-@(require_results)
-is_file :: proc(path: string) -> bool {
-	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
-	wpath := win32.utf8_to_wstring(path, context.temp_allocator)
-	attribs := win32.GetFileAttributesW(wpath)
-
-	if attribs != win32.INVALID_FILE_ATTRIBUTES {
-		return attribs & win32.FILE_ATTRIBUTE_DIRECTORY == 0
-	}
-	return false
-}
-
-@(require_results)
-is_dir :: proc(path: string) -> bool {
-	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
-	wpath := win32.utf8_to_wstring(path, context.temp_allocator)
-	attribs := win32.GetFileAttributesW(wpath)
-
-	if attribs != win32.INVALID_FILE_ATTRIBUTES {
-		return attribs & win32.FILE_ATTRIBUTE_DIRECTORY != 0
-	}
-	return false
-}
-
-// NOTE(tetra): GetCurrentDirectory is not thread safe with SetCurrentDirectory and GetFullPathName
-@private cwd_lock := win32.SRWLOCK{} // zero is initialized
-
-@(require_results)
-get_current_directory :: proc(allocator := context.allocator) -> string {
-	win32.AcquireSRWLockExclusive(&cwd_lock)
-
-	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
-
-	sz_utf16 := win32.GetCurrentDirectoryW(0, nil)
-	dir_buf_wstr, _ := make([]u16, sz_utf16, context.temp_allocator) // the first time, it _includes_ the NUL.
-
-	sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr))
-	assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL.
-
-	win32.ReleaseSRWLockExclusive(&cwd_lock)
-
-	return win32.utf16_to_utf8(dir_buf_wstr, allocator) or_else ""
-}
-
-set_current_directory :: proc(path: string) -> (err: Error) {
-	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
-	wstr := win32.utf8_to_wstring(path, context.temp_allocator)
-
-	win32.AcquireSRWLockExclusive(&cwd_lock)
-
-	if !win32.SetCurrentDirectoryW(wstr) {
-		err = get_last_error()
-	}
-
-	win32.ReleaseSRWLockExclusive(&cwd_lock)
-
-	return
-}
-change_directory :: set_current_directory
-
-make_directory :: proc(path: string, mode: u32 = 0) -> (err: Error) {
-	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
-	// Mode is unused on Windows, but is needed on *nix
-	wpath := win32.utf8_to_wstring(path, context.temp_allocator)
-
-	if !win32.CreateDirectoryW(wpath, nil) {
-		err = get_last_error()
-	}
-	return
-}
-
-
-remove_directory :: proc(path: string) -> (err: Error) {
-	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
-	wpath := win32.utf8_to_wstring(path, context.temp_allocator)
-
-	if !win32.RemoveDirectoryW(wpath) {
-		err = get_last_error()
-	}
-	return
-}
-
-
-
-@(private, require_results)
-is_abs :: proc(path: string) -> bool {
-	if len(path) > 0 && path[0] == '/' {
-		return true
-	}
-	when ODIN_OS == .Windows {
-		if len(path) > 2 {
-			switch path[0] {
-			case 'A'..='Z', 'a'..='z':
-				return path[1] == ':' && is_path_separator(path[2])
-			}
-		}
-	}
-	return false
-}
-
-@(private, require_results)
-fix_long_path :: proc(path: string) -> string {
-	if len(path) < 248 {
-		return path
-	}
-
-	if len(path) >= 2 && path[:2] == `\\` {
-		return path
-	}
-	if !is_abs(path) {
-		return path
-	}
-
-	prefix :: `\\?`
-
-	path_buf, _ := make([]byte, len(prefix)+len(path)+len(`\`), context.temp_allocator)
-	copy(path_buf, prefix)
-	n := len(path)
-	r, w := 0, len(prefix)
-	for r < n {
-		switch {
-		case is_path_separator(path[r]):
-			r += 1
-		case path[r] == '.' && (r+1 == n || is_path_separator(path[r+1])):
-			r += 1
-		case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || is_path_separator(path[r+2])):
-			return path
-		case:
-			path_buf[w] = '\\'
-			w += 1
-			for ; r < n && !is_path_separator(path[r]); r += 1 {
-				path_buf[w] = path[r]
-				w += 1
-			}
-		}
-	}
-
-	if w == len(`\\?\c:`) {
-		path_buf[w] = '\\'
-		w += 1
-	}
-	return string(path_buf[:w])
-}
-
-
-link :: proc(old_name, new_name: string) -> (err: Error) {
-	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
-	n := win32.utf8_to_wstring(fix_long_path(new_name))
-	o := win32.utf8_to_wstring(fix_long_path(old_name))
-	return Platform_Error(win32.CreateHardLinkW(n, o, nil))
-}
-
-unlink :: proc(path: string) -> (err: Error) {
-	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
-	wpath := win32.utf8_to_wstring(path, context.temp_allocator)
-
-	if !win32.DeleteFileW(wpath) {
-		err = get_last_error()
-	}
-	return
-}
-
-
-
-rename :: proc(old_path, new_path: string) -> (err: Error) {
-	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
-	from := win32.utf8_to_wstring(old_path, context.temp_allocator)
-	to := win32.utf8_to_wstring(new_path, context.temp_allocator)
-
-	if !win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) {
-		err = get_last_error()
-	}
-	return
-}
-
-
-ftruncate :: proc(fd: Handle, length: i64) -> (err: Error) {
-	curr_off := seek(fd, 0, 1) or_return
-	defer seek(fd, curr_off, 0)
-	_= seek(fd, length, 0) or_return
-	ok := win32.SetEndOfFile(win32.HANDLE(fd))
-	if !ok {
-		return get_last_error()
-	}
-	return nil
-}
-
-truncate :: proc(path: string, length: i64) -> (err: Error) {
-	fd := open(path, O_WRONLY|O_CREATE, 0o666) or_return
-	defer close(fd)
-	return ftruncate(fd, length)
-}
-
-
-remove :: proc(name: string) -> Error {
-	p := win32.utf8_to_wstring(fix_long_path(name))
-	err, err1: win32.DWORD
-	if !win32.DeleteFileW(p) {
-		err = win32.GetLastError()
-	}
-	if err == 0 {
-		return nil
-	}
-	if !win32.RemoveDirectoryW(p) {
-		err1 = win32.GetLastError()
-	}
-	if err1 == 0 {
-		return nil
-	}
-
-	if err != err1 {
-		a := win32.GetFileAttributesW(p)
-		if a == ~u32(0) {
-			err = win32.GetLastError()
-		} else {
-			if a & win32.FILE_ATTRIBUTE_DIRECTORY != 0 {
-				err = err1
-			} else if a & win32.FILE_ATTRIBUTE_READONLY != 0 {
-				if win32.SetFileAttributesW(p, a &~ win32.FILE_ATTRIBUTE_READONLY) {
-					err = 0
-					if !win32.DeleteFileW(p) {
-						err = win32.GetLastError()
-					}
-				}
-			}
-		}
-	}
-
-	return Platform_Error(err)
-}
-
-
-@(require_results)
-pipe :: proc() -> (r, w: Handle, err: Error) {
-	sa: win32.SECURITY_ATTRIBUTES
-	sa.nLength = size_of(win32.SECURITY_ATTRIBUTES)
-	sa.bInheritHandle = true
-	if !win32.CreatePipe((^win32.HANDLE)(&r), (^win32.HANDLE)(&w), &sa, 0) {
-		err = get_last_error()
-	}
-	return
-}
-

+ 1 - 1
core/os/os2/dir_linux.odin

@@ -13,7 +13,7 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info
 
 @(require_results)
 _read_directory_iterator_create :: proc(f: ^File) -> (Read_Directory_Iterator, Error) {
-	return {}, nil
+	return {}, .Unsupported
 }
 
 _read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) {

+ 4 - 6
core/os/os2/dir_windows.odin

@@ -16,28 +16,25 @@ find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW, al
 	}
 	path := concatenate({base_path, `\`, win32_utf16_to_utf8(d.cFileName[:], temp_allocator()) or_else ""}, allocator) or_return
 
+	handle := win32.HANDLE(_open_internal(path, {.Read}, 0o666) or_else 0)
+	defer win32.CloseHandle(handle)
 
 	fi.fullpath = path
 	fi.name = basename(path)
 	fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
 
-	fi.type, fi.mode = _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, d.dwReserved0)
+	fi.type, fi.mode = _file_type_mode_from_file_attributes(d.dwFileAttributes, handle, d.dwReserved0)
 
 	fi.creation_time     = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime))
 	fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime))
 	fi.access_time       = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime))
 
-
-	handle := win32.HANDLE(_open_internal(path, {.Read}, 0o666) or_else 0)
-	defer win32.CloseHandle(handle)
-
 	if file_id_info: win32.FILE_ID_INFO; handle != nil && win32.GetFileInformationByHandleEx(handle, .FileIdInfo, &file_id_info, size_of(file_id_info)) {
 		#assert(size_of(fi.inode) == size_of(file_id_info.FileId))
 		#assert(size_of(fi.inode) == 16)
 		runtime.mem_copy_non_overlapping(&fi.inode, &file_id_info.FileId, 16)
 	}
 
-
 	return
 }
 
@@ -137,5 +134,6 @@ _read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) {
 		return
 	}
 	file_info_delete(it.impl.prev_fi, file_allocator())
+	delete(it.impl.path, file_allocator())
 	win32.FindClose(it.impl.find_handle)
 }

+ 3 - 19
core/os/os2/file_linux.odin

@@ -272,28 +272,12 @@ _truncate :: proc(f: ^File, size: i64) -> Error {
 }
 
 _remove :: proc(name: string) -> Error {
-	is_dir_fd :: proc(fd: linux.Fd) -> bool {
-		s: linux.Stat
-		if linux.fstat(fd, &s) != .NONE {
-			return false
-		}
-		return linux.S_ISDIR(s.mode)
-	}
-
 	TEMP_ALLOCATOR_GUARD()
 	name_cstr := temp_cstring(name) or_return
 
-	fd, errno := linux.open(name_cstr, {.NOFOLLOW})
-	#partial switch (errno) {
-	case .ELOOP:
-		/* symlink */
-	case .NONE:
-		defer linux.close(fd)
-		if is_dir_fd(fd) {
-			return _get_platform_error(linux.rmdir(name_cstr))
-		}
-	case:
-		return _get_platform_error(errno)
+	if fd, errno := linux.open(name_cstr, _OPENDIR_FLAGS); errno == .NONE {
+		linux.close(fd)
+		return _get_platform_error(linux.rmdir(name_cstr))
 	}
 
 	return _get_platform_error(linux.unlink(name_cstr))

+ 3 - 3
core/os/os2/file_windows.odin

@@ -50,9 +50,9 @@ init_std_files :: proc() {
 }
 @(fini)
 fini_std_files :: proc() {
-	close(stdin)
-	close(stdout)
-	close(stderr)
+	_destroy((^File_Impl)(stdin.impl))
+	_destroy((^File_Impl)(stdout.impl))
+	_destroy((^File_Impl)(stderr.impl))
 }
 
 

+ 1 - 1
core/os/os2/heap_linux.odin

@@ -415,7 +415,7 @@ _region_resize :: proc(alloc: ^Allocation_Header, new_size: int, alloc_is_free_l
 	back_idx := -1
 	idx: u16
 	infinite: for {
-		for i := 0; i < len(region_iter.hdr.free_list); i += 1 {
+		for i := 0; i < int(region_iter.hdr.free_list_len); i += 1 {
 			idx = region_iter.hdr.free_list[i]
 			if _get_block_count(region_iter.memory[idx]) >= new_block_count {
 				break infinite

+ 10 - 1
core/os/os2/process.odin

@@ -290,12 +290,21 @@ process_open :: proc(pid: int, flags := Process_Open_Flags {}) -> (Process, Erro
 	return _process_open(pid, flags)
 }
 
+
+/*
+OS-specific process attributes.
+*/
+Process_Attributes :: struct {
+	sys_attr: _Sys_Process_Attributes,
+}
+
 /*
 	The description of how a process should be created.
 */
 Process_Desc :: struct {
 	// OS-specific attributes.
-	sys_attr: _Sys_Process_Attributes,
+	sys_attr: Process_Attributes,
+
 	// The working directory of the process. If the string has length 0, the
 	// working directory is assumed to be the current working directory of the
 	// current process.

+ 11 - 36
core/os/os2/process_linux.odin

@@ -384,14 +384,6 @@ _Sys_Process_Attributes :: struct {}
 
 @(private="package")
 _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
-	has_executable_permissions :: proc(fd: linux.Fd) -> bool {
-		backing: [48]u8
-		b := strings.builder_from_bytes(backing[:])
-		strings.write_string(&b, "/proc/self/fd/")
-		strings.write_int(&b, int(fd))
-		return linux.access(strings.to_cstring(&b), linux.X_OK) == .NONE
-	}
-
 	TEMP_ALLOCATOR_GUARD()
 
 	if len(desc.command) == 0 {
@@ -411,7 +403,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
 	}
 
 	// search PATH if just a plain name is provided
-	exe_fd: linux.Fd
+	exe_path: cstring
 	executable_name := desc.command[0]
 	if strings.index_byte(executable_name, '/') < 0 {
 		path_env := get_env("PATH", temp_allocator())
@@ -426,16 +418,11 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
 			strings.write_byte(&exe_builder, '/')
 			strings.write_string(&exe_builder, executable_name)
 
-			exe_path := strings.to_cstring(&exe_builder)
-			if exe_fd, errno = linux.openat(dir_fd, exe_path, {.PATH, .CLOEXEC}); errno != .NONE {
-				continue
-			}
-			if !has_executable_permissions(exe_fd) {
-				linux.close(exe_fd)
-				continue
+			exe_path = strings.to_cstring(&exe_builder)
+			if linux.access(exe_path, linux.X_OK) == .NONE {
+				found = true
+				break
 			}
-			found = true
-			break
 		}
 		if !found {
 			// check in cwd to match windows behavior
@@ -443,29 +430,18 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
 			strings.write_string(&exe_builder, "./")
 			strings.write_string(&exe_builder, executable_name)
 
-			exe_path := strings.to_cstring(&exe_builder)
-			if exe_fd, errno = linux.openat(dir_fd, exe_path, {.PATH, .CLOEXEC}); errno != .NONE {
+			exe_path = strings.to_cstring(&exe_builder)
+			if linux.access(exe_path, linux.X_OK) != .NONE {
 				return process, .Not_Exist
 			}
-			if !has_executable_permissions(exe_fd) {
-				linux.close(exe_fd)
-				return process, .Permission_Denied
-			}
 		}
 	} else {
-		exe_path := temp_cstring(executable_name) or_return
-		if exe_fd, errno = linux.openat(dir_fd, exe_path, {.PATH, .CLOEXEC}); errno != .NONE {
-			return process, _get_platform_error(errno)
-		}
-		if !has_executable_permissions(exe_fd) {
-			linux.close(exe_fd)
-			return process, .Permission_Denied
+		exe_path = temp_cstring(executable_name) or_return
+		if linux.access(exe_path, linux.X_OK) != .NONE {
+			return process, .Not_Exist
 		}
 	}
 
-	// At this point, we have an executable.
-	defer linux.close(exe_fd)
-
 	// args and environment need to be a list of cstrings
 	// that are terminated by a nil pointer.
 	cargs := make([]cstring, len(desc.command) + 1, temp_allocator()) or_return
@@ -492,7 +468,6 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
 	}
 	defer linux.close(child_pipe_fds[READ])
 
-
 	// TODO: This is the traditional textbook implementation with fork.
 	//       A more efficient implementation with vfork:
 	//
@@ -573,7 +548,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
 			write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno)
 		}
 
-		errno = linux.execveat(exe_fd, "", &cargs[0], env, {.AT_EMPTY_PATH})
+		errno = linux.execveat(dir_fd, exe_path, &cargs[0], env)
 		assert(errno != nil)
 		write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno)
 	}

+ 1 - 1
core/os/os2/process_windows.odin

@@ -442,7 +442,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
 		stderr_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd)
 	}
 	if desc.stdin != nil {
-		stdin_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd)
+		stdin_handle = win32.HANDLE((^File_Impl)(desc.stdin.impl).fd)
 	}
 
 	working_dir_w := (win32_utf8_to_wstring(desc.working_dir, temp_allocator()) or_else nil) if len(desc.working_dir) > 0 else nil

+ 27 - 11
core/os/os2/stat_windows.odin

@@ -72,7 +72,11 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runt
 	ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa)
 	if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
 		// Not a symlink
-		return _file_info_from_win32_file_attribute_data(&fa, name, allocator)
+		fi = _file_info_from_win32_file_attribute_data(&fa, name, allocator) or_return
+		if fi.type == .Undetermined {
+			fi.type = _file_type_from_create_file(wname, create_file_attributes)
+		}
+		return
 	}
 
 	err := 0 if ok else win32.GetLastError()
@@ -86,7 +90,11 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runt
 		}
 		win32.FindClose(sh)
 
-		return _file_info_from_win32_find_data(&fd, name, allocator)
+		fi = _file_info_from_win32_find_data(&fd, name, allocator) or_return
+		if fi.type == .Undetermined {
+			fi.type = _file_type_from_create_file(wname, create_file_attributes)
+		}
+		return
 	}
 
 	h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil)
@@ -194,28 +202,36 @@ file_type :: proc(h: win32.HANDLE) -> File_Type {
 	return .Undetermined
 }
 
+_file_type_from_create_file :: proc(wname: win32.wstring, create_file_attributes: u32) -> File_Type {
+	h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil)
+	if h == win32.INVALID_HANDLE_VALUE {
+		return .Undetermined
+	}
+	defer win32.CloseHandle(h)
+	return file_type(h)
+}
+
 _file_type_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (type: File_Type, mode: int) {
 	if file_attributes & win32.FILE_ATTRIBUTE_READONLY != 0 {
 		mode |= 0o444
 	} else {
 		mode |= 0o666
 	}
+
 	is_sym := false
 	if file_attributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
 		is_sym = false
 	} else {
 		is_sym = ReparseTag == win32.IO_REPARSE_TAG_SYMLINK || ReparseTag == win32.IO_REPARSE_TAG_MOUNT_POINT
 	}
+
 	if is_sym {
 		type = .Symlink
-	} else {
-		if file_attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 {
-			type = .Directory
-			mode |= 0o111
-		}
-		if h != nil {
-			type = file_type(h)
-		}
+	} else if file_attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 {
+		type = .Directory
+		mode |= 0o111
+	} else if h != nil {
+		type = file_type(h)
 	}
 	return
 }
@@ -267,7 +283,7 @@ _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HA
 	fi.name = basename(path)
 	fi.inode = u128(u64(d.nFileIndexHigh)<<32 + u64(d.nFileIndexLow))
 	fi.size  = i64(d.nFileSizeHigh)<<32  + i64(d.nFileSizeLow)
-	type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
+	type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, h, 0)
 	fi.type = type
 	fi.mode |= mode
 	fi.creation_time     = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime))

+ 3 - 5
core/os/os_darwin.odin

@@ -679,7 +679,7 @@ get_last_error_string :: proc() -> string {
 
 
 @(require_results)
-open :: proc(path: string, flags: int = O_RDWR, mode: int = 0) -> (handle: Handle, err: Error) {
+open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (handle: Handle, err: Error) {
 	isDir := is_dir_path(path)
 	flags := flags
 	if isDir {
@@ -1026,7 +1026,7 @@ absolute_path_from_handle :: proc(fd: Handle) -> (path: string, err: Error) {
 }
 
 @(require_results)
-absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) {
+absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) {
 	rel := rel
 	if rel == "" {
 		rel = "."
@@ -1041,9 +1041,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) {
 	}
 	defer _unix_free(rawptr(path_ptr))
 
-	path = strings.clone(string(path_ptr))
-
-	return path, nil
+	return strings.clone(string(path_ptr), allocator)
 }
 
 access :: proc(path: string, mask: int) -> bool {

+ 2 - 5
core/os/os_freebsd.odin

@@ -789,7 +789,7 @@ absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) {
 }
 
 @(require_results)
-absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) {
+absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) {
 	rel := rel
 	if rel == "" {
 		rel = "."
@@ -804,10 +804,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) {
 	}
 	defer _unix_free(rawptr(path_ptr))
 
-
-	path = strings.clone(string(path_ptr))
-
-	return path, nil
+	return strings.clone(string(path_ptr), allocator)
 }
 
 access :: proc(path: string, mask: int) -> (bool, Error) {

+ 2 - 4
core/os/os_haiku.odin

@@ -431,7 +431,7 @@ absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) {
 }
 
 @(require_results)
-absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) {
+absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) {
 	rel := rel
 	if rel == "" {
 		rel = "."
@@ -447,9 +447,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) {
 	defer _unix_free(path_ptr)
 
 	path_cstr := cstring(path_ptr)
-	path = strings.clone(string(path_cstr))
-
-	return path, nil
+	return strings.clone(string(path_cstr), allocator)
 }
 
 access :: proc(path: string, mask: int) -> (bool, Error) {

+ 3 - 5
core/os/os_linux.odin

@@ -490,7 +490,7 @@ foreign libc {
 	@(link_name="free")             _unix_free          :: proc(ptr: rawptr) ---
 	@(link_name="realloc")          _unix_realloc       :: proc(ptr: rawptr, size: c.size_t) -> rawptr ---
 
-	@(link_name="execvp")           _unix_execvp       :: proc(path: cstring, argv: [^]cstring) -> int ---
+	@(link_name="execvp")           _unix_execvp       :: proc(path: cstring, argv: [^]cstring) -> c.int ---
 	@(link_name="getenv")           _unix_getenv        :: proc(cstring) -> cstring ---
 	@(link_name="putenv")           _unix_putenv        :: proc(cstring) -> c.int ---
 	@(link_name="setenv")           _unix_setenv        :: proc(key: cstring, value: cstring, overwrite: c.int) -> c.int ---
@@ -917,7 +917,7 @@ absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) {
 }
 
 @(require_results)
-absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) {
+absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) {
 	rel := rel
 	if rel == "" {
 		rel = "."
@@ -932,9 +932,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) {
 	}
 	defer _unix_free(rawptr(path_ptr))
 
-	path = strings.clone(string(path_ptr))
-
-	return path, nil
+	return strings.clone(string(path_ptr), allocator)
 }
 
 access :: proc(path: string, mask: int) -> (bool, Error) {

+ 2 - 4
core/os/os_netbsd.odin

@@ -844,7 +844,7 @@ absolute_path_from_handle :: proc(fd: Handle) -> (path: string, err: Error) {
 }
 
 @(require_results)
-absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) {
+absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) {
 	rel := rel
 	if rel == "" {
 		rel = "."
@@ -859,9 +859,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) {
 	}
 	defer _unix_free(rawptr(path_ptr))
 
-	path = strings.clone(string(path_ptr))
-
-	return path, nil
+	return strings.clone(string(path_ptr), allocator)
 }
 
 access :: proc(path: string, mask: int) -> (bool, Error) {

+ 2 - 4
core/os/os_openbsd.odin

@@ -758,7 +758,7 @@ absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) {
 }
 
 @(require_results)
-absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) {
+absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) {
 	rel := rel
 	if rel == "" {
 		rel = "."
@@ -773,9 +773,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) {
 	}
 	defer _unix_free(rawptr(path_ptr))
 
-	path = strings.clone(string(path_ptr))
-
-	return path, nil
+	return strings.clone(string(path_ptr), allocator)
 }
 
 access :: proc(path: string, mask: int) -> (bool, Error) {

+ 578 - 3
core/os/os_windows.odin

@@ -4,15 +4,13 @@ package os
 import win32 "core:sys/windows"
 import "base:runtime"
 import "base:intrinsics"
+import "core:unicode/utf16"
 
 Handle    :: distinct uintptr
 File_Time :: distinct u64
 
-
 INVALID_HANDLE :: ~Handle(0)
 
-
-
 O_RDONLY   :: 0x00000
 O_WRONLY   :: 0x00001
 O_RDWR     :: 0x00002
@@ -278,3 +276,580 @@ is_windows_11 :: proc "contextless" () -> bool {
 	osvi := get_windows_version_w()
 	return (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber >= WINDOWS_11_BUILD_CUTOFF)
 }
+
+@(require_results)
+is_path_separator :: proc(c: byte) -> bool {
+	return c == '/' || c == '\\'
+}
+
+@(require_results)
+open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Error) {
+	if len(path) == 0 {
+		return INVALID_HANDLE, General_Error.Not_Exist
+	}
+
+	access: u32
+	switch mode & (O_RDONLY|O_WRONLY|O_RDWR) {
+	case O_RDONLY: access = win32.FILE_GENERIC_READ
+	case O_WRONLY: access = win32.FILE_GENERIC_WRITE
+	case O_RDWR:   access = win32.FILE_GENERIC_READ | win32.FILE_GENERIC_WRITE
+	}
+
+	if mode&O_CREATE != 0 {
+		access |= win32.FILE_GENERIC_WRITE
+	}
+	if mode&O_APPEND != 0 {
+		access &~= win32.FILE_GENERIC_WRITE
+		access |=  win32.FILE_APPEND_DATA
+	}
+
+	share_mode := win32.FILE_SHARE_READ|win32.FILE_SHARE_WRITE
+	sa: ^win32.SECURITY_ATTRIBUTES = nil
+	sa_inherit := win32.SECURITY_ATTRIBUTES{nLength = size_of(win32.SECURITY_ATTRIBUTES), bInheritHandle = true}
+	if mode&O_CLOEXEC == 0 {
+		sa = &sa_inherit
+	}
+
+	create_mode: u32
+	switch {
+	case mode&(O_CREATE|O_EXCL) == (O_CREATE | O_EXCL):
+		create_mode = win32.CREATE_NEW
+	case mode&(O_CREATE|O_TRUNC) == (O_CREATE | O_TRUNC):
+		create_mode = win32.CREATE_ALWAYS
+	case mode&O_CREATE == O_CREATE:
+		create_mode = win32.OPEN_ALWAYS
+	case mode&O_TRUNC == O_TRUNC:
+		create_mode = win32.TRUNCATE_EXISTING
+	case:
+		create_mode = win32.OPEN_EXISTING
+	}
+	wide_path := win32.utf8_to_wstring(path)
+	handle := Handle(win32.CreateFileW(wide_path, access, share_mode, sa, create_mode, win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS, nil))
+	if handle != INVALID_HANDLE {
+		return handle, nil
+	}
+
+	return INVALID_HANDLE, get_last_error()
+}
+
+close :: proc(fd: Handle) -> Error {
+	if !win32.CloseHandle(win32.HANDLE(fd)) {
+		return get_last_error()
+	}
+	return nil
+}
+
+flush :: proc(fd: Handle) -> (err: Error) {
+	if !win32.FlushFileBuffers(win32.HANDLE(fd)) {
+		err = get_last_error()
+	}
+	return
+}
+
+
+
+write :: proc(fd: Handle, data: []byte) -> (int, Error) {
+	if len(data) == 0 {
+		return 0, nil
+	}
+
+	single_write_length: win32.DWORD
+	total_write: i64
+	length := i64(len(data))
+
+	for total_write < length {
+		remaining := length - total_write
+		to_write := win32.DWORD(min(i32(remaining), MAX_RW))
+
+		e := win32.WriteFile(win32.HANDLE(fd), &data[total_write], to_write, &single_write_length, nil)
+		if single_write_length <= 0 || !e {
+			return int(total_write), get_last_error()
+		}
+		total_write += i64(single_write_length)
+	}
+	return int(total_write), nil
+}
+
+@(private="file", require_results)
+read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) {
+	if len(b) == 0 {
+		return 0, nil
+	}
+
+	BUF_SIZE :: 386
+	buf16: [BUF_SIZE]u16
+	buf8: [4*BUF_SIZE]u8
+
+	for n < len(b) && err == nil {
+		min_read := max(len(b)/4, 1 if len(b) > 0 else 0)
+		max_read := u32(min(BUF_SIZE, min_read))
+		if max_read == 0 {
+			break
+		}
+
+		single_read_length: u32
+		ok := win32.ReadConsoleW(handle, &buf16[0], max_read, &single_read_length, nil)
+		if !ok {
+			err = get_last_error()
+		}
+
+		buf8_len := utf16.decode_to_utf8(buf8[:], buf16[:single_read_length])
+		src := buf8[:buf8_len]
+
+		ctrl_z := false
+		for i := 0; i < len(src) && n < len(b); i += 1 {
+			x := src[i]
+			if x == 0x1a { // ctrl-z
+				ctrl_z = true
+				break
+			}
+			b[n] = x
+			n += 1
+		}
+		if ctrl_z || single_read_length < max_read {
+			break
+		}
+
+		// NOTE(bill): if the last two values were a newline, then it is expected that
+		// this is the end of the input
+		if n >= 2 && single_read_length == max_read && string(b[n-2:n]) == "\r\n" {
+			break
+		}
+
+	}
+
+	return
+}
+
+read :: proc(fd: Handle, data: []byte) -> (total_read: int, err: Error) {
+	if len(data) == 0 {
+		return 0, nil
+	}
+
+	handle := win32.HANDLE(fd)
+
+	m: u32
+	is_console := win32.GetConsoleMode(handle, &m)
+	length := len(data)
+
+	// NOTE(Jeroen): `length` can't be casted to win32.DWORD here because it'll overflow if > 4 GiB and return 0 if exactly that.
+	to_read := min(i64(length), MAX_RW)
+
+	if is_console {
+		total_read, err = read_console(handle, data[total_read:][:to_read])
+		if err != nil {
+			return total_read, err
+		}
+	} else {
+		// NOTE(Jeroen): So we cast it here *after* we've ensured that `to_read` is at most MAX_RW (1 GiB)
+		bytes_read: win32.DWORD
+		if e := win32.ReadFile(handle, &data[total_read], win32.DWORD(to_read), &bytes_read, nil); e {
+			// Successful read can mean two things, including EOF, see:
+			// https://learn.microsoft.com/en-us/windows/win32/fileio/testing-for-the-end-of-a-file
+			if bytes_read == 0 {
+				return 0, .EOF
+			} else {
+				return int(bytes_read), nil
+			}
+		} else {
+			return 0, get_last_error()
+		}
+	}
+	return total_read, nil
+}
+
+seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) {
+	w: u32
+	switch whence {
+	case 0: w = win32.FILE_BEGIN
+	case 1: w = win32.FILE_CURRENT
+	case 2: w = win32.FILE_END
+	case:
+		return 0, .Invalid_Whence
+	}
+	hi := i32(offset>>32)
+	lo := i32(offset)
+	ft := win32.GetFileType(win32.HANDLE(fd))
+	if ft == win32.FILE_TYPE_PIPE {
+		return 0, .File_Is_Pipe
+	}
+
+	dw_ptr := win32.SetFilePointer(win32.HANDLE(fd), lo, &hi, w)
+	if dw_ptr == win32.INVALID_SET_FILE_POINTER {
+		err := get_last_error()
+		return 0, err
+	}
+	return i64(hi)<<32 + i64(dw_ptr), nil
+}
+
+@(require_results)
+file_size :: proc(fd: Handle) -> (i64, Error) {
+	length: win32.LARGE_INTEGER
+	err: Error
+	if !win32.GetFileSizeEx(win32.HANDLE(fd), &length) {
+		err = get_last_error()
+	}
+	return i64(length), err
+}
+
+
+@(private)
+MAX_RW :: 1<<30
+
+@(private)
+pread :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
+	curr_off := seek(fd, 0, 1) or_return
+	defer seek(fd, curr_off, 0)
+
+	buf := data
+	if len(buf) > MAX_RW {
+		buf = buf[:MAX_RW]
+	}
+
+	o := win32.OVERLAPPED{
+		OffsetHigh = u32(offset>>32),
+		Offset = u32(offset),
+	}
+
+	// TODO(bill): Determine the correct behaviour for consoles
+
+	h := win32.HANDLE(fd)
+	done: win32.DWORD
+	e: Error
+	if !win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o) {
+		e = get_last_error()
+		done = 0
+	}
+	return int(done), e
+}
+@(private)
+pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
+	curr_off := seek(fd, 0, 1) or_return
+	defer seek(fd, curr_off, 0)
+
+	buf := data
+	if len(buf) > MAX_RW {
+		buf = buf[:MAX_RW]
+	}
+
+	o := win32.OVERLAPPED{
+		OffsetHigh = u32(offset>>32),
+		Offset = u32(offset),
+	}
+
+	h := win32.HANDLE(fd)
+	done: win32.DWORD
+	e: Error
+	if !win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o) {
+		e = get_last_error()
+		done = 0
+	}
+	return int(done), e
+}
+
+/*
+read_at returns n: 0, err: 0 on EOF
+*/
+read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
+	if offset < 0 {
+		return 0, .Invalid_Offset
+	}
+
+	b, offset := data, offset
+	for len(b) > 0 {
+		m, e := pread(fd, b, offset)
+		if e == ERROR_EOF {
+			err = nil
+			break
+		}
+		if e != nil {
+			err = e
+			break
+		}
+		n += m
+		b = b[m:]
+		offset += i64(m)
+	}
+	return
+}
+
+write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
+	if offset < 0 {
+		return 0, .Invalid_Offset
+	}
+
+	b, offset := data, offset
+	for len(b) > 0 {
+		m := pwrite(fd, b, offset) or_return
+		n += m
+		b = b[m:]
+		offset += i64(m)
+	}
+	return
+}
+
+
+
+// NOTE(bill): Uses startup to initialize it
+stdin  := get_std_handle(uint(win32.STD_INPUT_HANDLE))
+stdout := get_std_handle(uint(win32.STD_OUTPUT_HANDLE))
+stderr := get_std_handle(uint(win32.STD_ERROR_HANDLE))
+
+
+@(require_results)
+get_std_handle :: proc "contextless" (h: uint) -> Handle {
+	fd := win32.GetStdHandle(win32.DWORD(h))
+	return Handle(fd)
+}
+
+
+exists :: proc(path: string) -> bool {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+	wpath := win32.utf8_to_wstring(path, context.temp_allocator)
+	attribs := win32.GetFileAttributesW(wpath)
+
+	return attribs != win32.INVALID_FILE_ATTRIBUTES
+}
+
+@(require_results)
+is_file :: proc(path: string) -> bool {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+	wpath := win32.utf8_to_wstring(path, context.temp_allocator)
+	attribs := win32.GetFileAttributesW(wpath)
+
+	if attribs != win32.INVALID_FILE_ATTRIBUTES {
+		return attribs & win32.FILE_ATTRIBUTE_DIRECTORY == 0
+	}
+	return false
+}
+
+@(require_results)
+is_dir :: proc(path: string) -> bool {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+	wpath := win32.utf8_to_wstring(path, context.temp_allocator)
+	attribs := win32.GetFileAttributesW(wpath)
+
+	if attribs != win32.INVALID_FILE_ATTRIBUTES {
+		return attribs & win32.FILE_ATTRIBUTE_DIRECTORY != 0
+	}
+	return false
+}
+
+// NOTE(tetra): GetCurrentDirectory is not thread safe with SetCurrentDirectory and GetFullPathName
+@private cwd_lock := win32.SRWLOCK{} // zero is initialized
+
+@(require_results)
+get_current_directory :: proc(allocator := context.allocator) -> string {
+	win32.AcquireSRWLockExclusive(&cwd_lock)
+
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
+
+	sz_utf16 := win32.GetCurrentDirectoryW(0, nil)
+	dir_buf_wstr, _ := make([]u16, sz_utf16, context.temp_allocator) // the first time, it _includes_ the NUL.
+
+	sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr))
+	assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL.
+
+	win32.ReleaseSRWLockExclusive(&cwd_lock)
+
+	return win32.utf16_to_utf8(dir_buf_wstr, allocator) or_else ""
+}
+
+set_current_directory :: proc(path: string) -> (err: Error) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+	wstr := win32.utf8_to_wstring(path, context.temp_allocator)
+
+	win32.AcquireSRWLockExclusive(&cwd_lock)
+
+	if !win32.SetCurrentDirectoryW(wstr) {
+		err = get_last_error()
+	}
+
+	win32.ReleaseSRWLockExclusive(&cwd_lock)
+
+	return
+}
+change_directory :: set_current_directory
+
+make_directory :: proc(path: string, mode: u32 = 0) -> (err: Error) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+	// Mode is unused on Windows, but is needed on *nix
+	wpath := win32.utf8_to_wstring(path, context.temp_allocator)
+
+	if !win32.CreateDirectoryW(wpath, nil) {
+		err = get_last_error()
+	}
+	return
+}
+
+
+remove_directory :: proc(path: string) -> (err: Error) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+	wpath := win32.utf8_to_wstring(path, context.temp_allocator)
+
+	if !win32.RemoveDirectoryW(wpath) {
+		err = get_last_error()
+	}
+	return
+}
+
+
+
+@(private, require_results)
+is_abs :: proc(path: string) -> bool {
+	if len(path) > 0 && path[0] == '/' {
+		return true
+	}
+	when ODIN_OS == .Windows {
+		if len(path) > 2 {
+			switch path[0] {
+			case 'A'..='Z', 'a'..='z':
+				return path[1] == ':' && is_path_separator(path[2])
+			}
+		}
+	}
+	return false
+}
+
+@(private, require_results)
+fix_long_path :: proc(path: string) -> string {
+	if len(path) < 248 {
+		return path
+	}
+
+	if len(path) >= 2 && path[:2] == `\\` {
+		return path
+	}
+	if !is_abs(path) {
+		return path
+	}
+
+	prefix :: `\\?`
+
+	path_buf, _ := make([]byte, len(prefix)+len(path)+len(`\`), context.temp_allocator)
+	copy(path_buf, prefix)
+	n := len(path)
+	r, w := 0, len(prefix)
+	for r < n {
+		switch {
+		case is_path_separator(path[r]):
+			r += 1
+		case path[r] == '.' && (r+1 == n || is_path_separator(path[r+1])):
+			r += 1
+		case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || is_path_separator(path[r+2])):
+			return path
+		case:
+			path_buf[w] = '\\'
+			w += 1
+			for ; r < n && !is_path_separator(path[r]); r += 1 {
+				path_buf[w] = path[r]
+				w += 1
+			}
+		}
+	}
+
+	if w == len(`\\?\c:`) {
+		path_buf[w] = '\\'
+		w += 1
+	}
+	return string(path_buf[:w])
+}
+
+
+link :: proc(old_name, new_name: string) -> (err: Error) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+	n := win32.utf8_to_wstring(fix_long_path(new_name))
+	o := win32.utf8_to_wstring(fix_long_path(old_name))
+	return Platform_Error(win32.CreateHardLinkW(n, o, nil))
+}
+
+unlink :: proc(path: string) -> (err: Error) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+	wpath := win32.utf8_to_wstring(path, context.temp_allocator)
+
+	if !win32.DeleteFileW(wpath) {
+		err = get_last_error()
+	}
+	return
+}
+
+
+
+rename :: proc(old_path, new_path: string) -> (err: Error) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+	from := win32.utf8_to_wstring(old_path, context.temp_allocator)
+	to := win32.utf8_to_wstring(new_path, context.temp_allocator)
+
+	if !win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) {
+		err = get_last_error()
+	}
+	return
+}
+
+
+ftruncate :: proc(fd: Handle, length: i64) -> (err: Error) {
+	curr_off := seek(fd, 0, 1) or_return
+	defer seek(fd, curr_off, 0)
+	_= seek(fd, length, 0) or_return
+	ok := win32.SetEndOfFile(win32.HANDLE(fd))
+	if !ok {
+		return get_last_error()
+	}
+	return nil
+}
+
+truncate :: proc(path: string, length: i64) -> (err: Error) {
+	fd := open(path, O_WRONLY|O_CREATE, 0o666) or_return
+	defer close(fd)
+	return ftruncate(fd, length)
+}
+
+
+remove :: proc(name: string) -> Error {
+	p := win32.utf8_to_wstring(fix_long_path(name))
+	err, err1: win32.DWORD
+	if !win32.DeleteFileW(p) {
+		err = win32.GetLastError()
+	}
+	if err == 0 {
+		return nil
+	}
+	if !win32.RemoveDirectoryW(p) {
+		err1 = win32.GetLastError()
+	}
+	if err1 == 0 {
+		return nil
+	}
+
+	if err != err1 {
+		a := win32.GetFileAttributesW(p)
+		if a == ~u32(0) {
+			err = win32.GetLastError()
+		} else {
+			if a & win32.FILE_ATTRIBUTE_DIRECTORY != 0 {
+				err = err1
+			} else if a & win32.FILE_ATTRIBUTE_READONLY != 0 {
+				if win32.SetFileAttributesW(p, a &~ win32.FILE_ATTRIBUTE_READONLY) {
+					err = 0
+					if !win32.DeleteFileW(p) {
+						err = win32.GetLastError()
+					}
+				}
+			}
+		}
+	}
+
+	return Platform_Error(err)
+}
+
+
+@(require_results)
+pipe :: proc() -> (r, w: Handle, err: Error) {
+	sa: win32.SECURITY_ATTRIBUTES
+	sa.nLength = size_of(win32.SECURITY_ATTRIBUTES)
+	sa.bInheritHandle = true
+	if !win32.CreatePipe((^win32.HANDLE)(&r), (^win32.HANDLE)(&w), &sa, 0) {
+		err = get_last_error()
+	}
+	return
+}

+ 7 - 0
core/path/filepath/match.odin

@@ -246,6 +246,13 @@ glob :: proc(pattern: string, allocator := context.allocator) -> (matches: []str
 	if err != .None {
 		return
 	}
+	defer {
+		for s in m {
+			delete(s)
+		}
+		delete(m)
+	}
+
 	dmatches := make([dynamic]string, 0, 0)
 	for d in m {
 		dmatches, err = _glob(d, file, &dmatches)

+ 5 - 32
core/path/filepath/path_unix.odin

@@ -1,14 +1,10 @@
 #+build linux, darwin, freebsd, openbsd, netbsd
 package filepath
 
-when ODIN_OS == .Darwin {
-	foreign import libc "system:System.framework"
-} else {
-	foreign import libc "system:c"
-}
-
 import "base:runtime"
+
 import "core:strings"
+import "core:sys/posix"
 
 SEPARATOR :: '/'
 SEPARATOR_STRING :: `/`
@@ -28,11 +24,11 @@ abs :: proc(path: string, allocator := context.allocator) -> (string, bool) {
 		rel = "."
 	}
 	rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator)
-	path_ptr := realpath(rel_cstr, nil)
+	path_ptr := posix.realpath(rel_cstr, nil)
 	if path_ptr == nil {
-		return "", __error()^ == 0
+		return "", posix.errno() == nil
 	}
-	defer _unix_free(rawptr(path_ptr))
+	defer posix.free(path_ptr)
 
 	path_str := strings.clone(string(path_ptr), allocator)
 	return path_str, true
@@ -48,26 +44,3 @@ join :: proc(elems: []string, allocator := context.allocator) -> (joined: string
 	}
 	return "", nil
 }
-
-@(private)
-foreign libc {
-	realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring ---
-	@(link_name="free") _unix_free :: proc(ptr: rawptr) ---
-
-}
-when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD {
-	@(private)
-	foreign libc {
-		@(link_name="__error")          __error :: proc() -> ^i32 ---
-	}
-} else when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
-	@(private)
-	foreign libc {
-		@(link_name="__errno")		__error :: proc() -> ^i32 ---
-	}
-} else {
-	@(private)
-	foreign libc {
-		@(link_name="__errno_location") __error :: proc() -> ^i32 ---
-	}
-}

+ 2 - 0
core/prof/spall/doc.odin

@@ -18,6 +18,8 @@ Example:
 		defer spall.context_destroy(&spall_ctx)
 
 		buffer_backing := make([]u8, spall.BUFFER_DEFAULT_SIZE)
+		defer delete(buffer_backing)
+
 		spall_buffer = spall.buffer_create(buffer_backing, u32(sync.current_thread_id()))
 		defer spall.buffer_destroy(&spall_ctx, &spall_buffer)
 

+ 48 - 54
core/reflect/reflect.odin

@@ -31,8 +31,6 @@ Type_Info_Enum                   :: runtime.Type_Info_Enum
 Type_Info_Map                    :: runtime.Type_Info_Map
 Type_Info_Bit_Set                :: runtime.Type_Info_Bit_Set
 Type_Info_Simd_Vector            :: runtime.Type_Info_Simd_Vector
-Type_Info_Relative_Pointer       :: runtime.Type_Info_Relative_Pointer
-Type_Info_Relative_Multi_Pointer :: runtime.Type_Info_Relative_Multi_Pointer
 Type_Info_Matrix                 :: runtime.Type_Info_Matrix
 Type_Info_Soa_Pointer            :: runtime.Type_Info_Soa_Pointer
 Type_Info_Bit_Field              :: runtime.Type_Info_Bit_Field
@@ -67,8 +65,6 @@ Type_Kind :: enum {
 	Map,
 	Bit_Set,
 	Simd_Vector,
-	Relative_Pointer,
-	Relative_Multi_Pointer,
 	Matrix,
 	Soa_Pointer,
 	Bit_Field,
@@ -80,35 +76,33 @@ type_kind :: proc(T: typeid) -> Type_Kind {
 	ti := type_info_of(T)
 	if ti != nil {
 		switch _ in ti.variant {
-		case Type_Info_Named:                  return .Named
-		case Type_Info_Integer:                return .Integer
-		case Type_Info_Rune:                   return .Rune
-		case Type_Info_Float:                  return .Float
-		case Type_Info_Complex:                return .Complex
-		case Type_Info_Quaternion:             return .Quaternion
-		case Type_Info_String:                 return .String
-		case Type_Info_Boolean:                return .Boolean
-		case Type_Info_Any:                    return .Any
-		case Type_Info_Type_Id:                return .Type_Id
-		case Type_Info_Pointer:                return .Pointer
-		case Type_Info_Multi_Pointer:          return .Multi_Pointer
-		case Type_Info_Procedure:              return .Procedure
-		case Type_Info_Array:                  return .Array
-		case Type_Info_Enumerated_Array:       return .Enumerated_Array
-		case Type_Info_Dynamic_Array:          return .Dynamic_Array
-		case Type_Info_Slice:                  return .Slice
-		case Type_Info_Parameters:             return .Tuple
-		case Type_Info_Struct:                 return .Struct
-		case Type_Info_Union:                  return .Union
-		case Type_Info_Enum:                   return .Enum
-		case Type_Info_Map:                    return .Map
-		case Type_Info_Bit_Set:                return .Bit_Set
-		case Type_Info_Simd_Vector:            return .Simd_Vector
-		case Type_Info_Relative_Pointer:       return .Relative_Pointer
-		case Type_Info_Relative_Multi_Pointer: return .Relative_Multi_Pointer
-		case Type_Info_Matrix:                 return .Matrix
-		case Type_Info_Soa_Pointer:            return .Soa_Pointer
-		case Type_Info_Bit_Field:              return .Bit_Field
+		case Type_Info_Named:            return .Named
+		case Type_Info_Integer:          return .Integer
+		case Type_Info_Rune:             return .Rune
+		case Type_Info_Float:            return .Float
+		case Type_Info_Complex:          return .Complex
+		case Type_Info_Quaternion:       return .Quaternion
+		case Type_Info_String:           return .String
+		case Type_Info_Boolean:          return .Boolean
+		case Type_Info_Any:              return .Any
+		case Type_Info_Type_Id:          return .Type_Id
+		case Type_Info_Pointer:          return .Pointer
+		case Type_Info_Multi_Pointer:    return .Multi_Pointer
+		case Type_Info_Procedure:        return .Procedure
+		case Type_Info_Array:            return .Array
+		case Type_Info_Enumerated_Array: return .Enumerated_Array
+		case Type_Info_Dynamic_Array:    return .Dynamic_Array
+		case Type_Info_Slice:            return .Slice
+		case Type_Info_Parameters:       return .Tuple
+		case Type_Info_Struct:           return .Struct
+		case Type_Info_Union:            return .Union
+		case Type_Info_Enum:             return .Enum
+		case Type_Info_Map:              return .Map
+		case Type_Info_Bit_Set:          return .Bit_Set
+		case Type_Info_Simd_Vector:      return .Simd_Vector
+		case Type_Info_Matrix:           return .Matrix
+		case Type_Info_Soa_Pointer:      return .Soa_Pointer
+		case Type_Info_Bit_Field:        return .Bit_Field
 		}
 
 	}
@@ -723,6 +717,27 @@ enum_name_from_value_any :: proc(value: any) -> (name: string, ok: bool) {
 	return
 }
 
+/*
+Returns whether the value given has a defined name in the enum type.
+*/
+@(require_results)
+enum_value_has_name :: proc(value: $T) -> bool where intrinsics.type_is_enum(T) {
+	when len(T) == cap(T) {
+		return value >= min(T) && value <= max(T)
+	} else {
+		if value < min(T) || value > max(T) {
+			return false
+		}
+
+		for valid_value in T {
+			if valid_value == value {
+				return true
+			}
+		}
+
+		return false
+	}
+}
 
 
 
@@ -1467,21 +1482,6 @@ as_string :: proc(a: any) -> (value: string, valid: bool) {
 	return
 }
 
-@(require_results)
-relative_pointer_to_absolute :: proc(a: any) -> rawptr {
-	if a == nil { return nil }
-	a := a
-	ti := runtime.type_info_core(type_info_of(a.id))
-	a.id = ti.id
-
-	#partial switch info in ti.variant {
-	case Type_Info_Relative_Pointer:
-		return relative_pointer_to_absolute_raw(a.data, info.base_integer.id)
-	}
-	return nil
-}
-
-
 @(require_results)
 relative_pointer_to_absolute_raw :: proc(data: rawptr, base_integer_id: typeid) -> rawptr {
 	_handle :: proc(ptr: ^$T) -> rawptr where intrinsics.type_is_integer(T) {
@@ -1543,10 +1543,6 @@ as_pointer :: proc(a: any) -> (value: rawptr, valid: bool) {
 		case cstring: value = rawptr(v)
 		case: valid = false
 		}
-
-	case Type_Info_Relative_Pointer:
-		valid = true
-		value = relative_pointer_to_absolute_raw(a.data, info.base_integer.id)
 	}
 
 	return
@@ -1656,8 +1652,6 @@ equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_
 		Type_Info_Bit_Set,
 		Type_Info_Enum,
 		Type_Info_Simd_Vector,
-		Type_Info_Relative_Pointer,
-		Type_Info_Relative_Multi_Pointer,
 		Type_Info_Soa_Pointer,
 		Type_Info_Matrix:
 		return runtime.memory_compare(a.data, b.data, t.size) == 0

+ 0 - 32
core/reflect/types.odin

@@ -158,14 +158,6 @@ are_types_identical :: proc(a, b: ^Type_Info) -> bool {
 	case Type_Info_Simd_Vector:
 		y := b.variant.(Type_Info_Simd_Vector) or_return
 		return x.count == y.count && x.elem == y.elem
-
-	case Type_Info_Relative_Pointer:
-		y := b.variant.(Type_Info_Relative_Pointer) or_return
-		return x.base_integer == y.base_integer && x.pointer == y.pointer
-
-	case Type_Info_Relative_Multi_Pointer:
-		y := b.variant.(Type_Info_Relative_Multi_Pointer) or_return
-		return x.base_integer == y.base_integer && x.pointer == y.pointer
 		
 	case Type_Info_Matrix:
 		y := b.variant.(Type_Info_Matrix) or_return
@@ -392,18 +384,6 @@ is_simd_vector :: proc(info: ^Type_Info) -> bool {
 	_, ok := type_info_base(info).variant.(Type_Info_Simd_Vector)
 	return ok
 }
-@(require_results)
-is_relative_pointer :: proc(info: ^Type_Info) -> bool {
-	if info == nil { return false }
-	_, ok := type_info_base(info).variant.(Type_Info_Relative_Pointer)
-	return ok
-}
-@(require_results)
-is_relative_multi_pointer :: proc(info: ^Type_Info) -> bool {
-	if info == nil { return false }
-	_, ok := type_info_base(info).variant.(Type_Info_Relative_Multi_Pointer)
-	return ok
-}
 
 
 @(require_results)
@@ -736,18 +716,6 @@ write_type_writer :: #force_no_inline proc(w: io.Writer, ti: ^Type_Info, n_writt
 		io.write_i64(w, i64(info.count), 10, &n) or_return
 		io.write_byte(w, ']',                &n) or_return
 		write_type(w, info.elem,             &n) or_return
-
-	case Type_Info_Relative_Pointer:
-		io.write_string(w, "#relative(", &n) or_return
-		write_type(w, info.base_integer, &n) or_return
-		io.write_string(w, ") ",         &n) or_return
-		write_type(w, info.pointer,      &n) or_return
-
-	case Type_Info_Relative_Multi_Pointer:
-		io.write_string(w, "#relative(", &n) or_return
-		write_type(w, info.base_integer, &n) or_return
-		io.write_string(w, ") ",         &n) or_return
-		write_type(w, info.pointer,      &n) or_return
 		
 	case Type_Info_Matrix:
 		if info.layout == .Row_Major {

+ 41 - 16
core/slice/slice.odin

@@ -48,22 +48,41 @@ to_type :: proc(buf: []u8, $T: typeid) -> (T, bool) #optional_ok {
 }
 
 /*
-	Turn a slice of one type, into a slice of another type.
-
-	Only converts the type and length of the slice itself.
-	The length is rounded down to the nearest whole number of items.
-
-	```
-	large_items := []i64{1, 2, 3, 4}
-	small_items := slice.reinterpret([]i32, large_items)
-	assert(len(small_items) == 8)
-	```
-	```
-	small_items := []byte{1, 0, 0, 0, 0, 0, 0, 0,
-						  2, 0, 0, 0}
-	large_items := slice.reinterpret([]i64, small_items)
-	assert(len(large_items) == 1) // only enough bytes to make 1 x i64; two would need at least 8 bytes.
-	```
+Turn a slice of one type, into a slice of another type.
+
+Only converts the type and length of the slice itself.
+The length is rounded down to the nearest whole number of items.
+
+Example:
+
+	import "core:fmt"
+	import "core:slice"
+
+	i64s_as_i32s :: proc() {
+		large_items := []i64{1, 2, 3, 4}
+		small_items := slice.reinterpret([]i32, large_items)
+		assert(len(small_items) == 8)
+		fmt.println(large_items, "->", small_items)
+	}
+
+	bytes_as_i64s :: proc() {
+		small_items := [12]byte{}
+		small_items[0] = 1
+		small_items[8] = 2
+		large_items := slice.reinterpret([]i64, small_items[:])
+		assert(len(large_items) == 1) // only enough bytes to make 1 x i64; two would need at least 8 bytes.
+		fmt.println(small_items, "->", large_items)
+	}
+
+	reinterpret_example :: proc() {
+		i64s_as_i32s()
+		bytes_as_i64s()
+	}
+
+Output:
+	[1, 2, 3, 4] -> [1, 0, 2, 0, 3, 0, 4, 0]
+	[1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0] -> [1]
+
 */
 @(require_results)
 reinterpret :: proc "contextless" ($T: typeid/[]$U, s: []$V) -> []U {
@@ -471,6 +490,12 @@ is_empty :: proc(a: $T/[]$E) -> bool {
 	return len(a) == 0
 }
 
+// Gets the byte size of the backing data
+@(require_results)
+size :: proc "contextless" (a: $T/[]$E) -> int {
+	return len(a) * size_of(E)
+}
+
 
 
 @(require_results)

+ 2 - 1
core/strconv/strconv.odin

@@ -1121,6 +1121,7 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) {
 			break trunc_block
 		}
 		f := f64(mantissa)
+		f_abs := f
 		if neg {
 			f = -f
 		}
@@ -1132,7 +1133,7 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) {
 				f *= pow10[exp-22]
 				exp = 22
 			}
-			if f > 1e15 || f < 1e-15 {
+			if f_abs > 1e15 || f_abs < 1e-15 {
 				break trunc_block
 			}
 			return f * pow10[exp], nr, true

+ 2 - 1
core/strings/strings.odin

@@ -1872,7 +1872,8 @@ index_multi :: proc(s: string, substrs: []string) -> (idx: int, width: int) {
 	lowest_index := len(s)
 	found := false
 	for substr in substrs {
-		if i := index(s, substr); i >= 0 {
+		haystack := s[:min(len(s), lowest_index + len(substr))]
+		if i := index(haystack, substr); i >= 0 {
 			if i < lowest_index {
 				lowest_index = i
 				width = len(substr)

+ 5 - 15
core/sync/futex_wasm.odin

@@ -12,8 +12,8 @@ _futex_wait :: proc "contextless" (f: ^Futex, expected: u32) -> bool {
 	when !intrinsics.has_target_feature("atomics") {
 		panic_contextless("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it")
 	} else {
-		s := intrinsics.wasm_memory_atomic_wait32((^u32)(f), expected, -1)
-		return s != 0
+		_ = intrinsics.wasm_memory_atomic_wait32((^u32)(f), expected, -1)
+		return true
 	}
 }
 
@@ -22,7 +22,7 @@ _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, durati
 		panic_contextless("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it")
 	} else {
 		s := intrinsics.wasm_memory_atomic_wait32((^u32)(f), expected, i64(duration))
-		return s != 0
+		return s != 2
 	}
 }
 
@@ -30,12 +30,7 @@ _futex_signal :: proc "contextless" (f: ^Futex) {
 	when !intrinsics.has_target_feature("atomics") {
 		panic_contextless("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it")
 	} else {
-		loop: for {
-			s := intrinsics.wasm_memory_atomic_notify32((^u32)(f), 1)
-			if s >= 1 {
-				return
-			}
-		}
+		_ = intrinsics.wasm_memory_atomic_notify32((^u32)(f), 1)
 	}
 }
 
@@ -43,12 +38,7 @@ _futex_broadcast :: proc "contextless" (f: ^Futex) {
 	when !intrinsics.has_target_feature("atomics") {
 		panic_contextless("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it")
 	} else {
-		loop: for {
-			s := intrinsics.wasm_memory_atomic_notify32((^u32)(f), ~u32(0))
-			if s >= 0 {
-				return
-			}
-		}
+		_ = intrinsics.wasm_memory_atomic_notify32((^u32)(f), max(u32))
 	}
 }
 

+ 1 - 1
core/sync/primitives_atomic.odin

@@ -67,7 +67,7 @@ atomic_mutex_unlock :: proc "contextless" (m: ^Atomic_Mutex) {
 
 	switch atomic_exchange_explicit(&m.state, .Unlocked, .Release) {
 	case .Unlocked:
-		unreachable()
+		// Kind of okay - unlocking while already unlocked.
 	case .Locked:
 		// Okay
 	case .Waiting:

+ 10 - 0
core/sys/darwin/Foundation/NSApplication.odin

@@ -108,6 +108,16 @@ Application_setMainMenu :: proc "c" (self: ^Application, menu: ^Menu) {
 	msgSend(nil, self, "setMainMenu:", menu)
 }
 
+@(objc_type=Application, objc_name="mainWindow")
+Application_mainWindow :: proc "c" (self: ^Application) -> ^Window {
+	return msgSend(^Window, self, "mainWindow")
+}
+
+@(objc_type=Application, objc_name="keyWindow")
+Application_keyWindow :: proc "c" (self: ^Application) -> ^Window {
+	return msgSend(^Window, self, "keyWindow")
+}
+
 @(objc_type=Application, objc_name="windows")
 Application_windows :: proc "c" (self: ^Application) -> ^Array {
 	return msgSend(^Array, self, "windows")

+ 5 - 0
core/sys/darwin/Foundation/NSObjectProtocol.odin

@@ -0,0 +1,5 @@
+package objc_Foundation
+
+@(objc_class="NSObjectProtocol")
+ObjectProtocol :: struct {using _: Object}
+// TODO: implement NSObjectProtocol

+ 203 - 0
core/sys/darwin/Foundation/NSProcessInfo.odin

@@ -0,0 +1,203 @@
+package objc_Foundation
+
+import "base:intrinsics"
+
+import "core:c"
+
+@(objc_class="NSProcessInfo")
+ProcessInfo :: struct {using _: Object}
+
+// Getting the Process Information Agent
+
+@(objc_type=ProcessInfo, objc_name="processInfo", objc_is_class_method=true)
+ProcessInfo_processInfo :: proc "c" () -> ^ProcessInfo {
+	return msgSend(^ProcessInfo, ProcessInfo, "processInfo")
+}
+
+// Accessing Process Information
+
+@(objc_type=ProcessInfo, objc_name="arguments")
+ProcessInfo_arguments :: proc "c" (self: ^ProcessInfo) -> ^Array {
+	return msgSend(^Array, self, "arguments")
+}
+
+@(objc_type=ProcessInfo, objc_name="environment")
+ProcessInfo_environment :: proc "c" (self: ^ProcessInfo) -> ^Dictionary {
+	return msgSend(^Dictionary, self, "environment")
+}
+
+@(objc_type=ProcessInfo, objc_name="globallyUniqueString")
+ProcessInfo_globallyUniqueString :: proc "c" (self: ^ProcessInfo) -> ^String {
+	return msgSend(^String, self, "globallyUniqueString")
+}
+
+@(objc_type=ProcessInfo, objc_name="isMacCatalystApp")
+ProcessInfo_isMacCatalystApp :: proc "c" (self: ^ProcessInfo) -> bool {
+	return msgSend(bool, self, "isMacCatalystApp")
+}
+
+@(objc_type=ProcessInfo, objc_name="isiOSAppOnMac")
+ProcessInfo_isiOSAppOnMac :: proc "c" (self: ^ProcessInfo) -> bool {
+	return msgSend(bool, self, "isiOSAppOnMac")
+}
+
+@(objc_type=ProcessInfo, objc_name="processIdentifier")
+ProcessInfo_processIdentifier :: proc "c" (self: ^ProcessInfo) -> c.int {
+	return msgSend(c.int, self, "processIdentifier")
+}
+
+@(objc_type=ProcessInfo, objc_name="processName")
+ProcessInfo_processName :: proc "c" (self: ^ProcessInfo) -> ^String {
+	return msgSend(^String, self, "processName")
+}
+
+// Accessing User Information 
+
+@(objc_type=ProcessInfo, objc_name="userName")
+ProcessInfo_userName :: proc "c" (self: ^ProcessInfo) -> ^String {
+	return msgSend(^String, self, "userName")
+}
+
+@(objc_type=ProcessInfo, objc_name="fullUserName")
+ProcessInfo_fullUserName :: proc "c" (self: ^ProcessInfo) -> ^String {
+	return msgSend(^String, self, "fullUserName")
+}
+
+// Sudden Application Termination
+
+@(objc_type=ProcessInfo, objc_name="disableSuddenTermination")
+ProcessInfo_disableSuddenTermination :: proc "c" (self: ^ProcessInfo) {
+	msgSend(nil, self, "disableSuddenTermination")
+}
+
+@(objc_type=ProcessInfo, objc_name="enableSuddenTermination")
+ProcessInfo_enableSuddenTermination :: proc "c" (self: ^ProcessInfo) {
+	msgSend(nil, self, "enableSuddenTermination")
+}
+
+// Controlling Automatic Termination
+
+@(objc_type=ProcessInfo, objc_name="disableAutomaticTermination")
+ProcessInfo_disableAutomaticTermination :: proc "c" (self: ^ProcessInfo, reason: ^String) {
+	msgSend(nil, self, "disableAutomaticTermination:", reason)
+}
+
+@(objc_type=ProcessInfo, objc_name="enableAutomaticTermination")
+ProcessInfo_enableAutomaticTermination :: proc "c" (self: ^ProcessInfo, reason: ^String) {
+	msgSend(nil, self, "enableAutomaticTermination:", reason)
+}
+
+@(objc_type=ProcessInfo, objc_name="automaticTerminationSupportEnabled")
+ProcessInfo_automaticTerminationSupportEnabled :: proc "c" (self: ^ProcessInfo) -> bool {
+	return msgSend(bool, self, "automaticTerminationSupportEnabled")
+}
+
+@(objc_type=ProcessInfo, objc_name="setAutomaticTerminationSupportEnabled")
+ProcessInfo_setAutomaticTerminationSupportEnabled :: proc "c" (self: ^ProcessInfo, automaticTerminationSupportEnabled: bool) {
+	msgSend(nil, self, "setAutomaticTerminationSupportEnabled:", automaticTerminationSupportEnabled)
+}
+
+// Getting Host Information
+
+@(objc_type=ProcessInfo, objc_name="hostName")
+ProcessInfo_hostName :: proc "c" (self: ^ProcessInfo) -> ^String {
+	return msgSend(^String, self, "hostName")
+}
+
+@(objc_type=ProcessInfo, objc_name="operatingSystemVersionString")
+ProcessInfo_operatingSystemVersionString :: proc "c" (self: ^ProcessInfo) -> ^String {
+	return msgSend(^String, self, "operatingSystemVersionString")
+}
+
+@(objc_type=ProcessInfo, objc_name="operatingSystemVersion")
+ProcessInfo_operatingSystemVersion :: proc "c" (self: ^ProcessInfo) -> OperatingSystemVersion {
+	return msgSend(OperatingSystemVersion, self, "operatingSystemVersion")
+}
+
+@(objc_type=ProcessInfo, objc_name="isOperatingSystemAtLeastVersion")
+ProcessInfo_isOperatingSystemAtLeastVersion :: proc "c" (self: ^ProcessInfo, version: OperatingSystemVersion) -> bool {
+	return msgSend(bool, self, "isOperatingSystemAtLeastVersion:", version)
+}
+
+// Getting Computer Information
+
+@(objc_type=ProcessInfo, objc_name="processorCount")
+ProcessInfo_processorCount :: proc "c" (self: ^ProcessInfo) -> UInteger {
+	return msgSend(UInteger, self, "processorCount")
+}
+
+@(objc_type=ProcessInfo, objc_name="activeProcessorCount")
+ProcessInfo_activeProcessorCount :: proc "c" (self: ^ProcessInfo) -> UInteger {
+	return msgSend(UInteger, self, "activeProcessorCount")
+}
+
+@(objc_type=ProcessInfo, objc_name="physicalMemory")
+ProcessInfo_physicalMemory :: proc "c" (self: ^ProcessInfo) -> c.ulonglong {
+	return msgSend(c.ulonglong, self, "physicalMemory")
+}
+
+@(objc_type=ProcessInfo, objc_name="systemUptime")
+ProcessInfo_systemUptime :: proc "c" (self: ^ProcessInfo) -> TimeInterval {
+	return msgSend(TimeInterval, self, "systemUptime")
+}
+
+// Managing Activities
+
+@(private)
+log2 :: intrinsics.constant_log2
+
+ActivityOptionsBits :: enum u64 {
+	IdleDisplaySleepDisabled             = log2(1099511627776),  // Require the screen to stay powered on.
+	IdleSystemSleepDisabled              = log2(1048576),        // Prevent idle sleep.
+	SuddenTerminationDisabled            = log2(16384),          // Prevent sudden termination.
+	AutomaticTerminationDisabled         = log2(32768),          // Prevent automatic termination.
+	AnimationTrackingEnabled             = log2(35184372088832), // Track activity with an animation signpost interval.
+	TrackingEnabled                      = log2(70368744177664), // Track activity with a signpost interval.
+	UserInitiated                        = log2(16777215),       // Performing a user-requested action.
+	UserInitiatedAllowingIdleSystemSleep = log2(15728639),       // Performing a user-requested action, but the system can sleep on idle.
+	Background                           = log2(255),            // Initiated some kind of work, but not as the direct result of a user request.
+	LatencyCritical                      = log2(1095216660480),  // Requires the highest amount of timer and I/O precision available.
+	UserInteractive                      = log2(1095233437695),  // Responding to user interaction.
+}
+ActivityOptions :: bit_set[ActivityOptionsBits; u64]
+
+@(objc_type=ProcessInfo, objc_name="beginActivityWithOptions")
+ProcessInfo_beginActivityWithOptions :: proc "c" (self: ^ProcessInfo, options: ActivityOptions, reason: ^String) -> ^ObjectProtocol {
+	return msgSend(^ObjectProtocol, self, "beginActivityWithOptions:reason:", options, reason)
+}
+
+@(objc_type=ProcessInfo, objc_name="endActivity")
+ProcessInfo_endActivity :: proc "c" (self: ^ProcessInfo, activity: ^ObjectProtocol) {
+	msgSend(nil, self, "endActivity:", activity)
+}
+
+@(objc_type=ProcessInfo, objc_name="performActivityWithOptions")
+ProcessInfo_performActivityWithOptions :: proc "c" (self: ^ProcessInfo, options: ActivityOptions, reason: ^String, block: proc "c" ()) {
+	msgSend(nil, self, "performActivityWithOptions:reason:usingBlock:", options, reason, block)
+}
+
+@(objc_type=ProcessInfo, objc_name="performExpiringActivityWithReason")
+ProcessInfo_performExpiringActivityWithReason :: proc "c" (self: ^ProcessInfo, reason: ^String, block: proc "c" (expired: bool)) {
+	msgSend(nil, self, "performExpiringActivityWithReason:usingBlock:", reason, block)
+}
+
+// Getting the Thermal State
+
+ProcessInfoThermalState :: enum c.long {
+	Nominal,
+	Fair,
+	Serious,
+	Critical,
+}
+
+@(objc_type=ProcessInfo, objc_name="thermalState")
+ProcessInfo_thermalState :: proc "c" (self: ^ProcessInfo) -> ProcessInfoThermalState {
+	return msgSend(ProcessInfoThermalState, self, "thermalState")
+}
+
+// Determining Whether Low Power Mode is Enabled
+
+@(objc_type=ProcessInfo, objc_name="isLowPowerModeEnabled")
+ProcessInfo_isLowPowerModeEnabled :: proc "c" (self: ^ProcessInfo) -> bool {
+	return msgSend(bool, self, "isLowPowerModeEnabled")
+}

+ 2 - 2
core/sys/darwin/Foundation/NSTypes.odin

@@ -20,7 +20,7 @@ BOOL :: bool // TODO(bill): should this be `distinct`?
 YES  :: true
 NO   :: false
 
-OperatingSystemVersion :: struct #packed {
+OperatingSystemVersion :: struct #align(8) {
 	majorVersion: Integer,
 	minorVersion: Integer,
 	patchVersion: Integer,
@@ -58,4 +58,4 @@ when size_of(Float) == 8 {
 } else {
 	_POINT_ENCODING :: "{NSPoint=ff}"
 	_SIZE_ENCODING :: "{NSSize=ff}"
-}
+}

+ 46 - 1
core/sys/info/cpu_intel.odin

@@ -28,6 +28,23 @@ CPU_Feature :: enum u64 {
 	ssse3,     // Supplemental streaming SIMD extension 3
 	sse41,     // Streaming SIMD extension 4 and 4.1
 	sse42,     // Streaming SIMD extension 4 and 4.2
+
+	avx512bf16,         // Vector Neural Network Instructions supporting bfloat16
+	avx512bitalg,       // Bit Algorithms
+	avx512bw,           // Byte and Word instructions
+	avx512cd,           // Conflict Detection instructions
+	avx512dq,           // Doubleword and Quadword instructions
+	avx512er,           // Exponential and Reciprocal instructions
+	avx512f,            // Foundation
+	avx512fp16,         // Vector 16-bit float instructions
+	avx512ifma,         // Integer Fused Multiply Add
+	avx512pf,           // Prefetch instructions
+	avx512vbmi,         // Vector Byte Manipulation Instructions
+	avx512vbmi2,        // Vector Byte Manipulation Instructions 2
+	avx512vl,           // Vector Length extensions
+	avx512vnni,         // Vector Neural Network Instructions
+	avx512vp2intersect, // Vector Pair Intersection to a Pair of Mask Registers
+	avx512vpopcntdq,    // Vector Population Count for Doubleword and Quadword
 }
 
 CPU_Features :: distinct bit_set[CPU_Feature; u64]
@@ -82,9 +99,11 @@ init_cpu_features :: proc "c" () {
 	//
 	// See: crbug.com/375968
 	os_supports_avx := false
+	os_supports_avx512 := false
 	if .os_xsave in set && is_set(26, ecx1) {
 		eax, _ := xgetbv(0)
 		os_supports_avx = is_set(1, eax) && is_set(2, eax)
+		os_supports_avx512 = is_set(5, eax) && is_set(6, eax) && is_set(7, eax)
 	}
 	if os_supports_avx {
 		try_set(&set, .avx, 28, ecx1)
@@ -94,11 +113,37 @@ init_cpu_features :: proc "c" () {
 		return
 	}
 
-	_, ebx7, _, _ := cpuid(7, 0)
+	_, ebx7, ecx7, edx7 := cpuid(7, 0)
 	try_set(&set, .bmi1, 3, ebx7)
 	if os_supports_avx {
 		try_set(&set, .avx2, 5, ebx7)
 	}
+	if os_supports_avx512 {
+		try_set(&set, .avx512f,    16, ebx7)
+		try_set(&set, .avx512dq,   17, ebx7)
+		try_set(&set, .avx512ifma, 21, ebx7)
+		try_set(&set, .avx512pf,   26, ebx7)
+		try_set(&set, .avx512er,   27, ebx7)
+		try_set(&set, .avx512cd,   28, ebx7)
+		try_set(&set, .avx512bw,   30, ebx7)
+
+		// XMM/YMM are also required for 128/256-bit instructions
+		if os_supports_avx {
+			try_set(&set, .avx512vl, 31, ebx7)
+		}
+
+		try_set(&set, .avx512vbmi,       1, ecx7)
+		try_set(&set, .avx512vbmi2,      6, ecx7)
+		try_set(&set, .avx512vnni,      11, ecx7)
+		try_set(&set, .avx512bitalg,    12, ecx7)
+		try_set(&set, .avx512vpopcntdq, 14, ecx7)
+
+		try_set(&set, .avx512vp2intersect,  8, edx7)
+		try_set(&set, .avx512fp16,         23, edx7)
+
+		eax7_1, _, _, _ := cpuid(7, 1)
+		try_set(&set, .avx512bf16, 5, eax7_1)
+	}
 	try_set(&set, .bmi2,    8, ebx7)
 	try_set(&set, .erms,    9, ebx7)
 	try_set(&set, .rdseed, 18, ebx7)

+ 1 - 1
core/sys/info/doc.odin

@@ -4,7 +4,7 @@ Made available under Odin's BSD-3 license.
 
 List of contributors:
 	Jeroen van Rijn: Initial implementation.
-	Laytan: ARM and RISC-V CPU feature detection.
+	Laytan: ARM and RISC-V CPU feature detection, iOS/macOS platform overhaul.
 */
 
 /*

+ 71 - 551
core/sys/info/platform_darwin.odin

@@ -1,581 +1,101 @@
 package sysinfo
 
-import sys "core:sys/unix"
-import "core:strconv"
-import "core:strings"
-import "base:runtime"
+import    "core:strconv"
+import    "core:strings"
+import    "core:sys/unix"
+import NS "core:sys/darwin/Foundation"
 
 @(private)
 version_string_buf: [1024]u8
 
 @(init, private)
-init_os_version :: proc () {
-	os_version.platform = .MacOS
+init_platform :: proc() {
+	ws :: strings.write_string
+	wi :: strings.write_int
 
-	// Start building display version
 	b := strings.builder_from_bytes(version_string_buf[:])
 
-	mib := []i32{sys.CTL_KERN, sys.KERN_OSVERSION}
-	build_buf: [12]u8
+	version: NS.OperatingSystemVersion
+	{
+		NS.scoped_autoreleasepool() 
 
-	ok := sys.sysctl(mib, &build_buf)
-	if !ok {
-		strings.write_string(&b, "macOS Unknown")
-		os_version.as_string = strings.to_string(b)
-		return
-	}
+		info    := NS.ProcessInfo.processInfo()
+		version  = info->operatingSystemVersion()
+		mem     := info->physicalMemory()
 
-	build := string(cstring(&build_buf[0]))
+		ram.total_ram = int(mem)
+	}
 
-	// Do we have an exact match?
-	match: Darwin_Match
-	rel, exact := macos_release_map[build]
+	macos_version = {int(version.majorVersion), int(version.minorVersion), int(version.patchVersion)}
 
-	if exact {
-		match = .Exact
+	when ODIN_PLATFORM_SUBTARGET == .iOS {
+		os_version.platform = .iOS
+		ws(&b, "iOS")
 	} else {
-		// Match on XNU kernel version
-		mib = []i32{sys.CTL_KERN, sys.KERN_OSRELEASE}
-		version_bits: [12]u8 // enough for 999.999.999\x00
-		have_kernel_version := sys.sysctl(mib, &version_bits)
-
-		major_ok, minor_ok, patch_ok: bool
-
-		tmp := runtime.default_temp_allocator_temp_begin()
-
-		triplet := strings.split(string(cstring(&version_bits[0])), ".", context.temp_allocator)
-		if len(triplet) != 3 {
-			have_kernel_version = false
-		} else {
-			rel.darwin.x, major_ok = strconv.parse_int(triplet[0])
-			rel.darwin.y, minor_ok = strconv.parse_int(triplet[1])
-			rel.darwin.z, patch_ok = strconv.parse_int(triplet[2])
-
-			if !(major_ok && minor_ok && patch_ok) {
-				have_kernel_version = false
+		os_version.platform = .MacOS
+		switch version.majorVersion {
+		case 15: ws(&b, "macOS Sequoia")
+		case 14: ws(&b, "macOS Sonoma")
+		case 13: ws(&b, "macOS Ventura")
+		case 12: ws(&b, "macOS Monterey")
+		case 11: ws(&b, "macOS Big Sur")
+		case 10:
+			switch version.minorVersion {
+			case 15: ws(&b, "macOS Catalina")
+			case 14: ws(&b, "macOS Mojave")
+			case 13: ws(&b, "macOS High Sierra")
+			case 12: ws(&b, "macOS Sierra")
+			case 11: ws(&b, "OS X El Capitan")
+			case 10: ws(&b, "OS X Yosemite")
+			case:
+				// `ProcessInfo.operatingSystemVersion` is 10.10 and up.
+				unreachable()
 			}
+		case:
+			// New version not yet added here.
+			assert(version.majorVersion > 15)
+			ws(&b, "macOS Unknown")
 		}
-
-		runtime.default_temp_allocator_temp_end(tmp)
-
-		if !have_kernel_version {
-			// We don't know the kernel version, but we do know the build
-			strings.write_string(&b, "macOS Unknown (build ")
-			l := strings.builder_len(b)
-			strings.write_string(&b, build)
-			os_version.version = strings.to_string(b)[l:]
-			strings.write_rune(&b, ')')
-			os_version.as_string = strings.to_string(b)
-			return
-		}
-		rel, match = map_darwin_kernel_version_to_macos_release(build, rel.darwin)
 	}
 
-	os_version.major = rel.darwin.x
-	os_version.minor = rel.darwin.y
-	os_version.patch = rel.darwin.z
+	ws(&b, " ")
+	wi(&b, int(version.majorVersion))
+	ws(&b, ".")
+	wi(&b, int(version.minorVersion))
+	ws(&b, ".")
+	wi(&b, int(version.patchVersion))
 
-	macos_version = transmute(Version)rel.release.version
+	{
+		build_buf: [12]u8
+		mib := []i32{unix.CTL_KERN, unix.KERN_OSVERSION}
+		ok := unix.sysctl(mib, &build_buf)
+		build := string(cstring(raw_data(build_buf[:]))) if ok else "Unknown"
 
-	strings.write_string(&b, rel.os_name)
-	if match == .Exact || match == .Nearest {
-		strings.write_rune(&b, ' ')
-		strings.write_string(&b, rel.release.name)
-		strings.write_rune(&b, ' ')
-		strings.write_int(&b, rel.release.version.x)
-		if rel.release.version.y > 0 || rel.release.version.z > 0 {
-			strings.write_rune(&b, '.')
-			strings.write_int(&b, rel.release.version.y)
-		}
-		if rel.release.version.z > 0 {
-			strings.write_rune(&b, '.')
-			strings.write_int(&b, rel.release.version.z)
-		}
-		if match == .Nearest {
-			strings.write_rune(&b, '?')
-		}
-	} else {
-		strings.write_string(&b, " Unknown")
-	}
-
-	strings.write_string(&b, " (build ")
-	l := strings.builder_len(b)
-	strings.write_string(&b, build)
-	os_version.version = strings.to_string(b)[l:]
-
-	strings.write_string(&b, ", kernel ")
-	strings.write_int(&b, rel.darwin.x)
-	strings.write_rune(&b, '.')
-	strings.write_int(&b, rel.darwin.y)
-	strings.write_rune(&b, '.')
-	strings.write_int(&b, rel.darwin.z)
-	strings.write_rune(&b, ')')
-
-	os_version.as_string = strings.to_string(b)
-}
-
-@(init, private)
-init_ram :: proc() {
-	// Retrieve RAM info using `sysctl`
+		ws(&b, " (build ")
 
-	mib := []i32{sys.CTL_HW, sys.HW_MEMSIZE}
-	mem_size: u64
-	if sys.sysctl(mib, &mem_size) {
-		ram.total_ram = int(mem_size)
+		build_start := len(b.buf)
+		ws(&b, build)
+		os_version.version = string(b.buf[build_start:][:len(build)])
 	}
-}
-
-@(private)
-Darwin_To_Release :: struct {
-	darwin:      [3]int, // Darwin kernel triplet
-	os_name:     string, // OS X, MacOS
-	release:     struct {
-		name:    string, // Monterey, Mojave, etc.
-		version: [3]int, // 12.4, etc.
-	},
-}
-
-// Important: Order from lowest to highest kernel version
-@(private)
-macos_release_map: map[string]Darwin_To_Release = {
-	// MacOS Tiger
-	"8A428"      = {{8, 0, 0},   "macOS", {"Tiger",         {10,  4, 0}}},
-	"8A432"      = {{8, 0, 0},   "macOS", {"Tiger",         {10,  4, 0}}},
-	"8B15"       = {{8, 1, 0},   "macOS", {"Tiger",         {10,  4, 1}}},
-	"8B17"       = {{8, 1, 0},   "macOS", {"Tiger",         {10,  4, 1}}},
-	"8C46"       = {{8, 2, 0},   "macOS", {"Tiger",         {10,  4, 2}}},
-	"8C47"       = {{8, 2, 0},   "macOS", {"Tiger",         {10,  4, 2}}},
-	"8E102"      = {{8, 2, 0},   "macOS", {"Tiger",         {10,  4, 2}}},
-	"8E45"       = {{8, 2, 0},   "macOS", {"Tiger",         {10,  4, 2}}},
-	"8E90"       = {{8, 2, 0},   "macOS", {"Tiger",         {10,  4, 2}}},
-	"8F46"       = {{8, 3, 0},   "macOS", {"Tiger",         {10,  4, 3}}},
-	"8G32"       = {{8, 4, 0},   "macOS", {"Tiger",         {10,  4, 4}}},
-	"8G1165"     = {{8, 4, 0},   "macOS", {"Tiger",         {10,  4, 4}}},
-	"8H14"       = {{8, 5, 0},   "macOS", {"Tiger",         {10,  4, 5}}},
-	"8G1454"     = {{8, 5, 0},   "macOS", {"Tiger",         {10,  4, 5}}},
-	"8I127"      = {{8, 6, 0},   "macOS", {"Tiger",         {10,  4, 6}}},
-	"8I1119"     = {{8, 6, 0},   "macOS", {"Tiger",         {10,  4, 6}}},
-	"8J135"      = {{8, 7, 0},   "macOS", {"Tiger",         {10,  4, 7}}},
-	"8J2135a"    = {{8, 7, 0},   "macOS", {"Tiger",         {10,  4, 7}}},
-	"8K1079"     = {{8, 7, 0},   "macOS", {"Tiger",         {10,  4, 7}}},
-	"8N5107"     = {{8, 7, 0},   "macOS", {"Tiger",         {10,  4, 7}}},
-	"8L127"      = {{8, 8, 0},   "macOS", {"Tiger",         {10,  4, 8}}},
-	"8L2127"     = {{8, 8, 0},   "macOS", {"Tiger",         {10,  4, 8}}},
-	"8P135"      = {{8, 9, 0},   "macOS", {"Tiger",         {10,  4, 9}}},
-	"8P2137"     = {{8, 9, 0},   "macOS", {"Tiger",         {10,  4, 9}}},
-	"8R218"      = {{8, 10, 0},  "macOS", {"Tiger",         {10,  4, 10}}},
-	"8R2218"     = {{8, 10, 0},  "macOS", {"Tiger",         {10,  4, 10}}},
-	"8R2232"     = {{8, 10, 0},  "macOS", {"Tiger",         {10,  4, 10}}},
-	"8S165"      = {{8, 11, 0},  "macOS", {"Tiger",         {10,  4, 11}}},
-	"8S2167"     = {{8, 11, 0},  "macOS", {"Tiger",         {10,  4, 11}}},
-
-	// MacOS Leopard
-	"9A581"      = {{9, 0, 0},   "macOS", {"Leopard",       {10,  5, 0}}},
-	"9B18"       = {{9, 1, 0},   "macOS", {"Leopard",       {10,  5, 1}}},
-	"9B2117"     = {{9, 1, 1},   "macOS", {"Leopard",       {10,  5, 1}}},
-	"9C31"       = {{9, 2, 0},   "macOS", {"Leopard",       {10,  5, 2}}},
-	"9C7010"     = {{9, 2, 0},   "macOS", {"Leopard",       {10,  5, 2}}},
-	"9D34"       = {{9, 3, 0},   "macOS", {"Leopard",       {10,  5, 3}}},
-	"9E17"       = {{9, 4, 0},   "macOS", {"Leopard",       {10,  5, 4}}},
-	"9F33"       = {{9, 5, 0},   "macOS", {"Leopard",       {10,  5, 5}}},
-	"9G55"       = {{9, 6, 0},   "macOS", {"Leopard",       {10,  5, 6}}},
-	"9G66"       = {{9, 6, 0},   "macOS", {"Leopard",       {10,  5, 6}}},
-	"9G71"       = {{9, 6, 0},   "macOS", {"Leopard",       {10,  5, 6}}},
-	"9J61"       = {{9, 7, 0},   "macOS", {"Leopard",       {10,  5, 7}}},
-	"9L30"       = {{9, 8, 0},   "macOS", {"Leopard",       {10,  5, 8}}},
-	"9L34"       = {{9, 8, 0},   "macOS", {"Leopard",       {10,  5, 8}}},
 
-	// MacOS Snow Leopard
-	"10A432"     = {{10, 0, 0},  "macOS", {"Snow Leopard",  {10,  6, 0}}},
-	"10A433"     = {{10, 0, 0},  "macOS", {"Snow Leopard",  {10,  6, 0}}},
-	"10B504"     = {{10, 1, 0},  "macOS", {"Snow Leopard",  {10,  6, 1}}},
-	"10C540"     = {{10, 2, 0},  "macOS", {"Snow Leopard",  {10,  6, 2}}},
-	"10D573"     = {{10, 3, 0},  "macOS", {"Snow Leopard",  {10,  6, 3}}},
-	"10D575"     = {{10, 3, 0},  "macOS", {"Snow Leopard",  {10,  6, 3}}},
-	"10D578"     = {{10, 3, 0},  "macOS", {"Snow Leopard",  {10,  6, 3}}},
-	"10F569"     = {{10, 4, 0},  "macOS", {"Snow Leopard",  {10,  6, 4}}},
-	"10H574"     = {{10, 5, 0},  "macOS", {"Snow Leopard",  {10,  6, 5}}},
-	"10J567"     = {{10, 6, 0},  "macOS", {"Snow Leopard",  {10,  6, 6}}},
-	"10J869"     = {{10, 7, 0},  "macOS", {"Snow Leopard",  {10,  6, 7}}},
-	"10J3250"    = {{10, 7, 0},  "macOS", {"Snow Leopard",  {10,  6, 7}}},
-	"10J4138"    = {{10, 7, 0},  "macOS", {"Snow Leopard",  {10,  6, 7}}},
-	"10K540"     = {{10, 8, 0},  "macOS", {"Snow Leopard",  {10,  6, 8}}},
-	"10K549"     = {{10, 8, 0},  "macOS", {"Snow Leopard",  {10,  6, 8}}},
-
-	// MacOS Lion
-	"11A511"     = {{11, 0, 0},  "macOS", {"Lion",          {10,  7, 0}}},
-	"11A511s"    = {{11, 0, 0},  "macOS", {"Lion",          {10,  7, 0}}},
-	"11A2061"    = {{11, 0, 2},  "macOS", {"Lion",          {10,  7, 0}}},
-	"11A2063"    = {{11, 0, 2},  "macOS", {"Lion",          {10,  7, 0}}},
-	"11B26"      = {{11, 1, 0},  "macOS", {"Lion",          {10,  7, 1}}},
-	"11B2118"    = {{11, 1, 0},  "macOS", {"Lion",          {10,  7, 1}}},
-	"11C74"      = {{11, 2, 0},  "macOS", {"Lion",          {10,  7, 2}}},
-	"11D50"      = {{11, 3, 0},  "macOS", {"Lion",          {10,  7, 3}}},
-	"11E53"      = {{11, 4, 0},  "macOS", {"Lion",          {10,  7, 4}}},
-	"11G56"      = {{11, 4, 2},  "macOS", {"Lion",          {10,  7, 5}}},
-	"11G63"      = {{11, 4, 2},  "macOS", {"Lion",          {10,  7, 5}}},
-
-	// MacOS Mountain Lion
-	"12A269"     = {{12, 0, 0},  "macOS", {"Mountain Lion", {10,  8, 0}}},
-	"12B19"      = {{12, 1, 0},  "macOS", {"Mountain Lion", {10,  8, 1}}},
-	"12C54"      = {{12, 2, 0},  "macOS", {"Mountain Lion", {10,  8, 2}}},
-	"12C60"      = {{12, 2, 0},  "macOS", {"Mountain Lion", {10,  8, 2}}},
-	"12C2034"    = {{12, 2, 0},  "macOS", {"Mountain Lion", {10,  8, 2}}},
-	"12C3104"    = {{12, 2, 0},  "macOS", {"Mountain Lion", {10,  8, 2}}},
-	"12D78"      = {{12, 3, 0},  "macOS", {"Mountain Lion", {10,  8, 3}}},
-	"12E55"      = {{12, 4, 0},  "macOS", {"Mountain Lion", {10,  8, 4}}},
-	"12E3067"    = {{12, 4, 0},  "macOS", {"Mountain Lion", {10,  8, 4}}},
-	"12E4022"    = {{12, 4, 0},  "macOS", {"Mountain Lion", {10,  8, 4}}},
-	"12F37"      = {{12, 5, 0},  "macOS", {"Mountain Lion", {10,  8, 5}}},
-	"12F45"      = {{12, 5, 0},  "macOS", {"Mountain Lion", {10,  8, 5}}},
-	"12F2501"    = {{12, 5, 0},  "macOS", {"Mountain Lion", {10,  8, 5}}},
-	"12F2518"    = {{12, 5, 0},  "macOS", {"Mountain Lion", {10,  8, 5}}},
-	"12F2542"    = {{12, 5, 0},  "macOS", {"Mountain Lion", {10,  8, 5}}},
-	"12F2560"    = {{12, 5, 0},  "macOS", {"Mountain Lion", {10,  8, 5}}},
-
-	// MacOS Mavericks
-	"13A603"     = {{13, 0, 0},  "macOS", {"Mavericks",     {10,  9, 0}}},
-	"13B42"      = {{13, 0, 0},  "macOS", {"Mavericks",     {10,  9, 1}}},
-	"13C64"      = {{13, 1, 0},  "macOS", {"Mavericks",     {10,  9, 2}}},
-	"13C1021"    = {{13, 1, 0},  "macOS", {"Mavericks",     {10,  9, 2}}},
-	"13D65"      = {{13, 2, 0},  "macOS", {"Mavericks",     {10,  9, 3}}},
-	"13E28"      = {{13, 3, 0},  "macOS", {"Mavericks",     {10,  9, 4}}},
-	"13F34"      = {{13, 4, 0},  "macOS", {"Mavericks",     {10,  9, 5}}},
-	"13F1066"    = {{13, 4, 0},  "macOS", {"Mavericks",     {10,  9, 5}}},
-	"13F1077"    = {{13, 4, 0},  "macOS", {"Mavericks",     {10,  9, 5}}},
-	"13F1096"    = {{13, 4, 0},  "macOS", {"Mavericks",     {10,  9, 5}}},
-	"13F1112"    = {{13, 4, 0},  "macOS", {"Mavericks",     {10,  9, 5}}},
-	"13F1134"    = {{13, 4, 0},  "macOS", {"Mavericks",     {10,  9, 5}}},
-	"13F1507"    = {{13, 4, 0},  "macOS", {"Mavericks",     {10,  9, 5}}},
-	"13F1603"    = {{13, 4, 0},  "macOS", {"Mavericks",     {10,  9, 5}}},
-	"13F1712"    = {{13, 4, 0},  "macOS", {"Mavericks",     {10,  9, 5}}},
-	"13F1808"    = {{13, 4, 0},  "macOS", {"Mavericks",     {10,  9, 5}}},
-	"13F1911"    = {{13, 4, 0},  "macOS", {"Mavericks",     {10,  9, 5}}},
-
-	// MacOS Yosemite
-	"14A389"     = {{14, 0, 0},  "macOS", {"Yosemite",      {10, 10, 0}}},
-	"14B25"      = {{14, 0, 0},  "macOS", {"Yosemite",      {10, 10, 1}}},
-	"14C109"     = {{14, 1, 0},  "macOS", {"Yosemite",      {10, 10, 2}}},
-	"14C1510"    = {{14, 1, 0},  "macOS", {"Yosemite",      {10, 10, 2}}},
-	"14C2043"    = {{14, 1, 0},  "macOS", {"Yosemite",      {10, 10, 2}}},
-	"14C1514"    = {{14, 1, 0},  "macOS", {"Yosemite",      {10, 10, 2}}},
-	"14C2513"    = {{14, 1, 0},  "macOS", {"Yosemite",      {10, 10, 2}}},
-	"14D131"     = {{14, 3, 0},  "macOS", {"Yosemite",      {10, 10, 3}}},
-	"14D136"     = {{14, 3, 0},  "macOS", {"Yosemite",      {10, 10, 3}}},
-	"14E46"      = {{14, 4, 0},  "macOS", {"Yosemite",      {10, 10, 4}}},
-	"14F27"      = {{14, 5, 0},  "macOS", {"Yosemite",      {10, 10, 5}}},
-	"14F1021"    = {{14, 5, 0},  "macOS", {"Yosemite",      {10, 10, 5}}},
-	"14F1505"    = {{14, 5, 0},  "macOS", {"Yosemite",      {10, 10, 5}}},
-	"14F1509"    = {{14, 5, 0},  "macOS", {"Yosemite",      {10, 10, 5}}},
-	"14F1605"    = {{14, 5, 0},  "macOS", {"Yosemite",      {10, 10, 5}}},
-	"14F1713"    = {{14, 5, 0},  "macOS", {"Yosemite",      {10, 10, 5}}},
-	"14F1808"    = {{14, 5, 0},  "macOS", {"Yosemite",      {10, 10, 5}}},
-	"14F1909"    = {{14, 5, 0},  "macOS", {"Yosemite",      {10, 10, 5}}},
-	"14F1912"    = {{14, 5, 0},  "macOS", {"Yosemite",      {10, 10, 5}}},
-	"14F2009"    = {{14, 5, 0},  "macOS", {"Yosemite",      {10, 10, 5}}},
-	"14F2109"    = {{14, 5, 0},  "macOS", {"Yosemite",      {10, 10, 5}}},
-	"14F2315"    = {{14, 5, 0},  "macOS", {"Yosemite",      {10, 10, 5}}},
-	"14F2411"    = {{14, 5, 0},  "macOS", {"Yosemite",      {10, 10, 5}}},
-	"14F2511"    = {{14, 5, 0},  "macOS", {"Yosemite",      {10, 10, 5}}},
-
-	// MacOS El Capitan
-	"15A284"     = {{15, 0, 0}, "macOS", {"El Capitan",     {10, 11, 0}}},
-	"15B42"      = {{15, 0, 0}, "macOS", {"El Capitan",     {10, 11, 1}}},
-	"15C50"      = {{15, 2, 0}, "macOS", {"El Capitan",     {10, 11, 2}}},
-	"15D21"      = {{15, 3, 0}, "macOS", {"El Capitan",     {10, 11, 3}}},
-	"15E65"      = {{15, 4, 0}, "macOS", {"El Capitan",     {10, 11, 4}}},
-	"15F34"      = {{15, 5, 0}, "macOS", {"El Capitan",     {10, 11, 5}}},
-	"15G31"      = {{15, 6, 0}, "macOS", {"El Capitan",     {10, 11, 6}}},
-	"15G1004"    = {{15, 6, 0}, "macOS", {"El Capitan",     {10, 11, 6}}},
-	"15G1011"    = {{15, 6, 0}, "macOS", {"El Capitan",     {10, 11, 6}}},
-	"15G1108"    = {{15, 6, 0}, "macOS", {"El Capitan",     {10, 11, 6}}},
-	"15G1212"    = {{15, 6, 0}, "macOS", {"El Capitan",     {10, 11, 6}}},
-	"15G1217"    = {{15, 6, 0}, "macOS", {"El Capitan",     {10, 11, 6}}},
-	"15G1421"    = {{15, 6, 0}, "macOS", {"El Capitan",     {10, 11, 6}}},
-	"15G1510"    = {{15, 6, 0}, "macOS", {"El Capitan",     {10, 11, 6}}},
-	"15G1611"    = {{15, 6, 0}, "macOS", {"El Capitan",     {10, 11, 6}}},
-	"15G17023"   = {{15, 6, 0}, "macOS", {"El Capitan",     {10, 11, 6}}},
-	"15G18013"   = {{15, 6, 0}, "macOS", {"El Capitan",     {10, 11, 6}}},
-	"15G19009"   = {{15, 6, 0}, "macOS", {"El Capitan",     {10, 11, 6}}},
-	"15G20015"   = {{15, 6, 0}, "macOS", {"El Capitan",     {10, 11, 6}}},
-	"15G21013"   = {{15, 6, 0}, "macOS", {"El Capitan",     {10, 11, 6}}},
-	"15G22010"   = {{15, 6, 0}, "macOS", {"El Capitan",     {10, 11, 6}}},
-
-	// MacOS Sierra
-	"16A323"     = {{16, 0, 0}, "macOS", {"Sierra",         {10, 12, 0}}},
-	"16B2555"    = {{16, 1, 0}, "macOS", {"Sierra",         {10, 12, 1}}},
-	"16B2657"    = {{16, 1, 0}, "macOS", {"Sierra",         {10, 12, 1}}},
-	"16C67"      = {{16, 3, 0}, "macOS", {"Sierra",         {10, 12, 2}}},
-	"16C68"      = {{16, 3, 0}, "macOS", {"Sierra",         {10, 12, 2}}},
-	"16D32"      = {{16, 4, 0}, "macOS", {"Sierra",         {10, 12, 3}}},
-	"16E195"     = {{16, 5, 0}, "macOS", {"Sierra",         {10, 12, 4}}},
-	"16F73"      = {{16, 6, 0}, "macOS", {"Sierra",         {10, 12, 5}}},
-	"16F2073"    = {{16, 6, 0}, "macOS", {"Sierra",         {10, 12, 5}}},
-	"16G29"      = {{16, 7, 0}, "macOS", {"Sierra",         {10, 12, 6}}},
-	"16G1036"    = {{16, 7, 0}, "macOS", {"Sierra",         {10, 12, 6}}},
-	"16G1114"    = {{16, 7, 0}, "macOS", {"Sierra",         {10, 12, 6}}},
-	"16G1212"    = {{16, 7, 0}, "macOS", {"Sierra",         {10, 12, 6}}},
-	"16G1314"    = {{16, 7, 0}, "macOS", {"Sierra",         {10, 12, 6}}},
-	"16G1408"    = {{16, 7, 0}, "macOS", {"Sierra",         {10, 12, 6}}},
-	"16G1510"    = {{16, 7, 0}, "macOS", {"Sierra",         {10, 12, 6}}},
-	"16G1618"    = {{16, 7, 0}, "macOS", {"Sierra",         {10, 12, 6}}},
-	"16G1710"    = {{16, 7, 0}, "macOS", {"Sierra",         {10, 12, 6}}},
-	"16G1815"    = {{16, 7, 0}, "macOS", {"Sierra",         {10, 12, 6}}},
-	"16G1917"    = {{16, 7, 0}, "macOS", {"Sierra",         {10, 12, 6}}},
-	"16G1918"    = {{16, 7, 0}, "macOS", {"Sierra",         {10, 12, 6}}},
-	"16G2016"    = {{16, 7, 0}, "macOS", {"Sierra",         {10, 12, 6}}},
-	"16G2127"    = {{16, 7, 0}, "macOS", {"Sierra",         {10, 12, 6}}},
-	"16G2128"    = {{16, 7, 0}, "macOS", {"Sierra",         {10, 12, 6}}},
-	"16G2136"    = {{16, 7, 0}, "macOS", {"Sierra",         {10, 12, 6}}},
-
-	// MacOS High Sierra
-	"17A365"     = {{17, 0, 0}, "macOS", {"High Sierra",    {10, 13, 0}}},
-	"17A405"     = {{17, 0, 0}, "macOS", {"High Sierra",    {10, 13, 0}}},
-	"17B48"      = {{17, 2, 0}, "macOS", {"High Sierra",    {10, 13, 1}}},
-	"17B1002"    = {{17, 2, 0}, "macOS", {"High Sierra",    {10, 13, 1}}},
-	"17B1003"    = {{17, 2, 0}, "macOS", {"High Sierra",    {10, 13, 1}}},
-	"17C88"      = {{17, 3, 0}, "macOS", {"High Sierra",    {10, 13, 2}}},
-	"17C89"      = {{17, 3, 0}, "macOS", {"High Sierra",    {10, 13, 2}}},
-	"17C205"     = {{17, 3, 0}, "macOS", {"High Sierra",    {10, 13, 2}}},
-	"17C2205"    = {{17, 3, 0}, "macOS", {"High Sierra",    {10, 13, 2}}},
-	"17D47"      = {{17, 4, 0}, "macOS", {"High Sierra",    {10, 13, 3}}},
-	"17D2047"    = {{17, 4, 0}, "macOS", {"High Sierra",    {10, 13, 3}}},
-	"17D102"     = {{17, 4, 0}, "macOS", {"High Sierra",    {10, 13, 3}}},
-	"17D2102"    = {{17, 4, 0}, "macOS", {"High Sierra",    {10, 13, 3}}},
-	"17E199"     = {{17, 5, 0}, "macOS", {"High Sierra",    {10, 13, 4}}},
-	"17E202"     = {{17, 5, 0}, "macOS", {"High Sierra",    {10, 13, 4}}},
-	"17F77"      = {{17, 6, 0}, "macOS", {"High Sierra",    {10, 13, 5}}},
-	"17G65"      = {{17, 7, 0}, "macOS", {"High Sierra",    {10, 13, 6}}},
-	"17G2208"    = {{17, 7, 0}, "macOS", {"High Sierra",    {10, 13, 6}}},
-	"17G2307"    = {{17, 7, 0}, "macOS", {"High Sierra",    {10, 13, 6}}},
-	"17G3025"    = {{17, 7, 0}, "macOS", {"High Sierra",    {10, 13, 6}}},
-	"17G4015"    = {{17, 7, 0}, "macOS", {"High Sierra",    {10, 13, 6}}},
-	"17G5019"    = {{17, 7, 0}, "macOS", {"High Sierra",    {10, 13, 6}}},
-	"17G6029"    = {{17, 7, 0}, "macOS", {"High Sierra",    {10, 13, 6}}},
-	"17G6030"    = {{17, 7, 0}, "macOS", {"High Sierra",    {10, 13, 6}}},
-	"17G7024"    = {{17, 7, 0}, "macOS", {"High Sierra",    {10, 13, 6}}},
-	"17G8029"    = {{17, 7, 0}, "macOS", {"High Sierra",    {10, 13, 6}}},
-	"17G8030"    = {{17, 7, 0}, "macOS", {"High Sierra",    {10, 13, 6}}},
-	"17G8037"    = {{17, 7, 0}, "macOS", {"High Sierra",    {10, 13, 6}}},
-	"17G9016"    = {{17, 7, 0}, "macOS", {"High Sierra",    {10, 13, 6}}},
-	"17G10021"   = {{17, 7, 0}, "macOS", {"High Sierra",    {10, 13, 6}}},
-	"17G11023"   = {{17, 7, 0}, "macOS", {"High Sierra",    {10, 13, 6}}},
-	"17G12034"   = {{17, 7, 0}, "macOS", {"High Sierra",    {10, 13, 6}}},
-	"17G13033"   = {{17, 7, 0}, "macOS", {"High Sierra",    {10, 13, 6}}},
-	"17G13035"   = {{17, 7, 0}, "macOS", {"High Sierra",    {10, 13, 6}}},
-	"17G14019"   = {{17, 7, 0}, "macOS", {"High Sierra",    {10, 13, 6}}},
-	"17G14033"   = {{17, 7, 0}, "macOS", {"High Sierra",    {10, 13, 6}}},
-	"17G14042"   = {{17, 7, 0}, "macOS", {"High Sierra",    {10, 13, 6}}},
-
-	// MacOS Mojave
-	"18A391"     = {{18, 0, 0}, "macOS", {"Mojave",         {10, 14, 0}}},
-	"18B75"      = {{18, 2, 0}, "macOS", {"Mojave",         {10, 14, 1}}},
-	"18B2107"    = {{18, 2, 0}, "macOS", {"Mojave",         {10, 14, 1}}},
-	"18B3094"    = {{18, 2, 0}, "macOS", {"Mojave",         {10, 14, 1}}},
-	"18C54"      = {{18, 2, 0}, "macOS", {"Mojave",         {10, 14, 2}}},
-	"18D42"      = {{18, 2, 0}, "macOS", {"Mojave",         {10, 14, 3}}},
-	"18D43"      = {{18, 2, 0}, "macOS", {"Mojave",         {10, 14, 3}}},
-	"18D109"     = {{18, 2, 0}, "macOS", {"Mojave",         {10, 14, 3}}},
-	"18E226"     = {{18, 5, 0}, "macOS", {"Mojave",         {10, 14, 4}}},
-	"18E227"     = {{18, 5, 0}, "macOS", {"Mojave",         {10, 14, 4}}},
-	"18F132"     = {{18, 6, 0}, "macOS", {"Mojave",         {10, 14, 5}}},
-	"18G84"      = {{18, 7, 0}, "macOS", {"Mojave",         {10, 14, 6}}},
-	"18G87"      = {{18, 7, 0}, "macOS", {"Mojave",         {10, 14, 6}}},
-	"18G95"      = {{18, 7, 0}, "macOS", {"Mojave",         {10, 14, 6}}},
-	"18G103"     = {{18, 7, 0}, "macOS", {"Mojave",         {10, 14, 6}}},
-	"18G1012"    = {{18, 7, 0}, "macOS", {"Mojave",         {10, 14, 6}}},
-	"18G2022"    = {{18, 7, 0}, "macOS", {"Mojave",         {10, 14, 6}}},
-	"18G3020"    = {{18, 7, 0}, "macOS", {"Mojave",         {10, 14, 6}}},
-	"18G4032"    = {{18, 7, 0}, "macOS", {"Mojave",         {10, 14, 6}}},
-	"18G5033"    = {{18, 7, 0}, "macOS", {"Mojave",         {10, 14, 6}}},
-	"18G6020"    = {{18, 7, 0}, "macOS", {"Mojave",         {10, 14, 6}}},
-	"18G6032"    = {{18, 7, 0}, "macOS", {"Mojave",         {10, 14, 6}}},
-	"18G6042"    = {{18, 7, 0}, "macOS", {"Mojave",         {10, 14, 6}}},
-	"18G7016"    = {{18, 7, 0}, "macOS", {"Mojave",         {10, 14, 6}}},
-	"18G8012"    = {{18, 7, 0}, "macOS", {"Mojave",         {10, 14, 6}}},
-	"18G8022"    = {{18, 7, 0}, "macOS", {"Mojave",         {10, 14, 6}}},
-	"18G9028"    = {{18, 7, 0}, "macOS", {"Mojave",         {10, 14, 6}}},
-	"18G9216"    = {{18, 7, 0}, "macOS", {"Mojave",         {10, 14, 6}}},
-	"18G9323"    = {{18, 7, 0}, "macOS", {"Mojave",         {10, 14, 6}}},
-
-	// MacOS Catalina
-	"19A583"     = {{19, 0, 0}, "macOS", {"Catalina",       {10, 15, 0}}},
-	"19A602"     = {{19, 0, 0}, "macOS", {"Catalina",       {10, 15, 0}}},
-	"19A603"     = {{19, 0, 0}, "macOS", {"Catalina",       {10, 15, 0}}},
-	"19B88"      = {{19, 0, 0}, "macOS", {"Catalina",       {10, 15, 1}}},
-	"19C57"      = {{19, 2, 0}, "macOS", {"Catalina",       {10, 15, 2}}},
-	"19C58"      = {{19, 2, 0}, "macOS", {"Catalina",       {10, 15, 2}}},
-	"19D76"      = {{19, 3, 0}, "macOS", {"Catalina",       {10, 15, 3}}},
-	"19E266"     = {{19, 4, 0}, "macOS", {"Catalina",       {10, 15, 4}}},
-	"19E287"     = {{19, 4, 0}, "macOS", {"Catalina",       {10, 15, 4}}},
-	"19F96"      = {{19, 5, 0}, "macOS", {"Catalina",       {10, 15, 5}}},
-	"19F101"     = {{19, 5, 0}, "macOS", {"Catalina",       {10, 15, 5}}},
-	"19G73"      = {{19, 6, 0}, "macOS", {"Catalina",       {10, 15, 6}}},
-	"19G2021"    = {{19, 6, 0}, "macOS", {"Catalina",       {10, 15, 6}}},
-	"19H2"       = {{19, 6, 0}, "macOS", {"Catalina",       {10, 15, 7}}},
-	"19H4"       = {{19, 6, 0}, "macOS", {"Catalina",       {10, 15, 7}}},
-	"19H15"      = {{19, 6, 0}, "macOS", {"Catalina",       {10, 15, 7}}},
-	"19H114"     = {{19, 6, 0}, "macOS", {"Catalina",       {10, 15, 7}}},
-	"19H512"     = {{19, 6, 0}, "macOS", {"Catalina",       {10, 15, 7}}},
-	"19H524"     = {{19, 6, 0}, "macOS", {"Catalina",       {10, 15, 7}}},
-	"19H1030"    = {{19, 6, 0}, "macOS", {"Catalina",       {10, 15, 7}}},
-	"19H1217"    = {{19, 6, 0}, "macOS", {"Catalina",       {10, 15, 7}}},
-	"19H1323"    = {{19, 6, 0}, "macOS", {"Catalina",       {10, 15, 7}}},
-	"19H1417"    = {{19, 6, 0}, "macOS", {"Catalina",       {10, 15, 7}}},
-	"19H1419"    = {{19, 6, 0}, "macOS", {"Catalina",       {10, 15, 7}}},
-	"19H1519"    = {{19, 6, 0}, "macOS", {"Catalina",       {10, 15, 7}}},
-	"19H1615"    = {{19, 6, 0}, "macOS", {"Catalina",       {10, 15, 7}}},
-	"19H1713"    = {{19, 6, 0}, "macOS", {"Catalina",       {10, 15, 7}}},
-	"19H1715"    = {{19, 6, 0}, "macOS", {"Catalina",       {10, 15, 7}}},
-	"19H1824"    = {{19, 6, 0}, "macOS", {"Catalina",       {10, 15, 7}}},
-	"19H1922"    = {{19, 6, 0}, "macOS", {"Catalina",       {10, 15, 7}}},
-	"19H2026"    = {{19, 6, 0}, "macOS", {"Catalina",       {10, 15, 7}}},
-
-	// MacOS Big Sur
-	"20A2411"    = {{20, 1, 0}, "macOS", {"Big Sur",        {11, 0, 0}}},
-	"20B29"      = {{20, 1, 0}, "macOS", {"Big Sur",        {11, 0, 1}}},
-	"20B50"      = {{20, 1, 0}, "macOS", {"Big Sur",        {11, 0, 1}}},
-	"20C69"      = {{20, 2, 0}, "macOS", {"Big Sur",        {11, 1, 0}}},
-	"20D64"      = {{20, 3, 0}, "macOS", {"Big Sur",        {11, 2, 0}}},
-	"20D74"      = {{20, 3, 0}, "macOS", {"Big Sur",        {11, 2, 1}}},
-	"20D75"      = {{20, 3, 0}, "macOS", {"Big Sur",        {11, 2, 1}}},
-	"20D80"      = {{20, 3, 0}, "macOS", {"Big Sur",        {11, 2, 2}}},
-	"20D91"      = {{20, 3, 0}, "macOS", {"Big Sur",        {11, 2, 3}}},
-	"20E232"     = {{20, 4, 0}, "macOS", {"Big Sur",        {11, 3, 0}}},
-	"20E241"     = {{20, 4, 0}, "macOS", {"Big Sur",        {11, 3, 1}}},
-	"20F71"      = {{20, 5, 0}, "macOS", {"Big Sur",        {11, 4, 0}}},
-	"20G71"      = {{20, 6, 0}, "macOS", {"Big Sur",        {11, 5, 0}}},
-	"20G80"      = {{20, 6, 0}, "macOS", {"Big Sur",        {11, 5, 1}}},
-	"20G95"      = {{20, 6, 0}, "macOS", {"Big Sur",        {11, 5, 2}}},
-	"20G165"     = {{20, 6, 0}, "macOS", {"Big Sur",        {11, 6, 0}}},
-	"20G224"     = {{20, 6, 0}, "macOS", {"Big Sur",        {11, 6, 1}}},
-	"20G314"     = {{20, 6, 0}, "macOS", {"Big Sur",        {11, 6, 2}}},
-	"20G415"     = {{20, 6, 0}, "macOS", {"Big Sur",        {11, 6, 3}}},
-	"20G417"     = {{20, 6, 0}, "macOS", {"Big Sur",        {11, 6, 4}}},
-	"20G527"     = {{20, 6, 0}, "macOS", {"Big Sur",        {11, 6, 5}}},
-	"20G624"     = {{20, 6, 0}, "macOS", {"Big Sur",        {11, 6, 6}}},
-	"20G630"     = {{20, 6, 0}, "macOS", {"Big Sur",        {11, 6, 7}}},
-	"20G730"     = {{20, 6, 0}, "macOS", {"Big Sur",        {11, 6, 8}}},
-	"20G817"     = {{20, 6, 0}, "macOS", {"Big Sur",        {11, 7, 0}}},
-	"20G918"     = {{20, 6, 0}, "macOS", {"Big Sur",        {11, 7, 1}}},
-	"20G1020"    = {{20, 6, 0}, "macOS", {"Big Sur",        {11, 7, 2}}},
-	"20G1116"    = {{20, 6, 0}, "macOS", {"Big Sur",        {11, 7, 3}}},
-	"20G1120"    = {{20, 6, 0}, "macOS", {"Big Sur",        {11, 7, 4}}},
-	"20G1225"    = {{20, 6, 0}, "macOS", {"Big Sur",        {11, 7, 5}}},
-	"20G1231"    = {{20, 6, 0}, "macOS", {"Big Sur",        {11, 7, 6}}},
-	"20G1345"    = {{20, 6, 0}, "macOS", {"Big Sur",        {11, 7, 7}}},
-	"20G1351"    = {{20, 6, 0}, "macOS", {"Big Sur",        {11, 7, 8}}},
-	"20G1426"    = {{20, 6, 0}, "macOS", {"Big Sur",        {11, 7, 9}}},
-	"20G1427"    = {{20, 6, 0}, "macOS", {"Big Sur",        {11, 7, 10}}},
-
-	// MacOS Monterey
-	"21A344"     = {{21, 0, 1}, "macOS", {"Monterey",       {12, 0, 0}}},
-	"21A559"     = {{21, 1, 0}, "macOS", {"Monterey",       {12, 0, 1}}},
-	"21C52"      = {{21, 2, 0}, "macOS", {"Monterey",       {12, 1, 0}}},
-	"21D49"      = {{21, 3, 0}, "macOS", {"Monterey",       {12, 2, 0}}},
-	"21D62"      = {{21, 3, 0}, "macOS", {"Monterey",       {12, 2, 1}}},
-	"21E230"     = {{21, 4, 0}, "macOS", {"Monterey",       {12, 3, 0}}},
-	"21E258"     = {{21, 4, 0}, "macOS", {"Monterey",       {12, 3, 1}}},
-	"21F79"      = {{21, 5, 0}, "macOS", {"Monterey",       {12, 4, 0}}},
-	"21F2081"    = {{21, 5, 0}, "macOS", {"Monterey",       {12, 4, 0}}},
-	"21F2092"    = {{21, 5, 0}, "macOS", {"Monterey",       {12, 4, 0}}},
-	"21G72"      = {{21, 6, 0}, "macOS", {"Monterey",       {12, 5, 0}}},
-	"21G83"      = {{21, 6, 0}, "macOS", {"Monterey",       {12, 5, 1}}},
-	"21G115"     = {{21, 6, 0}, "macOS", {"Monterey",       {12, 6, 0}}},
-	"21G217"     = {{21, 6, 0}, "macOS", {"Monterey",       {12, 6, 1}}},
-	"21G320"     = {{21, 6, 0}, "macOS", {"Monterey",       {12, 6, 2}}},
-	"21G419"     = {{21, 6, 0}, "macOS", {"Monterey",       {12, 6, 3}}},
-	"21G526"     = {{21, 6, 0}, "macOS", {"Monterey",       {12, 6, 4}}},
-	"21G531"     = {{21, 6, 0}, "macOS", {"Monterey",       {12, 6, 5}}},
-	"21G646"     = {{21, 6, 0}, "macOS", {"Monterey",       {12, 6, 6}}},
-	"21G651"     = {{21, 6, 0}, "macOS", {"Monterey",       {12, 6, 7}}},
-	"21G725"     = {{21, 6, 0}, "macOS", {"Monterey",       {12, 6, 8}}},
-	"21G726"     = {{21, 6, 0}, "macOS", {"Monterey",       {12, 6, 9}}},
-	"21G816"     = {{21, 6, 0}, "macOS", {"Monterey",       {12, 7, 0}}},
-	"21G920"     = {{21, 6, 0}, "macOS", {"Monterey",       {12, 7, 1}}},
-	"21G1974"    = {{21, 6, 0}, "macOS", {"Monterey",       {12, 7, 2}}},
-
-	// MacOS Ventura 
-	"22A380"     = {{22, 1, 0}, "macOS", {"Ventura",        {13, 0, 0}}},
-	"22A400"     = {{22, 1, 0}, "macOS", {"Ventura",        {13, 0, 1}}},
-	"22C65"	     = {{22, 2, 0}, "macOS", {"Ventura",        {13, 1, 0}}},
-	"22D49"	     = {{22, 3, 0}, "macOS", {"Ventura",        {13, 2, 0}}},
-	"22D68"	     = {{22, 3, 0}, "macOS", {"Ventura",        {13, 2, 1}}},
-	"22E252"     = {{22, 4, 0}, "macOS", {"Ventura",        {13, 3, 0}}},
-	"22E261"     = {{22, 4, 0}, "macOS", {"Ventura",        {13, 3, 1}}},
-	"22F66"	     = {{22, 5, 0}, "macOS", {"Ventura",        {13, 4, 0}}},
-	"22F82"	     = {{22, 5, 0}, "macOS", {"Ventura",        {13, 4, 1}}},
-	"22E772610a" = {{22, 5, 0}, "macOS", {"Ventura",        {13, 4, 1}}},
-	"22F770820d" = {{22, 5, 0}, "macOS", {"Ventura",        {13, 4, 1}}},
-	"22G74"	     = {{22, 6, 0}, "macOS", {"Ventura",        {13, 5, 0}}},
-	"22G90"	     = {{22, 6, 0}, "macOS", {"Ventura",        {13, 5, 1}}},
-	"22G91"	     = {{22, 6, 0}, "macOS", {"Ventura",        {13, 5, 2}}},
-	"22G120"     = {{22, 6, 0}, "macOS", {"Ventura",        {13, 6, 0}}},
-	"22G313"     = {{22, 6, 0}, "macOS", {"Ventura",        {13, 6, 1}}},
-	"22G320"     = {{22, 6, 0}, "macOS", {"Ventura",        {13, 6, 2}}},
-
-	// MacOS Sonoma 
-	"23A344"     = {{23, 0, 0}, "macOS", {"Sonoma",         {14, 0, 0}}},
-	"23B74"      = {{23, 1, 0}, "macOS", {"Sonoma",         {14, 1, 0}}},
-	"23B81"      = {{23, 1, 0}, "macOS", {"Sonoma",         {14, 1, 1}}},
-	"23B2082"    = {{23, 1, 0}, "macOS", {"Sonoma",         {14, 1, 1}}},
-	"23B92"      = {{23, 1, 0}, "macOS", {"Sonoma",         {14, 1, 2}}},
-	"23B2091"    = {{23, 1, 0}, "macOS", {"Sonoma",         {14, 1, 2}}},
-	"23C64"      = {{23, 2, 0}, "macOS", {"Sonoma",         {14, 2, 0}}},
-	"23C71"      = {{23, 2, 0}, "macOS", {"Sonoma",         {14, 2, 1}}},
-	"23D56"      = {{23, 3, 0}, "macOS", {"Sonoma",         {14, 3, 0}}},
-	"23D60"      = {{23, 3, 0}, "macOS", {"Sonoma",         {14, 3, 1}}},
-	"23E214"     = {{23, 4, 0}, "macOS", {"Sonoma",         {14, 4, 0}}},
-	"23E224"     = {{23, 4, 0}, "macOS", {"Sonoma",         {14, 4, 1}}},
-	"23F79"      = {{23, 5, 0}, "macOS", {"Sonoma",         {14, 5, 0}}},
-	"23G80"      = {{23, 6, 0}, "macOS", {"Sonoma",         {14, 6, 0}}},
-	"23G93"      = {{23, 6, 0}, "macOS", {"Sonoma",         {14, 6, 1}}},
-	"23H124"     = {{23, 6, 0}, "macOS", {"Sonoma",         {14, 7, 0}}},
-
-	// MacOS Sequoia
-	"24A335"     = {{24, 0, 0}, "macOS", {"Sequoia",        {15, 0, 0}}},
-	"24A348"     = {{24, 0, 0}, "macOS", {"Sequoia",        {15, 0, 1}}},
-}
-
-@(private)
-Darwin_Match :: enum {
-	Unknown,
-	Exact,
-	Nearest,
-}
+	{
+		// Match on XNU kernel version
+		version_bits: [12]u8 // enough for 999.999.999\x00
+		mib := []i32{unix.CTL_KERN, unix.KERN_OSRELEASE}
+		ok := unix.sysctl(mib, &version_bits)
+		kernel := string(cstring(raw_data(version_bits[:]))) if ok else "Unknown"
 
-@(private)
-map_darwin_kernel_version_to_macos_release :: proc(build: string, darwin: [3]int) -> (res: Darwin_To_Release, match: Darwin_Match) {
-	// Find exact release match if possible.
-	if v, v_ok := macos_release_map[build]; v_ok {
-		return v, .Exact
-	}
+		major, _, tail  := strings.partition(kernel, ".")
+		minor, _, patch := strings.partition(tail, ".")
 
-	nearest: Darwin_To_Release
-	for _, v in macos_release_map {
-		// Try an exact match on XNU version first.
-		if darwin == v.darwin {
-			return v, .Exact
-		}
+		os_version.major, _ = strconv.parse_int(major, 10)
+		os_version.minor, _ = strconv.parse_int(minor, 10)
+		os_version.patch, _ = strconv.parse_int(patch, 10)
 
-		// Major kernel version needs to match exactly,
-		// otherwise the release is considered .Unknown
-		if darwin.x == v.darwin.x {
-			if nearest == {} {
-				nearest = v
-			}
-			if darwin.y >= v.darwin.y && v.darwin != nearest.darwin {
-				nearest = v
-				if darwin.z >= v.darwin.z && v.darwin != nearest.darwin {
-					nearest = v
-				}
-			}
-		}
+		ws(&b, ", kernel ")
+		ws(&b, kernel)
+		ws(&b, ")")
 	}
 
-	if nearest == {} {
-		return {darwin, "macOS", {"Unknown", {}}}, .Unknown
-	} else {
-		return nearest, .Nearest
-	}
+	os_version.as_string = string(b.buf[:])
 }

+ 89 - 37
core/sys/linux/bits.odin

@@ -152,43 +152,65 @@ Errno :: enum i32 {
 	RDONLY flag is not present, because it has the value of 0, i.e. it is the
 	default, unless WRONLY or RDWR is specified.
 */
-Open_Flags_Bits :: enum {
-	WRONLY    = 0,
-	RDWR      = 1,
-	CREAT     = 6,
-	EXCL      = 7,
-	NOCTTY    = 8,
-	TRUNC     = 9,
-	APPEND    = 10,
-	NONBLOCK  = 11,
-	DSYNC     = 12,
-	ASYNC     = 13,
-	DIRECT    = 14,
-	LARGEFILE = 15,
-	DIRECTORY = 16,
-	NOFOLLOW  = 17,
-	NOATIME   = 18,
-	CLOEXEC   = 19,
-	PATH      = 21,
-}
-// https://github.com/torvalds/linux/blob/7367539ad4b0f8f9b396baf02110962333719a48/include/uapi/asm-generic/fcntl.h#L19
-#assert(1 << uint(Open_Flags_Bits.WRONLY)    == 0o0000000_1)
-#assert(1 << uint(Open_Flags_Bits.RDWR)      == 0o0000000_2)
-#assert(1 << uint(Open_Flags_Bits.CREAT)     == 0o00000_100)
-#assert(1 << uint(Open_Flags_Bits.EXCL)      == 0o00000_200)
-#assert(1 << uint(Open_Flags_Bits.NOCTTY)    == 0o00000_400)
-#assert(1 << uint(Open_Flags_Bits.TRUNC)     == 0o0000_1000)
-#assert(1 << uint(Open_Flags_Bits.APPEND)    == 0o0000_2000)
-#assert(1 << uint(Open_Flags_Bits.NONBLOCK)  == 0o0000_4000)
-#assert(1 << uint(Open_Flags_Bits.DSYNC)     == 0o000_10000)
-#assert(1 << uint(Open_Flags_Bits.ASYNC)     == 0o000_20000)
-#assert(1 << uint(Open_Flags_Bits.DIRECT)    == 0o000_40000)
-#assert(1 << uint(Open_Flags_Bits.LARGEFILE) == 0o00_100000)
-#assert(1 << uint(Open_Flags_Bits.DIRECTORY) == 0o00_200000)
-#assert(1 << uint(Open_Flags_Bits.NOFOLLOW)  == 0o00_400000)
-#assert(1 << uint(Open_Flags_Bits.NOATIME)   == 0o0_1000000)
-#assert(1 << uint(Open_Flags_Bits.CLOEXEC)   == 0o0_2000000)
-#assert(1 << uint(Open_Flags_Bits.PATH)      == 0o_10000000)
+when ODIN_ARCH != .arm64 && ODIN_ARCH != .arm32 {
+	Open_Flags_Bits :: enum {
+		WRONLY    = 0,
+		RDWR      = 1,
+		CREAT     = 6,
+		EXCL      = 7,
+		NOCTTY    = 8,
+		TRUNC     = 9,
+		APPEND    = 10,
+		NONBLOCK  = 11,
+		DSYNC     = 12,
+		ASYNC     = 13,
+		DIRECT    = 14,
+		LARGEFILE = 15,
+		DIRECTORY = 16,
+		NOFOLLOW  = 17,
+		NOATIME   = 18,
+		CLOEXEC   = 19,
+		PATH      = 21,
+	}
+	// https://github.com/torvalds/linux/blob/7367539ad4b0f8f9b396baf02110962333719a48/include/uapi/asm-generic/fcntl.h#L19
+	#assert(1 << uint(Open_Flags_Bits.WRONLY)    == 0o0000000_1)
+	#assert(1 << uint(Open_Flags_Bits.RDWR)      == 0o0000000_2)
+	#assert(1 << uint(Open_Flags_Bits.CREAT)     == 0o00000_100)
+	#assert(1 << uint(Open_Flags_Bits.EXCL)      == 0o00000_200)
+	#assert(1 << uint(Open_Flags_Bits.NOCTTY)    == 0o00000_400)
+	#assert(1 << uint(Open_Flags_Bits.TRUNC)     == 0o0000_1000)
+	#assert(1 << uint(Open_Flags_Bits.APPEND)    == 0o0000_2000)
+	#assert(1 << uint(Open_Flags_Bits.NONBLOCK)  == 0o0000_4000)
+	#assert(1 << uint(Open_Flags_Bits.DSYNC)     == 0o000_10000)
+	#assert(1 << uint(Open_Flags_Bits.ASYNC)     == 0o000_20000)
+	#assert(1 << uint(Open_Flags_Bits.DIRECT)    == 0o000_40000)
+	#assert(1 << uint(Open_Flags_Bits.LARGEFILE) == 0o00_100000)
+	#assert(1 << uint(Open_Flags_Bits.DIRECTORY) == 0o00_200000)
+	#assert(1 << uint(Open_Flags_Bits.NOFOLLOW)  == 0o00_400000)
+	#assert(1 << uint(Open_Flags_Bits.NOATIME)   == 0o0_1000000)
+	#assert(1 << uint(Open_Flags_Bits.CLOEXEC)   == 0o0_2000000)
+	#assert(1 << uint(Open_Flags_Bits.PATH)      == 0o_10000000)
+} else {
+	Open_Flags_Bits :: enum {
+		WRONLY    = 0,
+		RDWR      = 1,
+		CREAT     = 6,
+		EXCL      = 7,
+		NOCTTY    = 8,
+		TRUNC     = 9,
+		APPEND    = 10,
+		NONBLOCK  = 11,
+		DSYNC     = 12,
+		ASYNC     = 13,
+		DIRECTORY = 14,
+		NOFOLLOW  = 15,
+		DIRECT    = 16,
+		LARGEFILE = 17,
+		NOATIME   = 18,
+		CLOEXEC   = 19,
+		PATH      = 21,
+	}
+}
 
 /*
 	Bits for FD_Flags bitset
@@ -519,6 +541,36 @@ Fd_Poll_Events_Bits :: enum {
 	RDHUP  = 13,
 }
 
+Inotify_Init_Bits :: enum {
+	NONBLOCK = 11,
+	CLOEXEC  = 19,
+}
+
+Inotify_Event_Bits :: enum u32 {
+	ACCESS        = 0,
+	MODIFY        = 1,
+	ATTRIB        = 2,
+	CLOSE_WRITE   = 3,
+	CLOSE_NOWRITE = 4,
+	OPEN          = 5,
+	MOVED_FROM    = 6,
+	MOVED_TO      = 7,
+	CREATE        = 8,
+	DELETE        = 9,
+	DELETE_SELF   = 10,
+	MOVE_SELF     = 11,
+	UNMOUNT       = 13,
+	Q_OVERFLOW    = 14,
+	IGNORED       = 15,
+	ONLYDIR       = 24,
+	DONT_FOLLOW   = 25,
+	EXCL_UNLINK   = 26,
+	MASK_CREATE   = 28,
+	MASK_ADD      = 29,
+	ISDIR         = 30,
+	ONESHOT       = 31,
+}
+
 /*
 	Bits for Mem_Protection bitfield
 */

+ 25 - 0
core/sys/linux/constants.odin

@@ -135,6 +135,31 @@ STATX_BASIC_STATS :: Statx_Mask {
 	.BLOCKS,
 }
 
+IN_ALL_EVENTS	:: Inotify_Event_Mask {
+	.ACCESS,
+	.MODIFY,
+	.ATTRIB,
+	.CLOSE_WRITE,
+	.CLOSE_NOWRITE,
+	.OPEN,
+	.MOVED_FROM,
+	.MOVED_TO,
+	.CREATE,
+	.DELETE,
+	.DELETE_SELF,
+	.MOVE_SELF,
+}
+
+IN_CLOSE :: Inotify_Event_Mask {
+	.CLOSE_WRITE,
+	.CLOSE_NOWRITE,
+}
+
+IN_MOVE :: Inotify_Event_Mask {
+	.MOVED_FROM,
+	.MOVED_TO,
+}
+
 /*
 	Tell `shmget` to create a new key
 */

+ 22 - 3
core/sys/linux/sys.odin

@@ -2536,11 +2536,30 @@ waitid :: proc "contextless" (id_type: Id_Type, id: Id, sig_info: ^Sig_Info, opt
 
 // TODO(flysand): ioprio_get
 
-// TODO(flysand): inotify_init
+inotify_init :: proc "contextless" () -> (Fd, Errno) {
+	when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 {
+		ret := syscall(SYS_inotify_init1, 0)
+		return errno_unwrap(ret, Fd)
+	} else {
+		ret := syscall(SYS_inotify_init)
+		return errno_unwrap(ret, Fd)
+	}
+}
 
-// TODO(flysand): inotify_add_watch
+inotify_init1 :: proc "contextless" (flags: Inotify_Init_Flags) -> (Fd, Errno) {
+	ret := syscall(SYS_inotify_init1, transmute(i32)flags)
+	return errno_unwrap(ret, Fd)
+}
+
+inotify_add_watch :: proc "contextless" (fd: Fd, pathname: cstring, mask: Inotify_Event_Mask) -> (Wd, Errno) {
+	ret := syscall(SYS_inotify_add_watch, fd, transmute(uintptr) pathname, transmute(u32) mask)
+	return errno_unwrap(ret, Wd)
+}
 
-// TODO(flysand): inotify_rm_watch
+inotify_rm_watch :: proc "contextless" (fd: Fd, wd: Wd) -> (Errno) {
+	ret := syscall(SYS_inotify_rm_watch, fd, wd)
+	return Errno(-ret)
+}
 
 // TODO(flysand): migrate_pages
 

+ 27 - 0
core/sys/linux/types.odin

@@ -30,6 +30,11 @@ Id :: distinct uint
 */
 Fd  :: distinct i32
 
+/*
+	Represents a watch descriptor.
+*/
+Wd  :: distinct i32
+
 /*
 	Type for PID file descriptors.
 */
@@ -343,6 +348,18 @@ Poll_Fd :: struct {
 	revents: Fd_Poll_Events,
 }
 
+Inotify_Init_Flags :: bit_set[Inotify_Init_Bits; i32]
+
+Inotify_Event :: struct {
+	wd:     Wd,
+	mask:   Inotify_Event_Mask,
+	cookie: u32,
+	len:    u32,
+	name:   [0]u8,
+}
+
+Inotify_Event_Mask :: bit_set[Inotify_Event_Bits; u32]
+
 /*
 	Specifies protection for memory pages.
 */
@@ -667,6 +684,14 @@ Address_Family :: distinct Protocol_Family
 */
 Socket_Msg :: bit_set[Socket_Msg_Bits; i32]
 
+/*
+	Struct representing a generic socket address.
+*/
+Sock_Addr :: struct #packed {
+	sa_family: Address_Family,
+	sa_data:   [14]u8,
+}
+
 /*
 	Struct representing IPv4 socket address.
 */
@@ -674,6 +699,7 @@ Sock_Addr_In :: struct #packed {
 	sin_family: Address_Family,
 	sin_port:   u16be,
 	sin_addr:   [4]u8,
+	sin_zero:   [size_of(Sock_Addr) - size_of(Address_Family) - size_of(u16be) - size_of([4]u8)]u8,
 }
 
 /*
@@ -703,6 +729,7 @@ Sock_Addr_Any :: struct #raw_union {
 		family: Address_Family,
 		port:   u16be,
 	},
+	using generic: Sock_Addr,
 	using ipv4: Sock_Addr_In,
 	using ipv6: Sock_Addr_In6,
 	using uds: Sock_Addr_Un,

+ 1 - 0
core/sys/posix/arpa_inet.odin

@@ -1,3 +1,4 @@
+#+build darwin, linux, freebsd, openbsd, netbsd
 package posix
 
 import "core:c"

+ 41 - 26
core/sys/posix/dirent.odin

@@ -1,3 +1,4 @@
+#+build darwin, linux, freebsd, openbsd, netbsd
 package posix
 
 import "core:c"
@@ -29,12 +30,12 @@ foreign lib {
 			panic(string(posix.strerror(posix.errno())))
 		}
 		defer posix.free(list)
-	
+
 		entries := list[:ret]
-	 	for entry in entries {
-	 		log.info(entry)
-	 		posix.free(entry)
-	 	}
+		for entry in entries {
+			log.info(entry)
+			posix.free(entry)
+		}
 
 	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/scandir.html ]]
 	*/
@@ -53,15 +54,6 @@ foreign lib {
 	*/
 	closedir :: proc(dirp: DIR) -> result ---
 
-	/*
-	Return a file descriptor referring to the same directory as the dirp argument.
-
-	// TODO: this is a macro on NetBSD?
-
-	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dirfd.html ]]
-	*/
-	dirfd :: proc(dirp: DIR) -> FD ---
-
 	/*
 	Equivalent to the opendir() function except that the directory is specified by a file descriptor
 	rather than by a name.
@@ -89,16 +81,16 @@ foreign lib {
 	Returns nil when the end is reached or an error occurred (which sets errno).
 
 	Example:
-	 	posix.set_errno(.NONE)
-	 	entry := posix.readdir(dirp)
-	 	if entry == nil {
-	 		if errno := posix.errno(); errno != .NONE {
-	 			panic(string(posix.strerror(errno)))
-	 		} else {
-	 			fmt.println("end of directory stream")
-	 		}
-	 	} else {
-	 		fmt.println(entry)
+		posix.set_errno(.NONE)
+		entry := posix.readdir(dirp)
+		if entry == nil {
+			if errno := posix.errno(); errno != .NONE {
+				panic(string(posix.strerror(errno)))
+			} else {
+				fmt.println("end of directory stream")
+			}
+		} else {
+			fmt.println(entry)
 		}
 
 	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/readdir.html ]]
@@ -160,11 +152,36 @@ when ODIN_OS == .NetBSD {
 	@(private) LSCANDIR   :: "__scandir30"
 	@(private) LOPENDIR   :: "__opendir30"
 	@(private) LREADDIR   :: "__readdir30"
+
+	/*
+	Return a file descriptor referring to the same directory as the dirp argument.
+
+	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dirfd.html ]]
+	*/
+	dirfd :: proc "c" (dirp: DIR) -> FD {
+		_dirdesc :: struct {
+			dd_fd: FD,
+
+			// more stuff...
+		}
+
+		return (^_dirdesc)(dirp).dd_fd
+	}
+
 } else {
 	@(private) LALPHASORT :: "alphasort" + INODE_SUFFIX
 	@(private) LSCANDIR   :: "scandir"   + INODE_SUFFIX
 	@(private) LOPENDIR   :: "opendir"   + INODE_SUFFIX
 	@(private) LREADDIR   :: "readdir"   + INODE_SUFFIX
+
+	foreign lib {
+		/*
+		Return a file descriptor referring to the same directory as the dirp argument.
+
+		[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dirfd.html ]]
+		*/
+		dirfd :: proc(dirp: DIR) -> FD ---
+	}
 }
 
 when ODIN_OS == .Darwin {
@@ -210,6 +227,4 @@ when ODIN_OS == .Darwin {
 			d_name:   [256]c.char `fmt:"s,0"`, /* [PSX] entry name */
 		}
 
-} else {
-	#panic("posix is unimplemented for the current target")
 }

+ 1 - 2
core/sys/posix/dlfcn.odin

@@ -1,3 +1,4 @@
+#+build darwin, linux, freebsd, openbsd, netbsd
 package posix
 
 import "core:c"
@@ -120,7 +121,5 @@ when ODIN_OS == .Darwin {
 	_RTLD_LOCAL  :: 0
 	RTLD_LOCAL   :: RTLD_Flags{}
 
-} else {
-	#panic("posix is unimplemented for the current target")
 }
 

+ 80 - 2
core/sys/posix/errno.odin

@@ -1,3 +1,4 @@
+#+build windows, darwin, linux, freebsd, openbsd, netbsd
 package posix
 
 import "core:c"
@@ -456,7 +457,84 @@ when ODIN_OS == .Darwin {
 
 	EOWNERDEAD      :: 130
 	ENOTRECOVERABLE :: 131
-} else {
-	#panic("posix is unimplemented for the current target")
+} else when ODIN_OS == .Windows {
+	E2BIG           :: 7
+	EACCES          :: 13
+	EADDRINUSE      :: 100
+	EADDRNOTAVAIL   :: 101
+	EAFNOSUPPORT    :: 102
+	EAGAIN          :: 11
+	EALREADY        :: 103
+	EBADF           :: 9
+	EBADMSG         :: 104
+	EBUSY           :: 16
+	ECANCELED       :: 105
+	ECHILD          :: 10
+	ECONNABORTED    :: 106
+	ECONNREFUSED    :: 107
+	ECONNRESET      :: 108
+	EDEADLK         :: 36
+	EDESTADDRREQ    :: 109
+	EDQUOT          :: -1 // NOTE: not defined
+	EEXIST          :: 17
+	EFAULT          :: 14
+	EFBIG           :: 27
+	EHOSTUNREACH    :: 110
+	EIDRM           :: 111
+	EINPROGRESS     :: 112
+	EINTR           :: 4
+	EINVAL          :: 22
+	EIO             :: 5
+	EISCONN         :: 113
+	EISDIR          :: 21
+	ELOOP           :: 114
+	EMFILE          :: 24
+	EMLINK          :: 31
+	EMSGSIZE        :: 115
+	EMULTIHOP       :: -1 // NOTE: not defined
+	ENAMETOOLONG    :: 38
+	ENETDOWN        :: 116
+	ENETRESET       :: 117
+	ENETUNREACH     :: 118
+	ENFILE          :: 23
+	ENOBUFS         :: 119
+	ENODATA         :: 120
+	ENODEV          :: 19
+	ENOENT          :: 2
+	ENOEXEC         :: 8
+	ENOLCK          :: 39
+	ENOLINK         :: 121
+	ENOMEM          :: 12
+	ENOMSG          :: 122
+	ENOPROTOOPT     :: 123
+	ENOSPC          :: 28
+	ENOSR           :: 124
+	ENOSTR          :: 125
+	ENOSYS          :: 40
+	ENOTCONN        :: 126
+	ENOTDIR         :: 20
+	ENOTEMPTY       :: 41
+	ENOTRECOVERABLE :: 127
+	ENOTSOCK        :: 128
+	ENOTSUP         :: 129
+	ENOTTY          :: 25
+	ENXIO           :: 6
+	EOPNOTSUPP      :: 130
+	EOVERFLOW       :: 132
+	EOWNERDEAD      :: 133
+	EPERM           :: 1
+	EPIPE           :: 32
+	EPROTO          :: 134
+	EPROTONOSUPPORT :: 135
+	EPROTOTYPE      :: 136
+	EROFS           :: 30
+	ESPIPE          :: 29
+	ESRCH           :: 3
+	ESTALE          :: -1 // NOTE: not defined
+	ETIME           :: 137
+	ETIMEDOUT       :: 138
+	ETXTBSY         :: 139
+	EWOULDBLOCK     :: 140
+	EXDEV           :: 18
 }
 

+ 3 - 4
core/sys/posix/fcntl.odin

@@ -1,3 +1,4 @@
+#+build linux, darwin, openbsd, freebsd, netbsd
 package posix
 
 import "core:c"
@@ -466,13 +467,11 @@ when ODIN_OS == .Darwin {
 	AT_REMOVEDIR        :: 0x200
 
 	flock :: struct {
+		l_type:   Lock_Type, /* [PSX] type of lock. */
+		l_whence: c.short,   /* [PSX] flag (Whence) of starting offset. */
 		l_start:  off_t,     /* [PSX] relative offset in bytes. */
 		l_len:    off_t,     /* [PSX] size; if 0 then until EOF. */
 		l_pid:    pid_t,     /* [PSX] process ID of the process holding the lock. */
-		l_type:   Lock_Type, /* [PSX] type of lock. */
-		l_whence: c.short,   /* [PSX] flag (Whence) of starting offset. */
 	}
 
-} else {
-	#panic("posix is unimplemented for the current target")
 }

+ 1 - 2
core/sys/posix/fnmatch.odin

@@ -1,3 +1,4 @@
+#+build darwin, linux, openbsd, freebsd, netbsd
 package posix
 
 import "core:c"
@@ -61,6 +62,4 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS
 	FNM_NOESCAPE :: 0x02
 	FNM_PERIOD   :: 0x04
 
-} else {
-	#panic("posix is unimplemented for the current target")
 }

+ 1 - 2
core/sys/posix/glob.odin

@@ -1,3 +1,4 @@
+#+build linux, darwin, netbsd, openbsd, freebsd
 package posix
 
 import "core:c"
@@ -203,6 +204,4 @@ when ODIN_OS == .Darwin {
 	GLOB_ABORTED :: 2
 	GLOB_NOMATCH :: 3
 
-} else {
-	#panic("posix is unimplemented for the current target")
 }

+ 1 - 2
core/sys/posix/grp.odin

@@ -1,3 +1,4 @@
+#+build linux, darwin, netbsd, openbsd, freebsd
 package posix
 
 import "core:c"
@@ -125,6 +126,4 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS
 		gr_mem:    [^]cstring, /* [PSX] group members */
 	}
 
-} else {
-	#panic("posix is unimplemented for the current target")
 }

+ 1 - 0
core/sys/posix/iconv.odin

@@ -1,3 +1,4 @@
+#+build linux, darwin, netbsd, openbsd, freebsd
 package posix
 
 import "core:c"

+ 1 - 0
core/sys/posix/langinfo.odin

@@ -1,3 +1,4 @@
+#+build linux, darwin, netbsd, openbsd, freebsd
 package posix
 
 import "core:c"

+ 8 - 0
core/sys/posix/libgen.odin

@@ -1,3 +1,4 @@
+#+build linux, darwin, netbsd, openbsd, freebsd
 package posix
 
 when ODIN_OS == .Darwin {
@@ -56,6 +57,7 @@ foreign lib {
 
 	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/basename.html ]]
 	*/
+	@(link_name=LBASENAME)
 	basename :: proc(path: cstring) -> cstring ---
 
 	/*
@@ -72,3 +74,9 @@ foreign lib {
 	*/
 	dirname :: proc(path: cstring) -> cstring ---
 }
+
+when ODIN_OS == .Linux {
+	@(private) LBASENAME :: "__xpg_basename"
+} else {
+	@(private) LBASENAME :: "basename"
+}

+ 1 - 2
core/sys/posix/limits.odin

@@ -1,3 +1,4 @@
+#+build linux, darwin, netbsd, openbsd, freebsd
 package posix
 
 // limits.h - implementation-defined constants
@@ -549,6 +550,4 @@ when ODIN_OS == .Darwin {
 	NL_TEXTMAX :: 2048 // 255 on glibc, 2048 on musl
 	NZERO      :: 20
 
-} else {
-	#panic("posix is unimplemented for the current target")
 }

部分文件因为文件数量过多而无法显示