Merge branch 'master' into extend_win32_api_types

This commit is contained in:
VladPavliuk
2025-01-03 18:53:48 +02:00
277 changed files with 10913 additions and 10169 deletions
+1 -1
View File
@@ -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
View File
@@ -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:
-12
View File
@@ -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
View File
@@ -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(intrinsics.type_bit_set_underlying_type(S))s))
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")
}
} }
+2
View File
@@ -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
} }
-12
View File
@@ -487,18 +487,6 @@ print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) {
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[")
print_u64(u64(info.row_count)) print_u64(u64(info.row_count))
+19
View File
@@ -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
View File
Binary file not shown.
+5 -2
View File
@@ -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
View File
@@ -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 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 or 18" 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
./odin run examples/demo -vet -strict-style -- Hellope World
fi
} }
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
@@ -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
}
File diff suppressed because it is too large Load Diff
-154
View File
@@ -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
}
-31
View File
@@ -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
-68
View File
@@ -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
}
-169
View File
@@ -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,
}
-667
View File
@@ -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)
}
-116
View File
@@ -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,
}
+1 -1
View File
@@ -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 |
+133
View File
@@ -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
View File
@@ -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 {
+6 -4
View File
@@ -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,8 +36,8 @@ main :: proc() {
fmt.println("hellope =", sym.hellope^) fmt.println("hellope =", sym.hellope^)
} }
count, ok = dynlib.initialize_symbols(&sym, "lib.dll", "foo_", "_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.dll (%p).\n", ok, count, sym._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))
+30 -20
View File
@@ -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) { load_library :: proc(path: string, global_symbols := false, allocator := context.temp_allocator) -> (library: Library, did_load: bool) {
return _load_library(path, global_symbols) 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 { symbol_address :: proc(library: Library, symbol: string, allocator := context.temp_allocator) -> (ptr: rawptr, found: bool) #optional_ok {
return _symbol_address(library, symbol) 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 // First, (re)load the library.
handle: Library
for field in reflect.struct_fields_zipped(T) {
if field.name == handle_field_name {
field_ptr := rawptr(uintptr(symbol_table) + field.offset)
// We appear to be hot reloading. Unload previous incarnation of the library.
if old_handle := (^Library)(field_ptr)^; old_handle != nil {
unload_library(old_handle) or_return
}
handle = load_library(library_path) or_return
(^Library)(field_ptr)^ = handle
break
}
}
// Buffer to concatenate the prefix + symbol name. // Buffer to concatenate the prefix + symbol name.
prefixed_symbol_buf: [2048]u8 = --- prefixed_symbol_buf: [2048]u8 = ---
count = 0 count = 0
for field in reflect.struct_fields_zipped(T) { for field in reflect.struct_fields_zipped(T) {
// If we're not the library handle, the field needs to be a pointer type, be it a procedure pointer or an exported global.
if field.name == handle_field_name || !(reflect.is_procedure(field.type) || reflect.is_pointer(field.type)) {
continue
}
// Calculate address of struct member // Calculate address of struct member
field_ptr := rawptr(uintptr(symbol_table) + field.offset) 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 {
// We appear to be hot reloading. Unload previous incarnation of the library.
if old_handle := (^Library)(field_ptr)^; old_handle != nil {
unload_library(old_handle) or_return
}
(^Library)(field_ptr)^ = handle
continue
}
// 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)) {
continue
}
// 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
+6 -2
View File
@@ -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,7 +14,7 @@ _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
} }
+21 -9
View File
@@ -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) { import "core:strings"
flags := os.RTLD_NOW 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) { _symbol_address :: proc(library: Library, symbol: string, allocator: runtime.Allocator) -> (ptr: rawptr, found: bool) {
ptr = os.dlsym(rawptr(library), symbol) 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
} }
+6 -2
View File
@@ -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)
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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)))
-6
View File
@@ -193,12 +193,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
View File
@@ -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
+47 -23
View File
@@ -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 str := unquote_string(token, p.spec, p.allocator) or_return
if unmarshal_string_token(p, any{v.data, ti.id}, str, ti) { dest := any{v.data, ti.id}
return nil 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)
@@ -394,14 +418,14 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
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
View File
@@ -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
View File
@@ -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
-128
View File
@@ -397,131 +397,3 @@ exif :: proc(c: image.PNG_Chunk) -> (res: Exif, ok: bool) {
*/ */
compute_buffer_size :: image.compute_buffer_size 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
}
}
+8 -4
View File
@@ -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 0:
case 1: write_byte(w, '0', &n) or_return write_string(w, "00", &n) or_return
case 2: write_string(w, s, &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
View File
@@ -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 { 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) 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 { create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logger_Opts, ident := "", allocator := context.allocator) -> Logger {
data := new(File_Console_Logger_Data) 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) { destroy_console_logger :: proc(log: Logger, allocator := context.allocator) {
free(log.data) 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
View File
@@ -5,17 +5,17 @@ Multi_Logger_Data :: struct {
loggers: []Logger, loggers: []Logger,
} }
create_multi_logger :: proc(logs: ..Logger) -> Logger { create_multi_logger :: proc(logs: ..Logger, allocator := context.allocator) -> Logger {
data := new(Multi_Logger_Data) data := new(Multi_Logger_Data, allocator)
data.loggers = make([]Logger, len(logs)) 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) delete(data.loggers, allocator)
free(data) 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
View File
@@ -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
View File
@@ -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{
@@ -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
View File
@@ -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
View File
@@ -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 0h3de5d8fd1fd19ccd, // 1.58962301576546568060e-10
0hbe5ae5e5a9291f5d, // -2.50507477628578072866e-8 0hbe5ae5e5a9291f5d, // -2.50507477628578072866e-8
0h3ec71de3567d48a1, // 2.75573136213857245213e-6 0h3ec71de3567d48a1, // 2.75573136213857245213e-6
0hbf2a01a019bfdf03, // -1.98412698295895385996e-4 0hbf2a01a019bfdf03, // -1.98412698295895385996e-4
0h3f8111111110f7d0, // 8.33333333332211858878e-3 0h3f8111111110f7d0, // 8.33333333332211858878e-3
0hbfc5555555555548, // -1.66666666666666307295e-1 0hbfc5555555555548, // -1.66666666666666307295e-1
} }
// cos coefficients // cos coefficients
+63 -38
View File
@@ -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 choice_enum :: proc($T: typeid, gen := context.random_generator) -> T where intrinsics.type_is_enum(T) {
where when size_of(T) <= 8 && len(T) == cap(T) {
intrinsics.type_is_enum(T), when intrinsics.type_is_unsigned(intrinsics.type_core_type(T)) &&
size_of(T) <= 8, u64(max(T)) > u64(max(i64)) {
len(T) == cap(T) /* Only allow contiguous enum types */ \ i := uint64(gen) % u64(len(T))
{ i += u64(min(T))
when intrinsics.type_is_unsigned(intrinsics.type_core_type(T)) && return T(i)
u64(max(T)) > u64(max(i64)) { } else {
i := uint64(gen) % u64(len(T)) i := int63_max(i64(len(T)), gen)
i += u64(min(T)) i += i64(min(T))
return T(i) return T(i)
}
} else { } else {
i := int63_max(i64(len(T)), gen) values := runtime.type_info_base(type_info_of(T)).variant.(runtime.Type_Info_Enum).values
i += i64(min(T)) return T(choice(values))
return T(i)
} }
} }
/*
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
}
-23
View File
@@ -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.
+3 -8
View File
@@ -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
} }
/* /*
@@ -684,10 +686,3 @@ 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
View File
@@ -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
View File
@@ -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 { if len(file_tags.build) > 0 {
os_and_arch_correct ||= target.os in kind.os && target.arch in kind.arch 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
View File
@@ -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
View File
@@ -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
} }
-584
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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}) if fd, errno := linux.open(name_cstr, _OPENDIR_FLAGS); errno == .NONE {
#partial switch (errno) { linux.close(fd)
case .ELOOP: return _get_platform_error(linux.rmdir(name_cstr))
/* 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)
} }
return _get_platform_error(linux.unlink(name_cstr)) return _get_platform_error(linux.unlink(name_cstr))
+3 -3
View File
@@ -50,9 +50,9 @@ init_std_files :: proc() {
} }
@(fini) @(fini)
fini_std_files :: proc() { fini_std_files :: proc() {
close(stdin) _destroy((^File_Impl)(stdin.impl))
close(stdout) _destroy((^File_Impl)(stdout.impl))
close(stderr) _destroy((^File_Impl)(stderr.impl))
} }
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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) exe_path = strings.to_cstring(&exe_builder)
if exe_fd, errno = linux.openat(dir_fd, exe_path, {.PATH, .CLOEXEC}); errno != .NONE { if linux.access(exe_path, linux.X_OK) == .NONE {
continue found = true
break
} }
if !has_executable_permissions(exe_fd) {
linux.close(exe_fd)
continue
}
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) exe_path = strings.to_cstring(&exe_builder)
if exe_fd, errno = linux.openat(dir_fd, exe_path, {.PATH, .CLOEXEC}); errno != .NONE { 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 exe_path = temp_cstring(executable_name) or_return
if exe_fd, errno = linux.openat(dir_fd, exe_path, {.PATH, .CLOEXEC}); errno != .NONE { if linux.access(exe_path, linux.X_OK) != .NONE {
return process, _get_platform_error(errno) return process, .Not_Exist
}
if !has_executable_permissions(exe_fd) {
linux.close(exe_fd)
return process, .Permission_Denied
} }
} }
// 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
View File
@@ -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
View File
@@ -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 { } else if file_attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 {
if file_attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { type = .Directory
type = .Directory mode |= 0o111
mode |= 0o111 } else if h != nil {
} type = file_type(h)
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
View File
@@ -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 strings.clone(string(path_ptr), allocator)
return path, nil
} }
access :: proc(path: string, mask: int) -> bool { access :: proc(path: string, mask: int) -> bool {
+2 -5
View File
@@ -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))
return strings.clone(string(path_ptr), allocator)
path = strings.clone(string(path_ptr))
return path, nil
} }
access :: proc(path: string, mask: int) -> (bool, Error) { access :: proc(path: string, mask: int) -> (bool, Error) {
+2 -4
View File
@@ -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 strings.clone(string(path_cstr), allocator)
return path, nil
} }
access :: proc(path: string, mask: int) -> (bool, Error) { access :: proc(path: string, mask: int) -> (bool, Error) {
+3 -5
View File
@@ -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 strings.clone(string(path_ptr), allocator)
return path, nil
} }
access :: proc(path: string, mask: int) -> (bool, Error) { access :: proc(path: string, mask: int) -> (bool, Error) {
+2 -4
View File
@@ -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 strings.clone(string(path_ptr), allocator)
return path, nil
} }
access :: proc(path: string, mask: int) -> (bool, Error) { access :: proc(path: string, mask: int) -> (bool, Error) {
+2 -4
View File
@@ -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 strings.clone(string(path_ptr), allocator)
return path, nil
} }
access :: proc(path: string, mask: int) -> (bool, Error) { access :: proc(path: string, mask: int) -> (bool, Error) {
+578 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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_Named: return .Named
case Type_Info_Integer: return .Integer case Type_Info_Integer: return .Integer
case Type_Info_Rune: return .Rune case Type_Info_Rune: return .Rune
case Type_Info_Float: return .Float case Type_Info_Float: return .Float
case Type_Info_Complex: return .Complex case Type_Info_Complex: return .Complex
case Type_Info_Quaternion: return .Quaternion case Type_Info_Quaternion: return .Quaternion
case Type_Info_String: return .String case Type_Info_String: return .String
case Type_Info_Boolean: return .Boolean case Type_Info_Boolean: return .Boolean
case Type_Info_Any: return .Any case Type_Info_Any: return .Any
case Type_Info_Type_Id: return .Type_Id case Type_Info_Type_Id: return .Type_Id
case Type_Info_Pointer: return .Pointer case Type_Info_Pointer: return .Pointer
case Type_Info_Multi_Pointer: return .Multi_Pointer case Type_Info_Multi_Pointer: return .Multi_Pointer
case Type_Info_Procedure: return .Procedure case Type_Info_Procedure: return .Procedure
case Type_Info_Array: return .Array case Type_Info_Array: return .Array
case Type_Info_Enumerated_Array: return .Enumerated_Array case Type_Info_Enumerated_Array: return .Enumerated_Array
case Type_Info_Dynamic_Array: return .Dynamic_Array case Type_Info_Dynamic_Array: return .Dynamic_Array
case Type_Info_Slice: return .Slice case Type_Info_Slice: return .Slice
case Type_Info_Parameters: return .Tuple case Type_Info_Parameters: return .Tuple
case Type_Info_Struct: return .Struct case Type_Info_Struct: return .Struct
case Type_Info_Union: return .Union case Type_Info_Union: return .Union
case Type_Info_Enum: return .Enum case Type_Info_Enum: return .Enum
case Type_Info_Map: return .Map case Type_Info_Map: return .Map
case Type_Info_Bit_Set: return .Bit_Set case Type_Info_Bit_Set: return .Bit_Set
case Type_Info_Simd_Vector: return .Simd_Vector case Type_Info_Simd_Vector: return .Simd_Vector
case Type_Info_Relative_Pointer: return .Relative_Pointer case Type_Info_Matrix: return .Matrix
case Type_Info_Relative_Multi_Pointer: return .Relative_Multi_Pointer case Type_Info_Soa_Pointer: return .Soa_Pointer
case Type_Info_Matrix: return .Matrix case Type_Info_Bit_Field: return .Bit_Field
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
-32
View File
@@ -159,14 +159,6 @@ are_types_identical :: proc(a, b: ^Type_Info) -> bool {
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
if x.row_count != y.row_count { return false } if x.row_count != y.row_count { return false }
@@ -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)
@@ -737,18 +717,6 @@ write_type_writer :: #force_no_inline proc(w: io.Writer, ti: ^Type_Info, n_writt
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 {
io.write_string(w, "#row_major ", &n) or_return io.write_string(w, "#row_major ", &n) or_return
+39 -14
View File
@@ -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. Turn a slice of one type, into a slice of another type.
Only converts the type and length of the slice itself. Only converts the type and length of the slice itself.
The length is rounded down to the nearest whole number of items. 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]
```
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.
```
*/ */
@(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
View File
@@ -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
View File
@@ -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
View File
@@ -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) _ = intrinsics.wasm_memory_atomic_wait32((^u32)(f), expected, -1)
return s != 0 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 { _ = intrinsics.wasm_memory_atomic_notify32((^u32)(f), 1)
s := intrinsics.wasm_memory_atomic_notify32((^u32)(f), 1)
if s >= 1 {
return
}
}
} }
} }
@@ -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 { _ = intrinsics.wasm_memory_atomic_notify32((^u32)(f), max(u32))
s := intrinsics.wasm_memory_atomic_notify32((^u32)(f), ~u32(0))
if s >= 0 {
return
}
}
} }
} }
+1 -1
View File
@@ -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:
@@ -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")
@@ -0,0 +1,5 @@
package objc_Foundation
@(objc_class="NSObjectProtocol")
ObjectProtocol :: struct {using _: Object}
// TODO: implement NSObjectProtocol
@@ -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")
}
+1 -1
View File
@@ -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,
+46 -1
View File
@@ -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
View File
@@ -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.
*/ */
/* /*
+78 -558
View File
@@ -1,581 +1,101 @@
package sysinfo package sysinfo
import sys "core:sys/unix" import "core:strconv"
import "core:strconv" import "core:strings"
import "core:strings" import "core:sys/unix"
import "base:runtime" 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 () { init_platform :: proc() {
os_version.platform = .MacOS 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} version: NS.OperatingSystemVersion
build_buf: [12]u8 {
NS.scoped_autoreleasepool()
ok := sys.sysctl(mib, &build_buf) info := NS.ProcessInfo.processInfo()
if !ok { version = info->operatingSystemVersion()
strings.write_string(&b, "macOS Unknown") mem := info->physicalMemory()
os_version.as_string = strings.to_string(b)
return ram.total_ram = int(mem)
} }
build := string(cstring(&build_buf[0])) macos_version = {int(version.majorVersion), int(version.minorVersion), int(version.patchVersion)}
// Do we have an exact match? when ODIN_PLATFORM_SUBTARGET == .iOS {
match: Darwin_Match os_version.platform = .iOS
rel, exact := macos_release_map[build] ws(&b, "iOS")
if exact {
match = .Exact
} else { } else {
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")
}
}
ws(&b, " ")
wi(&b, int(version.majorVersion))
ws(&b, ".")
wi(&b, int(version.minorVersion))
ws(&b, ".")
wi(&b, int(version.patchVersion))
{
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"
ws(&b, " (build ")
build_start := len(b.buf)
ws(&b, build)
os_version.version = string(b.buf[build_start:][:len(build)])
}
{
// Match on XNU kernel version // Match on XNU kernel version
mib = []i32{sys.CTL_KERN, sys.KERN_OSRELEASE}
version_bits: [12]u8 // enough for 999.999.999\x00 version_bits: [12]u8 // enough for 999.999.999\x00
have_kernel_version := sys.sysctl(mib, &version_bits) 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"
major_ok, minor_ok, patch_ok: bool major, _, tail := strings.partition(kernel, ".")
minor, _, patch := strings.partition(tail, ".")
tmp := runtime.default_temp_allocator_temp_begin() os_version.major, _ = strconv.parse_int(major, 10)
os_version.minor, _ = strconv.parse_int(minor, 10)
os_version.patch, _ = strconv.parse_int(patch, 10)
triplet := strings.split(string(cstring(&version_bits[0])), ".", context.temp_allocator) ws(&b, ", kernel ")
if len(triplet) != 3 { ws(&b, kernel)
have_kernel_version = false ws(&b, ")")
} 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
}
}
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.as_string = string(b.buf[:])
os_version.minor = rel.darwin.y
os_version.patch = rel.darwin.z
macos_version = transmute(Version)rel.release.version
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`
mib := []i32{sys.CTL_HW, sys.HW_MEMSIZE}
mem_size: u64
if sys.sysctl(mib, &mem_size) {
ram.total_ram = int(mem_size)
}
}
@(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,
}
@(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
}
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
}
// 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
}
}
}
}
if nearest == {} {
return {darwin, "macOS", {"Unknown", {}}}, .Unknown
} else {
return nearest, .Nearest
}
} }
+88 -36
View File
@@ -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 { when ODIN_ARCH != .arm64 && ODIN_ARCH != .arm32 {
WRONLY = 0, Open_Flags_Bits :: enum {
RDWR = 1, WRONLY = 0,
CREAT = 6, RDWR = 1,
EXCL = 7, CREAT = 6,
NOCTTY = 8, EXCL = 7,
TRUNC = 9, NOCTTY = 8,
APPEND = 10, TRUNC = 9,
NONBLOCK = 11, APPEND = 10,
DSYNC = 12, NONBLOCK = 11,
ASYNC = 13, DSYNC = 12,
DIRECT = 14, ASYNC = 13,
LARGEFILE = 15, DIRECT = 14,
DIRECTORY = 16, LARGEFILE = 15,
NOFOLLOW = 17, DIRECTORY = 16,
NOATIME = 18, NOFOLLOW = 17,
CLOEXEC = 19, NOATIME = 18,
PATH = 21, 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,
}
} }
// 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)
/* /*
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
View File
@@ -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
View File
@@ -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)
}
// TODO(flysand): inotify_rm_watch 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)
}
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
View File
@@ -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
View File
@@ -1,3 +1,4 @@
#+build darwin, linux, freebsd, openbsd, netbsd
package posix package posix
import "core:c" import "core:c"
+40 -25
View File
@@ -1,3 +1,4 @@
#+build darwin, linux, freebsd, openbsd, netbsd
package posix package posix
import "core:c" import "core:c"
@@ -31,10 +32,10 @@ foreign lib {
defer posix.free(list) defer posix.free(list)
entries := list[:ret] entries := list[:ret]
for entry in entries { for entry in entries {
log.info(entry) log.info(entry)
posix.free(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) posix.set_errno(.NONE)
entry := posix.readdir(dirp) entry := posix.readdir(dirp)
if entry == nil { if entry == nil {
if errno := posix.errno(); errno != .NONE { if errno := posix.errno(); errno != .NONE {
panic(string(posix.strerror(errno))) panic(string(posix.strerror(errno)))
} else { } else {
fmt.println("end of directory stream") fmt.println("end of directory stream")
} }
} else { } else {
fmt.println(entry) 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
View File
@@ -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
View File
@@ -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 { } else when ODIN_OS == .Windows {
#panic("posix is unimplemented for the current target") 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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1,3 +1,4 @@
#+build linux, darwin, netbsd, openbsd, freebsd
package posix package posix
import "core:c" import "core:c"
+1
View File
@@ -1,3 +1,4 @@
#+build linux, darwin, netbsd, openbsd, freebsd
package posix package posix
import "core:c" import "core:c"
+8
View File
@@ -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
View File
@@ -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 have changed in this diff Show More