Browse Source

Merge branch 'master' into extend_win32_api_types

VladPavliuk 8 months ago
parent
commit
470c05a902
100 changed files with 1905 additions and 4609 deletions
  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
      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
     name: NetBSD Build, Check, and Test
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
     env:
     env:
-      PKGSRC_BRANCH: 2024Q2
+      PKGSRC_BRANCH: 2024Q3
     steps:
     steps:
     - uses: actions/checkout@v4
     - uses: actions/checkout@v4
     - name: Build, Check, and Test
     - name: Build, Check, and Test

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

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

+ 0 - 12
base/runtime/core.odin

@@ -171,14 +171,6 @@ Type_Info_Simd_Vector :: struct {
 	elem_size:  int,
 	elem_size:  int,
 	count:      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 {
 Type_Info_Matrix :: struct {
 	elem:         ^Type_Info,
 	elem:         ^Type_Info,
 	elem_size:    int,
 	elem_size:    int,
@@ -241,8 +233,6 @@ Type_Info :: struct {
 		Type_Info_Map,
 		Type_Info_Map,
 		Type_Info_Bit_Set,
 		Type_Info_Bit_Set,
 		Type_Info_Simd_Vector,
 		Type_Info_Simd_Vector,
-		Type_Info_Relative_Pointer,
-		Type_Info_Relative_Multi_Pointer,
 		Type_Info_Matrix,
 		Type_Info_Matrix,
 		Type_Info_Soa_Pointer,
 		Type_Info_Soa_Pointer,
 		Type_Info_Bit_Field,
 		Type_Info_Bit_Field,
@@ -275,8 +265,6 @@ Typeid_Kind :: enum u8 {
 	Map,
 	Map,
 	Bit_Set,
 	Bit_Set,
 	Simd_Vector,
 	Simd_Vector,
-	Relative_Pointer,
-	Relative_Multi_Pointer,
 	Matrix,
 	Matrix,
 	Soa_Pointer,
 	Soa_Pointer,
 	Bit_Field,
 	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`.
 // Note: Prefer using the procedure group `make`.
 @(builtin, require_results)
 @(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
 	m.allocator = allocator
 	return m
 	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`.
 // Note: Prefer using the procedure group `make`.
 @(builtin, require_results)
 @(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)
 	make_map_expr_error_loc(loc, capacity)
 	context.allocator = allocator
 	context.allocator = allocator
 
 
@@ -939,19 +939,7 @@ map_upsert :: proc(m: ^$T/map[$K]$V, key: K, value: V, loc := #caller_location)
 
 
 @builtin
 @builtin
 card :: proc "contextless" (s: $S/bit_set[$E; $U]) -> int {
 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)
 @(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 {
 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
 	context.allocator = allocator
+	array.allocator = allocator
 	reserve_soa(&array, 0, loc) or_return
 	reserve_soa(&array, 0, loc) or_return
 	return array, nil
 	return array, nil
 }
 }
@@ -149,6 +150,7 @@ make_soa_dynamic_array :: proc($T: typeid/#soa[dynamic]$E, allocator := context.
 @(builtin, require_results)
 @(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 {
 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
 	context.allocator = allocator
+	array.allocator = allocator
 	resize_soa(&array, length, loc) or_return
 	resize_soa(&array, length, loc) or_return
 	return array, nil
 	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_u64(u64(info.count))
 		print_byte(']')
 		print_byte(']')
 		print_type(info.elem)
 		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:
 	case Type_Info_Matrix:
 		print_string("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
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_DATE_TIME=%%i
 )
 )
 set curr_year=%CURR_DATE_TIME:~0,4%
 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_full="%V1%.%V2%.%V3%.%V4%"
 set odin_version_raw="dev-%V1%-%V2%"
 set odin_version_raw="dev-%V1%-%V2%"
 
 
-
 set compiler_flags= -nologo -Oi -TP -fp:precise -Gm- -MP -FC -EHsc- -GR- -GF
 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 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
 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.
 # 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 [ -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@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"
     elif [ -n "$(command -v $(brew --prefix llvm@14)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@14)/bin/llvm-config"
     fi
     fi
@@ -33,13 +34,15 @@ fi
 
 
 if [ -z "$LLVM_CONFIG" ]; then
 if [ -z "$LLVM_CONFIG" ]; then
 	# darwin, linux, openbsd
 	# 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-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-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-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-12)" ]; then LLVM_CONFIG="llvm-config-12"
 	elif [ -n "$(command -v llvm-config-11)" ]; then LLVM_CONFIG="llvm-config-11"
 	elif [ -n "$(command -v llvm-config-11)" ]; then LLVM_CONFIG="llvm-config-11"
 	# freebsd
 	# 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-config18)" ]; then  LLVM_CONFIG="llvm-config18"
 	elif [ -n "$(command -v llvm-config17)" ]; then  LLVM_CONFIG="llvm-config17"
 	elif [ -n "$(command -v llvm-config17)" ]; then  LLVM_CONFIG="llvm-config17"
 	elif [ -n "$(command -v llvm-config14)" ]; then  LLVM_CONFIG="llvm-config14"
 	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_MINOR="$(echo $LLVM_VERSION | awk -F. '{print $2}')"
 LLVM_VERSION_PATCH="$(echo $LLVM_VERSION | awk -F. '{print $3}')"
 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
 fi
 
 
 case "$OS_NAME" in
 case "$OS_NAME" in
 Darwin)
 Darwin)
 	if [ "$OS_ARCH" = "arm64" ]; then
 	if [ "$OS_ARCH" = "arm64" ]; then
 		if [ $LLVM_VERSION_MAJOR -lt 13 ]; 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
 	fi
 	fi
 
 
@@ -152,9 +155,7 @@ build_odin() {
 }
 }
 
 
 run_demo() {
 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
 if [ $# -eq 0 ]; then
@@ -166,14 +167,20 @@ if [ $# -eq 0 ]; then
 elif [ $# -eq 1 ]; then
 elif [ $# -eq 1 ]; then
 	case $1 in
 	case $1 in
 	report)
 	report)
-		[ ! -f "./odin" ] && build_odin debug
+		if [ ! -f "./odin" ]; then
+			build_odin debug
+			run_demo
+		fi
 		./odin report
 		./odin report
 		;;
 		;;
+	debug)
+		build_odin debug
+		run_demo
+		;;
 	*)
 	*)
 		build_odin $1
 		build_odin $1
 		;;
 		;;
 	esac
 	esac
-	run_demo
 else
 else
 	error "Too many arguments!"
 	error "Too many arguments!"
 fi
 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                                    |
 | `<inttypes.h>`    | Fully projected                                    |
 | `<iso646.h>`      | Not applicable, use Odin's operators               |
 | `<iso646.h>`      | Not applicable, use Odin's operators               |
 | `<limits.h>`      | Not projected                                      |
 | `<limits.h>`      | Not projected                                      |
-| `<locale.h>`      | Not projected                                      |
+| `<locale.h>`      | Fully projected                                    |
 | `<math.h>`        | Mostly projected, see [limitations](#Limitations)  |
 | `<math.h>`        | Mostly projected, see [limitations](#Limitations)  |
 | `<setjmp.h>`      | Fully projected                                    |
 | `<setjmp.h>`      | Fully projected                                    |
 | `<signal.h>`      | Fully projected                                    |
 | `<signal.h>`      | Fully projected                                    |
@@ -70,4 +70,4 @@ with the following copyright.
 
 
 ```
 ```
 Copyright 2021 Dale Weiler <[email protected]>.
 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
 	package main
 
 
 	import "core:bytes"
 	import "core:bytes"
+	import "core:compress/zlib"
 	import "core:fmt"
 	import "core:fmt"
 
 
 	main :: proc() {
 	main :: proc() {
@@ -36,7 +37,7 @@ Example:
 		buf: bytes.Buffer
 		buf: bytes.Buffer
 
 
 		// We can pass ", true" to inflate a raw DEFLATE stream instead of a ZLIB wrapped one.
 		// 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)
 		defer bytes.buffer_destroy(&buf)
 
 
 		if err != nil {
 		if err != nil {

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

@@ -21,12 +21,14 @@ Symbols :: struct {
 main :: proc() {
 main :: proc() {
 	sym: Symbols
 	sym: Symbols
 
 
+	LIB_PATH :: "lib." + dynlib.LIBRARY_FILE_EXTENSION
+
 	// Load symbols from `lib.dll` into Symbols struct.
 	// Load symbols from `lib.dll` into Symbols struct.
 	// Each struct field is prefixed with `foo_` before lookup in the DLL's symbol table.
 	// 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.
 	// 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)
 	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 {
 	if count > 0 {
 		fmt.println("42 + 42 =", sym.add(42, 42))
 		fmt.println("42 + 42 =", sym.add(42, 42))
@@ -34,12 +36,12 @@ main :: proc() {
 		fmt.println("hellope =", sym.hellope^)
 		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 {
 	if count > 0 {
 		fmt.println("42 + 42 =", sym.add(42, 42))
 		fmt.println("42 + 42 =", sym.add(42, 42))
 		fmt.println("84 - 13 =", sym.sub(84, 13))
 		fmt.println("84 - 13 =", sym.sub(84, 13))
 		fmt.println("hellope =", sym.hellope^)
 		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
 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
 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.
 library available to resolve references in subsequently loaded libraries.
@@ -37,8 +42,8 @@ Example:
 		fmt.println("The library %q was successfully loaded", LIBRARY_PATH)
 		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) {
 ) -> (count: int = -1, ok: bool = false) where intrinsics.type_is_struct(T) {
 	assert(symbol_table != nil)
 	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) {
 	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 {
 		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.
 			// We appear to be hot reloading. Unload previous incarnation of the library.
 			if old_handle := (^Library)(field_ptr)^; old_handle != nil {
 			if old_handle := (^Library)(field_ptr)^; old_handle != nil {
 				unload_library(old_handle) or_return
 				unload_library(old_handle) or_return
 			}
 			}
+
+			handle = load_library(library_path) or_return
 			(^Library)(field_ptr)^ = handle
 			(^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
 			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
 		// Let's look up or construct the symbol name to find in the library
 		prefixed_name: string
 		prefixed_name: string
 
 
@@ -174,4 +184,4 @@ initialize_symbols :: proc(
 // Returns an error message for the last failed procedure call.
 // Returns an error message for the last failed procedure call.
 last_error :: proc() -> string {
 last_error :: proc() -> string {
 	return _last_error()
 	return _last_error()
-}
+}

+ 7 - 3
core/dynlib/lib_js.odin

@@ -2,7 +2,11 @@
 #+private
 #+private
 package dynlib
 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
 	return nil, false
 }
 }
 
 
@@ -10,10 +14,10 @@ _unload_library :: proc(library: Library) -> bool {
 	return false
 	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
 	return nil, false
 }
 }
 
 
 _last_error :: proc() -> string {
 _last_error :: proc() -> string {
 	return ""
 	return ""
-}
+}

+ 22 - 10
core/dynlib/lib_unix.odin

@@ -2,28 +2,40 @@
 #+private
 #+private
 package dynlib
 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 {
 	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
 	return Library(lib), lib != nil
 }
 }
 
 
 _unload_library :: proc(library: Library) -> bool {
 _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
 	found = ptr != nil
 	return
 	return
 }
 }
 
 
 _last_error :: proc() -> string {
 _last_error :: proc() -> string {
-	err := os.dlerror()
+	err := string(posix.dlerror())
 	return "unknown" if err == "" else err
 	return "unknown" if err == "" else err
-}
+}

+ 7 - 3
core/dynlib/lib_windows.odin

@@ -2,11 +2,15 @@
 #+private
 #+private
 package dynlib
 package dynlib
 
 
+import "base:runtime"
+
 import win32 "core:sys/windows"
 import win32 "core:sys/windows"
 import "core:strings"
 import "core:strings"
 import "core:reflect"
 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
 	// NOTE(bill): 'global_symbols' is here only for consistency with POSIX which has RTLD_GLOBAL
 	wide_path := win32.utf8_to_wstring(path, allocator)
 	wide_path := win32.utf8_to_wstring(path, allocator)
 	defer free(wide_path, allocator)
 	defer free(wide_path, allocator)
@@ -19,7 +23,7 @@ _unload_library :: proc(library: Library) -> bool {
 	return bool(ok)
 	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)
 	c_str := strings.clone_to_cstring(symbol, allocator)
 	defer delete(c_str, allocator)
 	defer delete(c_str, allocator)
 	ptr = win32.GetProcAddress(cast(win32.HMODULE)library, c_str)
 	ptr = win32.GetProcAddress(cast(win32.HMODULE)library, c_str)
@@ -31,4 +35,4 @@ _last_error :: proc() -> string {
 	err := win32.System_Error(win32.GetLastError())
 	err := win32.System_Error(win32.GetLastError())
 	err_msg := reflect.enum_string(err)
 	err_msg := reflect.enum_string(err)
 	return "unknown" if err_msg == "" else err_msg
 	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
 					case: return false
 					}
 					}
 				}
 				}
-				return false
+				return true
 			}
 			}
 
 
 			if keys_all_strings(v) {
 			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,
 		loc := #caller_location,
 	) -> (out_of_space: bool, err: Unmarshal_Error) {
 	) -> (out_of_space: bool, err: Unmarshal_Error) {
 		for idx: uintptr = 0; length == -1 || idx < uintptr(length); idx += 1 {
 		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
 			hdr := _decode_header(d.reader) or_return
 			
 			
 			// Double size if out of capacity.
 			// 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 }
 				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)
 			err = _unmarshal_value(d, elem, hdr, allocator=allocator, loc=loc)
 			if length == -1 && err == .Break { break }
 			if length == -1 && err == .Break { break }
 			if err != nil { return }
 			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           := (^mem.Raw_Dynamic_Array)(v.data)
 		raw.data       = raw_data(data) 
 		raw.data       = raw_data(data) 
 		raw.len        = 0
 		raw.len        = 0
-		raw.cap        = length
+		raw.cap        = scap
 		raw.allocator  = context.allocator
 		raw.allocator  = context.allocator
 
 
 		_ = assign_array(d, raw, t.elem, length) or_return
 		_ = 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
 		unknown := length == -1
 		fields := reflect.struct_fields_zipped(ti.id)
 		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.
 			// Decode key, keys can only be strings.
 			key: string
 			key: string
 			if keyv, kerr := decode_key(d, v, context.temp_allocator); unknown && kerr == .Break {
 			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.
 				// Skips unused map entries.
 				if use_field_idx < 0 {
 				if use_field_idx < 0 {
+					val := err_conv(_decode_from_decoder(d, allocator=context.temp_allocator)) or_return
+					destroy(val, context.temp_allocator)
 					continue
 					continue
 				}
 				}
 			}
 			}
@@ -672,6 +676,17 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header,
 			fany  := any{ptr, field.type.id}
 			fany  := any{ptr, field.type.id}
 			_unmarshal_value(d, fany, _decode_header(r) or_return) or_return
 			_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
 		return
 
 
 	case reflect.Type_Info_Map:
 	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.name = read_name(r) or_return
 			layer.components = read_value(r, u8) or_return
 			layer.components = read_value(r, u8) or_return
 			type := read_value(r, Layer_Data_Type) 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 {
 				if r.print_error {
 					fmt.eprintf("HxA Error: file '%s' has layer data type %d. Maximum value is %d\n",
 					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)))
 								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:
 	case runtime.Type_Info_Simd_Vector:
 		return .Unsupported_Type
 		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:
 	case runtime.Type_Info_Matrix:
 		return .Unsupported_Type
 		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)
 			skip_digits(t)
 		}
 		}
 		if t.r == 'e' || t.r == 'E' {
 		if t.r == 'e' || t.r == 'E' {
+			token.kind = .Float
 			switch r := next_rune(t); r {
 			switch r := next_rune(t); r {
 			case '+', '-':
 			case '+', '-':
 				next_rune(t)
 				next_rune(t)
@@ -485,7 +486,7 @@ is_valid_string_literal :: proc(str: string, spec: Specification) -> bool {
 	case '"':
 	case '"':
 		// okay
 		// okay
 	case '\'':
 	case '\'':
-		if spec != .JSON {
+		if spec == .JSON {
 			return false
 			return false
 		}
 		}
 		// okay
 		// okay

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

@@ -172,20 +172,33 @@ assign_float :: proc(val: any, f: $T) -> bool {
 
 
 
 
 @(private)
 @(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
 	val := val
 	switch &dst in val {
 	switch &dst in val {
 	case string:
 	case string:
 		dst = str
 		dst = str
-		return true
+		return true, nil
 	case cstring:  
 	case cstring:  
 		if str == "" {
 		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 {
 		} else {
 			// NOTE: This is valid because 'clone_string' appends a NUL terminator
 			// NOTE: This is valid because 'clone_string' appends a NUL terminator
 			dst = cstring(raw_data(str)) 
 			dst = cstring(raw_data(str)) 
 		}
 		}
-		return true
+		ok = true
+		return
 	}
 	}
 	
 	
 	#partial switch variant in ti.variant {
 	#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 {
 		for name, i in variant.names {
 			if name == str {
 			if name == str {
 				assign_int(val, variant.values[i])
 				assign_int(val, variant.values[i])
-				return true
+				return true, nil
 			}
 			}
 		}
 		}
 		// TODO(bill): should this be an error or not?
 		// TODO(bill): should this be an error or not?
-		return true
+		return true, nil
 		
 		
 	case reflect.Type_Info_Integer:
 	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) {
 		if assign_int(val, i) {
-			return true
+			return true, nil
 		}
 		}
 		if assign_float(val, i) {
 		if assign_float(val, i) {
-			return true
+			return true, nil
 		}
 		}
 	case reflect.Type_Info_Float:
 	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) {
 		if assign_int(val, f) {
-			return true
+			return true, nil
 		}
 		}
 		if assign_float(val, f) {
 		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:
 	case .Ident:
 		advance_token(p)
 		advance_token(p)
 		if p.spec == .MJSON {
 		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
 				return nil
 			}
 			}
 		}
 		}
@@ -312,13 +331,18 @@ unmarshal_value :: proc(p: ^Parser, v: any) -> (err: Unmarshal_Error) {
 		
 		
 	case .String:
 	case .String:
 		advance_token(p)
 		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:
 	case .Open_Brace:
 		return unmarshal_object(p, v, .Close_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 {
 		if .raw_union in t.flags {
 			return UNSUPPORTED_TYPE
 			return UNSUPPORTED_TYPE
 		}
 		}
-	
+
+		fields := reflect.struct_fields_zipped(ti.id)
+		
 		struct_loop: for p.curr_token.kind != end_token {
 		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)
 			defer delete(key, p.allocator)
 			
 			
 			unmarshal_expect_token(p, .Colon)						
 			unmarshal_expect_token(p, .Colon)						
 			
 			
-			fields := reflect.struct_fields_zipped(ti.id)
-
 			field_test :: #force_inline proc "contextless" (field_used: [^]byte, offset: uintptr) -> bool {
 			field_test :: #force_inline proc "contextless" (field_used: [^]byte, offset: uintptr) -> bool {
 				prev_set := field_used[offset/8] & byte(offset&7) != 0
 				prev_set := field_used[offset/8] & byte(offset&7) != 0
 				field_used[offset/8] |= byte(offset&7)
 				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_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)
 			intrinsics.mem_zero(field_used, field_used_bytes)
 
 
 			use_field_idx := -1
 			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 '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 '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 '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 'X': _fmt_int(fi, u, 16, false, 8*size_of(rawptr), __DIGITS_UPPER)
 
 
 	case:
 	case:
@@ -3002,18 +3002,6 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
 	case runtime.Type_Info_Bit_Set:
 	case runtime.Type_Info_Bit_Set:
 		fmt_bit_set(fi, v, verb = verb)
 		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:
 	case runtime.Type_Info_Matrix:
 		fmt_matrix(fi, v, verb, info)
 		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) {
 load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
 	loader := _internal_loaders[which(data)]
 	loader := _internal_loaders[which(data)]
 	if loader == nil {
 	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)
 	return loader(data, options, allocator)
 }
 }
@@ -185,7 +193,7 @@ which_bytes :: proc(data: []byte) -> Which_File_Type {
 		return .HDR
 		return .HDR
 	case s[:4] == "\x38\x42\x50\x53":
 	case s[:4] == "\x38\x42\x50\x53":
 		return .PSD
 		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
 		return .PIC
 	case s[:4] == "\x69\x63\x6e\x73":
 	case s[:4] == "\x69\x63\x6e\x73":
 		return .ICNS
 		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
 	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
 			buf: [2]byte
 			s := strconv.append_bits(buf[:], u64(r), 16, true, 64, strconv.digits, nil)
 			s := strconv.append_bits(buf[:], u64(r), 16, true, 64, strconv.digits, nil)
 			switch len(s) {
 			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 {
 		} else {
 			write_rune(w, r, &n) or_return
 			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 {
 			} else {
 				write_byte(w, '\\', &n) or_return
 				write_byte(w, '\\', &n) or_return
 				write_byte(w, 'U', &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
 					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,
 	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.file_handle = h
 	data.ident = ident
 	data.ident = ident
 	return Logger{file_console_logger_proc, data, lowest, opt}
 	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
 	data := cast(^File_Console_Logger_Data)log.data
 	if data.file_handle != os.INVALID_HANDLE {
 	if data.file_handle != os.INVALID_HANDLE {
 		os.close(data.file_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.file_handle = os.INVALID_HANDLE
 	data.ident = ident
 	data.ident = ident
 	return Logger{file_console_logger_proc, data, lowest, opt}
 	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) {
 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,
 	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)
 	copy(data.loggers, logs)
 	return Logger{multi_logger_proc, data, Level.Debug, nil}
 	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)
 	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,
 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)
 @(require_results)
 quaternion64_dot :: proc "contextless" (a, b: $T/quaternion64) -> (c: f16) {
 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)
 @(require_results)
 quaternion128_dot :: proc "contextless" (a, b: $T/quaternion128) -> (c: f32) {
 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)
 @(require_results)
 quaternion256_dot :: proc "contextless" (a, b: $T/quaternion256) -> (c: f64) {
 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}
 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}
 length :: proc{vector_length, quaternion_length}
 length2 :: proc{vector_length2, quaternion_length2}
 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)
 @(require_results)
 projection :: proc "contextless" (x, normal: $T/[$N]$E) -> T where IS_NUMERIC(E) {
 projection :: proc "contextless" (x, normal: $T/[$N]$E) -> T where IS_NUMERIC(E) {
 	return dot(x, normal) / dot(normal, normal) * normal
 	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_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)} }
 @(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{
 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) 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) 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) 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) 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) 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) }
 @(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) 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) 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) 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) 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) 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) }
 @(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
 	b := n
-	for i in 2..<k {
+	for i in 2..=k {
 		b = (b * (n+1-i))/i
 		b = (b * (n+1-i))/i
 	}
 	}
 	return b
 	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
 // sin coefficients
 @(private="file")
 @(private="file")
 _sin := [?]f64{
 _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
 // cos coefficients

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

@@ -29,30 +29,6 @@ Reset the seed used by the context.random_generator.
 Inputs:
 Inputs:
 - seed: The seed value
 - 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:
 Example:
 	import "core:math/rand"
 	import "core:math/rand"
 	import "core:fmt"
 	import "core:fmt"
@@ -670,20 +646,69 @@ choice :: proc(array: $T/[]$E, gen := context.random_generator) -> (res: E) {
 
 
 
 
 @(require_results)
 @(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 {
 	} 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
 	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.
 Allocate memory from an arena.
 
 
@@ -786,14 +778,6 @@ stack_init :: proc(s: ^Stack, data: []byte) {
 	s.peak_used   = 0
 	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.
 Allocate memory from stack.
 
 
@@ -1162,13 +1146,6 @@ small_stack_init :: proc(s: ^Small_Stack, data: []byte) {
 	s.peak_used = 0
 	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.
 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
 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.
 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 {
 is_aligned :: proc "contextless" (x: rawptr, align: int) -> bool {
 	p := uintptr(x)
 	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)
 	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:
 // Define non-posix needed flags:
 when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD {
 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 {
 } 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) {
 _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)
 	result := posix.mmap(nil, size, {}, flags)
 	if result == posix.MAP_FAILED {
 	if result == posix.MAP_FAILED {
 		return nil, .Out_Of_Memory
 		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,
 	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 {
 File_Tags :: struct {
 	build_project_name: [][]string,
 	build_project_name: [][]string,
 	build:              []Build_Kind,
 	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:])
 					append(build_project_names, build_project_name_strings[index_start:])
 				}
 				}
 			case "build":
 			case "build":
+
+				if len(build_kinds) > 0 {
+					append(build_kinds, BUILD_KIND_NEWLINE_MARKER)
+				}
+
 				kinds_loop: for {
 				kinds_loop: for {
 					os_positive: runtime.Odin_OS_Types
 					os_positive: runtime.Odin_OS_Types
 					os_negative: 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
 		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
 	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 {
 	if p.expr_level >= 0 {
 		end: ^ast.Expr
 		end: ^ast.Expr
 		if !is_mutable && len(values) > 0 {
 		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.docs = docs
 	decl.names = names
 	decl.names = names
 	decl.type = type
 	decl.type = type

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

@@ -331,7 +331,7 @@ scan_escape :: proc(t: ^Tokenizer) -> bool {
 		n -= 1
 		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")
 		error(t, offset, "escape sequence is an invalid Unicode code point")
 		return false
 		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)
 @(require_results)
 _read_directory_iterator_create :: proc(f: ^File) -> (Read_Directory_Iterator, Error) {
 _read_directory_iterator_create :: proc(f: ^File) -> (Read_Directory_Iterator, Error) {
-	return {}, nil
+	return {}, .Unsupported
 }
 }
 
 
 _read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) {
 _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
 	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.fullpath = path
 	fi.name = basename(path)
 	fi.name = basename(path)
 	fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
 	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.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.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))
 	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)) {
 	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) == size_of(file_id_info.FileId))
 		#assert(size_of(fi.inode) == 16)
 		#assert(size_of(fi.inode) == 16)
 		runtime.mem_copy_non_overlapping(&fi.inode, &file_id_info.FileId, 16)
 		runtime.mem_copy_non_overlapping(&fi.inode, &file_id_info.FileId, 16)
 	}
 	}
 
 
-
 	return
 	return
 }
 }
 
 
@@ -137,5 +134,6 @@ _read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) {
 		return
 		return
 	}
 	}
 	file_info_delete(it.impl.prev_fi, file_allocator())
 	file_info_delete(it.impl.prev_fi, file_allocator())
+	delete(it.impl.path, file_allocator())
 	win32.FindClose(it.impl.find_handle)
 	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 {
 _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()
 	TEMP_ALLOCATOR_GUARD()
 	name_cstr := temp_cstring(name) or_return
 	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))
 	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)
 fini_std_files :: proc() {
 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
 	back_idx := -1
 	idx: u16
 	idx: u16
 	infinite: for {
 	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]
 			idx = region_iter.hdr.free_list[i]
 			if _get_block_count(region_iter.memory[idx]) >= new_block_count {
 			if _get_block_count(region_iter.memory[idx]) >= new_block_count {
 				break infinite
 				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)
 	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.
 	The description of how a process should be created.
 */
 */
 Process_Desc :: struct {
 Process_Desc :: struct {
 	// OS-specific attributes.
 	// 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
 	// 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
 	// working directory is assumed to be the current working directory of the
 	// current process.
 	// current process.

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

@@ -384,14 +384,6 @@ _Sys_Process_Attributes :: struct {}
 
 
 @(private="package")
 @(private="package")
 _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
 _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()
 	TEMP_ALLOCATOR_GUARD()
 
 
 	if len(desc.command) == 0 {
 	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
 	// search PATH if just a plain name is provided
-	exe_fd: linux.Fd
+	exe_path: cstring
 	executable_name := desc.command[0]
 	executable_name := desc.command[0]
 	if strings.index_byte(executable_name, '/') < 0 {
 	if strings.index_byte(executable_name, '/') < 0 {
 		path_env := get_env("PATH", temp_allocator())
 		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_byte(&exe_builder, '/')
 			strings.write_string(&exe_builder, executable_name)
 			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 {
 		if !found {
 			// check in cwd to match windows behavior
 			// 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, "./")
 			strings.write_string(&exe_builder, executable_name)
 			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
 				return process, .Not_Exist
 			}
 			}
-			if !has_executable_permissions(exe_fd) {
-				linux.close(exe_fd)
-				return process, .Permission_Denied
-			}
 		}
 		}
 	} else {
 	} 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
 	// args and environment need to be a list of cstrings
 	// that are terminated by a nil pointer.
 	// that are terminated by a nil pointer.
 	cargs := make([]cstring, len(desc.command) + 1, temp_allocator()) or_return
 	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])
 	defer linux.close(child_pipe_fds[READ])
 
 
-
 	// TODO: This is the traditional textbook implementation with fork.
 	// TODO: This is the traditional textbook implementation with fork.
 	//       A more efficient implementation with vfork:
 	//       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)
 			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)
 		assert(errno != nil)
 		write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno)
 		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)
 		stderr_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd)
 	}
 	}
 	if desc.stdin != nil {
 	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
 	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)
 	ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa)
 	if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
 	if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
 		// Not a symlink
 		// 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()
 	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)
 		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)
 	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
 	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) {
 _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 {
 	if file_attributes & win32.FILE_ATTRIBUTE_READONLY != 0 {
 		mode |= 0o444
 		mode |= 0o444
 	} else {
 	} else {
 		mode |= 0o666
 		mode |= 0o666
 	}
 	}
+
 	is_sym := false
 	is_sym := false
 	if file_attributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
 	if file_attributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
 		is_sym = false
 		is_sym = false
 	} else {
 	} else {
 		is_sym = ReparseTag == win32.IO_REPARSE_TAG_SYMLINK || ReparseTag == win32.IO_REPARSE_TAG_MOUNT_POINT
 		is_sym = ReparseTag == win32.IO_REPARSE_TAG_SYMLINK || ReparseTag == win32.IO_REPARSE_TAG_MOUNT_POINT
 	}
 	}
+
 	if is_sym {
 	if is_sym {
 		type = .Symlink
 		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
 	return
 }
 }
@@ -267,7 +283,7 @@ _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HA
 	fi.name = basename(path)
 	fi.name = basename(path)
 	fi.inode = u128(u64(d.nFileIndexHigh)<<32 + u64(d.nFileIndexLow))
 	fi.inode = u128(u64(d.nFileIndexHigh)<<32 + u64(d.nFileIndexLow))
 	fi.size  = i64(d.nFileSizeHigh)<<32  + i64(d.nFileSizeLow)
 	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.type = type
 	fi.mode |= mode
 	fi.mode |= mode
 	fi.creation_time     = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime))
 	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)
 @(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)
 	isDir := is_dir_path(path)
 	flags := flags
 	flags := flags
 	if isDir {
 	if isDir {
@@ -1026,7 +1026,7 @@ absolute_path_from_handle :: proc(fd: Handle) -> (path: string, err: Error) {
 }
 }
 
 
 @(require_results)
 @(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
 	rel := rel
 	if rel == "" {
 	if rel == "" {
 		rel = "."
 		rel = "."
@@ -1041,9 +1041,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) {
 	}
 	}
 	defer _unix_free(rawptr(path_ptr))
 	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 {
 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)
 @(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
 	rel := rel
 	if rel == "" {
 	if rel == "" {
 		rel = "."
 		rel = "."
@@ -804,10 +804,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) {
 	}
 	}
 	defer _unix_free(rawptr(path_ptr))
 	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) {
 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)
 @(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
 	rel := rel
 	if rel == "" {
 	if rel == "" {
 		rel = "."
 		rel = "."
@@ -447,9 +447,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) {
 	defer _unix_free(path_ptr)
 	defer _unix_free(path_ptr)
 
 
 	path_cstr := cstring(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) {
 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="free")             _unix_free          :: proc(ptr: rawptr) ---
 	@(link_name="realloc")          _unix_realloc       :: proc(ptr: rawptr, size: c.size_t) -> 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="getenv")           _unix_getenv        :: proc(cstring) -> cstring ---
 	@(link_name="putenv")           _unix_putenv        :: proc(cstring) -> c.int ---
 	@(link_name="putenv")           _unix_putenv        :: proc(cstring) -> c.int ---
 	@(link_name="setenv")           _unix_setenv        :: proc(key: cstring, value: cstring, overwrite: c.int) -> 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)
 @(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
 	rel := rel
 	if rel == "" {
 	if rel == "" {
 		rel = "."
 		rel = "."
@@ -932,9 +932,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) {
 	}
 	}
 	defer _unix_free(rawptr(path_ptr))
 	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) {
 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)
 @(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
 	rel := rel
 	if rel == "" {
 	if rel == "" {
 		rel = "."
 		rel = "."
@@ -859,9 +859,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) {
 	}
 	}
 	defer _unix_free(rawptr(path_ptr))
 	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) {
 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)
 @(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
 	rel := rel
 	if rel == "" {
 	if rel == "" {
 		rel = "."
 		rel = "."
@@ -773,9 +773,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Error) {
 	}
 	}
 	defer _unix_free(rawptr(path_ptr))
 	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) {
 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 win32 "core:sys/windows"
 import "base:runtime"
 import "base:runtime"
 import "base:intrinsics"
 import "base:intrinsics"
+import "core:unicode/utf16"
 
 
 Handle    :: distinct uintptr
 Handle    :: distinct uintptr
 File_Time :: distinct u64
 File_Time :: distinct u64
 
 
-
 INVALID_HANDLE :: ~Handle(0)
 INVALID_HANDLE :: ~Handle(0)
 
 
-
-
 O_RDONLY   :: 0x00000
 O_RDONLY   :: 0x00000
 O_WRONLY   :: 0x00001
 O_WRONLY   :: 0x00001
 O_RDWR     :: 0x00002
 O_RDWR     :: 0x00002
@@ -278,3 +276,580 @@ is_windows_11 :: proc "contextless" () -> bool {
 	osvi := get_windows_version_w()
 	osvi := get_windows_version_w()
 	return (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber >= WINDOWS_11_BUILD_CUTOFF)
 	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 {
 	if err != .None {
 		return
 		return
 	}
 	}
+	defer {
+		for s in m {
+			delete(s)
+		}
+		delete(m)
+	}
+
 	dmatches := make([dynamic]string, 0, 0)
 	dmatches := make([dynamic]string, 0, 0)
 	for d in m {
 	for d in m {
 		dmatches, err = _glob(d, file, &dmatches)
 		dmatches, err = _glob(d, file, &dmatches)

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

@@ -1,14 +1,10 @@
 #+build linux, darwin, freebsd, openbsd, netbsd
 #+build linux, darwin, freebsd, openbsd, netbsd
 package filepath
 package filepath
 
 
-when ODIN_OS == .Darwin {
-	foreign import libc "system:System.framework"
-} else {
-	foreign import libc "system:c"
-}
-
 import "base:runtime"
 import "base:runtime"
+
 import "core:strings"
 import "core:strings"
+import "core:sys/posix"
 
 
 SEPARATOR :: '/'
 SEPARATOR :: '/'
 SEPARATOR_STRING :: `/`
 SEPARATOR_STRING :: `/`
@@ -28,11 +24,11 @@ abs :: proc(path: string, allocator := context.allocator) -> (string, bool) {
 		rel = "."
 		rel = "."
 	}
 	}
 	rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator)
 	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 {
 	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)
 	path_str := strings.clone(string(path_ptr), allocator)
 	return path_str, true
 	return path_str, true
@@ -48,26 +44,3 @@ join :: proc(elems: []string, allocator := context.allocator) -> (joined: string
 	}
 	}
 	return "", nil
 	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)
 		defer spall.context_destroy(&spall_ctx)
 
 
 		buffer_backing := make([]u8, spall.BUFFER_DEFAULT_SIZE)
 		buffer_backing := make([]u8, spall.BUFFER_DEFAULT_SIZE)
+		defer delete(buffer_backing)
+
 		spall_buffer = spall.buffer_create(buffer_backing, u32(sync.current_thread_id()))
 		spall_buffer = spall.buffer_create(buffer_backing, u32(sync.current_thread_id()))
 		defer spall.buffer_destroy(&spall_ctx, &spall_buffer)
 		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_Map                    :: runtime.Type_Info_Map
 Type_Info_Bit_Set                :: runtime.Type_Info_Bit_Set
 Type_Info_Bit_Set                :: runtime.Type_Info_Bit_Set
 Type_Info_Simd_Vector            :: runtime.Type_Info_Simd_Vector
 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_Matrix                 :: runtime.Type_Info_Matrix
 Type_Info_Soa_Pointer            :: runtime.Type_Info_Soa_Pointer
 Type_Info_Soa_Pointer            :: runtime.Type_Info_Soa_Pointer
 Type_Info_Bit_Field              :: runtime.Type_Info_Bit_Field
 Type_Info_Bit_Field              :: runtime.Type_Info_Bit_Field
@@ -67,8 +65,6 @@ Type_Kind :: enum {
 	Map,
 	Map,
 	Bit_Set,
 	Bit_Set,
 	Simd_Vector,
 	Simd_Vector,
-	Relative_Pointer,
-	Relative_Multi_Pointer,
 	Matrix,
 	Matrix,
 	Soa_Pointer,
 	Soa_Pointer,
 	Bit_Field,
 	Bit_Field,
@@ -80,35 +76,33 @@ type_kind :: proc(T: typeid) -> Type_Kind {
 	ti := type_info_of(T)
 	ti := type_info_of(T)
 	if ti != nil {
 	if ti != nil {
 		switch _ in ti.variant {
 		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
 	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
 	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)
 @(require_results)
 relative_pointer_to_absolute_raw :: proc(data: rawptr, base_integer_id: typeid) -> rawptr {
 relative_pointer_to_absolute_raw :: proc(data: rawptr, base_integer_id: typeid) -> rawptr {
 	_handle :: proc(ptr: ^$T) -> rawptr where intrinsics.type_is_integer(T) {
 	_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 cstring: value = rawptr(v)
 		case: valid = false
 		case: valid = false
 		}
 		}
-
-	case Type_Info_Relative_Pointer:
-		valid = true
-		value = relative_pointer_to_absolute_raw(a.data, info.base_integer.id)
 	}
 	}
 
 
 	return
 	return
@@ -1656,8 +1652,6 @@ equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_
 		Type_Info_Bit_Set,
 		Type_Info_Bit_Set,
 		Type_Info_Enum,
 		Type_Info_Enum,
 		Type_Info_Simd_Vector,
 		Type_Info_Simd_Vector,
-		Type_Info_Relative_Pointer,
-		Type_Info_Relative_Multi_Pointer,
 		Type_Info_Soa_Pointer,
 		Type_Info_Soa_Pointer,
 		Type_Info_Matrix:
 		Type_Info_Matrix:
 		return runtime.memory_compare(a.data, b.data, t.size) == 0
 		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:
 	case Type_Info_Simd_Vector:
 		y := b.variant.(Type_Info_Simd_Vector) or_return
 		y := b.variant.(Type_Info_Simd_Vector) or_return
 		return x.count == y.count && x.elem == y.elem
 		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:
 	case Type_Info_Matrix:
 		y := b.variant.(Type_Info_Matrix) or_return
 		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)
 	_, ok := type_info_base(info).variant.(Type_Info_Simd_Vector)
 	return ok
 	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)
 @(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_i64(w, i64(info.count), 10, &n) or_return
 		io.write_byte(w, ']',                &n) or_return
 		io.write_byte(w, ']',                &n) or_return
 		write_type(w, info.elem,             &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:
 	case Type_Info_Matrix:
 		if info.layout == .Row_Major {
 		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)
 @(require_results)
 reinterpret :: proc "contextless" ($T: typeid/[]$U, s: []$V) -> []U {
 reinterpret :: proc "contextless" ($T: typeid/[]$U, s: []$V) -> []U {
@@ -471,6 +490,12 @@ is_empty :: proc(a: $T/[]$E) -> bool {
 	return len(a) == 0
 	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)
 @(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
 			break trunc_block
 		}
 		}
 		f := f64(mantissa)
 		f := f64(mantissa)
+		f_abs := f
 		if neg {
 		if neg {
 			f = -f
 			f = -f
 		}
 		}
@@ -1132,7 +1133,7 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) {
 				f *= pow10[exp-22]
 				f *= pow10[exp-22]
 				exp = 22
 				exp = 22
 			}
 			}
-			if f > 1e15 || f < 1e-15 {
+			if f_abs > 1e15 || f_abs < 1e-15 {
 				break trunc_block
 				break trunc_block
 			}
 			}
 			return f * pow10[exp], nr, true
 			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)
 	lowest_index := len(s)
 	found := false
 	found := false
 	for substr in substrs {
 	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 {
 			if i < lowest_index {
 				lowest_index = i
 				lowest_index = i
 				width = len(substr)
 				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") {
 	when !intrinsics.has_target_feature("atomics") {
 		panic_contextless("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it")
 		panic_contextless("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it")
 	} else {
 	} 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")
 		panic_contextless("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it")
 	} else {
 	} else {
 		s := intrinsics.wasm_memory_atomic_wait32((^u32)(f), expected, i64(duration))
 		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") {
 	when !intrinsics.has_target_feature("atomics") {
 		panic_contextless("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it")
 		panic_contextless("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it")
 	} else {
 	} 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") {
 	when !intrinsics.has_target_feature("atomics") {
 		panic_contextless("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it")
 		panic_contextless("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it")
 	} else {
 	} 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) {
 	switch atomic_exchange_explicit(&m.state, .Unlocked, .Release) {
 	case .Unlocked:
 	case .Unlocked:
-		unreachable()
+		// Kind of okay - unlocking while already unlocked.
 	case .Locked:
 	case .Locked:
 		// Okay
 		// Okay
 	case .Waiting:
 	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)
 	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")
 @(objc_type=Application, objc_name="windows")
 Application_windows :: proc "c" (self: ^Application) -> ^Array {
 Application_windows :: proc "c" (self: ^Application) -> ^Array {
 	return msgSend(^Array, self, "windows")
 	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
 YES  :: true
 NO   :: false
 NO   :: false
 
 
-OperatingSystemVersion :: struct #packed {
+OperatingSystemVersion :: struct #align(8) {
 	majorVersion: Integer,
 	majorVersion: Integer,
 	minorVersion: Integer,
 	minorVersion: Integer,
 	patchVersion: Integer,
 	patchVersion: Integer,
@@ -58,4 +58,4 @@ when size_of(Float) == 8 {
 } else {
 } else {
 	_POINT_ENCODING :: "{NSPoint=ff}"
 	_POINT_ENCODING :: "{NSPoint=ff}"
 	_SIZE_ENCODING :: "{NSSize=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
 	ssse3,     // Supplemental streaming SIMD extension 3
 	sse41,     // Streaming SIMD extension 4 and 4.1
 	sse41,     // Streaming SIMD extension 4 and 4.1
 	sse42,     // Streaming SIMD extension 4 and 4.2
 	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]
 CPU_Features :: distinct bit_set[CPU_Feature; u64]
@@ -82,9 +99,11 @@ init_cpu_features :: proc "c" () {
 	//
 	//
 	// See: crbug.com/375968
 	// See: crbug.com/375968
 	os_supports_avx := false
 	os_supports_avx := false
+	os_supports_avx512 := false
 	if .os_xsave in set && is_set(26, ecx1) {
 	if .os_xsave in set && is_set(26, ecx1) {
 		eax, _ := xgetbv(0)
 		eax, _ := xgetbv(0)
 		os_supports_avx = is_set(1, eax) && is_set(2, eax)
 		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 {
 	if os_supports_avx {
 		try_set(&set, .avx, 28, ecx1)
 		try_set(&set, .avx, 28, ecx1)
@@ -94,11 +113,37 @@ init_cpu_features :: proc "c" () {
 		return
 		return
 	}
 	}
 
 
-	_, ebx7, _, _ := cpuid(7, 0)
+	_, ebx7, ecx7, edx7 := cpuid(7, 0)
 	try_set(&set, .bmi1, 3, ebx7)
 	try_set(&set, .bmi1, 3, ebx7)
 	if os_supports_avx {
 	if os_supports_avx {
 		try_set(&set, .avx2, 5, ebx7)
 		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, .bmi2,    8, ebx7)
 	try_set(&set, .erms,    9, ebx7)
 	try_set(&set, .erms,    9, ebx7)
 	try_set(&set, .rdseed, 18, 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:
 List of contributors:
 	Jeroen van Rijn: Initial implementation.
 	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
 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)
 @(private)
 version_string_buf: [1024]u8
 version_string_buf: [1024]u8
 
 
 @(init, private)
 @(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[:])
 	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 {
 	} 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
 	RDONLY flag is not present, because it has the value of 0, i.e. it is the
 	default, unless WRONLY or RDWR is specified.
 	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
 	Bits for FD_Flags bitset
@@ -519,6 +541,36 @@ Fd_Poll_Events_Bits :: enum {
 	RDHUP  = 13,
 	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
 	Bits for Mem_Protection bitfield
 */
 */

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

@@ -135,6 +135,31 @@ STATX_BASIC_STATS :: Statx_Mask {
 	.BLOCKS,
 	.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
 	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): 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
 // TODO(flysand): migrate_pages
 
 

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

@@ -30,6 +30,11 @@ Id :: distinct uint
 */
 */
 Fd  :: distinct i32
 Fd  :: distinct i32
 
 
+/*
+	Represents a watch descriptor.
+*/
+Wd  :: distinct i32
+
 /*
 /*
 	Type for PID file descriptors.
 	Type for PID file descriptors.
 */
 */
@@ -343,6 +348,18 @@ Poll_Fd :: struct {
 	revents: Fd_Poll_Events,
 	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.
 	Specifies protection for memory pages.
 */
 */
@@ -667,6 +684,14 @@ Address_Family :: distinct Protocol_Family
 */
 */
 Socket_Msg :: bit_set[Socket_Msg_Bits; i32]
 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.
 	Struct representing IPv4 socket address.
 */
 */
@@ -674,6 +699,7 @@ Sock_Addr_In :: struct #packed {
 	sin_family: Address_Family,
 	sin_family: Address_Family,
 	sin_port:   u16be,
 	sin_port:   u16be,
 	sin_addr:   [4]u8,
 	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,
 		family: Address_Family,
 		port:   u16be,
 		port:   u16be,
 	},
 	},
+	using generic: Sock_Addr,
 	using ipv4: Sock_Addr_In,
 	using ipv4: Sock_Addr_In,
 	using ipv6: Sock_Addr_In6,
 	using ipv6: Sock_Addr_In6,
 	using uds: Sock_Addr_Un,
 	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
 package posix
 
 
 import "core:c"
 import "core:c"

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

@@ -1,3 +1,4 @@
+#+build darwin, linux, freebsd, openbsd, netbsd
 package posix
 package posix
 
 
 import "core:c"
 import "core:c"
@@ -29,12 +30,12 @@ foreign lib {
 			panic(string(posix.strerror(posix.errno())))
 			panic(string(posix.strerror(posix.errno())))
 		}
 		}
 		defer posix.free(list)
 		defer posix.free(list)
-	
+
 		entries := list[:ret]
 		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 ]]
 	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/scandir.html ]]
 	*/
 	*/
@@ -53,15 +54,6 @@ foreign lib {
 	*/
 	*/
 	closedir :: proc(dirp: DIR) -> result ---
 	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
 	Equivalent to the opendir() function except that the directory is specified by a file descriptor
 	rather than by a name.
 	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).
 	Returns nil when the end is reached or an error occurred (which sets errno).
 
 
 	Example:
 	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 ]]
 	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/readdir.html ]]
@@ -160,11 +152,36 @@ when ODIN_OS == .NetBSD {
 	@(private) LSCANDIR   :: "__scandir30"
 	@(private) LSCANDIR   :: "__scandir30"
 	@(private) LOPENDIR   :: "__opendir30"
 	@(private) LOPENDIR   :: "__opendir30"
 	@(private) LREADDIR   :: "__readdir30"
 	@(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 {
 } else {
 	@(private) LALPHASORT :: "alphasort" + INODE_SUFFIX
 	@(private) LALPHASORT :: "alphasort" + INODE_SUFFIX
 	@(private) LSCANDIR   :: "scandir"   + INODE_SUFFIX
 	@(private) LSCANDIR   :: "scandir"   + INODE_SUFFIX
 	@(private) LOPENDIR   :: "opendir"   + INODE_SUFFIX
 	@(private) LOPENDIR   :: "opendir"   + INODE_SUFFIX
 	@(private) LREADDIR   :: "readdir"   + 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 {
 when ODIN_OS == .Darwin {
@@ -210,6 +227,4 @@ when ODIN_OS == .Darwin {
 			d_name:   [256]c.char `fmt:"s,0"`, /* [PSX] entry name */
 			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
 package posix
 
 
 import "core:c"
 import "core:c"
@@ -120,7 +121,5 @@ when ODIN_OS == .Darwin {
 	_RTLD_LOCAL  :: 0
 	_RTLD_LOCAL  :: 0
 	RTLD_LOCAL   :: RTLD_Flags{}
 	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
 package posix
 
 
 import "core:c"
 import "core:c"
@@ -456,7 +457,84 @@ when ODIN_OS == .Darwin {
 
 
 	EOWNERDEAD      :: 130
 	EOWNERDEAD      :: 130
 	ENOTRECOVERABLE :: 131
 	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
 package posix
 
 
 import "core:c"
 import "core:c"
@@ -466,13 +467,11 @@ when ODIN_OS == .Darwin {
 	AT_REMOVEDIR        :: 0x200
 	AT_REMOVEDIR        :: 0x200
 
 
 	flock :: struct {
 	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_start:  off_t,     /* [PSX] relative offset in bytes. */
 		l_len:    off_t,     /* [PSX] size; if 0 then until EOF. */
 		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_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
 package posix
 
 
 import "core:c"
 import "core:c"
@@ -61,6 +62,4 @@ when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS
 	FNM_NOESCAPE :: 0x02
 	FNM_NOESCAPE :: 0x02
 	FNM_PERIOD   :: 0x04
 	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
 package posix
 
 
 import "core:c"
 import "core:c"
@@ -203,6 +204,4 @@ when ODIN_OS == .Darwin {
 	GLOB_ABORTED :: 2
 	GLOB_ABORTED :: 2
 	GLOB_NOMATCH :: 3
 	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
 package posix
 
 
 import "core:c"
 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 */
 		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
 package posix
 
 
 import "core:c"
 import "core:c"

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

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

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

@@ -1,3 +1,4 @@
+#+build linux, darwin, netbsd, openbsd, freebsd
 package posix
 package posix
 
 
 when ODIN_OS == .Darwin {
 when ODIN_OS == .Darwin {
@@ -56,6 +57,7 @@ foreign lib {
 
 
 	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/basename.html ]]
 	[[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/basename.html ]]
 	*/
 	*/
+	@(link_name=LBASENAME)
 	basename :: proc(path: cstring) -> cstring ---
 	basename :: proc(path: cstring) -> cstring ---
 
 
 	/*
 	/*
@@ -72,3 +74,9 @@ foreign lib {
 	*/
 	*/
 	dirname :: proc(path: cstring) -> cstring ---
 	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
 package posix
 
 
 // limits.h - implementation-defined constants
 // limits.h - implementation-defined constants
@@ -549,6 +550,4 @@ when ODIN_OS == .Darwin {
 	NL_TEXTMAX :: 2048 // 255 on glibc, 2048 on musl
 	NL_TEXTMAX :: 2048 // 255 on glibc, 2048 on musl
 	NZERO      :: 20
 	NZERO      :: 20
 
 
-} else {
-	#panic("posix is unimplemented for the current target")
 }
 }

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