diff options
| author | jason <jkercher@rlcsystems.com> | 2022-05-16 13:49:57 -0400 |
|---|---|---|
| committer | jason <jkercher@rlcsystems.com> | 2022-05-16 13:49:57 -0400 |
| commit | fff23e2bbbd1574debce9e0dee894f3cc84a04c4 (patch) | |
| tree | 4055ea217375d34693861b39fc284e411f7c0366 /core | |
| parent | 97d1a6787189d7630650612f44c393f7a635019a (diff) | |
| parent | 33895b6d927c70167f3bfa64c6cc1c15c4e428c5 (diff) | |
merge from upstream and convert to ^File types
Diffstat (limited to 'core')
160 files changed, 17529 insertions, 3988 deletions
diff --git a/core/builtin/builtin.odin b/core/builtin/builtin.odin index 259fdef37..d1cdfa6e7 100644 --- a/core/builtin/builtin.odin +++ b/core/builtin/builtin.odin @@ -1,90 +1,90 @@ // This is purely for documentation package builtin -nil :: nil; -false :: 0!=0; -true :: 0==0; - -ODIN_OS :: ODIN_OS; -ODIN_ARCH :: ODIN_ARCH; -ODIN_ENDIAN :: ODIN_ENDIAN; -ODIN_VENDOR :: ODIN_VENDOR; -ODIN_VERSION :: ODIN_VERSION; -ODIN_ROOT :: ODIN_ROOT; -ODIN_DEBUG :: ODIN_DEBUG; - -byte :: u8; // alias - -bool :: bool; -b8 :: b8; -b16 :: b16; -b32 :: b32; -b64 :: b64; - -i8 :: i8; -u8 :: u8; -i16 :: i16; -u16 :: u16; -i32 :: i32; -u32 :: u32; -i64 :: i64; -u64 :: u64; - -i128 :: i128; -u128 :: u128; - -rune :: rune; - -f16 :: f16; -f32 :: f32; -f64 :: f64; - -complex32 :: complex32; -complex64 :: complex64; -complex128 :: complex128; - -quaternion64 :: quaternion64; -quaternion128 :: quaternion128; -quaternion256 :: quaternion256; - -int :: int; -uint :: uint; -uintptr :: uintptr; - -rawptr :: rawptr; -string :: string; -cstring :: cstring; -any :: any; - -typeid :: typeid; +nil :: nil +false :: 0!=0 +true :: 0==0 + +ODIN_OS :: ODIN_OS +ODIN_ARCH :: ODIN_ARCH +ODIN_ENDIAN :: ODIN_ENDIAN +ODIN_VENDOR :: ODIN_VENDOR +ODIN_VERSION :: ODIN_VERSION +ODIN_ROOT :: ODIN_ROOT +ODIN_DEBUG :: ODIN_DEBUG + +byte :: u8 // alias + +bool :: bool +b8 :: b8 +b16 :: b16 +b32 :: b32 +b64 :: b64 + +i8 :: i8 +u8 :: u8 +i16 :: i16 +u16 :: u16 +i32 :: i32 +u32 :: u32 +i64 :: i64 +u64 :: u64 + +i128 :: i128 +u128 :: u128 + +rune :: rune + +f16 :: f16 +f32 :: f32 +f64 :: f64 + +complex32 :: complex32 +complex64 :: complex64 +complex128 :: complex128 + +quaternion64 :: quaternion64 +quaternion128 :: quaternion128 +quaternion256 :: quaternion256 + +int :: int +uint :: uint +uintptr :: uintptr + +rawptr :: rawptr +string :: string +cstring :: cstring +any :: any + +typeid :: typeid // Endian Specific Types -i16le :: i16le; -u16le :: u16le; -i32le :: i32le; -u32le :: u32le; -i64le :: i64le; -u64le :: u64le; -i128le :: i128le; -u128le :: u128le; - -i16be :: i16be; -u16be :: u16be; -i32be :: i32be; -u32be :: u32be; -i64be :: i64be; -u64be :: u64be; -i128be :: i128be; -u128be :: u128be; - - -f16le :: f16le; -f32le :: f32le; -f64le :: f64le; - -f16be :: f16be; -f32be :: f32be; -f64be :: f64be; +i16le :: i16le +u16le :: u16le +i32le :: i32le +u32le :: u32le +i64le :: i64le +u64le :: u64le +i128le :: i128le +u128le :: u128le + +i16be :: i16be +u16be :: u16be +i32be :: i32be +u32be :: u32be +i64be :: i64be +u64be :: u64be +i128be :: i128be +u128be :: u128be + + +f16le :: f16le +f32le :: f32le +f64le :: f64le + +f16be :: f16be +f32be :: f32be +f64be :: f64be diff --git a/core/bytes/bytes.odin b/core/bytes/bytes.odin index 09a3ed259..f1737f3c5 100644 --- a/core/bytes/bytes.odin +++ b/core/bytes/bytes.odin @@ -10,7 +10,14 @@ clone :: proc(s: []byte, allocator := context.allocator, loc := #caller_location return c[:len(s)] } -ptr_from_slice :: proc(str: []byte) -> ^byte { +clone_safe :: proc(s: []byte, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: mem.Allocator_Error) { + c := make([]byte, len(s), allocator, loc) or_return + copy(c, s) + return c[:len(s)], nil +} + +ptr_from_slice :: ptr_from_bytes +ptr_from_bytes :: proc(str: []byte) -> ^byte { d := transmute(mem.Raw_String)str return d.data } @@ -134,6 +141,25 @@ join :: proc(a: [][]byte, sep: []byte, allocator := context.allocator) -> []byte return b } +join_safe :: proc(a: [][]byte, sep: []byte, allocator := context.allocator) -> (data: []byte, err: mem.Allocator_Error) { + if len(a) == 0 { + return nil, nil + } + + n := len(sep) * (len(a) - 1) + for s in a { + n += len(s) + } + + b := make([]byte, n, allocator) or_return + i := copy(b, a[0]) + for s in a[1:] { + i += copy(b[i:], sep) + i += copy(b[i:], s) + } + return b, nil +} + concatenate :: proc(a: [][]byte, allocator := context.allocator) -> []byte { if len(a) == 0 { return nil @@ -151,6 +177,24 @@ concatenate :: proc(a: [][]byte, allocator := context.allocator) -> []byte { return b } +concatenate_safe :: proc(a: [][]byte, allocator := context.allocator) -> (data: []byte, err: mem.Allocator_Error) { + if len(a) == 0 { + return nil, nil + } + + n := 0 + for s in a { + n += len(s) + } + b := make([]byte, n, allocator) or_return + i := 0 + for s in a { + i += copy(b[i:], s) + } + return b, nil +} + + @private _split :: proc(s, sep: []byte, sep_save, n: int, allocator := context.allocator) -> [][]byte { s, n := s, n diff --git a/core/compress/common.odin b/core/compress/common.odin index 58aeac25b..f4e378269 100644 --- a/core/compress/common.odin +++ b/core/compress/common.odin @@ -128,7 +128,6 @@ Deflate_Error :: enum { BType_3, } - // General I/O context for ZLIB, LZW, etc. Context_Memory_Input :: struct #packed { input_data: []u8, @@ -151,7 +150,6 @@ when size_of(rawptr) == 8 { #assert(size_of(Context_Memory_Input) == 52) } - Context_Stream_Input :: struct #packed { input_data: []u8, input: io.Stream, @@ -185,8 +183,6 @@ Context_Stream_Input :: struct #packed { This simplifies end-of-stream handling where bits may be left in the bit buffer. */ -// TODO: Make these return compress.Error errors. - input_size_from_memory :: proc(z: ^Context_Memory_Input) -> (res: i64, err: Error) { return i64(len(z.input_data)), nil } diff --git a/core/compress/gzip/example.odin b/core/compress/gzip/example.odin index 0e2c2b9f6..c010d5979 100644 --- a/core/compress/gzip/example.odin +++ b/core/compress/gzip/example.odin @@ -45,7 +45,7 @@ main :: proc() { if len(args) < 2 { stderr("No input file specified.\n") - err := load(slice=TEST, buf=&buf, known_gzip_size=len(TEST)) + err := load(data=TEST, buf=&buf, known_gzip_size=len(TEST)) if err == nil { stdout("Displaying test vector: ") stdout(bytes.buffer_to_string(&buf)) diff --git a/core/compress/gzip/gzip.odin b/core/compress/gzip/gzip.odin index 4482d4a7e..4de4d1b63 100644 --- a/core/compress/gzip/gzip.odin +++ b/core/compress/gzip/gzip.odin @@ -102,7 +102,7 @@ E_Deflate :: compress.Deflate_Error GZIP_MAX_PAYLOAD_SIZE :: i64(max(u32le)) -load :: proc{load_from_slice, load_from_file, load_from_context} +load :: proc{load_from_bytes, load_from_file, load_from_context} load_from_file :: proc(filename: string, buf: ^bytes.Buffer, expected_output_size := -1, allocator := context.allocator) -> (err: Error) { context.allocator = allocator @@ -112,16 +112,16 @@ load_from_file :: proc(filename: string, buf: ^bytes.Buffer, expected_output_siz err = E_General.File_Not_Found if ok { - err = load_from_slice(data, buf, len(data), expected_output_size) + err = load_from_bytes(data, buf, len(data), expected_output_size) } return } -load_from_slice :: proc(slice: []u8, buf: ^bytes.Buffer, known_gzip_size := -1, expected_output_size := -1, allocator := context.allocator) -> (err: Error) { +load_from_bytes :: proc(data: []byte, buf: ^bytes.Buffer, known_gzip_size := -1, expected_output_size := -1, allocator := context.allocator) -> (err: Error) { buf := buf z := &compress.Context_Memory_Input{ - input_data = slice, + input_data = data, output = buf, } return load_from_context(z, buf, known_gzip_size, expected_output_size, allocator) diff --git a/core/compress/shoco/model.odin b/core/compress/shoco/model.odin new file mode 100644 index 000000000..bbc38903d --- /dev/null +++ b/core/compress/shoco/model.odin @@ -0,0 +1,148 @@ +/* + This file was generated, so don't edit this by hand. + Transliterated from https://github.com/Ed-von-Schleck/shoco/blob/master/shoco_model.h, + which is an English word model. +*/ + +// package shoco is an implementation of the shoco short string compressor +package shoco + +DEFAULT_MODEL :: Shoco_Model { + min_char = 39, + max_char = 122, + characters_by_id = { + 'e', 'a', 'i', 'o', 't', 'h', 'n', 'r', 's', 'l', 'u', 'c', 'w', 'm', 'd', 'b', 'p', 'f', 'g', 'v', 'y', 'k', '-', 'H', 'M', 'T', '\'', 'B', 'x', 'I', 'W', 'L', + }, + ids_by_character = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26, -1, -1, -1, -1, -1, 22, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 27, -1, -1, -1, -1, -1, 23, 29, -1, -1, 31, 24, -1, -1, -1, -1, -1, -1, 25, -1, -1, 30, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 15, 11, 14, 0, 17, 18, 5, 2, -1, 21, 9, 13, 6, 3, 16, -1, 7, 8, 4, 10, 19, 12, 28, 20, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }, + successors_by_bigram = { + 7, 4, 12, -1, 6, -1, 1, 0, 3, 5, -1, 9, -1, 8, 2, -1, 15, 14, -1, 10, 11, -1, -1, -1, -1, -1, -1, -1, 13, -1, -1, -1, + 1, -1, 6, -1, 1, -1, 0, 3, 2, 4, 15, 11, -1, 9, 5, 10, 13, -1, 12, 8, 7, 14, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 9, 11, -1, 4, 2, -1, 0, 8, 1, 5, -1, 6, -1, 3, 7, 15, -1, 12, 10, 13, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 14, 7, 5, -1, 1, 2, 8, 9, 0, 15, 6, 4, 11, -1, 12, 3, -1, 10, -1, 13, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 2, 4, 3, 1, 5, 0, -1, 6, 10, 9, 7, 12, 11, -1, -1, -1, -1, 13, -1, -1, 8, -1, 15, -1, -1, -1, 14, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, -1, -1, 5, 9, 10, 6, -1, -1, 8, 15, 11, -1, 14, -1, -1, 7, -1, 13, -1, -1, -1, 12, -1, -1, -1, -1, -1, + 2, 8, 7, 4, 3, -1, 9, -1, 6, 11, -1, 5, -1, -1, 0, -1, -1, 14, 1, 15, 10, 12, -1, -1, -1, -1, 13, -1, -1, -1, -1, -1, + 0, 3, 1, 2, 6, -1, 9, 8, 4, 12, 13, 10, -1, 11, 7, -1, -1, 15, 14, -1, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 6, 3, 4, 1, 2, -1, -1, 5, 10, 7, 9, 11, 12, -1, -1, 8, 14, -1, -1, 15, 13, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 6, 2, 5, 9, -1, -1, -1, 10, 1, 8, -1, 12, 14, 4, -1, 15, 7, -1, 13, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 8, 10, 9, 15, 1, -1, 4, 0, 3, 2, -1, 6, -1, 12, 11, 13, 7, 14, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 1, 3, 6, 0, 4, 2, -1, 7, 13, 8, 9, 11, -1, -1, 15, -1, -1, -1, -1, -1, 10, 5, 14, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 3, 0, 1, 4, -1, 2, 5, 6, 7, 8, -1, 14, -1, -1, 9, 15, -1, 12, -1, -1, -1, 10, 11, -1, -1, -1, 13, -1, -1, -1, -1, -1, + 0, 1, 3, 2, 15, -1, 12, -1, 7, 14, 4, -1, -1, 9, -1, 8, 5, 10, -1, -1, 6, -1, 13, -1, -1, -1, 11, -1, -1, -1, -1, -1, + 0, 3, 1, 2, -1, -1, 12, 6, 4, 9, 7, -1, -1, 14, 8, -1, -1, 15, 11, 13, 5, -1, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 5, 7, 2, 10, 13, -1, 6, 8, 1, 3, -1, -1, 14, 15, 11, -1, -1, -1, 12, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 2, 6, 3, 7, 10, -1, 1, 9, 4, 8, -1, -1, 15, -1, 12, 5, -1, -1, -1, 11, -1, 13, -1, -1, -1, 14, -1, -1, -1, -1, -1, + 1, 3, 4, 0, 7, -1, 12, 2, 11, 8, 6, 13, -1, -1, -1, -1, -1, 5, -1, -1, 10, 15, 9, -1, -1, -1, 14, -1, -1, -1, -1, -1, + 1, 3, 5, 2, 13, 0, 9, 4, 7, 6, 8, -1, -1, 15, -1, 11, -1, -1, 10, -1, 14, -1, 12, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 2, 1, 3, -1, -1, -1, 6, -1, -1, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 1, 11, 4, 0, 3, -1, 13, 12, 2, 7, -1, -1, 15, 10, 5, 8, 14, -1, -1, -1, -1, -1, 9, -1, -1, -1, 6, -1, -1, -1, -1, -1, + 0, 9, 2, 14, 15, 4, 1, 13, 3, 5, -1, -1, 10, -1, -1, -1, -1, 6, 12, -1, 7, -1, 8, -1, -1, -1, 11, -1, -1, -1, -1, -1, + -1, 2, 14, -1, 1, 5, 8, 7, 4, 12, -1, 6, 9, 11, 13, 3, 10, 15, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 3, 2, -1, -1, -1, -1, -1, -1, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 4, 3, 1, 5, -1, -1, -1, 0, -1, -1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 2, 8, 4, 1, -1, 0, -1, 6, -1, -1, 5, -1, 7, -1, -1, -1, -1, -1, -1, -1, 10, -1, -1, 9, -1, -1, -1, -1, -1, -1, -1, -1, + 12, 5, -1, -1, 1, -1, -1, 7, 0, 3, -1, 2, -1, 4, 6, -1, -1, -1, -1, 8, -1, -1, 15, -1, 13, 9, -1, -1, -1, -1, -1, 11, + 1, 3, 2, 4, -1, -1, -1, 5, -1, 7, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, 6, -1, -1, -1, -1, -1, -1, -1, -1, 8, -1, -1, + 5, 3, 4, 12, 1, 6, -1, -1, -1, -1, 8, 2, -1, -1, -1, -1, 0, 9, -1, -1, 11, -1, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 0, -1, 1, 12, 3, -1, -1, -1, -1, 5, -1, -1, -1, 2, -1, -1, -1, -1, -1, -1, -1, -1, 4, -1, -1, 6, -1, 10, + 2, 3, 1, 4, -1, 0, -1, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 7, -1, -1, -1, -1, -1, -1, -1, -1, 6, -1, -1, + 5, 1, 3, 0, -1, -1, -1, -1, -1, -1, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, -1, -1, -1, -1, -1, 9, -1, -1, 6, -1, 7, + }, + successors_reversed = { + 's', 't', 'c', 'l', 'm', 'a', 'd', 'r', 'v', 'T', 'A', 'L', 'e', 'M', 'Y', '-', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '-', 't', 'a', 'b', 's', 'h', 'c', 'r', 'n', 'w', 'p', 'm', 'l', 'd', 'i', 'f', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + 'u', 'e', 'i', 'a', 'o', 'r', 'y', 'l', 'I', 'E', 'R', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + 'e', 'a', 'o', 'i', 'u', 'A', 'y', 'E', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + 't', 'n', 'f', 's', '\'', 'm', 'I', 'N', 'A', 'E', 'L', 'Z', 'r', 'V', 'R', 'C', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + 'o', 'a', 'y', 'i', 'u', 'e', 'I', 'L', 'D', '\'', 'E', 'Y', '\x00', '\x00', '\x00', '\x00', + 'r', 'i', 'y', 'a', 'e', 'o', 'u', 'Y', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + 'h', 'o', 'e', 'E', 'i', 'u', 'r', 'w', 'a', 'H', 'y', 'R', 'Z', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + 'h', 'i', 'e', 'a', 'o', 'r', 'I', 'y', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + 'n', 't', 's', 'r', 'l', 'd', 'i', 'y', 'v', 'm', 'b', 'c', 'g', 'p', 'k', 'u', + 'e', 'l', 'o', 'u', 'y', 'a', 'r', 'i', 's', 'j', 't', 'b', 'v', 'h', 'm', 'd', + 'o', 'e', 'h', 'a', 't', 'k', 'i', 'r', 'l', 'u', 'y', 'c', 'q', 's', '-', 'd', + 'e', 'i', 'o', 'a', 's', 'y', 'r', 'u', 'd', 'l', '-', 'g', 'n', 'v', 'm', 'f', + 'r', 'n', 'd', 's', 'a', 'l', 't', 'e', 'm', 'c', 'v', 'y', 'i', 'x', 'f', 'p', + 'o', 'e', 'r', 'a', 'i', 'f', 'u', 't', 'l', '-', 'y', 's', 'n', 'c', '\'', 'k', + 'h', 'e', 'o', 'a', 'r', 'i', 'l', 's', 'u', 'n', 'g', 'b', '-', 't', 'y', 'm', + 'e', 'a', 'i', 'o', 't', 'r', 'u', 'y', 'm', 's', 'l', 'b', '\'', '-', 'f', 'd', + 'n', 's', 't', 'm', 'o', 'l', 'c', 'd', 'r', 'e', 'g', 'a', 'f', 'v', 'z', 'b', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + 'e', 'n', 'i', 's', 'h', 'l', 'f', 'y', '-', 'a', 'w', '\'', 'g', 'r', 'o', 't', + 'e', 'l', 'i', 'y', 'd', 'o', 'a', 'f', 'u', 't', 's', 'k', 'w', 'v', 'm', 'p', + 'e', 'a', 'o', 'i', 'u', 'p', 'y', 's', 'b', 'm', 'f', '\'', 'n', '-', 'l', 't', + 'd', 'g', 'e', 't', 'o', 'c', 's', 'i', 'a', 'n', 'y', 'l', 'k', '\'', 'f', 'v', + 'u', 'n', 'r', 'f', 'm', 't', 'w', 'o', 's', 'l', 'v', 'd', 'p', 'k', 'i', 'c', + 'e', 'r', 'a', 'o', 'l', 'p', 'i', 't', 'u', 's', 'h', 'y', 'b', '-', '\'', 'm', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + 'e', 'i', 'o', 'a', 's', 'y', 't', 'd', 'r', 'n', 'c', 'm', 'l', 'u', 'g', 'f', + 'e', 't', 'h', 'i', 'o', 's', 'a', 'u', 'p', 'c', 'l', 'w', 'm', 'k', 'f', 'y', + 'h', 'o', 'e', 'i', 'a', 't', 'r', 'u', 'y', 'l', 's', 'w', 'c', 'f', '\'', '-', + 'r', 't', 'l', 's', 'n', 'g', 'c', 'p', 'e', 'i', 'a', 'd', 'm', 'b', 'f', 'o', + 'e', 'i', 'a', 'o', 'y', 'u', 'r', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + 'a', 'i', 'h', 'e', 'o', 'n', 'r', 's', 'l', 'd', 'k', '-', 'f', '\'', 'c', 'b', + 'p', 't', 'c', 'a', 'i', 'e', 'h', 'q', 'u', 'f', '-', 'y', 'o', '\x00', '\x00', '\x00', + 'o', 'e', 's', 't', 'i', 'd', '\'', 'l', 'b', '-', 'm', 'a', 'r', 'n', 'p', 'w', + }, + + character_count = 32, + successor_count = 16, + + max_successor_n = 7, + packs = { + { 0x80000000, 1, 2, { 26, 24, 24, 24, 24, 24, 24, 24 }, { 15, 3, 0, 0, 0, 0, 0, 0 }, 0xc0, 0x80 }, + { 0xc0000000, 2, 4, { 25, 22, 19, 16, 16, 16, 16, 16 }, { 15, 7, 7, 7, 0, 0, 0, 0 }, 0xe0, 0xc0 }, + { 0xe0000000, 4, 8, { 23, 19, 15, 11, 8, 5, 2, 0 }, { 31, 15, 15, 15, 7, 7, 7, 3 }, 0xf0, 0xe0 }, + }, +}
\ No newline at end of file diff --git a/core/compress/shoco/shoco.odin b/core/compress/shoco/shoco.odin new file mode 100644 index 000000000..f94ce70b7 --- /dev/null +++ b/core/compress/shoco/shoco.odin @@ -0,0 +1,318 @@ +/* + Copyright 2022 Jeroen van Rijn <nom@duclavier.com>. + Made available under Odin's BSD-3 license. + + List of contributors: + Jeroen van Rijn: Initial implementation. + + An implementation of [shoco](https://github.com/Ed-von-Schleck/shoco) by Christian Schramm. +*/ + +// package shoco is an implementation of the shoco short string compressor +package shoco + +import "core:intrinsics" +import "core:compress" + +Shoco_Pack :: struct { + word: u32, + bytes_packed: i8, + bytes_unpacked: i8, + offsets: [8]u16, + masks: [8]i16, + header_mask: u8, + header: u8, +} + +Shoco_Model :: struct { + min_char: u8, + max_char: u8, + characters_by_id: []u8, + ids_by_character: [256]i16, + successors_by_bigram: []i8, + successors_reversed: []u8, + + character_count: u8, + successor_count: u8, + max_successor_n: i8, + packs: []Shoco_Pack, +} + +compress_bound :: proc(uncompressed_size: int) -> (worst_case_compressed_size: int) { + // Worst case compression happens when input is non-ASCII (128-255) + // Encoded as 0x00 + the byte in question. + return uncompressed_size * 2 +} + +decompress_bound :: proc(compressed_size: int, model := DEFAULT_MODEL) -> (maximum_decompressed_size: int) { + // Best case compression is 2:1 + most: f64 + for pack in model.packs { + val := f64(compressed_size) / f64(pack.bytes_packed) * f64(pack.bytes_unpacked) + most = max(most, val) + } + return int(most) +} + +find_best_encoding :: proc(indices: []i16, n_consecutive: i8, model := DEFAULT_MODEL) -> (res: int) { + for p := len(model.packs); p > 0; p -= 1 { + pack := model.packs[p - 1] + if n_consecutive >= pack.bytes_unpacked { + have_index := true + for i := 0; i < int(pack.bytes_unpacked); i += 1 { + if indices[i] > pack.masks[i] { + have_index = false + break + } + } + if have_index { + return p - 1 + } + } + } + return -1 +} + +validate_model :: proc(model: Shoco_Model) -> (int, compress.Error) { + if len(model.characters_by_id) != int(model.character_count) { + return 0, .Unknown_Compression_Method + } + + if len(model.successors_by_bigram) != int(model.character_count) * int(model.character_count) { + return 0, .Unknown_Compression_Method + } + + if len(model.successors_reversed) != int(model.successor_count) * int(model.max_char - model.min_char) { + return 0, .Unknown_Compression_Method + } + + // Model seems legit. + return 0, nil +} + +// Decompresses into provided buffer. +decompress_slice_to_output_buffer :: proc(input: []u8, output: []u8, model := DEFAULT_MODEL) -> (size: int, err: compress.Error) { + inp, inp_end := 0, len(input) + out, out_end := 0, len(output) + + validate_model(model) or_return + + for inp < inp_end { + val := transmute(i8)input[inp] + mark := int(-1) + + for val < 0 { + val <<= 1 + mark += 1 + } + + if mark > len(model.packs) { + return out, .Unknown_Compression_Method + } + + if mark < 0 { + if out >= out_end { + return out, .Output_Too_Short + } + + // Ignore the sentinel value for non-ASCII chars + if input[inp] == 0x00 { + inp += 1 + if inp >= inp_end { + return out, .Stream_Too_Short + } + } + output[out] = input[inp] + inp, out = inp + 1, out + 1 + + } else { + pack := model.packs[mark] + + if out + int(pack.bytes_unpacked) > out_end { + return out, .Output_Too_Short + } else if inp + int(pack.bytes_packed) > inp_end { + return out, .Stream_Too_Short + } + + code := intrinsics.unaligned_load((^u32)(&input[inp])) + when ODIN_ENDIAN == .Little { + code = intrinsics.byte_swap(code) + } + + // Unpack the leading char + offset := pack.offsets[0] + mask := pack.masks[0] + + last_chr := model.characters_by_id[(code >> offset) & u32(mask)] + output[out] = last_chr + + // Unpack the successor chars + for i := 1; i < int(pack.bytes_unpacked); i += 1 { + offset = pack.offsets[i] + mask = pack.masks[i] + + index_major := u32(last_chr - model.min_char) * u32(model.successor_count) + index_minor := (code >> offset) & u32(mask) + + last_chr = model.successors_reversed[index_major + index_minor] + + output[out + i] = last_chr + } + + out += int(pack.bytes_unpacked) + inp += int(pack.bytes_packed) + } + } + + return out, nil +} + +decompress_slice_to_string :: proc(input: []u8, model := DEFAULT_MODEL, allocator := context.allocator) -> (res: string, err: compress.Error) { + context.allocator = allocator + + if len(input) == 0 { + return "", .Stream_Too_Short + } + + max_output_size := decompress_bound(len(input), model) + + buf: [dynamic]u8 + if !resize(&buf, max_output_size) { + return "", .Out_Of_Memory + } + + length, result := decompress_slice_to_output_buffer(input, buf[:]) + resize(&buf, length) + return string(buf[:]), result +} +decompress :: proc{decompress_slice_to_output_buffer, decompress_slice_to_string} + +compress_string_to_buffer :: proc(input: string, output: []u8, model := DEFAULT_MODEL, allocator := context.allocator) -> (size: int, err: compress.Error) { + inp, inp_end := 0, len(input) + out, out_end := 0, len(output) + output := output + + validate_model(model) or_return + + indices := make([]i16, model.max_successor_n + 1) + defer delete(indices) + + last_resort := false + + encode: for inp < inp_end { + if last_resort { + last_resort = false + + if input[inp] & 0x80 == 0x80 { + // Non-ASCII case + if out + 2 > out_end { + return out, .Output_Too_Short + } + + // Put in a sentinel byte + output[out] = 0x00 + out += 1 + } else { + // An ASCII byte + if out + 1 > out_end { + return out, .Output_Too_Short + } + } + output[out] = input[inp] + out, inp = out + 1, inp + 1 + } else { + // Find the longest string of known successors + indices[0] = model.ids_by_character[input[inp]] + last_chr_index := indices[0] + + if last_chr_index < 0 { + last_resort = true + continue encode + } + + rest := inp_end - inp + n_consecutive: i8 = 1 + for ; n_consecutive <= model.max_successor_n; n_consecutive += 1 { + if inp_end > 0 && int(n_consecutive) == rest { + break + } + + current_index := model.ids_by_character[input[inp + int(n_consecutive)]] + if current_index < 0 { // '\0' is always -1 + break + } + + successor_index := model.successors_by_bigram[last_chr_index * i16(model.character_count) + current_index] + if successor_index < 0 { + break + } + + indices[n_consecutive] = i16(successor_index) + last_chr_index = current_index + } + + if n_consecutive < 2 { + last_resort = true + continue encode + } + + pack_n := find_best_encoding(indices, n_consecutive) + if pack_n >= 0 { + if out + int(model.packs[pack_n].bytes_packed) > out_end { + return out, .Output_Too_Short + } + + pack := model.packs[pack_n] + code := pack.word + + for i := 0; i < int(pack.bytes_unpacked); i += 1 { + code |= u32(indices[i]) << pack.offsets[i] + } + + // In the little-endian world, we need to swap what's in the register to match the memory representation. + when ODIN_ENDIAN == .Little { + code = intrinsics.byte_swap(code) + } + out_ptr := raw_data(output[out:]) + + switch pack.bytes_packed { + case 4: + intrinsics.unaligned_store(transmute(^u32)out_ptr, code) + case 2: + intrinsics.unaligned_store(transmute(^u16)out_ptr, u16(code)) + case 1: + intrinsics.unaligned_store(transmute(^u8)out_ptr, u8(code)) + case: + return out, .Unknown_Compression_Method + } + + out += int(pack.bytes_packed) + inp += int(pack.bytes_unpacked) + } else { + last_resort = true + continue encode + } + } + } + return out, nil +} + +compress_string :: proc(input: string, model := DEFAULT_MODEL, allocator := context.allocator) -> (output: []u8, err: compress.Error) { + context.allocator = allocator + + if len(input) == 0 { + return {}, .Stream_Too_Short + } + + max_output_size := compress_bound(len(input)) + + buf: [dynamic]u8 + if !resize(&buf, max_output_size) { + return {}, .Out_Of_Memory + } + + length, result := compress_string_to_buffer(input, buf[:]) + resize(&buf, length) + return buf[:length], result +} +compress :: proc{compress_string_to_buffer, compress_string}
\ No newline at end of file diff --git a/core/container/intrusive/list/intrusive_list.odin b/core/container/intrusive/list/intrusive_list.odin new file mode 100644 index 000000000..88e21edc5 --- /dev/null +++ b/core/container/intrusive/list/intrusive_list.odin @@ -0,0 +1,173 @@ +package container_intrusive_list + +import "core:intrinsics" + +// An intrusive doubly-linked list +// +// As this is an intrusive container, a `Node` must be embedded in your own +// structure which is conventionally called a "link". The use of `push_front` +// and `push_back` take the address of this node. Retrieving the data +// associated with the node requires finding the relative offset of the node +// of the parent structure. The parent type and field name are given to +// `iterator_*` procedures, or to the built-in `container_of` procedure. +// +// This data structure is two-pointers in size: +// 8 bytes on 32-bit platforms and 16 bytes on 64-bit platforms +List :: struct { + head: ^Node, + tail: ^Node, +} + + +Node :: struct { + next, prev: ^Node, +} + +push_front :: proc(list: ^List, node: ^Node) { + if list.head != nil { + list.head.prev = node + node.prev, node.next = nil, list.head + list.head = node + } else { + list.head, list.tail = node, node + node.prev, node.next = nil, nil + } +} + +push_back :: proc(list: ^List, node: ^Node) { + if list.tail != nil { + list.tail.next = node + node.prev, node.next = list.tail, nil + list.tail = node + } else { + list.head, list.tail = node, node + node.prev, node.next = nil, nil + } +} + +remove :: proc(list: ^List, node: ^Node) { + if node != nil { + if node.next != nil { + node.next.prev = node.prev + } + if node.prev != nil { + node.prev.next = node.next + } + if list.head == node { + list.head = node.next + } + if list.tail == node { + list.tail = node.prev + } + } +} + +remove_by_proc :: proc(list: ^List, to_erase: proc(^Node) -> bool) { + for node := list.head; node != nil; { + next := node.next + if to_erase(node) { + if node.next != nil { + node.next.prev = node.prev + } + if node.prev != nil { + node.prev.next = node.next + } + if list.head == node { + list.head = node.next + } + if list.tail == node { + list.tail = node.prev + } + } + node = next + } +} + + +is_empty :: proc(list: ^List) -> bool { + return list.head == nil +} + +pop_front :: proc(list: ^List) -> ^Node { + link := list.head + if link == nil { + return nil + } + if link.next != nil { + link.next.prev = link.prev + } + if link.prev != nil { + link.prev.next = link.next + } + if link == list.head { + list.head = link.next + } + if link == list.tail { + list.tail = link.prev + } + return link + +} +pop_back :: proc(list: ^List) -> ^Node { + link := list.tail + if link == nil { + return nil + } + if link.next != nil { + link.next.prev = link.prev + } + if link.prev != nil { + link.prev.next = link.next + } + if link == list.head { + list.head = link.next + } + if link == list.tail { + list.tail = link.prev + } + return link +} + + +Iterator :: struct($T: typeid) { + curr: ^Node, + offset: uintptr, +} + +iterator_head :: proc(list: List, $T: typeid, $field_name: string) -> Iterator(T) + where intrinsics.type_has_field(T, field_name), + intrinsics.type_field_type(T, field_name) == Node { + return {list.head, offset_of_by_string(T, field_name)} +} + +iterator_tail :: proc(list: List, $T: typeid, $field_name: string) -> Iterator(T) + where intrinsics.type_has_field(T, field_name), + intrinsics.type_field_type(T, field_name) == Node { + return {list.tail, offset_of_by_string(T, field_name)} +} + +iterator_from_node :: proc(node: ^Node, $T: typeid, $field_name: string) -> Iterator(T) + where intrinsics.type_has_field(T, field_name), + intrinsics.type_field_type(T, field_name) == Node { + return {node, offset_of_by_string(T, field_name)} +} + +iterate_next :: proc(it: ^Iterator($T)) -> (ptr: ^T, ok: bool) { + node := it.curr + if node == nil { + return nil, false + } + it.curr = node.next + + return (^T)(uintptr(node) - it.offset), true +} + +iterate_prev :: proc(it: ^Iterator($T)) -> (ptr: ^T, ok: bool) { + node := it.curr + if node == nil { + return nil, false + } + it.curr = node.prev + + return (^T)(uintptr(node) - it.offset), true +}
\ No newline at end of file diff --git a/core/container/lru/lru_cache.odin b/core/container/lru/lru_cache.odin index f8e6f7b46..b59f29f0c 100644 --- a/core/container/lru/lru_cache.odin +++ b/core/container/lru/lru_cache.odin @@ -60,19 +60,27 @@ clear :: proc(c: ^$C/Cache($Key, $Value), call_on_remove: bool) { set :: proc(c: ^$C/Cache($Key, $Value), key: Key, value: Value) -> runtime.Allocator_Error { if e, ok := c.entries[key]; ok { e.value = value + _pop_node(c, e) + _push_front_node(c, e) return nil } - e := new(Node(Key, Value), c.node_allocator) or_return + e : ^Node(Key, Value) = nil + assert(c.count <= c.capacity) + if c.count == c.capacity { + e = c.tail + _remove_node(c, e) + } + else { + c.count += 1 + e = new(Node(Key, Value), c.node_allocator) or_return + } + e.key = key e.value = value - _push_front_node(c, e) - if c.count > c.capacity { - _remove_node(c, c.tail) - } - c.entries[key] = e + return nil } @@ -122,6 +130,8 @@ remove :: proc(c: ^$C/Cache($Key, $Value), key: Key) -> bool { return false } _remove_node(c, e) + free(node, c.node_allocator) + c.count -= 1 return true } @@ -143,14 +153,9 @@ _remove_node :: proc(c: ^$C/Cache($Key, $Value), node: ^Node(Key, Value)) { node.prev = nil node.next = nil - c.count -= 1 - delete_key(&c.entries, node.key) _call_on_remove(c, node) - - free(node, c.node_allocator) - } @(private) @@ -171,8 +176,6 @@ _push_front_node :: proc(c: ^$C/Cache($Key, $Value), e: ^Node(Key, Value)) { c.tail = e } e.prev = nil - - c.count += 1 } @(private) @@ -180,6 +183,12 @@ _pop_node :: proc(c: ^$C/Cache($Key, $Value), e: ^Node(Key, Value)) { if e == nil { return } + if c.head == e { + c.head = e.next + } + if c.tail == e { + c.tail = e.prev + } if e.prev != nil { e.prev.next = e.next } diff --git a/core/container/small_array/small_array.odin b/core/container/small_array/small_array.odin index 5cd421c84..4dd16f30c 100644 --- a/core/container/small_array/small_array.odin +++ b/core/container/small_array/small_array.odin @@ -86,7 +86,7 @@ pop_back_safe :: proc(a: ^$A/Small_Array($N, $T)) -> (item: T, ok: bool) { return } -pop_front_safe :: proc(a: ^$A/Small_Array($N, $T)) -> (T, bool) { +pop_front_safe :: proc(a: ^$A/Small_Array($N, $T)) -> (item: T, ok: bool) { if N > 0 && a.len > 0 { item = a.data[0] s := slice(a) @@ -114,4 +114,4 @@ push_back_elems :: proc(a: ^$A/Small_Array($N, $T), items: ..T) { append_elem :: push_back append_elems :: push_back_elems push :: proc{push_back, push_back_elems} -append :: proc{push_back, push_back_elems}
\ No newline at end of file +append :: proc{push_back, push_back_elems} diff --git a/core/crypto/sha2/sha2.odin b/core/crypto/sha2/sha2.odin index 7c7b2da81..9792a4cb8 100644 --- a/core/crypto/sha2/sha2.odin +++ b/core/crypto/sha2/sha2.odin @@ -419,8 +419,10 @@ update :: proc(ctx: ^$T, data: []byte) { sha2_transf(ctx, shifted_message, block_nb) rem_len = new_len % CURR_BLOCK_SIZE - when T == Sha256_Context {copy(ctx.block[:], shifted_message[block_nb << 6:rem_len])} - else when T == Sha512_Context {copy(ctx.block[:], shifted_message[block_nb << 7:rem_len])} + if rem_len > 0 { + when T == Sha256_Context {copy(ctx.block[:], shifted_message[block_nb << 6:rem_len])} + else when T == Sha512_Context {copy(ctx.block[:], shifted_message[block_nb << 7:rem_len])} + } ctx.length = rem_len when T == Sha256_Context {ctx.tot_len += (block_nb + 1) << 6} diff --git a/core/encoding/csv/reader.odin b/core/encoding/csv/reader.odin index aecb73d7b..f8f1d4051 100644 --- a/core/encoding/csv/reader.odin +++ b/core/encoding/csv/reader.odin @@ -34,6 +34,10 @@ Reader :: struct { // If lazy_quotes is true, a quote may appear in an unquoted field and a non-doubled quote may appear in a quoted field lazy_quotes: bool, + // multiline_fields, when set to true, will treat a field starting with a " as a multiline string + // therefore, instead of reading until the next \n, it'll read until the next " + multiline_fields: bool, + // reuse_record controls whether calls to 'read' may return a slice using the backing buffer // for performance // By default, each call to 'read' returns a newly allocated slice @@ -194,32 +198,72 @@ is_valid_delim :: proc(r: rune) -> bool { @private _read_record :: proc(r: ^Reader, dst: ^[dynamic]string, allocator := context.allocator) -> ([]string, Error) { read_line :: proc(r: ^Reader) -> ([]byte, io.Error) { - line, err := bufio.reader_read_slice(&r.r, '\n') - if err == .Buffer_Full { - clear(&r.raw_buffer) - append(&r.raw_buffer, ..line) - for err == .Buffer_Full { - line, err = bufio.reader_read_slice(&r.r, '\n') + if !r.multiline_fields { + line, err := bufio.reader_read_slice(&r.r, '\n') + if err == .Buffer_Full { + clear(&r.raw_buffer) append(&r.raw_buffer, ..line) + for err == .Buffer_Full { + line, err = bufio.reader_read_slice(&r.r, '\n') + append(&r.raw_buffer, ..line) + } + line = r.raw_buffer[:] } - line = r.raw_buffer[:] - } - if len(line) > 0 && err == .EOF { - err = nil - if line[len(line)-1] == '\r' { - line = line[:len(line)-1] + if len(line) > 0 && err == .EOF { + err = nil + if line[len(line)-1] == '\r' { + line = line[:len(line)-1] + } } - } - r.line_count += 1 + r.line_count += 1 - // normalize \r\n to \n - n := len(line) - for n >= 2 && string(line[n-2:]) == "\r\n" { - line[n-2] = '\n' - line = line[:n-1] - } + // normalize \r\n to \n + n := len(line) + for n >= 2 && string(line[n-2:]) == "\r\n" { + line[n-2] = '\n' + line = line[:n-1] + } + return line, err + + } else { + // Reading a "line" that can possibly contain multiline fields. + // Unfortunately, this means we need to read a character at a time. - return line, err + err: io.Error + cur: rune + is_quoted: bool + + field_length := 0 + + clear(&r.raw_buffer) + + read_loop: for err == .None { + cur, _, err = bufio.reader_read_rune(&r.r) + + if err != .None { break read_loop } + + switch cur { + case '"': + is_quoted = field_length == 0 + field_length += 1 + + case '\n', '\r': + if !is_quoted { break read_loop } + + case r.comma: + field_length = 0 + + case: + field_length += 1 + } + + rune_buf, rune_len := utf8.encode_rune(cur) + append(&r.raw_buffer, ..rune_buf[:rune_len]) + } + + return r.raw_buffer[:], err + } + unreachable() } length_newline :: proc(b: []byte) -> int { diff --git a/core/encoding/endian/doc.odin b/core/encoding/endian/doc.odin new file mode 100644 index 000000000..754ffa583 --- /dev/null +++ b/core/encoding/endian/doc.odin @@ -0,0 +1,23 @@ +/* + Package endian implements sa simple translation between bytes and numbers with + specific endian encodings. + + buf: [100]u8 + put_u16(buf[:], .Little, 16) or_return + + You may ask yourself, why isn't `byte_order` platform Endianness by default, so we can write: + put_u16(buf[:], 16) or_return + + The answer is that very few file formats are written in native/platform endianness. Most of them specify the endianness of + each of their fields, or use a header field which specifies it for the entire file. + + e.g. a file which specifies it at the top for all fields could do this: + file_order := .Little if buf[0] == 0 else .Big + field := get_u16(buf[1:], file_order) or_return + + If on the other hand a field is *always* Big-Endian, you're wise to explicitly state it for the benefit of the reader, + be that your future self or someone else. + + field := get_u16(buf[:], .Big) or_return +*/ +package encoding_endian diff --git a/core/encoding/endian/endian.odin b/core/encoding/endian/endian.odin new file mode 100644 index 000000000..08bde3139 --- /dev/null +++ b/core/encoding/endian/endian.odin @@ -0,0 +1,153 @@ +package encoding_endian + +Byte_Order :: enum u8 { + Little, + Big, +} + +PLATFORM_BYTE_ORDER :: Byte_Order.Little when ODIN_ENDIAN == .Little else Byte_Order.Big + +get_u16 :: proc(b: []byte, order: Byte_Order) -> (v: u16, ok: bool) { + if len(b) < 2 { + return 0, false + } + #no_bounds_check if order == .Little { + v = u16(b[0]) | u16(b[1])<<8 + } else { + v = u16(b[1]) | u16(b[0])<<8 + } + return v, true +} +get_u32 :: proc(b: []byte, order: Byte_Order) -> (v: u32, ok: bool) { + if len(b) < 4 { + return 0, false + } + #no_bounds_check if order == .Little { + v = u32(b[0]) | u32(b[1])<<8 | u32(b[2])<<16 | u32(b[3])<<24 + } else { + v = u32(b[3]) | u32(b[2])<<8 | u32(b[1])<<16 | u32(b[0])<<24 + } + return v, true +} + +get_u64 :: proc(b: []byte, order: Byte_Order) -> (v: u64, ok: bool) { + if len(b) < 8 { + return 0, false + } + #no_bounds_check if order == .Little { + v = u64(b[0]) | u64(b[1])<<8 | u64(b[2])<<16 | u64(b[3])<<24 | + u64(b[4])<<32 | u64(b[5])<<40 | u64(b[6])<<48 | u64(b[7])<<56 + } else { + v = u64(b[7]) | u64(b[6])<<8 | u64(b[5])<<16 | u64(b[4])<<24 | + u64(b[3])<<32 | u64(b[2])<<40 | u64(b[1])<<48 | u64(b[0])<<56 + } + return v, true +} + +get_i16 :: proc(b: []byte, order: Byte_Order) -> (i16, bool) { + v, ok := get_u16(b, order) + return i16(v), ok +} +get_i32 :: proc(b: []byte, order: Byte_Order) -> (i32, bool) { + v, ok := get_u32(b, order) + return i32(v), ok +} +get_i64 :: proc(b: []byte, order: Byte_Order) -> (i64, bool) { + v, ok := get_u64(b, order) + return i64(v), ok +} + +get_f16 :: proc(b: []byte, order: Byte_Order) -> (f16, bool) { + v, ok := get_u16(b, order) + return transmute(f16)v, ok +} +get_f32 :: proc(b: []byte, order: Byte_Order) -> (f32, bool) { + v, ok := get_u32(b, order) + return transmute(f32)v, ok +} +get_f64 :: proc(b: []byte, order: Byte_Order) -> (f64, bool) { + v, ok := get_u64(b, order) + return transmute(f64)v, ok +} + + +put_u16 :: proc(b: []byte, order: Byte_Order, v: u16) -> bool { + if len(b) < 2 { + return false + } + #no_bounds_check if order == .Little { + b[0] = byte(v) + b[1] = byte(v >> 8) + } else { + b[0] = byte(v >> 8) + b[1] = byte(v) + } + return true +} +put_u32 :: proc(b: []byte, order: Byte_Order, v: u32) -> bool { + if len(b) < 4 { + return false + } + #no_bounds_check if order == .Little { + b[0] = byte(v) + b[1] = byte(v >> 8) + b[2] = byte(v >> 16) + b[3] = byte(v >> 24) + } else { + b[0] = byte(v >> 24) + b[1] = byte(v >> 16) + b[2] = byte(v >> 8) + b[3] = byte(v) + } + return true +} +put_u64 :: proc(b: []byte, order: Byte_Order, v: u64) -> bool { + if len(b) < 8 { + return false + } + #no_bounds_check if order == .Little { + b[0] = byte(v >> 0) + b[1] = byte(v >> 8) + b[2] = byte(v >> 16) + b[3] = byte(v >> 24) + b[4] = byte(v >> 32) + b[5] = byte(v >> 40) + b[6] = byte(v >> 48) + b[7] = byte(v >> 56) + } else { + b[0] = byte(v >> 56) + b[1] = byte(v >> 48) + b[2] = byte(v >> 40) + b[3] = byte(v >> 32) + b[4] = byte(v >> 24) + b[5] = byte(v >> 16) + b[6] = byte(v >> 8) + b[7] = byte(v) + } + return true +} + +put_i16 :: proc(b: []byte, order: Byte_Order, v: i16) -> bool { + return put_u16(b, order, u16(v)) +} + +put_i32 :: proc(b: []byte, order: Byte_Order, v: i32) -> bool { + return put_u32(b, order, u32(v)) +} + +put_i64 :: proc(b: []byte, order: Byte_Order, v: i64) -> bool { + return put_u64(b, order, u64(v)) +} + + +put_f16 :: proc(b: []byte, order: Byte_Order, v: f16) -> bool { + return put_u16(b, order, transmute(u16)v) +} + +put_f32 :: proc(b: []byte, order: Byte_Order, v: f32) -> bool { + return put_u32(b, order, transmute(u32)v) +} + +put_f64 :: proc(b: []byte, order: Byte_Order, v: f64) -> bool { + return put_u64(b, order, transmute(u64)v) +} diff --git a/core/encoding/entity/LICENSE_table.md b/core/encoding/entity/LICENSE_table.md new file mode 100644 index 000000000..51e3f34b5 --- /dev/null +++ b/core/encoding/entity/LICENSE_table.md @@ -0,0 +1,21 @@ +# License
+
+By obtaining, using and/or copying this work, you (the licensee) agree that you have read, understood, and will comply with the following terms and conditions.
+
+Permission to copy, modify, and distribute this software and its documentation, with or without modification, for any purpose and without fee or royalty is hereby granted, provided that you include the following on ALL copies of the software and documentation or portions thereof, including modifications:
+
+The full text of this NOTICE in a location viewable to users of the redistributed or derivative work.
+Any pre-existing intellectual property disclaimers, notices, or terms and conditions. If none exist, the W3C Software Short Notice should be included (hypertext is preferred, text is permitted) within the body of any redistributed or derivative code.
+
+Notice of any changes or modifications to the files, including the date changes were made. (We recommend you provide URIs to the location from which the code is derived.)
+
+# Disclaimers
+
+THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.
+
+COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENTATION.
+
+The name and trademarks of copyright holders may NOT be used in advertising or publicity pertaining to the software without specific, written prior permission. Title to copyright in this software and any associated documentation will at all times remain with copyright holders.
+
+# Notes
+This version: http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231
\ No newline at end of file diff --git a/core/encoding/entity/entity.odin b/core/encoding/entity/entity.odin new file mode 100644 index 000000000..db1a5ad0b --- /dev/null +++ b/core/encoding/entity/entity.odin @@ -0,0 +1,374 @@ +package unicode_entity +/* + A unicode entity encoder/decoder + + Copyright 2021 Jeroen van Rijn <nom@duclavier.com>. + Made available under Odin's BSD-3 license. + + This code has several procedures to map unicode runes to/from different textual encodings. + - SGML/XML/HTML entity + -- &#<decimal>; + -- &#x<hexadecimal>; + -- &<entity name>; (If the lookup tables are compiled in). + Reference: https://www.w3.org/2003/entities/2007xml/unicode.xml + + - URL encode / decode %hex entity + Reference: https://datatracker.ietf.org/doc/html/rfc3986/#section-2.1 + + List of contributors: + Jeroen van Rijn: Initial implementation. +*/ + +import "core:unicode/utf8" +import "core:unicode" +import "core:strings" + +MAX_RUNE_CODEPOINT :: int(unicode.MAX_RUNE) + +write_rune :: strings.write_rune_builder +write_string :: strings.write_string_builder + +Error :: enum u8 { + None = 0, + Tokenizer_Is_Nil, + + Illegal_NUL_Character, + Illegal_UTF_Encoding, + Illegal_BOM, + + CDATA_Not_Terminated, + Comment_Not_Terminated, + Invalid_Entity_Encoding, +} + +Tokenizer :: struct { + r: rune, + w: int, + + src: string, + offset: int, + read_offset: int, +} + +CDATA_START :: "<![CDATA[" +CDATA_END :: "]]>" + +COMMENT_START :: "<!--" +COMMENT_END :: "-->" + +/* + Default: CDATA and comments are passed through unchanged. +*/ +XML_Decode_Option :: enum u8 { + /* + Do not decode & entities. It decodes by default. + If given, overrides `Decode_CDATA`. + */ + No_Entity_Decode, + + /* + CDATA is unboxed. + */ + Unbox_CDATA, + + /* + Unboxed CDATA is decoded as well. + Ignored if `.Unbox_CDATA` is not given. + */ + Decode_CDATA, + + /* + Comments are stripped. + */ + Comment_Strip, +} +XML_Decode_Options :: bit_set[XML_Decode_Option; u8] + +/* + Decode a string that may include SGML/XML/HTML entities. + The caller has to free the result. +*/ +decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator := context.allocator) -> (decoded: string, err: Error) { + context.allocator = allocator + + l := len(input) + if l == 0 { return "", .None } + + builder := strings.make_builder() + defer strings.destroy_builder(&builder) + + t := Tokenizer{src=input} + in_data := false + + loop: for { + advance(&t) or_return + if t.r < 0 { break loop } + + /* + Below here we're never inside a CDATA tag. + At most we'll see the start of one, but that doesn't affect the logic. + */ + switch t.r { + case '<': + /* + Might be the start of a CDATA tag or comment. + + We don't need to check if we need to write a `<`, because if it isn't CDATA or a comment, + it couldn't have been part of an XML tag body to be decoded here. + + Keep in mind that we could already *be* inside a CDATA tag. + If so, write `>` as a literal and continue. + */ + if in_data { + write_rune(&builder, '<') + continue + } + in_data = _handle_xml_special(&t, &builder, options) or_return + + case ']': + /* + If we're unboxing _and_ decoding CDATA, we'll have to check for the end tag. + */ + if in_data { + if t.read_offset + len(CDATA_END) < len(t.src) { + if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END { + in_data = false + t.read_offset += len(CDATA_END) - 1 + } + } + continue + } else { + write_rune(&builder, ']') + } + + case: + if in_data && .Decode_CDATA not_in options { + /* + Unboxed, but undecoded. + */ + write_rune(&builder, t.r) + continue + } + + if t.r == '&' { + if entity, entity_err := _extract_xml_entity(&t); entity_err != .None { + /* + We read to the end of the string without closing the entity. + Pass through as-is. + */ + write_string(&builder, entity) + } else { + + if .No_Entity_Decode not_in options { + if decoded, ok := xml_decode_entity(entity); ok { + write_rune(&builder, decoded) + continue + } + } + + /* + Literal passthrough because the decode failed or we want entities not decoded. + */ + write_string(&builder, "&") + write_string(&builder, entity) + write_string(&builder, ";") + } + } else { + write_rune(&builder, t.r) + } + } + } + + return strings.clone(strings.to_string(builder), allocator), err +} + +advance :: proc(t: ^Tokenizer) -> (err: Error) { + if t == nil { return .Tokenizer_Is_Nil } + using t + + #no_bounds_check { + if read_offset < len(src) { + offset = read_offset + r, w = rune(src[read_offset]), 1 + switch { + case r == 0: + return .Illegal_NUL_Character + case r >= utf8.RUNE_SELF: + r, w = utf8.decode_rune_in_string(src[read_offset:]) + if r == utf8.RUNE_ERROR && w == 1 { + return .Illegal_UTF_Encoding + } else if r == utf8.RUNE_BOM && offset > 0 { + return .Illegal_BOM + } + } + read_offset += w + return .None + } else { + offset = len(src) + r = -1 + return + } + } +} + +xml_decode_entity :: proc(entity: string) -> (decoded: rune, ok: bool) { + entity := entity + if len(entity) == 0 { return -1, false } + + switch entity[0] { + case '#': + base := 10 + val := 0 + entity = entity[1:] + + if len(entity) == 0 { return -1, false } + + if entity[0] == 'x' || entity[0] == 'X' { + base = 16 + entity = entity[1:] + } + + for len(entity) > 0 { + r := entity[0] + switch r { + case '0'..'9': + val *= base + val += int(r - '0') + + case 'a'..'f': + if base == 10 { return -1, false } + val *= base + val += int(r - 'a' + 10) + + case 'A'..'F': + if base == 10 { return -1, false } + val *= base + val += int(r - 'A' + 10) + + case: + return -1, false + } + + if val > MAX_RUNE_CODEPOINT { return -1, false } + entity = entity[1:] + } + return rune(val), true + + case: + /* + Named entity. + */ + return named_xml_entity_to_rune(entity) + } +} + +/* + Private XML helper to extract `&<stuff>;` entity. +*/ +@(private="file") +_extract_xml_entity :: proc(t: ^Tokenizer) -> (entity: string, err: Error) { + assert(t != nil && t.r == '&') + + /* + All of these would be in the ASCII range. + Even if one is not, it doesn't matter. All characters we need to compare to extract are. + */ + using t + + length := len(t.src) + found := false + + #no_bounds_check { + for read_offset < length { + if src[read_offset] == ';' { + found = true + read_offset += 1 + break + } + read_offset += 1 + } + } + + if found { + return string(src[offset + 1 : read_offset - 1]), .None + } + return string(src[offset : read_offset]), .Invalid_Entity_Encoding +} + +/* + Private XML helper for CDATA and comments. +*/ +@(private="file") +_handle_xml_special :: proc(t: ^Tokenizer, builder: ^strings.Builder, options: XML_Decode_Options) -> (in_data: bool, err: Error) { + assert(t != nil && t.r == '<') + if t.read_offset + len(CDATA_START) >= len(t.src) { return false, .None } + + if string(t.src[t.offset:][:len(CDATA_START)]) == CDATA_START { + t.read_offset += len(CDATA_START) - 1 + + if .Unbox_CDATA in options && .Decode_CDATA in options { + /* + We're unboxing _and_ decoding CDATA + */ + return true, .None + } + + /* + CDATA is passed through. + */ + offset := t.offset + + /* + Scan until end of CDATA. + */ + for { + advance(t) or_return + if t.r < 0 { return true, .CDATA_Not_Terminated } + + if t.read_offset + len(CDATA_END) < len(t.src) { + if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END { + t.read_offset += len(CDATA_END) - 1 + + cdata := string(t.src[offset : t.read_offset]) + + if .Unbox_CDATA in options { + cdata = cdata[len(CDATA_START):] + cdata = cdata[:len(cdata) - len(CDATA_END)] + } + + write_string(builder, cdata) + return false, .None + } + } + } + + } else if string(t.src[t.offset:][:len(COMMENT_START)]) == COMMENT_START { + t.read_offset += len(COMMENT_START) + /* + Comment is passed through by default. + */ + offset := t.offset + + /* + Scan until end of Comment. + */ + for { + advance(t) or_return + if t.r < 0 { return true, .Comment_Not_Terminated } + + if t.read_offset + len(COMMENT_END) < len(t.src) { + if string(t.src[t.offset:][:len(COMMENT_END)]) == COMMENT_END { + t.read_offset += len(COMMENT_END) - 1 + + if .Comment_Strip not_in options { + comment := string(t.src[offset : t.read_offset]) + write_string(builder, comment) + } + return false, .None + } + } + } + + } + return false, .None +}
\ No newline at end of file diff --git a/core/encoding/entity/example/entity_example.odin b/core/encoding/entity/example/entity_example.odin new file mode 100644 index 000000000..6fc377f9d --- /dev/null +++ b/core/encoding/entity/example/entity_example.odin @@ -0,0 +1,76 @@ +package unicode_entity_example + +import "core:encoding/xml" +import "core:strings" +import "core:mem" +import "core:fmt" +import "core:time" + +doc_print :: proc(doc: ^xml.Document) { + buf: strings.Builder + defer strings.destroy_builder(&buf) + w := strings.to_writer(&buf) + + xml.print(w, doc) + fmt.println(strings.to_string(buf)) +} + +_entities :: proc() { + doc: ^xml.Document + err: xml.Error + + DOC :: #load("../../../../tests/core/assets/XML/unicode.xml") + + OPTIONS :: xml.Options{ + flags = { + .Ignore_Unsupported, .Intern_Comments, + }, + expected_doctype = "", + } + + parse_duration: time.Duration + + { + time.SCOPED_TICK_DURATION(&parse_duration) + doc, err = xml.parse(DOC, OPTIONS) + } + defer xml.destroy(doc) + + doc_print(doc) + + ms := time.duration_milliseconds(parse_duration) + + speed := (f64(1000.0) / ms) * f64(len(DOC)) / 1_024.0 / 1_024.0 + + fmt.printf("Parse time: %.2f ms (%.2f MiB/s).\n", ms, speed) + fmt.printf("Error: %v\n", err) +} + +_main :: proc() { + using fmt + + options := xml.Options{ flags = { .Ignore_Unsupported, .Intern_Comments, .Unbox_CDATA, .Decode_SGML_Entities }} + + doc, _ := xml.parse(#load("test.html"), options) + + defer xml.destroy(doc) + doc_print(doc) +} + +main :: proc() { + using fmt + + track: mem.Tracking_Allocator + mem.tracking_allocator_init(&track, context.allocator) + context.allocator = mem.tracking_allocator(&track) + + // _main() + _entities() + + if len(track.allocation_map) > 0 { + println() + for _, v in track.allocation_map { + printf("%v Leaked %v bytes.\n", v.location, v.size) + } + } +}
\ No newline at end of file diff --git a/core/encoding/entity/example/test.html b/core/encoding/entity/example/test.html new file mode 100644 index 000000000..ebbc6470c --- /dev/null +++ b/core/encoding/entity/example/test.html @@ -0,0 +1,28 @@ +<html>
+ <head>
+ <title>Entity Reference Test</title>
+ <style>
+ body {
+ background: #000; color: #eee;
+ width: 40%;
+ margin-left: auto;
+ margin-right: auto;
+ font-size: 14pt;
+ }
+ </style>
+ </head>
+ <body>
+ <h1>Entity Reference Test</h1>
+ <div id="test_cdata_in_comment" foo="">
+ Foozle]! © <!-- <![CDATA[ ® ]]> -->42&;1234&
+ </div>
+ <!-- EXPECTED: Foozle]! © 42&;1234& -->
+ <div id="test_cdata_unwrap_and_passthrough">
+ Foozle]! © <![CDATA[BOX ® /BOX]]>42&;1234&
+ </div>
+ <!-- EXPECTED: Foozle]! © BOX ® /BOX42&;1234& -->
+ <div>
+ | | | fj ` \ ® ϱ ∳ ⁏
+ </div>
+ </body>
+</html>
\ No newline at end of file diff --git a/core/encoding/entity/generated.odin b/core/encoding/entity/generated.odin new file mode 100644 index 000000000..9afdcae6d --- /dev/null +++ b/core/encoding/entity/generated.odin @@ -0,0 +1,7493 @@ +package unicode_entity + +/* + ------ GENERATED ------ DO NOT EDIT ------ GENERATED ------ DO NOT EDIT ------ GENERATED ------ +*/ + +/* + This file is generated from "https://www.w3.org/2003/entities/2007xml/unicode.xml". + + UPDATE: + - Ensure the XML file was downloaded using "tests\core\download_assets.py". + - Run "core/unicode/tools/generate_entity_table.odin" + + Odin unicode generated tables: https://github.com/odin-lang/Odin/tree/master/core/encoding/entity + + Copyright © 2021 World Wide Web Consortium, (Massachusetts Institute of Technology, + European Research Consortium for Informatics and Mathematics, Keio University, Beihang). + + All Rights Reserved. + + This work is distributed under the W3C® Software License [1] in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + [1] http://www.w3.org/Consortium/Legal/copyright-software + + See also: LICENSE_table.md +*/ + +// `<` +XML_NAME_TO_RUNE_MIN_LENGTH :: 2 +// `∳` +XML_NAME_TO_RUNE_MAX_LENGTH :: 31 + + +/* + Input: + entity_name - a string, like "copy" that describes a user-encoded Unicode entity as used in XML. + + Output: + "decoded" - The decoded rune if found by name, or -1 otherwise. + "ok" - true if found, false if not. + + IMPORTANT: XML processors (including browsers) treat these names as case-sensitive. So do we. +*/ +named_xml_entity_to_rune :: proc(name: string) -> (decoded: rune, ok: bool) { + /* + Early out if the name is too short or too long. + min as a precaution in case the generated table has a bogus value. + */ + if len(name) < min(1, XML_NAME_TO_RUNE_MIN_LENGTH) || len(name) > XML_NAME_TO_RUNE_MAX_LENGTH { + return -1, false + } + + switch rune(name[0]) { + + case 'A': + switch name { + case "AElig": + // LATIN CAPITAL LETTER AE + return rune(0xc6), true + case "AMP": + // AMPERSAND + return rune(0x26), true + case "Aacgr": + // GREEK CAPITAL LETTER ALPHA WITH TONOS + return rune(0x0386), true + case "Aacute": + // LATIN CAPITAL LETTER A WITH ACUTE + return rune(0xc1), true + case "Abreve": + // LATIN CAPITAL LETTER A WITH BREVE + return rune(0x0102), true + case "Acirc": + // LATIN CAPITAL LETTER A WITH CIRCUMFLEX + return rune(0xc2), true + case "Acy": + // CYRILLIC CAPITAL LETTER A + return rune(0x0410), true + case "Afr": + // MATHEMATICAL FRAKTUR CAPITAL A + return rune(0x01d504), true + case "Agrave": + // LATIN CAPITAL LETTER A WITH GRAVE + return rune(0xc0), true + case "Agr": + // GREEK CAPITAL LETTER ALPHA + return rune(0x0391), true + case "Alpha": + // GREEK CAPITAL LETTER ALPHA + return rune(0x0391), true + case "Amacr": + // LATIN CAPITAL LETTER A WITH MACRON + return rune(0x0100), true + case "And": + // DOUBLE LOGICAL AND + return rune(0x2a53), true + case "Aogon": + // LATIN CAPITAL LETTER A WITH OGONEK + return rune(0x0104), true + case "Aopf": + // MATHEMATICAL DOUBLE-STRUCK CAPITAL A + return rune(0x01d538), true + case "ApplyFunction": + // FUNCTION APPLICATION + return rune(0x2061), true + case "Aring": + // LATIN CAPITAL LETTER A WITH RING ABOVE + return rune(0xc5), true + case "Ascr": + // MATHEMATICAL SCRIPT CAPITAL A + return rune(0x01d49c), true + case "Assign": + // COLON EQUALS + return rune(0x2254), true + case "Ast": + // TWO ASTERISKS ALIGNED VERTICALLY + return rune(0x2051), true + case "Atilde": + // LATIN CAPITAL LETTER A WITH TILDE + return rune(0xc3), true + case "Auml": + // LATIN CAPITAL LETTER A WITH DIAERESIS + return rune(0xc4), true + } + + case 'B': + switch name { + case "Backslash": + // SET MINUS + return rune(0x2216), true + case "Barint": + // INTEGRAL WITH DOUBLE STROKE + return rune(0x2a0e), true + case "Barv": + // SHORT DOWN TACK WITH OVERBAR + return rune(0x2ae7), true + case "Barwedl": + // LOGICAL AND WITH DOUBLE OVERBAR + return rune(0x2a5e), true + case "Barwed": + // PERSPECTIVE + return rune(0x2306), true + case "Bcy": + // CYRILLIC CAPITAL LETTER BE + return rune(0x0411), true + case "Because": + // BECAUSE + return rune(0x2235), true + case "Bernoullis": + // SCRIPT CAPITAL B + return rune(0x212c), true + case "Beta": + // GREEK CAPITAL LETTER BETA + return rune(0x0392), true + case "Bfr": + // MATHEMATICAL FRAKTUR CAPITAL B + return rune(0x01d505), true + case "Bgr": + // GREEK CAPITAL LETTER BETA + return rune(0x0392), true + case "Bopf": + // MATHEMATICAL DOUBLE-STRUCK CAPITAL B + return rune(0x01d539), true + case "Breve": + // BREVE + return rune(0x02d8), true + case "Bscr": + // SCRIPT CAPITAL B + return rune(0x212c), true + case "Bumpeq": + // GEOMETRICALLY EQUIVALENT TO + return rune(0x224e), true + case "Bvert": + // BOX DRAWINGS LIGHT TRIPLE DASH VERTICAL + return rune(0x2506), true + } + + case 'C': + switch name { + case "CHcy": + // CYRILLIC CAPITAL LETTER CHE + return rune(0x0427), true + case "COPY": + // COPYRIGHT SIGN + return rune(0xa9), true + case "Cacute": + // LATIN CAPITAL LETTER C WITH ACUTE + return rune(0x0106), true + case "CapitalDifferentialD": + // DOUBLE-STRUCK ITALIC CAPITAL D + return rune(0x2145), true + case "Cap": + // DOUBLE INTERSECTION + return rune(0x22d2), true + case "Cayleys": + // BLACK-LETTER CAPITAL C + return rune(0x212d), true + case "Ccaron": + // LATIN CAPITAL LETTER C WITH CARON + return rune(0x010c), true + case "Ccedil": + // LATIN CAPITAL LETTER C WITH CEDILLA + return rune(0xc7), true + case "Ccirc": + // LATIN CAPITAL LETTER C WITH CIRCUMFLEX + return rune(0x0108), true + case "Cconint": + // VOLUME INTEGRAL + return rune(0x2230), true + case "Cdot": + // LATIN CAPITAL LETTER C WITH DOT ABOVE + return rune(0x010a), true + case "Cedilla": + // CEDILLA + return rune(0xb8), true + case "CenterDot": + // MIDDLE DOT + return rune(0xb7), true + case "Cfr": + // BLACK-LETTER CAPITAL C + return rune(0x212d), true + case "Chi": + // GREEK CAPITAL LETTER CHI + return rune(0x03a7), true + case "CircleDot": + // CIRCLED DOT OPERATOR + return rune(0x2299), true + case "CircleMinus": + // CIRCLED MINUS + return rune(0x2296), true + case "CirclePlus": + // CIRCLED PLUS + return rune(0x2295), true + case "CircleTimes": + // CIRCLED TIMES + return rune(0x2297), true + case "ClockwiseContourIntegral": + // CLOCKWISE CONTOUR INTEGRAL + return rune(0x2232), true + case "CloseCurlyDoubleQuote": + // RIGHT DOUBLE QUOTATION MARK + return rune(0x201d), true + case "CloseCurlyQuote": + // RIGHT SINGLE QUOTATION MARK + return rune(0x2019), true + case "Colon": + // PROPORTION + return rune(0x2237), true + case "Colone": + // DOUBLE COLON EQUAL + return rune(0x2a74), true + case "Congruent": + // IDENTICAL TO + return rune(0x2261), true + case "Conint": + // SURFACE INTEGRAL + return rune(0x222f), true + case "ContourIntegral": + // CONTOUR INTEGRAL + return rune(0x222e), true + case "Copf": + // DOUBLE-STRUCK CAPITAL C + return rune(0x2102), true + case "Coproduct": + // N-ARY COPRODUCT + return rune(0x2210), true + case "CounterClockwiseContourIntegral": + // ANTICLOCKWISE CONTOUR INTEGRAL + return rune(0x2233), true + case "Cross": + // VECTOR OR CROSS PRODUCT + return rune(0x2a2f), true + case "Cscr": + // MATHEMATICAL SCRIPT CAPITAL C + return rune(0x01d49e), true + case "CupCap": + // EQUIVALENT TO + return rune(0x224d), true + case "Cup": + // DOUBLE UNION + return rune(0x22d3), true + } + + case 'D': + switch name { + case "DD": + // DOUBLE-STRUCK ITALIC CAPITAL D + return rune(0x2145), true + case "DDotrahd": + // RIGHTWARDS ARROW WITH DOTTED STEM + return rune(0x2911), true + case "DJcy": + // CYRILLIC CAPITAL LETTER DJE + return rune(0x0402), true + case "DScy": + // CYRILLIC CAPITAL LETTER DZE + return rune(0x0405), true + case "DZcy": + // CYRILLIC CAPITAL LETTER DZHE + return rune(0x040f), true + case "Dagger": + // DOUBLE DAGGER + return rune(0x2021), true + case "Darr": + // DOWNWARDS TWO HEADED ARROW + return rune(0x21a1), true + case "Dashv": + // VERTICAL BAR DOUBLE LEFT TURNSTILE + return rune(0x2ae4), true + case "Dcaron": + // LATIN CAPITAL LETTER D WITH CARON + return rune(0x010e), true + case "Dcy": + // CYRILLIC CAPITAL LETTER DE + return rune(0x0414), true + case "Del": + // NABLA + return rune(0x2207), true + case "Delta": + // GREEK CAPITAL LETTER DELTA + return rune(0x0394), true + case "Dfr": + // MATHEMATICAL FRAKTUR CAPITAL D + return rune(0x01d507), true + case "Dgr": + // GREEK CAPITAL LETTER DELTA + return rune(0x0394), true + case "DiacriticalAcute": + // ACUTE ACCENT + return rune(0xb4), true + case "DiacriticalDot": + // DOT ABOVE + return rune(0x02d9), true + case "DiacriticalDoubleAcute": + // DOUBLE ACUTE ACCENT + return rune(0x02dd), true + case "DiacriticalGrave": + // GRAVE ACCENT + return rune(0x60), true + case "DiacriticalTilde": + // SMALL TILDE + return rune(0x02dc), true + case "Diamond": + // DIAMOND OPERATOR + return rune(0x22c4), true + case "DifferentialD": + // DOUBLE-STRUCK ITALIC SMALL D + return rune(0x2146), true + case "Dopf": + // MATHEMATICAL DOUBLE-STRUCK CAPITAL D + return rune(0x01d53b), true + case "Dot": + // DIAERESIS + return rune(0xa8), true + case "DotDot": + // COMBINING FOUR DOTS ABOVE + return rune(0x20dc), true + case "DotEqual": + // APPROACHES THE LIMIT + return rune(0x2250), true + case "DoubleContourIntegral": + // SURFACE INTEGRAL + return rune(0x222f), true + case "DoubleDot": + // DIAERESIS + return rune(0xa8), true + case "DoubleDownArrow": + // DOWNWARDS DOUBLE ARROW + return rune(0x21d3), true + case "DoubleLeftArrow": + // LEFTWARDS DOUBLE ARROW + return rune(0x21d0), true + case "DoubleLeftRightArrow": + // LEFT RIGHT DOUBLE ARROW + return rune(0x21d4), true + case "DoubleLeftTee": + // VERTICAL BAR DOUBLE LEFT TURNSTILE + return rune(0x2ae4), true + case "DoubleLongLeftArrow": + // LONG LEFTWARDS DOUBLE ARROW + return rune(0x27f8), true + case "DoubleLongLeftRightArrow": + // LONG LEFT RIGHT DOUBLE ARROW + return rune(0x27fa), true + case "DoubleLongRightArrow": + // LONG RIGHTWARDS DOUBLE ARROW + return rune(0x27f9), true + case "DoubleRightArrow": + // RIGHTWARDS DOUBLE ARROW + return rune(0x21d2), true + case "DoubleRightTee": + // TRUE + return rune(0x22a8), true + case "DoubleUpArrow": + // UPWARDS DOUBLE ARROW + return rune(0x21d1), true + case "DoubleUpDownArrow": + // UP DOWN DOUBLE ARROW + return rune(0x21d5), true + case "DoubleVerticalBar": + // PARALLEL TO + return rune(0x2225), true + case "DownArrowUpArrow": + // DOWNWARDS ARROW LEFTWARDS OF UPWARDS ARROW + return rune(0x21f5), true + case "DownArrow": + // DOWNWARDS ARROW + return rune(0x2193), true + case "DownArrowBar": + // DOWNWARDS ARROW TO BAR + return rune(0x2913), true + case "DownBreve": + // COMBINING INVERTED BREVE + return rune(0x0311), true + case "DownLeftRightVector": + // LEFT BARB DOWN RIGHT BARB DOWN HARPOON + return rune(0x2950), true + case "DownLeftTeeVector": + // LEFTWARDS HARPOON WITH BARB DOWN FROM BAR + return rune(0x295e), true + case "DownLeftVector": + // LEFTWARDS HARPOON WITH BARB DOWNWARDS + return rune(0x21bd), true + case "DownLeftVectorBar": + // LEFTWARDS HARPOON WITH BARB DOWN TO BAR + return rune(0x2956), true + case "DownRightTeeVector": + // RIGHTWARDS HARPOON WITH BARB DOWN FROM BAR + return rune(0x295f), true + case "DownRightVector": + // RIGHTWARDS HARPOON WITH BARB DOWNWARDS + return rune(0x21c1), true + case "DownRightVectorBar": + // RIGHTWARDS HARPOON WITH BARB DOWN TO BAR + return rune(0x2957), true + case "DownTeeArrow": + // DOWNWARDS ARROW FROM BAR + return rune(0x21a7), true + case "DownTee": + // DOWN TACK + return rune(0x22a4), true + case "Downarrow": + // DOWNWARDS DOUBLE ARROW + return rune(0x21d3), true + case "Dscr": + // MATHEMATICAL SCRIPT CAPITAL D + return rune(0x01d49f), true + case "Dstrok": + // LATIN CAPITAL LETTER D WITH STROKE + return rune(0x0110), true + } + + case 'E': + switch name { + case "EEacgr": + // GREEK CAPITAL LETTER ETA WITH TONOS + return rune(0x0389), true + case "EEgr": + // GREEK CAPITAL LETTER ETA + return rune(0x0397), true + case "ENG": + // LATIN CAPITAL LETTER ENG + return rune(0x014a), true + case "ETH": + // LATIN CAPITAL LETTER ETH + return rune(0xd0), true + case "Eacgr": + // GREEK CAPITAL LETTER EPSILON WITH TONOS + return rune(0x0388), true + case "Eacute": + // LATIN CAPITAL LETTER E WITH ACUTE + return rune(0xc9), true + case "Ecaron": + // LATIN CAPITAL LETTER E WITH CARON + return rune(0x011a), true + case "Ecirc": + // LATIN CAPITAL LETTER E WITH CIRCUMFLEX + return rune(0xca), true + case "Ecy": + // CYRILLIC CAPITAL LETTER E + return rune(0x042d), true + case "Edot": + // LATIN CAPITAL LETTER E WITH DOT ABOVE + return rune(0x0116), true + case "Efr": + // MATHEMATICAL FRAKTUR CAPITAL E + return rune(0x01d508), true + case "Egrave": + // LATIN CAPITAL LETTER E WITH GRAVE + return rune(0xc8), true + case "Egr": + // GREEK CAPITAL LETTER EPSILON + return rune(0x0395), true + case "Element": + // ELEMENT OF + return rune(0x2208), true + case "Emacr": + // LATIN CAPITAL LETTER E WITH MACRON + return rune(0x0112), true + case "EmptySmallSquare": + // WHITE MEDIUM SQUARE + return rune(0x25fb), true + case "EmptyVerySmallSquare": + // WHITE SMALL SQUARE + return rune(0x25ab), true + case "Eogon": + // LATIN CAPITAL LETTER E WITH OGONEK + return rune(0x0118), true + case "Eopf": + // MATHEMATICAL DOUBLE-STRUCK CAPITAL E + return rune(0x01d53c), true + case "Epsilon": + // GREEK CAPITAL LETTER EPSILON + return rune(0x0395), true + case "EqualTilde": + // MINUS TILDE + return rune(0x2242), true + case "Equal": + // TWO CONSECUTIVE EQUALS SIGNS + return rune(0x2a75), true + case "Equilibrium": + // RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON + return rune(0x21cc), true + case "Escr": + // SCRIPT CAPITAL E + return rune(0x2130), true + case "Esim": + // EQUALS SIGN ABOVE TILDE OPERATOR + return rune(0x2a73), true + case "Eta": + // GREEK CAPITAL LETTER ETA + return rune(0x0397), true + case "Euml": + // LATIN CAPITAL LETTER E WITH DIAERESIS + return rune(0xcb), true + case "Exists": + // THERE EXISTS + return rune(0x2203), true + case "ExponentialE": + // DOUBLE-STRUCK ITALIC SMALL E + return rune(0x2147), true + } + + case 'F': + switch name { + case "Fcy": + // CYRILLIC CAPITAL LETTER EF + return rune(0x0424), true + case "Ffr": + // MATHEMATICAL FRAKTUR CAPITAL F + return rune(0x01d509), true + case "FilledSmallSquare": + // BLACK MEDIUM SQUARE + return rune(0x25fc), true + case "FilledVerySmallSquare": + // BLACK SMALL SQUARE + return rune(0x25aa), true + case "Fopf": + // MATHEMATICAL DOUBLE-STRUCK CAPITAL F + return rune(0x01d53d), true + case "ForAll": + // FOR ALL + return rune(0x2200), true + case "Fouriertrf": + // SCRIPT CAPITAL F + return rune(0x2131), true + case "Fscr": + // SCRIPT CAPITAL F + return rune(0x2131), true + } + + case 'G': + switch name { + case "GJcy": + // CYRILLIC CAPITAL LETTER GJE + return rune(0x0403), true + case "GT": + // GREATER-THAN SIGN + return rune(0x3e), true + case "Game": + // TURNED SANS-SERIF CAPITAL G + return rune(0x2141), true + case "Gamma": + // GREEK CAPITAL LETTER GAMMA + return rune(0x0393), true + case "Gammad": + // GREEK LETTER DIGAMMA + return rune(0x03dc), true + case "Gbreve": + // LATIN CAPITAL LETTER G WITH BREVE + return rune(0x011e), true + case "Gcedil": + // LATIN CAPITAL LETTER G WITH CEDILLA + return rune(0x0122), true + case "Gcirc": + // LATIN CAPITAL LETTER G WITH CIRCUMFLEX + return rune(0x011c), true + case "Gcy": + // CYRILLIC CAPITAL LETTER GHE + return rune(0x0413), true + case "Gdot": + // LATIN CAPITAL LETTER G WITH DOT ABOVE + return rune(0x0120), true + case "Gfr": + // MATHEMATICAL FRAKTUR CAPITAL G + return rune(0x01d50a), true + case "Ggr": + // GREEK CAPITAL LETTER GAMMA + return rune(0x0393), true + case "Gg": + // VERY MUCH GREATER-THAN + return rune(0x22d9), true + case "Gopf": + // MATHEMATICAL DOUBLE-STRUCK CAPITAL G + return rune(0x01d53e), true + case "GreaterEqual": + // GREATER-THAN OR EQUAL TO + return rune(0x2265), true + case "GreaterEqualLess": + // GREATER-THAN EQUAL TO OR LESS-THAN + return rune(0x22db), true + case "GreaterFullEqual": + // GREATER-THAN OVER EQUAL TO + return rune(0x2267), true + case "GreaterGreater": + // DOUBLE NESTED GREATER-THAN + return rune(0x2aa2), true + case "GreaterLess": + // GREATER-THAN OR LESS-THAN + return rune(0x2277), true + case "GreaterSlantEqual": + // GREATER-THAN OR SLANTED EQUAL TO + return rune(0x2a7e), true + case "GreaterTilde": + // GREATER-THAN OR EQUIVALENT TO + return rune(0x2273), true + case "Gscr": + // MATHEMATICAL SCRIPT CAPITAL G + return rune(0x01d4a2), true + case "Gt": + // MUCH GREATER-THAN + return rune(0x226b), true + } + + case 'H': + switch name { + case "HARDcy": + // CYRILLIC CAPITAL LETTER HARD SIGN + return rune(0x042a), true + case "Hacek": + // CARON + return rune(0x02c7), true + case "Hat": + // CIRCUMFLEX ACCENT + return rune(0x5e), true + case "Hcirc": + // LATIN CAPITAL LETTER H WITH CIRCUMFLEX + return rune(0x0124), true + case "Hfr": + // BLACK-LETTER CAPITAL H + return rune(0x210c), true + case "HilbertSpace": + // SCRIPT CAPITAL H + return rune(0x210b), true + case "Hopf": + // DOUBLE-STRUCK CAPITAL H + return rune(0x210d), true + case "HorizontalLine": + // BOX DRAWINGS LIGHT HORIZONTAL + return rune(0x2500), true + case "Hscr": + // SCRIPT CAPITAL H + return rune(0x210b), true + case "Hstrok": + // LATIN CAPITAL LETTER H WITH STROKE + return rune(0x0126), true + case "HumpDownHump": + // GEOMETRICALLY EQUIVALENT TO + return rune(0x224e), true + case "HumpEqual": + // DIFFERENCE BETWEEN + return rune(0x224f), true + } + + case 'I': + switch name { + case "IEcy": + // CYRILLIC CAPITAL LETTER IE + return rune(0x0415), true + case "IJlig": + // LATIN CAPITAL LIGATURE IJ + return rune(0x0132), true + case "IOcy": + // CYRILLIC CAPITAL LETTER IO + return rune(0x0401), true + case "Iacgr": + // GREEK CAPITAL LETTER IOTA WITH TONOS + return rune(0x038a), true + case "Iacute": + // LATIN CAPITAL LETTER I WITH ACUTE + return rune(0xcd), true + case "Icirc": + // LATIN CAPITAL LETTER I WITH CIRCUMFLEX + return rune(0xce), true + case "Icy": + // CYRILLIC CAPITAL LETTER I + return rune(0x0418), true + case "Idigr": + // GREEK CAPITAL LETTER IOTA WITH DIALYTIKA + return rune(0x03aa), true + case "Idot": + // LATIN CAPITAL LETTER I WITH DOT ABOVE + return rune(0x0130), true + case "Ifr": + // BLACK-LETTER CAPITAL I + return rune(0x2111), true + case "Igrave": + // LATIN CAPITAL LETTER I WITH GRAVE + return rune(0xcc), true + case "Igr": + // GREEK CAPITAL LETTER IOTA + return rune(0x0399), true + case "Imacr": + // LATIN CAPITAL LETTER I WITH MACRON + return rune(0x012a), true + case "ImaginaryI": + // DOUBLE-STRUCK ITALIC SMALL I + return rune(0x2148), true + case "Implies": + // RIGHTWARDS DOUBLE ARROW + return rune(0x21d2), true + case "Im": + // BLACK-LETTER CAPITAL I + return rune(0x2111), true + case "Integral": + // INTEGRAL + return rune(0x222b), true + case "Int": + // DOUBLE INTEGRAL + return rune(0x222c), true + case "Intersection": + // N-ARY INTERSECTION + return rune(0x22c2), true + case "InvisibleComma": + // INVISIBLE SEPARATOR + return rune(0x2063), true + case "InvisibleTimes": + // INVISIBLE TIMES + return rune(0x2062), true + case "Iogon": + // LATIN CAPITAL LETTER I WITH OGONEK + return rune(0x012e), true + case "Iopf": + // MATHEMATICAL DOUBLE-STRUCK CAPITAL I + return rune(0x01d540), true + case "Iota": + // GREEK CAPITAL LETTER IOTA + return rune(0x0399), true + case "Iscr": + // SCRIPT CAPITAL I + return rune(0x2110), true + case "Itilde": + // LATIN CAPITAL LETTER I WITH TILDE + return rune(0x0128), true + case "Iukcy": + // CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I + return rune(0x0406), true + case "Iuml": + // LATIN CAPITAL LETTER I WITH DIAERESIS + return rune(0xcf), true + } + + case 'J': + switch name { + case "Jcirc": + // LATIN CAPITAL LETTER J WITH CIRCUMFLEX + return rune(0x0134), true + case "Jcy": + // CYRILLIC CAPITAL LETTER SHORT I + return rune(0x0419), true + case "Jfr": + // MATHEMATICAL FRAKTUR CAPITAL J + return rune(0x01d50d), true + case "Jopf": + // MATHEMATICAL DOUBLE-STRUCK CAPITAL J + return rune(0x01d541), true + case "Jscr": + // MATHEMATICAL SCRIPT CAPITAL J + return rune(0x01d4a5), true + case "Jsercy": + // CYRILLIC CAPITAL LETTER JE + return rune(0x0408), true + case "Jukcy": + // CYRILLIC CAPITAL LETTER UKRAINIAN IE + return rune(0x0404), true + } + + case 'K': + switch name { + case "KHcy": + // CYRILLIC CAPITAL LETTER HA + return rune(0x0425), true + case "KHgr": + // GREEK CAPITAL LETTER CHI + return rune(0x03a7), true + case "KJcy": + // CYRILLIC CAPITAL LETTER KJE + return rune(0x040c), true + case "Kappa": + // GREEK CAPITAL LETTER KAPPA + return rune(0x039a), true + case "Kcedil": + // LATIN CAPITAL LETTER K WITH CEDILLA + return rune(0x0136), true + case "Kcy": + // CYRILLIC CAPITAL LETTER KA + return rune(0x041a), true + case "Kfr": + // MATHEMATICAL FRAKTUR CAPITAL K + return rune(0x01d50e), true + case "Kgr": + // GREEK CAPITAL LETTER KAPPA + return rune(0x039a), true + case "Kopf": + // MATHEMATICAL DOUBLE-STRUCK CAPITAL K + return rune(0x01d542), true + case "Kscr": + // MATHEMATICAL SCRIPT CAPITAL K + return rune(0x01d4a6), true + } + + case 'L': + switch name { + case "LJcy": + // CYRILLIC CAPITAL LETTER LJE + return rune(0x0409), true + case "LT": + // LESS-THAN SIGN + return rune(0x3c), true + case "Lacute": + // LATIN CAPITAL LETTER L WITH ACUTE + return rune(0x0139), true + case "Lambda": + // GREEK CAPITAL LETTER LAMDA + return rune(0x039b), true + case "Lang": + // MATHEMATICAL LEFT DOUBLE ANGLE BRACKET + return rune(0x27ea), true + case "Laplacetrf": + // SCRIPT CAPITAL L + return rune(0x2112), true + case "Larr": + // LEFTWARDS TWO HEADED ARROW + return rune(0x219e), true + case "Lcaron": + // LATIN CAPITAL LETTER L WITH CARON + return rune(0x013d), true + case "Lcedil": + // LATIN CAPITAL LETTER L WITH CEDILLA + return rune(0x013b), true + case "Lcy": + // CYRILLIC CAPITAL LETTER EL + return rune(0x041b), true + case "LeftAngleBracket": + // MATHEMATICAL LEFT ANGLE BRACKET + return rune(0x27e8), true + case "LeftArrowBar": + // LEFTWARDS ARROW TO BAR + return rune(0x21e4), true + case "LeftArrowRightArrow": + // LEFTWARDS ARROW OVER RIGHTWARDS ARROW + return rune(0x21c6), true + case "LeftArrow": + // LEFTWARDS ARROW + return rune(0x2190), true + case "LeftCeiling": + // LEFT CEILING + return rune(0x2308), true + case "LeftDoubleBracket": + // MATHEMATICAL LEFT WHITE SQUARE BRACKET + return rune(0x27e6), true + case "LeftDownTeeVector": + // DOWNWARDS HARPOON WITH BARB LEFT FROM BAR + return rune(0x2961), true + case "LeftDownVector": + // DOWNWARDS HARPOON WITH BARB LEFTWARDS + return rune(0x21c3), true + case "LeftDownVectorBar": + // DOWNWARDS HARPOON WITH BARB LEFT TO BAR + return rune(0x2959), true + case "LeftFloor": + // LEFT FLOOR + return rune(0x230a), true + case "LeftRightArrow": + // LEFT RIGHT ARROW + return rune(0x2194), true + case "LeftRightVector": + // LEFT BARB UP RIGHT BARB UP HARPOON + return rune(0x294e), true + case "LeftTeeArrow": + // LEFTWARDS ARROW FROM BAR + return rune(0x21a4), true + case "LeftTeeVector": + // LEFTWARDS HARPOON WITH BARB UP FROM BAR + return rune(0x295a), true + case "LeftTee": + // LEFT TACK + return rune(0x22a3), true + case "LeftTriangleBar": + // LEFT TRIANGLE BESIDE VERTICAL BAR + return rune(0x29cf), true + case "LeftTriangle": + // NORMAL SUBGROUP OF + return rune(0x22b2), true + case "LeftTriangleEqual": + // NORMAL SUBGROUP OF OR EQUAL TO + return rune(0x22b4), true + case "LeftUpDownVector": + // UP BARB LEFT DOWN BARB LEFT HARPOON + return rune(0x2951), true + case "LeftUpTeeVector": + // UPWARDS HARPOON WITH BARB LEFT FROM BAR + return rune(0x2960), true + case "LeftUpVector": + // UPWARDS HARPOON WITH BARB LEFTWARDS + return rune(0x21bf), true + case "LeftUpVectorBar": + // UPWARDS HARPOON WITH BARB LEFT TO BAR + return rune(0x2958), true + case "LeftVector": + // LEFTWARDS HARPOON WITH BARB UPWARDS + return rune(0x21bc), true + case "LeftVectorBar": + // LEFTWARDS HARPOON WITH BARB UP TO BAR + return rune(0x2952), true + case "Leftarrow": + // LEFTWARDS DOUBLE ARROW + return rune(0x21d0), true + case "Leftrightarrow": + // LEFT RIGHT DOUBLE ARROW + return rune(0x21d4), true + case "LessEqualGreater": + // LESS-THAN EQUAL TO OR GREATER-THAN + return rune(0x22da), true + case "LessFullEqual": + // LESS-THAN OVER EQUAL TO + return rune(0x2266), true + case "LessGreater": + // LESS-THAN OR GREATER-THAN + return rune(0x2276), true + case "LessLess": + // DOUBLE NESTED LESS-THAN + return rune(0x2aa1), true + case "LessSlantEqual": + // LESS-THAN OR SLANTED EQUAL TO + return rune(0x2a7d), true + case "LessTilde": + // LESS-THAN OR EQUIVALENT TO + return rune(0x2272), true + case "Lfr": + // MATHEMATICAL FRAKTUR CAPITAL L + return rune(0x01d50f), true + case "Lgr": + // GREEK CAPITAL LETTER LAMDA + return rune(0x039b), true + case "Lleftarrow": + // LEFTWARDS TRIPLE ARROW + return rune(0x21da), true + case "Ll": + // VERY MUCH LESS-THAN + return rune(0x22d8), true + case "Lmidot": + // LATIN CAPITAL LETTER L WITH MIDDLE DOT + return rune(0x013f), true + case "LongLeftArrow": + // LONG LEFTWARDS ARROW + return rune(0x27f5), true + case "LongLeftRightArrow": + // LONG LEFT RIGHT ARROW + return rune(0x27f7), true + case "LongRightArrow": + // LONG RIGHTWARDS ARROW + return rune(0x27f6), true + case "Longleftarrow": + // LONG LEFTWARDS DOUBLE ARROW + return rune(0x27f8), true + case "Longleftrightarrow": + // LONG LEFT RIGHT DOUBLE ARROW + return rune(0x27fa), true + case "Longrightarrow": + // LONG RIGHTWARDS DOUBLE ARROW + return rune(0x27f9), true + case "Lopf": + // MATHEMATICAL DOUBLE-STRUCK CAPITAL L + return rune(0x01d543), true + case "LowerLeftArrow": + // SOUTH WEST ARROW + return rune(0x2199), true + case "LowerRightArrow": + // SOUTH EAST ARROW + return rune(0x2198), true + case "Lscr": + // SCRIPT CAPITAL L + return rune(0x2112), true + case "Lsh": + // UPWARDS ARROW WITH TIP LEFTWARDS + return rune(0x21b0), true + case "Lstrok": + // LATIN CAPITAL LETTER L WITH STROKE + return rune(0x0141), true + case "Ltbar": + // DOUBLE NESTED LESS-THAN WITH UNDERBAR + return rune(0x2aa3), true + case "Lt": + // MUCH LESS-THAN + return rune(0x226a), true + } + + case 'M': + switch name { + case "Mapfrom": + // LEFTWARDS DOUBLE ARROW FROM BAR + return rune(0x2906), true + case "Mapto": + // RIGHTWARDS DOUBLE ARROW FROM BAR + return rune(0x2907), true + case "Map": + // RIGHTWARDS TWO-HEADED ARROW FROM BAR + return rune(0x2905), true + case "Mcy": + // CYRILLIC CAPITAL LETTER EM + return rune(0x041c), true + case "MediumSpace": + // MEDIUM MATHEMATICAL SPACE + return rune(0x205f), true + case "Mellintrf": + // SCRIPT CAPITAL M + return rune(0x2133), true + case "Mfr": + // MATHEMATICAL FRAKTUR CAPITAL M + return rune(0x01d510), true + case "Mgr": + // GREEK CAPITAL LETTER MU + return rune(0x039c), true + case "MinusPlus": + // MINUS-OR-PLUS SIGN + return rune(0x2213), true + case "Mopf": + // MATHEMATICAL DOUBLE-STRUCK CAPITAL M + return rune(0x01d544), true + case "Mscr": + // SCRIPT CAPITAL M + return rune(0x2133), true + case "Mu": + // GREEK CAPITAL LETTER MU + return rune(0x039c), true + } + + case 'N': + switch name { + case "NJcy": + // CYRILLIC CAPITAL LETTER NJE + return rune(0x040a), true + case "Nacute": + // LATIN CAPITAL LETTER N WITH ACUTE + return rune(0x0143), true + case "Ncaron": + // LATIN CAPITAL LETTER N WITH CARON + return rune(0x0147), true + case "Ncedil": + // LATIN CAPITAL LETTER N WITH CEDILLA + return rune(0x0145), true + case "Ncy": + // CYRILLIC CAPITAL LETTER EN + return rune(0x041d), true + case "NegativeMediumSpace": + // ZERO WIDTH SPACE + return rune(0x200b), true + case "NegativeThickSpace": + // ZERO WIDTH SPACE + return rune(0x200b), true + case "NegativeThinSpace": + // ZERO WIDTH SPACE + return rune(0x200b), true + case "NegativeVeryThinSpace": + // ZERO WIDTH SPACE + return rune(0x200b), true + case "NestedGreaterGreater": + // MUCH GREATER-THAN + return rune(0x226b), true + case "NestedLessLess": + // MUCH LESS-THAN + return rune(0x226a), true + case "NewLine": + // LINE FEED (LF) + return rune(0x0a), true + case "Nfr": + // MATHEMATICAL FRAKTUR CAPITAL N + return rune(0x01d511), true + case "Ngr": + // GREEK CAPITAL LETTER NU + return rune(0x039d), true + case "NoBreak": + // WORD JOINER + return rune(0x2060), true + case "NonBreakingSpace": + // NO-BREAK SPACE + return rune(0xa0), true + case "Nopf": + // DOUBLE-STRUCK CAPITAL N + return rune(0x2115), true + case "NotDoubleVerticalBar": + // NOT PARALLEL TO + return rune(0x2226), true + case "NotElement": + // NOT AN ELEMENT OF + return rune(0x2209), true + case "NotEqualTilde": + // MINUS TILDE with slash + return rune(0x2242), true + case "NotEqual": + // NOT EQUAL TO + return rune(0x2260), true + case "NotExists": + // THERE DOES NOT EXIST + return rune(0x2204), true + case "NotHumpDownHump": + // GEOMETRICALLY EQUIVALENT TO with slash + return rune(0x224e), true + case "NotHumpEqual": + // DIFFERENCE BETWEEN with slash + return rune(0x224f), true + case "NotLessGreater": + // NEITHER LESS-THAN NOR GREATER-THAN + return rune(0x2278), true + case "NotReverseElement": + // DOES NOT CONTAIN AS MEMBER + return rune(0x220c), true + case "NotTilde": + // NOT TILDE + return rune(0x2241), true + case "NotTildeEqual": + // NOT ASYMPTOTICALLY EQUAL TO + return rune(0x2244), true + case "NotTildeFullEqual": + // NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO + return rune(0x2247), true + case "NotTildeTilde": + // NOT ALMOST EQUAL TO + return rune(0x2249), true + case "NotVerticalBar": + // DOES NOT DIVIDE + return rune(0x2224), true + case "Not": + // DOUBLE STROKE NOT SIGN + return rune(0x2aec), true + case "NotCongruent": + // NOT IDENTICAL TO + return rune(0x2262), true + case "NotCupCap": + // NOT EQUIVALENT TO + return rune(0x226d), true + case "NotGreaterFullEqual": + // GREATER-THAN OVER EQUAL TO with slash + return rune(0x2267), true + case "NotGreaterGreater": + // MUCH GREATER THAN with slash + return rune(0x226b), true + case "NotGreaterSlantEqual": + // GREATER-THAN OR SLANTED EQUAL TO with slash + return rune(0x2a7e), true + case "NotGreater": + // NOT GREATER-THAN + return rune(0x226f), true + case "NotGreaterEqual": + // NEITHER GREATER-THAN NOR EQUAL TO + return rune(0x2271), true + case "NotGreaterLess": + // NEITHER GREATER-THAN NOR LESS-THAN + return rune(0x2279), true + case "NotGreaterTilde": + // NEITHER GREATER-THAN NOR EQUIVALENT TO + return rune(0x2275), true + case "NotLeftTriangleBar": + // LEFT TRIANGLE BESIDE VERTICAL BAR with slash + return rune(0x29cf), true + case "NotLeftTriangle": + // NOT NORMAL SUBGROUP OF + return rune(0x22ea), true + case "NotLeftTriangleEqual": + // NOT NORMAL SUBGROUP OF OR EQUAL TO + return rune(0x22ec), true + case "NotLessLess": + // MUCH LESS THAN with slash + return rune(0x226a), true + case "NotLessSlantEqual": + // LESS-THAN OR SLANTED EQUAL TO with slash + return rune(0x2a7d), true + case "NotLess": + // NOT LESS-THAN + return rune(0x226e), true + case "NotLessEqual": + // NEITHER LESS-THAN NOR EQUAL TO + return rune(0x2270), true + case "NotLessTilde": + // NEITHER LESS-THAN NOR EQUIVALENT TO + return rune(0x2274), true + case "NotNestedGreaterGreater": + // DOUBLE NESTED GREATER-THAN with slash + return rune(0x2aa2), true + case "NotNestedLessLess": + // DOUBLE NESTED LESS-THAN with slash + return rune(0x2aa1), true + case "NotPrecedesEqual": + // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN with slash + return rune(0x2aaf), true + case "NotPrecedes": + // DOES NOT PRECEDE + return rune(0x2280), true + case "NotPrecedesSlantEqual": + // DOES NOT PRECEDE OR EQUAL + return rune(0x22e0), true + case "NotRightTriangleBar": + // VERTICAL BAR BESIDE RIGHT TRIANGLE with slash + return rune(0x29d0), true + case "NotRightTriangle": + // DOES NOT CONTAIN AS NORMAL SUBGROUP + return rune(0x22eb), true + case "NotRightTriangleEqual": + // DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL + return rune(0x22ed), true + case "NotSquareSubset": + // SQUARE IMAGE OF with slash + return rune(0x228f), true + case "NotSquareSubsetEqual": + // NOT SQUARE IMAGE OF OR EQUAL TO + return rune(0x22e2), true + case "NotSquareSuperset": + // SQUARE ORIGINAL OF with slash + return rune(0x2290), true + case "NotSquareSupersetEqual": + // NOT SQUARE ORIGINAL OF OR EQUAL TO + return rune(0x22e3), true + case "NotSubset": + // SUBSET OF with vertical line + return rune(0x2282), true + case "NotSubsetEqual": + // NEITHER A SUBSET OF NOR EQUAL TO + return rune(0x2288), true + case "NotSucceedsEqual": + // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN with slash + return rune(0x2ab0), true + case "NotSucceedsTilde": + // SUCCEEDS OR EQUIVALENT TO with slash + return rune(0x227f), true + case "NotSucceeds": + // DOES NOT SUCCEED + return rune(0x2281), true + case "NotSucceedsSlantEqual": + // DOES NOT SUCCEED OR EQUAL + return rune(0x22e1), true + case "NotSuperset": + // SUPERSET OF with vertical line + return rune(0x2283), true + case "NotSupersetEqual": + // NEITHER A SUPERSET OF NOR EQUAL TO + return rune(0x2289), true + case "Nscr": + // MATHEMATICAL SCRIPT CAPITAL N + return rune(0x01d4a9), true + case "Ntilde": + // LATIN CAPITAL LETTER N WITH TILDE + return rune(0xd1), true + case "Nu": + // GREEK CAPITAL LETTER NU + return rune(0x039d), true + } + + case 'O': + switch name { + case "OElig": + // LATIN CAPITAL LIGATURE OE + return rune(0x0152), true + case "OHacgr": + // GREEK CAPITAL LETTER OMEGA WITH TONOS + return rune(0x038f), true + case "OHgr": + // GREEK CAPITAL LETTER OMEGA + return rune(0x03a9), true + case "Oacgr": + // GREEK CAPITAL LETTER OMICRON WITH TONOS + return rune(0x038c), true + case "Oacute": + // LATIN CAPITAL LETTER O WITH ACUTE + return rune(0xd3), true + case "Ocirc": + // LATIN CAPITAL LETTER O WITH CIRCUMFLEX + return rune(0xd4), true + case "Ocy": + // CYRILLIC CAPITAL LETTER O + return rune(0x041e), true + case "Odblac": + // LATIN CAPITAL LETTER O WITH DOUBLE ACUTE + return rune(0x0150), true + case "Ofr": + // MATHEMATICAL FRAKTUR CAPITAL O + return rune(0x01d512), true + case "Ograve": + // LATIN CAPITAL LETTER O WITH GRAVE + return rune(0xd2), true + case "Ogr": + // GREEK CAPITAL LETTER OMICRON + return rune(0x039f), true + case "Omacr": + // LATIN CAPITAL LETTER O WITH MACRON + return rune(0x014c), true + case "Omega": + // GREEK CAPITAL LETTER OMEGA + return rune(0x03a9), true + case "Omicron": + // GREEK CAPITAL LETTER OMICRON + return rune(0x039f), true + case "Oopf": + // MATHEMATICAL DOUBLE-STRUCK CAPITAL O + return rune(0x01d546), true + case "OpenCurlyDoubleQuote": + // LEFT DOUBLE QUOTATION MARK + return rune(0x201c), true + case "OpenCurlyQuote": + // LEFT SINGLE QUOTATION MARK + return rune(0x2018), true + case "Or": + // DOUBLE LOGICAL OR + return rune(0x2a54), true + case "Oscr": + // MATHEMATICAL SCRIPT CAPITAL O + return rune(0x01d4aa), true + case "Oslash": + // LATIN CAPITAL LETTER O WITH STROKE + return rune(0xd8), true + case "Otilde": + // LATIN CAPITAL LETTER O WITH TILDE + return rune(0xd5), true + case "Otimes": + // MULTIPLICATION SIGN IN DOUBLE CIRCLE + return rune(0x2a37), true + case "Ouml": + // LATIN CAPITAL LETTER O WITH DIAERESIS + return rune(0xd6), true + case "OverBar": + // OVERLINE + return rune(0x203e), true + case "OverBrace": + // TOP CURLY BRACKET + return rune(0x23de), true + case "OverBracket": + // TOP SQUARE BRACKET + return rune(0x23b4), true + case "OverParenthesis": + // TOP PARENTHESIS + return rune(0x23dc), true + } + + case 'P': + switch name { + case "PHgr": + // GREEK CAPITAL LETTER PHI + return rune(0x03a6), true + case "PSgr": + // GREEK CAPITAL LETTER PSI + return rune(0x03a8), true + case "PartialD": + // PARTIAL DIFFERENTIAL + return rune(0x2202), true + case "Pcy": + // CYRILLIC CAPITAL LETTER PE + return rune(0x041f), true + case "Pfr": + // MATHEMATICAL FRAKTUR CAPITAL P + return rune(0x01d513), true + case "Pgr": + // GREEK CAPITAL LETTER PI + return rune(0x03a0), true + case "Phi": + // GREEK CAPITAL LETTER PHI + return rune(0x03a6), true + case "Pi": + // GREEK CAPITAL LETTER PI + return rune(0x03a0), true + case "PlusMinus": + // PLUS-MINUS SIGN + return rune(0xb1), true + case "Poincareplane": + // BLACK-LETTER CAPITAL H + return rune(0x210c), true + case "Popf": + // DOUBLE-STRUCK CAPITAL P + return rune(0x2119), true + case "Product": + // N-ARY PRODUCT + return rune(0x220f), true + case "Proportional": + // PROPORTIONAL TO + return rune(0x221d), true + case "Proportion": + // PROPORTION + return rune(0x2237), true + case "Pr": + // DOUBLE PRECEDES + return rune(0x2abb), true + case "PrecedesEqual": + // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN + return rune(0x2aaf), true + case "Precedes": + // PRECEDES + return rune(0x227a), true + case "PrecedesSlantEqual": + // PRECEDES OR EQUAL TO + return rune(0x227c), true + case "PrecedesTilde": + // PRECEDES OR EQUIVALENT TO + return rune(0x227e), true + case "Prime": + // DOUBLE PRIME + return rune(0x2033), true + case "Pscr": + // MATHEMATICAL SCRIPT CAPITAL P + return rune(0x01d4ab), true + case "Psi": + // GREEK CAPITAL LETTER PSI + return rune(0x03a8), true + } + + case 'Q': + switch name { + case "QUOT": + // QUOTATION MARK + return rune(0x22), true + case "Qfr": + // MATHEMATICAL FRAKTUR CAPITAL Q + return rune(0x01d514), true + case "Qopf": + // DOUBLE-STRUCK CAPITAL Q + return rune(0x211a), true + case "Qscr": + // MATHEMATICAL SCRIPT CAPITAL Q + return rune(0x01d4ac), true + } + + case 'R': + switch name { + case "RBarr": + // RIGHTWARDS TWO-HEADED TRIPLE DASH ARROW + return rune(0x2910), true + case "REG": + // REGISTERED SIGN + return rune(0xae), true + case "Racute": + // LATIN CAPITAL LETTER R WITH ACUTE + return rune(0x0154), true + case "Rang": + // MATHEMATICAL RIGHT DOUBLE ANGLE BRACKET + return rune(0x27eb), true + case "Rarr": + // RIGHTWARDS TWO HEADED ARROW + return rune(0x21a0), true + case "Rarrtl": + // RIGHTWARDS TWO-HEADED ARROW WITH TAIL + return rune(0x2916), true + case "Rcaron": + // LATIN CAPITAL LETTER R WITH CARON + return rune(0x0158), true + case "Rcedil": + // LATIN CAPITAL LETTER R WITH CEDILLA + return rune(0x0156), true + case "Rcy": + // CYRILLIC CAPITAL LETTER ER + return rune(0x0420), true + case "ReverseElement": + // CONTAINS AS MEMBER + return rune(0x220b), true + case "ReverseEquilibrium": + // LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON + return rune(0x21cb), true + case "Re": + // BLACK-LETTER CAPITAL R + return rune(0x211c), true + case "ReverseUpEquilibrium": + // DOWNWARDS HARPOON WITH BARB LEFT BESIDE UPWARDS HARPOON WITH BARB RIGHT + return rune(0x296f), true + case "Rfr": + // BLACK-LETTER CAPITAL R + return rune(0x211c), true + case "Rgr": + // GREEK CAPITAL LETTER RHO + return rune(0x03a1), true + case "Rho": + // GREEK CAPITAL LETTER RHO + return rune(0x03a1), true + case "RightAngleBracket": + // MATHEMATICAL RIGHT ANGLE BRACKET + return rune(0x27e9), true + case "RightArrowBar": + // RIGHTWARDS ARROW TO BAR + return rune(0x21e5), true + case "RightArrow": + // RIGHTWARDS ARROW + return rune(0x2192), true + case "RightArrowLeftArrow": + // RIGHTWARDS ARROW OVER LEFTWARDS ARROW + return rune(0x21c4), true + case "RightCeiling": + // RIGHT CEILING + return rune(0x2309), true + case "RightDoubleBracket": + // MATHEMATICAL RIGHT WHITE SQUARE BRACKET + return rune(0x27e7), true + case "RightDownTeeVector": + // DOWNWARDS HARPOON WITH BARB RIGHT FROM BAR + return rune(0x295d), true + case "RightDownVector": + // DOWNWARDS HARPOON WITH BARB RIGHTWARDS + return rune(0x21c2), true + case "RightDownVectorBar": + // DOWNWARDS HARPOON WITH BARB RIGHT TO BAR + return rune(0x2955), true + case "RightFloor": + // RIGHT FLOOR + return rune(0x230b), true + case "RightTeeArrow": + // RIGHTWARDS ARROW FROM BAR + return rune(0x21a6), true + case "RightTeeVector": + // RIGHTWARDS HARPOON WITH BARB UP FROM BAR + return rune(0x295b), true + case "RightTee": + // RIGHT TACK + return rune(0x22a2), true + case "RightTriangleBar": + // VERTICAL BAR BESIDE RIGHT TRIANGLE + return rune(0x29d0), true + case "RightTriangle": + // CONTAINS AS NORMAL SUBGROUP + return rune(0x22b3), true + case "RightTriangleEqual": + // CONTAINS AS NORMAL SUBGROUP OR EQUAL TO + return rune(0x22b5), true + case "RightUpDownVector": + // UP BARB RIGHT DOWN BARB RIGHT HARPOON + return rune(0x294f), true + case "RightUpTeeVector": + // UPWARDS HARPOON WITH BARB RIGHT FROM BAR + return rune(0x295c), true + case "RightUpVector": + // UPWARDS HARPOON WITH BARB RIGHTWARDS + return rune(0x21be), true + case "RightUpVectorBar": + // UPWARDS HARPOON WITH BARB RIGHT TO BAR + return rune(0x2954), true + case "RightVector": + // RIGHTWARDS HARPOON WITH BARB UPWARDS + return rune(0x21c0), true + case "RightVectorBar": + // RIGHTWARDS HARPOON WITH BARB UP TO BAR + return rune(0x2953), true + case "Rightarrow": + // RIGHTWARDS DOUBLE ARROW + return rune(0x21d2), true + case "Ropf": + // DOUBLE-STRUCK CAPITAL R + return rune(0x211d), true + case "RoundImplies": + // RIGHT DOUBLE ARROW WITH ROUNDED HEAD + return rune(0x2970), true + case "Rrightarrow": + // RIGHTWARDS TRIPLE ARROW + return rune(0x21db), true + case "Rscr": + // SCRIPT CAPITAL R + return rune(0x211b), true + case "Rsh": + // UPWARDS ARROW WITH TIP RIGHTWARDS + return rune(0x21b1), true + case "RuleDelayed": + // RULE-DELAYED + return rune(0x29f4), true + } + + case 'S': + switch name { + case "SHCHcy": + // CYRILLIC CAPITAL LETTER SHCHA + return rune(0x0429), true + case "SHcy": + // CYRILLIC CAPITAL LETTER SHA + return rune(0x0428), true + case "SOFTcy": + // CYRILLIC CAPITAL LETTER SOFT SIGN + return rune(0x042c), true + case "Sacute": + // LATIN CAPITAL LETTER S WITH ACUTE + return rune(0x015a), true + case "Sc": + // DOUBLE SUCCEEDS + return rune(0x2abc), true + case "Scaron": + // LATIN CAPITAL LETTER S WITH CARON + return rune(0x0160), true + case "Scedil": + // LATIN CAPITAL LETTER S WITH CEDILLA + return rune(0x015e), true + case "Scirc": + // LATIN CAPITAL LETTER S WITH CIRCUMFLEX + return rune(0x015c), true + case "Scy": + // CYRILLIC CAPITAL LETTER ES + return rune(0x0421), true + case "Sfr": + // MATHEMATICAL FRAKTUR CAPITAL S + return rune(0x01d516), true + case "Sgr": + // GREEK CAPITAL LETTER SIGMA + return rune(0x03a3), true + case "ShortDownArrow": + // DOWNWARDS ARROW + return rune(0x2193), true + case "ShortLeftArrow": + // LEFTWARDS ARROW + return rune(0x2190), true + case "ShortRightArrow": + // RIGHTWARDS ARROW + return rune(0x2192), true + case "ShortUpArrow": + // UPWARDS ARROW + return rune(0x2191), true + case "Sigma": + // GREEK CAPITAL LETTER SIGMA + return rune(0x03a3), true + case "SmallCircle": + // RING OPERATOR + return rune(0x2218), true + case "Sopf": + // MATHEMATICAL DOUBLE-STRUCK CAPITAL S + return rune(0x01d54a), true + case "Sqrt": + // SQUARE ROOT + return rune(0x221a), true + case "SquareIntersection": + // SQUARE CAP + return rune(0x2293), true + case "SquareSubset": + // SQUARE IMAGE OF + return rune(0x228f), true + case "SquareSubsetEqual": + // SQUARE IMAGE OF OR EQUAL TO + return rune(0x2291), true + case "Square": + // WHITE SQUARE + return rune(0x25a1), true + case "SquareSuperset": + // SQUARE ORIGINAL OF + return rune(0x2290), true + case "SquareSupersetEqual": + // SQUARE ORIGINAL OF OR EQUAL TO + return rune(0x2292), true + case "SquareUnion": + // SQUARE CUP + return rune(0x2294), true + case "Sscr": + // MATHEMATICAL SCRIPT CAPITAL S + return rune(0x01d4ae), true + case "Star": + // STAR OPERATOR + return rune(0x22c6), true + case "Sub": + // DOUBLE SUBSET + return rune(0x22d0), true + case "Subset": + // DOUBLE SUBSET + return rune(0x22d0), true + case "SubsetEqual": + // SUBSET OF OR EQUAL TO + return rune(0x2286), true + case "Succeeds": + // SUCCEEDS + return rune(0x227b), true + case "SucceedsEqual": + // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN + return rune(0x2ab0), true + case "SucceedsSlantEqual": + // SUCCEEDS OR EQUAL TO + return rune(0x227d), true + case "SucceedsTilde": + // SUCCEEDS OR EQUIVALENT TO + return rune(0x227f), true + case "SuchThat": + // CONTAINS AS MEMBER + return rune(0x220b), true + case "Sum": + // N-ARY SUMMATION + return rune(0x2211), true + case "SupersetEqual": + // SUPERSET OF OR EQUAL TO + return rune(0x2287), true + case "Sup": + // DOUBLE SUPERSET + return rune(0x22d1), true + case "Superset": + // SUPERSET OF + return rune(0x2283), true + case "Supset": + // DOUBLE SUPERSET + return rune(0x22d1), true + } + + case 'T': + switch name { + case "THORN": + // LATIN CAPITAL LETTER THORN + return rune(0xde), true + case "THgr": + // GREEK CAPITAL LETTER THETA + return rune(0x0398), true + case "TRADE": + // TRADE MARK SIGN + return rune(0x2122), true + case "TSHcy": + // CYRILLIC CAPITAL LETTER TSHE + return rune(0x040b), true + case "TScy": + // CYRILLIC CAPITAL LETTER TSE + return rune(0x0426), true + case "Tab": + // CHARACTER TABULATION + return rune(0x09), true + case "Tau": + // GREEK CAPITAL LETTER TAU + return rune(0x03a4), true + case "Tcaron": + // LATIN CAPITAL LETTER T WITH CARON + return rune(0x0164), true + case "Tcedil": + // LATIN CAPITAL LETTER T WITH CEDILLA + return rune(0x0162), true + case "Tcy": + // CYRILLIC CAPITAL LETTER TE + return rune(0x0422), true + case "Tfr": + // MATHEMATICAL FRAKTUR CAPITAL T + return rune(0x01d517), true + case "Tgr": + // GREEK CAPITAL LETTER TAU + return rune(0x03a4), true + case "Therefore": + // THEREFORE + return rune(0x2234), true + case "Theta": + // GREEK CAPITAL LETTER THETA + return rune(0x0398), true + case "Thetav": + // GREEK CAPITAL THETA SYMBOL + return rune(0x03f4), true + case "ThickSpace": + // space of width 5/18 em + return rune(0x205f), true + case "ThinSpace": + // THIN SPACE + return rune(0x2009), true + case "Tilde": + // TILDE OPERATOR + return rune(0x223c), true + case "TildeEqual": + // ASYMPTOTICALLY EQUAL TO + return rune(0x2243), true + case "TildeFullEqual": + // APPROXIMATELY EQUAL TO + return rune(0x2245), true + case "TildeTilde": + // ALMOST EQUAL TO + return rune(0x2248), true + case "Topf": + // MATHEMATICAL DOUBLE-STRUCK CAPITAL T + return rune(0x01d54b), true + case "TripleDot": + // COMBINING THREE DOTS ABOVE + return rune(0x20db), true + case "Tscr": + // MATHEMATICAL SCRIPT CAPITAL T + return rune(0x01d4af), true + case "Tstrok": + // LATIN CAPITAL LETTER T WITH STROKE + return rune(0x0166), true + } + + case 'U': + switch name { + case "Uacgr": + // GREEK CAPITAL LETTER UPSILON WITH TONOS + return rune(0x038e), true + case "Uacute": + // LATIN CAPITAL LETTER U WITH ACUTE + return rune(0xda), true + case "Uarrocir": + // UPWARDS TWO-HEADED ARROW FROM SMALL CIRCLE + return rune(0x2949), true + case "Uarr": + // UPWARDS TWO HEADED ARROW + return rune(0x219f), true + case "Ubrcy": + // CYRILLIC CAPITAL LETTER SHORT U + return rune(0x040e), true + case "Ubreve": + // LATIN CAPITAL LETTER U WITH BREVE + return rune(0x016c), true + case "Ucirc": + // LATIN CAPITAL LETTER U WITH CIRCUMFLEX + return rune(0xdb), true + case "Ucy": + // CYRILLIC CAPITAL LETTER U + return rune(0x0423), true + case "Udblac": + // LATIN CAPITAL LETTER U WITH DOUBLE ACUTE + return rune(0x0170), true + case "Udigr": + // GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA + return rune(0x03ab), true + case "Ufr": + // MATHEMATICAL FRAKTUR CAPITAL U + return rune(0x01d518), true + case "Ugrave": + // LATIN CAPITAL LETTER U WITH GRAVE + return rune(0xd9), true + case "Ugr": + // GREEK CAPITAL LETTER UPSILON + return rune(0x03a5), true + case "Umacr": + // LATIN CAPITAL LETTER U WITH MACRON + return rune(0x016a), true + case "UnderBar": + // LOW LINE + return rune(0x5f), true + case "UnderBrace": + // BOTTOM CURLY BRACKET + return rune(0x23df), true + case "UnderBracket": + // BOTTOM SQUARE BRACKET + return rune(0x23b5), true + case "UnderParenthesis": + // BOTTOM PARENTHESIS + return rune(0x23dd), true + case "Union": + // N-ARY UNION + return rune(0x22c3), true + case "UnionPlus": + // MULTISET UNION + return rune(0x228e), true + case "Uogon": + // LATIN CAPITAL LETTER U WITH OGONEK + return rune(0x0172), true + case "Uopf": + // MATHEMATICAL DOUBLE-STRUCK CAPITAL U + return rune(0x01d54c), true + case "UpArrow": + // UPWARDS ARROW + return rune(0x2191), true + case "UpArrowBar": + // UPWARDS ARROW TO BAR + return rune(0x2912), true + case "UpArrowDownArrow": + // UPWARDS ARROW LEFTWARDS OF DOWNWARDS ARROW + return rune(0x21c5), true + case "UpDownArrow": + // UP DOWN ARROW + return rune(0x2195), true + case "UpEquilibrium": + // UPWARDS HARPOON WITH BARB LEFT BESIDE DOWNWARDS HARPOON WITH BARB RIGHT + return rune(0x296e), true + case "UpTee": + // UP TACK + return rune(0x22a5), true + case "UpTeeArrow": + // UPWARDS ARROW FROM BAR + return rune(0x21a5), true + case "Uparrow": + // UPWARDS DOUBLE ARROW + return rune(0x21d1), true + case "Updownarrow": + // UP DOWN DOUBLE ARROW + return rune(0x21d5), true + case "UpperLeftArrow": + // NORTH WEST ARROW + return rune(0x2196), true + case "UpperRightArrow": + // NORTH EAST ARROW + return rune(0x2197), true + case "Upsilon": + // GREEK CAPITAL LETTER UPSILON + return rune(0x03a5), true + case "Upsi": + // GREEK UPSILON WITH HOOK SYMBOL + return rune(0x03d2), true + case "Uring": + // LATIN CAPITAL LETTER U WITH RING ABOVE + return rune(0x016e), true + case "Uscr": + // MATHEMATICAL SCRIPT CAPITAL U + return rune(0x01d4b0), true + case "Utilde": + // LATIN CAPITAL LETTER U WITH TILDE + return rune(0x0168), true + case "Uuml": + // LATIN CAPITAL LETTER U WITH DIAERESIS + return rune(0xdc), true + } + + case 'V': + switch name { + case "VDash": + // DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE + return rune(0x22ab), true + case "Vbar": + // DOUBLE UP TACK + return rune(0x2aeb), true + case "Vcy": + // CYRILLIC CAPITAL LETTER VE + return rune(0x0412), true + case "Vdashl": + // LONG DASH FROM LEFT MEMBER OF DOUBLE VERTICAL + return rune(0x2ae6), true + case "Vdash": + // FORCES + return rune(0x22a9), true + case "Vee": + // N-ARY LOGICAL OR + return rune(0x22c1), true + case "Verbar": + // DOUBLE VERTICAL LINE + return rune(0x2016), true + case "Vert": + // DOUBLE VERTICAL LINE + return rune(0x2016), true + case "VerticalBar": + // DIVIDES + return rune(0x2223), true + case "VerticalLine": + // VERTICAL LINE + return rune(0x7c), true + case "VerticalSeparator": + // LIGHT VERTICAL BAR + return rune(0x2758), true + case "VerticalTilde": + // WREATH PRODUCT + return rune(0x2240), true + case "VeryThinSpace": + // HAIR SPACE + return rune(0x200a), true + case "Vfr": + // MATHEMATICAL FRAKTUR CAPITAL V + return rune(0x01d519), true + case "Vopf": + // MATHEMATICAL DOUBLE-STRUCK CAPITAL V + return rune(0x01d54d), true + case "Vscr": + // MATHEMATICAL SCRIPT CAPITAL V + return rune(0x01d4b1), true + case "Vvdash": + // TRIPLE VERTICAL BAR RIGHT TURNSTILE + return rune(0x22aa), true + } + + case 'W': + switch name { + case "Wcirc": + // LATIN CAPITAL LETTER W WITH CIRCUMFLEX + return rune(0x0174), true + case "Wedge": + // N-ARY LOGICAL AND + return rune(0x22c0), true + case "Wfr": + // MATHEMATICAL FRAKTUR CAPITAL W + return rune(0x01d51a), true + case "Wopf": + // MATHEMATICAL DOUBLE-STRUCK CAPITAL W + return rune(0x01d54e), true + case "Wscr": + // MATHEMATICAL SCRIPT CAPITAL W + return rune(0x01d4b2), true + } + + case 'X': + switch name { + case "Xfr": + // MATHEMATICAL FRAKTUR CAPITAL X + return rune(0x01d51b), true + case "Xgr": + // GREEK CAPITAL LETTER XI + return rune(0x039e), true + case "Xi": + // GREEK CAPITAL LETTER XI + return rune(0x039e), true + case "Xopf": + // MATHEMATICAL DOUBLE-STRUCK CAPITAL X + return rune(0x01d54f), true + case "Xscr": + // MATHEMATICAL SCRIPT CAPITAL X + return rune(0x01d4b3), true + } + + case 'Y': + switch name { + case "YAcy": + // CYRILLIC CAPITAL LETTER YA + return rune(0x042f), true + case "YIcy": + // CYRILLIC CAPITAL LETTER YI + return rune(0x0407), true + case "YUcy": + // CYRILLIC CAPITAL LETTER YU + return rune(0x042e), true + case "Yacute": + // LATIN CAPITAL LETTER Y WITH ACUTE + return rune(0xdd), true + case "Ycirc": + // LATIN CAPITAL LETTER Y WITH CIRCUMFLEX + return rune(0x0176), true + case "Ycy": + // CYRILLIC CAPITAL LETTER YERU + return rune(0x042b), true + case "Yfr": + // MATHEMATICAL FRAKTUR CAPITAL Y + return rune(0x01d51c), true + case "Yopf": + // MATHEMATICAL DOUBLE-STRUCK CAPITAL Y + return rune(0x01d550), true + case "Yscr": + // MATHEMATICAL SCRIPT CAPITAL Y + return rune(0x01d4b4), true + case "Yuml": + // LATIN CAPITAL LETTER Y WITH DIAERESIS + return rune(0x0178), true + } + + case 'Z': + switch name { + case "ZHcy": + // CYRILLIC CAPITAL LETTER ZHE + return rune(0x0416), true + case "Zacute": + // LATIN CAPITAL LETTER Z WITH ACUTE + return rune(0x0179), true + case "Zcaron": + // LATIN CAPITAL LETTER Z WITH CARON + return rune(0x017d), true + case "Zcy": + // CYRILLIC CAPITAL LETTER ZE + return rune(0x0417), true + case "Zdot": + // LATIN CAPITAL LETTER Z WITH DOT ABOVE + return rune(0x017b), true + case "ZeroWidthSpace": + // ZERO WIDTH SPACE + return rune(0x200b), true + case "Zeta": + // GREEK CAPITAL LETTER ZETA + return rune(0x0396), true + case "Zfr": + // BLACK-LETTER CAPITAL Z + return rune(0x2128), true + case "Zgr": + // GREEK CAPITAL LETTER ZETA + return rune(0x0396), true + case "Zopf": + // DOUBLE-STRUCK CAPITAL Z + return rune(0x2124), true + case "Zscr": + // MATHEMATICAL SCRIPT CAPITAL Z + return rune(0x01d4b5), true + } + + case 'a': + switch name { + case "aacgr": + // GREEK SMALL LETTER ALPHA WITH TONOS + return rune(0x03ac), true + case "aacute": + // LATIN SMALL LETTER A WITH ACUTE + return rune(0xe1), true + case "abreve": + // LATIN SMALL LETTER A WITH BREVE + return rune(0x0103), true + case "acE": + // INVERTED LAZY S with double underline + return rune(0x223e), true + case "acd": + // SINE WAVE + return rune(0x223f), true + case "acute": + // ACUTE ACCENT + return rune(0xb4), true + case "ac": + // INVERTED LAZY S + return rune(0x223e), true + case "acirc": + // LATIN SMALL LETTER A WITH CIRCUMFLEX + return rune(0xe2), true + case "actuary": + // COMBINING ANNUITY SYMBOL + return rune(0x20e7), true + case "acy": + // CYRILLIC SMALL LETTER A + return rune(0x0430), true + case "aelig": + // LATIN SMALL LETTER AE + return rune(0xe6), true + case "af": + // FUNCTION APPLICATION + return rune(0x2061), true + case "afr": + // MATHEMATICAL FRAKTUR SMALL A + return rune(0x01d51e), true + case "agr": + // GREEK SMALL LETTER ALPHA + return rune(0x03b1), true + case "agrave": + // LATIN SMALL LETTER A WITH GRAVE + return rune(0xe0), true + case "alefsym": + // ALEF SYMBOL + return rune(0x2135), true + case "aleph": + // ALEF SYMBOL + return rune(0x2135), true + case "alpha": + // GREEK SMALL LETTER ALPHA + return rune(0x03b1), true + case "amacr": + // LATIN SMALL LETTER A WITH MACRON + return rune(0x0101), true + case "amalg": + // AMALGAMATION OR COPRODUCT + return rune(0x2a3f), true + case "amp": + // AMPERSAND + return rune(0x26), true + case "andand": + // TWO INTERSECTING LOGICAL AND + return rune(0x2a55), true + case "andd": + // LOGICAL AND WITH HORIZONTAL DASH + return rune(0x2a5c), true + case "andslope": + // SLOPING LARGE AND + return rune(0x2a58), true + case "andv": + // LOGICAL AND WITH MIDDLE STEM + return rune(0x2a5a), true + case "and": + // LOGICAL AND + return rune(0x2227), true + case "angdnl": + // TURNED ANGLE + return rune(0x29a2), true + case "angdnr": + // ACUTE ANGLE + return rune(0x299f), true + case "ange": + // ANGLE WITH UNDERBAR + return rune(0x29a4), true + case "angles": + // ANGLE WITH S INSIDE + return rune(0x299e), true + case "angle": + // ANGLE + return rune(0x2220), true + case "angmsdaa": + // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING UP AND RIGHT + return rune(0x29a8), true + case "angmsdab": + // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING UP AND LEFT + return rune(0x29a9), true + case "angmsdac": + // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING DOWN AND RIGHT + return rune(0x29aa), true + case "angmsdad": + // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING DOWN AND LEFT + return rune(0x29ab), true + case "angmsdae": + // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING RIGHT AND UP + return rune(0x29ac), true + case "angmsdaf": + // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING LEFT AND UP + return rune(0x29ad), true + case "angmsdag": + // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING RIGHT AND DOWN + return rune(0x29ae), true + case "angmsdah": + // MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING LEFT AND DOWN + return rune(0x29af), true + case "angmsd": + // MEASURED ANGLE + return rune(0x2221), true + case "angrtvbd": + // MEASURED RIGHT ANGLE WITH DOT + return rune(0x299d), true + case "angrtvb": + // RIGHT ANGLE WITH ARC + return rune(0x22be), true + case "angsph": + // SPHERICAL ANGLE + return rune(0x2222), true + case "angst": + // LATIN CAPITAL LETTER A WITH RING ABOVE + return rune(0xc5), true + case "angupl": + // REVERSED ANGLE + return rune(0x29a3), true + case "angzarr": + // RIGHT ANGLE WITH DOWNWARDS ZIGZAG ARROW + return rune(0x237c), true + case "ang": + // ANGLE + return rune(0x2220), true + case "ang90": + // RIGHT ANGLE + return rune(0x221f), true + case "angrt": + // RIGHT ANGLE + return rune(0x221f), true + case "aogon": + // LATIN SMALL LETTER A WITH OGONEK + return rune(0x0105), true + case "aopf": + // MATHEMATICAL DOUBLE-STRUCK SMALL A + return rune(0x01d552), true + case "apE": + // APPROXIMATELY EQUAL OR EQUAL TO + return rune(0x2a70), true + case "apacir": + // ALMOST EQUAL TO WITH CIRCUMFLEX ACCENT + return rune(0x2a6f), true + case "ape": + // ALMOST EQUAL OR EQUAL TO + return rune(0x224a), true + case "apid": + // TRIPLE TILDE + return rune(0x224b), true + case "approxeq": + // ALMOST EQUAL OR EQUAL TO + return rune(0x224a), true + case "approx": + // ALMOST EQUAL TO + return rune(0x2248), true + case "ap": + // ALMOST EQUAL TO + return rune(0x2248), true + case "apos": + // APOSTROPHE + return rune(0x27), true + case "aring": + // LATIN SMALL LETTER A WITH RING ABOVE + return rune(0xe5), true + case "arrllsr": + // LEFTWARDS ARROW ABOVE SHORT RIGHTWARDS ARROW + return rune(0x2943), true + case "arrlrsl": + // RIGHTWARDS ARROW ABOVE SHORT LEFTWARDS ARROW + return rune(0x2942), true + case "arrsrll": + // SHORT RIGHTWARDS ARROW ABOVE LEFTWARDS ARROW + return rune(0x2944), true + case "ascr": + // MATHEMATICAL SCRIPT SMALL A + return rune(0x01d4b6), true + case "astb": + // SQUARED ASTERISK + return rune(0x29c6), true + case "ast": + // ASTERISK + return rune(0x2a), true + case "asympeq": + // EQUIVALENT TO + return rune(0x224d), true + case "asymp": + // ALMOST EQUAL TO + return rune(0x2248), true + case "atilde": + // LATIN SMALL LETTER A WITH TILDE + return rune(0xe3), true + case "auml": + // LATIN SMALL LETTER A WITH DIAERESIS + return rune(0xe4), true + case "awconint": + // ANTICLOCKWISE CONTOUR INTEGRAL + return rune(0x2233), true + case "awint": + // ANTICLOCKWISE INTEGRATION + return rune(0x2a11), true + } + + case 'b': + switch name { + case "b.Delta": + // MATHEMATICAL BOLD CAPITAL DELTA + return rune(0x01d6ab), true + case "b.Gamma": + // MATHEMATICAL BOLD CAPITAL GAMMA + return rune(0x01d6aa), true + case "b.Gammad": + // MATHEMATICAL BOLD CAPITAL DIGAMMA + return rune(0x01d7ca), true + case "b.Lambda": + // MATHEMATICAL BOLD CAPITAL LAMDA + return rune(0x01d6b2), true + case "b.Omega": + // MATHEMATICAL BOLD CAPITAL OMEGA + return rune(0x01d6c0), true + case "b.Phi": + // MATHEMATICAL BOLD CAPITAL PHI + return rune(0x01d6bd), true + case "b.Pi": + // MATHEMATICAL BOLD CAPITAL PI + return rune(0x01d6b7), true + case "b.Psi": + // MATHEMATICAL BOLD CAPITAL PSI + return rune(0x01d6bf), true + case "b.Sigma": + // MATHEMATICAL BOLD CAPITAL SIGMA + return rune(0x01d6ba), true + case "b.Theta": + // MATHEMATICAL BOLD CAPITAL THETA + return rune(0x01d6af), true + case "b.Upsi": + // MATHEMATICAL BOLD CAPITAL UPSILON + return rune(0x01d6bc), true + case "b.Xi": + // MATHEMATICAL BOLD CAPITAL XI + return rune(0x01d6b5), true + case "b.alpha": + // MATHEMATICAL BOLD SMALL ALPHA + return rune(0x01d6c2), true + case "b.beta": + // MATHEMATICAL BOLD SMALL BETA + return rune(0x01d6c3), true + case "b.chi": + // MATHEMATICAL BOLD SMALL CHI + return rune(0x01d6d8), true + case "b.delta": + // MATHEMATICAL BOLD SMALL DELTA + return rune(0x01d6c5), true + case "b.epsi": + // MATHEMATICAL BOLD SMALL EPSILON + return rune(0x01d6c6), true + case "b.epsiv": + // MATHEMATICAL BOLD EPSILON SYMBOL + return rune(0x01d6dc), true + case "b.eta": + // MATHEMATICAL BOLD SMALL ETA + return rune(0x01d6c8), true + case "b.gammad": + // MATHEMATICAL BOLD SMALL DIGAMMA + return rune(0x01d7cb), true + case "b.gamma": + // MATHEMATICAL BOLD SMALL GAMMA + return rune(0x01d6c4), true + case "b.iota": + // MATHEMATICAL BOLD SMALL IOTA + return rune(0x01d6ca), true + case "b.kappa": + // MATHEMATICAL BOLD SMALL KAPPA + return rune(0x01d6cb), true + case "b.kappav": + // MATHEMATICAL BOLD KAPPA SYMBOL + return rune(0x01d6de), true + case "b.lambda": + // MATHEMATICAL BOLD SMALL LAMDA + return rune(0x01d6cc), true + case "b.mu": + // MATHEMATICAL BOLD SMALL MU + return rune(0x01d6cd), true + case "b.nu": + // MATHEMATICAL BOLD SMALL NU + return rune(0x01d6ce), true + case "b.omega": + // MATHEMATICAL BOLD SMALL OMEGA + return rune(0x01d6da), true + case "b.phi": + // MATHEMATICAL BOLD SMALL PHI + return rune(0x01d6d7), true + case "b.phiv": + // MATHEMATICAL BOLD PHI SYMBOL + return rune(0x01d6df), true + case "b.pi": + // MATHEMATICAL BOLD SMALL PI + return rune(0x01d6d1), true + case "b.piv": + // MATHEMATICAL BOLD PI SYMBOL + return rune(0x01d6e1), true + case "b.psi": + // MATHEMATICAL BOLD SMALL PSI + return rune(0x01d6d9), true + case "b.rho": + // MATHEMATICAL BOLD SMALL RHO + return rune(0x01d6d2), true + case "b.rhov": + // MATHEMATICAL BOLD RHO SYMBOL + return rune(0x01d6e0), true + case "b.sigmav": + // MATHEMATICAL BOLD SMALL FINAL SIGMA + return rune(0x01d6d3), true + case "b.sigma": + // MATHEMATICAL BOLD SMALL SIGMA + return rune(0x01d6d4), true + case "b.tau": + // MATHEMATICAL BOLD SMALL TAU + return rune(0x01d6d5), true + case "b.thetas": + // MATHEMATICAL BOLD SMALL THETA + return rune(0x01d6c9), true + case "b.thetav": + // MATHEMATICAL BOLD THETA SYMBOL + return rune(0x01d6dd), true + case "b.upsi": + // MATHEMATICAL BOLD SMALL UPSILON + return rune(0x01d6d6), true + case "b.xi": + // MATHEMATICAL BOLD SMALL XI + return rune(0x01d6cf), true + case "b.zeta": + // MATHEMATICAL BOLD SMALL ZETA + return rune(0x01d6c7), true + case "bNot": + // REVERSED DOUBLE STROKE NOT SIGN + return rune(0x2aed), true + case "backcong": + // ALL EQUAL TO + return rune(0x224c), true + case "backepsilon": + // GREEK REVERSED LUNATE EPSILON SYMBOL + return rune(0x03f6), true + case "backprime": + // REVERSED PRIME + return rune(0x2035), true + case "backsimeq": + // REVERSED TILDE EQUALS + return rune(0x22cd), true + case "backsim": + // REVERSED TILDE + return rune(0x223d), true + case "barV": + // DOUBLE DOWN TACK + return rune(0x2aea), true + case "barvee": + // NOR + return rune(0x22bd), true + case "barwed": + // PROJECTIVE + return rune(0x2305), true + case "barwedge": + // PROJECTIVE + return rune(0x2305), true + case "bbrk": + // BOTTOM SQUARE BRACKET + return rune(0x23b5), true + case "bbrktbrk": + // BOTTOM SQUARE BRACKET OVER TOP SQUARE BRACKET + return rune(0x23b6), true + case "bcong": + // ALL EQUAL TO + return rune(0x224c), true + case "bcy": + // CYRILLIC SMALL LETTER BE + return rune(0x0431), true + case "bdlhar": + // DOWNWARDS HARPOON WITH BARB LEFT FROM BAR + return rune(0x2961), true + case "bdquo": + // DOUBLE LOW-9 QUOTATION MARK + return rune(0x201e), true + case "bdrhar": + // DOWNWARDS HARPOON WITH BARB RIGHT FROM BAR + return rune(0x295d), true + case "because": + // BECAUSE + return rune(0x2235), true + case "becaus": + // BECAUSE + return rune(0x2235), true + case "bemptyv": + // REVERSED EMPTY SET + return rune(0x29b0), true + case "bepsi": + // GREEK REVERSED LUNATE EPSILON SYMBOL + return rune(0x03f6), true + case "bernou": + // SCRIPT CAPITAL B + return rune(0x212c), true + case "beta": + // GREEK SMALL LETTER BETA + return rune(0x03b2), true + case "beth": + // BET SYMBOL + return rune(0x2136), true + case "between": + // BETWEEN + return rune(0x226c), true + case "bfr": + // MATHEMATICAL FRAKTUR SMALL B + return rune(0x01d51f), true + case "bgr": + // GREEK SMALL LETTER BETA + return rune(0x03b2), true + case "bigcap": + // N-ARY INTERSECTION + return rune(0x22c2), true + case "bigcirc": + // LARGE CIRCLE + return rune(0x25ef), true + case "bigcup": + // N-ARY UNION + return rune(0x22c3), true + case "bigodot": + // N-ARY CIRCLED DOT OPERATOR + return rune(0x2a00), true + case "bigoplus": + // N-ARY CIRCLED PLUS OPERATOR + return rune(0x2a01), true + case "bigotimes": + // N-ARY CIRCLED TIMES OPERATOR + return rune(0x2a02), true + case "bigsqcup": + // N-ARY SQUARE UNION OPERATOR + return rune(0x2a06), true + case "bigstar": + // BLACK STAR + return rune(0x2605), true + case "bigtriangledown": + // WHITE DOWN-POINTING TRIANGLE + return rune(0x25bd), true + case "bigtriangleup": + // WHITE UP-POINTING TRIANGLE + return rune(0x25b3), true + case "biguplus": + // N-ARY UNION OPERATOR WITH PLUS + return rune(0x2a04), true + case "bigvee": + // N-ARY LOGICAL OR + return rune(0x22c1), true + case "bigwedge": + // N-ARY LOGICAL AND + return rune(0x22c0), true + case "bkarow": + // RIGHTWARDS DOUBLE DASH ARROW + return rune(0x290d), true + case "blacklozenge": + // BLACK LOZENGE + return rune(0x29eb), true + case "blacksquare": + // BLACK SMALL SQUARE + return rune(0x25aa), true + case "blacktriangledown": + // BLACK DOWN-POINTING SMALL TRIANGLE + return rune(0x25be), true + case "blacktriangleleft": + // BLACK LEFT-POINTING SMALL TRIANGLE + return rune(0x25c2), true + case "blacktriangleright": + // BLACK RIGHT-POINTING SMALL TRIANGLE + return rune(0x25b8), true + case "blacktriangle": + // BLACK UP-POINTING SMALL TRIANGLE + return rune(0x25b4), true + case "blank": + // BLANK SYMBOL + return rune(0x2422), true + case "bldhar": + // LEFTWARDS HARPOON WITH BARB DOWN FROM BAR + return rune(0x295e), true + case "blk12": + // MEDIUM SHADE + return rune(0x2592), true + case "blk14": + // LIGHT SHADE + return rune(0x2591), true + case "blk34": + // DARK SHADE + return rune(0x2593), true + case "block": + // FULL BLOCK + return rune(0x2588), true + case "bluhar": + // LEFTWARDS HARPOON WITH BARB UP FROM BAR + return rune(0x295a), true + case "bnequiv": + // IDENTICAL TO with reverse slash + return rune(0x2261), true + case "bne": + // EQUALS SIGN with reverse slash + return rune(0x3d), true + case "bnot": + // REVERSED NOT SIGN + return rune(0x2310), true + case "bopf": + // MATHEMATICAL DOUBLE-STRUCK SMALL B + return rune(0x01d553), true + case "bot": + // UP TACK + return rune(0x22a5), true + case "bottom": + // UP TACK + return rune(0x22a5), true + case "bowtie": + // BOWTIE + return rune(0x22c8), true + case "boxDL": + // BOX DRAWINGS DOUBLE DOWN AND LEFT + return rune(0x2557), true + case "boxDR": + // BOX DRAWINGS DOUBLE DOWN AND RIGHT + return rune(0x2554), true + case "boxDl": + // BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE + return rune(0x2556), true + case "boxDr": + // BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE + return rune(0x2553), true + case "boxHD": + // BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL + return rune(0x2566), true + case "boxHU": + // BOX DRAWINGS DOUBLE UP AND HORIZONTAL + return rune(0x2569), true + case "boxHd": + // BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE + return rune(0x2564), true + case "boxHu": + // BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE + return rune(0x2567), true + case "boxH": + // BOX DRAWINGS DOUBLE HORIZONTAL + return rune(0x2550), true + case "boxUL": + // BOX DRAWINGS DOUBLE UP AND LEFT + return rune(0x255d), true + case "boxUR": + // BOX DRAWINGS DOUBLE UP AND RIGHT + return rune(0x255a), true + case "boxUl": + // BOX DRAWINGS UP DOUBLE AND LEFT SINGLE + return rune(0x255c), true + case "boxUr": + // BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE + return rune(0x2559), true + case "boxVH": + // BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL + return rune(0x256c), true + case "boxVL": + // BOX DRAWINGS DOUBLE VERTICAL AND LEFT + return rune(0x2563), true + case "boxVR": + // BOX DRAWINGS DOUBLE VERTICAL AND RIGHT + return rune(0x2560), true + case "boxVh": + // BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE + return rune(0x256b), true + case "boxVl": + // BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE + return rune(0x2562), true + case "boxVr": + // BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE + return rune(0x255f), true + case "boxV": + // BOX DRAWINGS DOUBLE VERTICAL + return rune(0x2551), true + case "boxbox": + // TWO JOINED SQUARES + return rune(0x29c9), true + case "boxdL": + // BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE + return rune(0x2555), true + case "boxdR": + // BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE + return rune(0x2552), true + case "boxdl": + // BOX DRAWINGS LIGHT DOWN AND LEFT + return rune(0x2510), true + case "boxdr": + // BOX DRAWINGS LIGHT DOWN AND RIGHT + return rune(0x250c), true + case "boxhU": + // BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE + return rune(0x2568), true + case "boxh": + // BOX DRAWINGS LIGHT HORIZONTAL + return rune(0x2500), true + case "boxhD": + // BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE + return rune(0x2565), true + case "boxhd": + // BOX DRAWINGS LIGHT DOWN AND HORIZONTAL + return rune(0x252c), true + case "boxhu": + // BOX DRAWINGS LIGHT UP AND HORIZONTAL + return rune(0x2534), true + case "boxminus": + // SQUARED MINUS + return rune(0x229f), true + case "boxplus": + // SQUARED PLUS + return rune(0x229e), true + case "boxtimes": + // SQUARED TIMES + return rune(0x22a0), true + case "boxuL": + // BOX DRAWINGS UP SINGLE AND LEFT DOUBLE + return rune(0x255b), true + case "boxuR": + // BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE + return rune(0x2558), true + case "boxul": + // BOX DRAWINGS LIGHT UP AND LEFT + return rune(0x2518), true + case "boxur": + // BOX DRAWINGS LIGHT UP AND RIGHT + return rune(0x2514), true + case "boxvL": + // BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE + return rune(0x2561), true + case "boxvR": + // BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE + return rune(0x255e), true + case "boxvl": + // BOX DRAWINGS LIGHT VERTICAL AND LEFT + return rune(0x2524), true + case "boxvr": + // BOX DRAWINGS LIGHT VERTICAL AND RIGHT + return rune(0x251c), true + case "boxv": + // BOX DRAWINGS LIGHT VERTICAL + return rune(0x2502), true + case "boxvH": + // BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE + return rune(0x256a), true + case "boxvh": + // BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL + return rune(0x253c), true + case "bprime": + // REVERSED PRIME + return rune(0x2035), true + case "brdhar": + // RIGHTWARDS HARPOON WITH BARB DOWN FROM BAR + return rune(0x295f), true + case "breve": + // BREVE + return rune(0x02d8), true + case "bruhar": + // RIGHTWARDS HARPOON WITH BARB UP FROM BAR + return rune(0x295b), true + case "brvbar": + // BROKEN BAR + return rune(0xa6), true + case "bscr": + // MATHEMATICAL SCRIPT SMALL B + return rune(0x01d4b7), true + case "bsemi": + // REVERSED SEMICOLON + return rune(0x204f), true + case "bsim": + // REVERSED TILDE + return rune(0x223d), true + case "bsime": + // REVERSED TILDE EQUALS + return rune(0x22cd), true + case "bsolb": + // SQUARED FALLING DIAGONAL SLASH + return rune(0x29c5), true + case "bsolhsub": + // REVERSE SOLIDUS PRECEDING SUBSET + return rune(0x27c8), true + case "bsol": + // REVERSE SOLIDUS + return rune(0x5c), true + case "btimes": + // SEMIDIRECT PRODUCT WITH BOTTOM CLOSED + return rune(0x2a32), true + case "bulhar": + // UPWARDS HARPOON WITH BARB LEFT FROM BAR + return rune(0x2960), true + case "bullet": + // BULLET + return rune(0x2022), true + case "bull": + // BULLET + return rune(0x2022), true + case "bump": + // GEOMETRICALLY EQUIVALENT TO + return rune(0x224e), true + case "bumpE": + // EQUALS SIGN WITH BUMPY ABOVE + return rune(0x2aae), true + case "bumpe": + // DIFFERENCE BETWEEN + return rune(0x224f), true + case "bumpeq": + // DIFFERENCE BETWEEN + return rune(0x224f), true + case "burhar": + // UPWARDS HARPOON WITH BARB RIGHT FROM BAR + return rune(0x295c), true + } + + case 'c': + switch name { + case "cacute": + // LATIN SMALL LETTER C WITH ACUTE + return rune(0x0107), true + case "cap": + // INTERSECTION + return rune(0x2229), true + case "capand": + // INTERSECTION WITH LOGICAL AND + return rune(0x2a44), true + case "capbrcup": + // INTERSECTION ABOVE BAR ABOVE UNION + return rune(0x2a49), true + case "capcap": + // INTERSECTION BESIDE AND JOINED WITH INTERSECTION + return rune(0x2a4b), true + case "capcup": + // INTERSECTION ABOVE UNION + return rune(0x2a47), true + case "capdot": + // INTERSECTION WITH DOT + return rune(0x2a40), true + case "capint": + // INTEGRAL WITH INTERSECTION + return rune(0x2a19), true + case "caps": + // INTERSECTION with serifs + return rune(0x2229), true + case "caret": + // CARET INSERTION POINT + return rune(0x2041), true + case "caron": + // CARON + return rune(0x02c7), true + case "ccaps": + // CLOSED INTERSECTION WITH SERIFS + return rune(0x2a4d), true + case "ccaron": + // LATIN SMALL LETTER C WITH CARON + return rune(0x010d), true + case "ccedil": + // LATIN SMALL LETTER C WITH CEDILLA + return rune(0xe7), true + case "ccirc": + // LATIN SMALL LETTER C WITH CIRCUMFLEX + return rune(0x0109), true + case "ccups": + // CLOSED UNION WITH SERIFS + return rune(0x2a4c), true + case "ccupssm": + // CLOSED UNION WITH SERIFS AND SMASH PRODUCT + return rune(0x2a50), true + case "cdot": + // LATIN SMALL LETTER C WITH DOT ABOVE + return rune(0x010b), true + case "cedil": + // CEDILLA + return rune(0xb8), true + case "cemptyv": + // EMPTY SET WITH SMALL CIRCLE ABOVE + return rune(0x29b2), true + case "centerdot": + // MIDDLE DOT + return rune(0xb7), true + case "cent": + // CENT SIGN + return rune(0xa2), true + case "cfr": + // MATHEMATICAL FRAKTUR SMALL C + return rune(0x01d520), true + case "chcy": + // CYRILLIC SMALL LETTER CHE + return rune(0x0447), true + case "check": + // CHECK MARK + return rune(0x2713), true + case "checkmark": + // CHECK MARK + return rune(0x2713), true + case "chi": + // GREEK SMALL LETTER CHI + return rune(0x03c7), true + case "circeq": + // RING EQUAL TO + return rune(0x2257), true + case "circlearrowleft": + // ANTICLOCKWISE OPEN CIRCLE ARROW + return rune(0x21ba), true + case "circlearrowright": + // CLOCKWISE OPEN CIRCLE ARROW + return rune(0x21bb), true + case "circledS": + // CIRCLED LATIN CAPITAL LETTER S + return rune(0x24c8), true + case "circledast": + // CIRCLED ASTERISK OPERATOR + return rune(0x229b), true + case "circledcirc": + // CIRCLED RING OPERATOR + return rune(0x229a), true + case "circleddash": + // CIRCLED DASH + return rune(0x229d), true + case "cire": + // RING EQUAL TO + return rune(0x2257), true + case "cir": + // WHITE CIRCLE + return rune(0x25cb), true + case "cirE": + // CIRCLE WITH TWO HORIZONTAL STROKES TO THE RIGHT + return rune(0x29c3), true + case "cirb": + // SQUARED SMALL CIRCLE + return rune(0x29c7), true + case "circ": + // MODIFIER LETTER CIRCUMFLEX ACCENT + return rune(0x02c6), true + case "circledR": + // REGISTERED SIGN + return rune(0xae), true + case "cirdarr": + // WHITE CIRCLE WITH DOWN ARROW + return rune(0x29ec), true + case "cirerr": + // ERROR-BARRED WHITE CIRCLE + return rune(0x29f2), true + case "cirfdarr": + // BLACK CIRCLE WITH DOWN ARROW + return rune(0x29ed), true + case "cirferr": + // ERROR-BARRED BLACK CIRCLE + return rune(0x29f3), true + case "cirfnint": + // CIRCULATION FUNCTION + return rune(0x2a10), true + case "cirmid": + // VERTICAL LINE WITH CIRCLE ABOVE + return rune(0x2aef), true + case "cirscir": + // CIRCLE WITH SMALL CIRCLE TO THE RIGHT + return rune(0x29c2), true + case "closur": + // CLOSE UP + return rune(0x2050), true + case "clubs": + // BLACK CLUB SUIT + return rune(0x2663), true + case "clubsuit": + // BLACK CLUB SUIT + return rune(0x2663), true + case "colone": + // COLON EQUALS + return rune(0x2254), true + case "coloneq": + // COLON EQUALS + return rune(0x2254), true + case "colon": + // COLON + return rune(0x3a), true + case "commat": + // COMMERCIAL AT + return rune(0x40), true + case "comma": + // COMMA + return rune(0x2c), true + case "comp": + // COMPLEMENT + return rune(0x2201), true + case "compfn": + // RING OPERATOR + return rune(0x2218), true + case "complement": + // COMPLEMENT + return rune(0x2201), true + case "complexes": + // DOUBLE-STRUCK CAPITAL C + return rune(0x2102), true + case "cong": + // APPROXIMATELY EQUAL TO + return rune(0x2245), true + case "congdot": + // CONGRUENT WITH DOT ABOVE + return rune(0x2a6d), true + case "conint": + // CONTOUR INTEGRAL + return rune(0x222e), true + case "copf": + // MATHEMATICAL DOUBLE-STRUCK SMALL C + return rune(0x01d554), true + case "coprod": + // N-ARY COPRODUCT + return rune(0x2210), true + case "copysr": + // SOUND RECORDING COPYRIGHT + return rune(0x2117), true + case "copy": + // COPYRIGHT SIGN + return rune(0xa9), true + case "crarr": + // DOWNWARDS ARROW WITH CORNER LEFTWARDS + return rune(0x21b5), true + case "cross": + // BALLOT X + return rune(0x2717), true + case "cscr": + // MATHEMATICAL SCRIPT SMALL C + return rune(0x01d4b8), true + case "csub": + // CLOSED SUBSET + return rune(0x2acf), true + case "csube": + // CLOSED SUBSET OR EQUAL TO + return rune(0x2ad1), true + case "csup": + // CLOSED SUPERSET + return rune(0x2ad0), true + case "csupe": + // CLOSED SUPERSET OR EQUAL TO + return rune(0x2ad2), true + case "ctdot": + // MIDLINE HORIZONTAL ELLIPSIS + return rune(0x22ef), true + case "cudarrl": + // RIGHT-SIDE ARC CLOCKWISE ARROW + return rune(0x2938), true + case "cudarrr": + // ARROW POINTING RIGHTWARDS THEN CURVING DOWNWARDS + return rune(0x2935), true + case "cuepr": + // EQUAL TO OR PRECEDES + return rune(0x22de), true + case "cuesc": + // EQUAL TO OR SUCCEEDS + return rune(0x22df), true + case "cularr": + // ANTICLOCKWISE TOP SEMICIRCLE ARROW + return rune(0x21b6), true + case "cularrp": + // TOP ARC ANTICLOCKWISE ARROW WITH PLUS + return rune(0x293d), true + case "cup": + // UNION + return rune(0x222a), true + case "cupbrcap": + // UNION ABOVE BAR ABOVE INTERSECTION + return rune(0x2a48), true + case "cupcap": + // UNION ABOVE INTERSECTION + return rune(0x2a46), true + case "cupcup": + // UNION BESIDE AND JOINED WITH UNION + return rune(0x2a4a), true + case "cupdot": + // MULTISET MULTIPLICATION + return rune(0x228d), true + case "cupint": + // INTEGRAL WITH UNION + return rune(0x2a1a), true + case "cupor": + // UNION WITH LOGICAL OR + return rune(0x2a45), true + case "cupre": + // PRECEDES OR EQUAL TO + return rune(0x227c), true + case "cups": + // UNION with serifs + return rune(0x222a), true + case "curarr": + // CLOCKWISE TOP SEMICIRCLE ARROW + return rune(0x21b7), true + case "curarrm": + // TOP ARC CLOCKWISE ARROW WITH MINUS + return rune(0x293c), true + case "curlyeqprec": + // EQUAL TO OR PRECEDES + return rune(0x22de), true + case "curlyeqsucc": + // EQUAL TO OR SUCCEEDS + return rune(0x22df), true + case "curlyvee": + // CURLY LOGICAL OR + return rune(0x22ce), true + case "curlywedge": + // CURLY LOGICAL AND + return rune(0x22cf), true + case "curren": + // CURRENCY SIGN + return rune(0xa4), true + case "curvearrowleft": + // ANTICLOCKWISE TOP SEMICIRCLE ARROW + return rune(0x21b6), true + case "curvearrowright": + // CLOCKWISE TOP SEMICIRCLE ARROW + return rune(0x21b7), true + case "cuvee": + // CURLY LOGICAL OR + return rune(0x22ce), true + case "cuwed": + // CURLY LOGICAL AND + return rune(0x22cf), true + case "cwconint": + // CLOCKWISE CONTOUR INTEGRAL + return rune(0x2232), true + case "cwint": + // CLOCKWISE INTEGRAL + return rune(0x2231), true + case "cylcty": + // CYLINDRICITY + return rune(0x232d), true + } + + case 'd': + switch name { + case "dAarr": + // DOWNWARDS TRIPLE ARROW + return rune(0x290b), true + case "dArr": + // DOWNWARDS DOUBLE ARROW + return rune(0x21d3), true + case "dHar": + // DOWNWARDS HARPOON WITH BARB LEFT BESIDE DOWNWARDS HARPOON WITH BARB RIGHT + return rune(0x2965), true + case "dagger": + // DAGGER + return rune(0x2020), true + case "dalembrt": + // SQUARE WITH CONTOURED OUTLINE + return rune(0x29e0), true + case "daleth": + // DALET SYMBOL + return rune(0x2138), true + case "darr2": + // DOWNWARDS PAIRED ARROWS + return rune(0x21ca), true + case "darr": + // DOWNWARDS ARROW + return rune(0x2193), true + case "darrb": + // DOWNWARDS ARROW TO BAR + return rune(0x2913), true + case "darrln": + // DOWNWARDS ARROW WITH HORIZONTAL STROKE + return rune(0x2908), true + case "dashv": + // LEFT TACK + return rune(0x22a3), true + case "dash": + // HYPHEN + return rune(0x2010), true + case "dashV": + // DOUBLE VERTICAL BAR LEFT TURNSTILE + return rune(0x2ae3), true + case "dbkarow": + // RIGHTWARDS TRIPLE DASH ARROW + return rune(0x290f), true + case "dblac": + // DOUBLE ACUTE ACCENT + return rune(0x02dd), true + case "dcaron": + // LATIN SMALL LETTER D WITH CARON + return rune(0x010f), true + case "dcy": + // CYRILLIC SMALL LETTER DE + return rune(0x0434), true + case "ddarr": + // DOWNWARDS PAIRED ARROWS + return rune(0x21ca), true + case "dd": + // DOUBLE-STRUCK ITALIC SMALL D + return rune(0x2146), true + case "ddagger": + // DOUBLE DAGGER + return rune(0x2021), true + case "ddotseq": + // EQUALS SIGN WITH TWO DOTS ABOVE AND TWO DOTS BELOW + return rune(0x2a77), true + case "deg": + // DEGREE SIGN + return rune(0xb0), true + case "delta": + // GREEK SMALL LETTER DELTA + return rune(0x03b4), true + case "demptyv": + // EMPTY SET WITH OVERBAR + return rune(0x29b1), true + case "dfisht": + // DOWN FISH TAIL + return rune(0x297f), true + case "dfr": + // MATHEMATICAL FRAKTUR SMALL D + return rune(0x01d521), true + case "dgr": + // GREEK SMALL LETTER DELTA + return rune(0x03b4), true + case "dharl": + // DOWNWARDS HARPOON WITH BARB LEFTWARDS + return rune(0x21c3), true + case "dharr": + // DOWNWARDS HARPOON WITH BARB RIGHTWARDS + return rune(0x21c2), true + case "diam": + // DIAMOND OPERATOR + return rune(0x22c4), true + case "diamdarr": + // BLACK DIAMOND WITH DOWN ARROW + return rune(0x29ea), true + case "diamerr": + // ERROR-BARRED WHITE DIAMOND + return rune(0x29f0), true + case "diamerrf": + // ERROR-BARRED BLACK DIAMOND + return rune(0x29f1), true + case "diamond": + // DIAMOND OPERATOR + return rune(0x22c4), true + case "diamondsuit": + // BLACK DIAMOND SUIT + return rune(0x2666), true + case "diams": + // BLACK DIAMOND SUIT + return rune(0x2666), true + case "die": + // DIAERESIS + return rune(0xa8), true + case "digamma": + // GREEK SMALL LETTER DIGAMMA + return rune(0x03dd), true + case "disin": + // ELEMENT OF WITH LONG HORIZONTAL STROKE + return rune(0x22f2), true + case "divideontimes": + // DIVISION TIMES + return rune(0x22c7), true + case "divonx": + // DIVISION TIMES + return rune(0x22c7), true + case "div": + // DIVISION SIGN + return rune(0xf7), true + case "divide": + // DIVISION SIGN + return rune(0xf7), true + case "djcy": + // CYRILLIC SMALL LETTER DJE + return rune(0x0452), true + case "dlarr": + // SOUTH WEST ARROW + return rune(0x2199), true + case "dlcorn": + // BOTTOM LEFT CORNER + return rune(0x231e), true + case "dlcrop": + // BOTTOM LEFT CROP + return rune(0x230d), true + case "dlharb": + // DOWNWARDS HARPOON WITH BARB LEFT TO BAR + return rune(0x2959), true + case "dollar": + // DOLLAR SIGN + return rune(0x24), true + case "dopf": + // MATHEMATICAL DOUBLE-STRUCK SMALL D + return rune(0x01d555), true + case "doteq": + // APPROACHES THE LIMIT + return rune(0x2250), true + case "doteqdot": + // GEOMETRICALLY EQUAL TO + return rune(0x2251), true + case "dotminus": + // DOT MINUS + return rune(0x2238), true + case "dotplus": + // DOT PLUS + return rune(0x2214), true + case "dotsquare": + // SQUARED DOT OPERATOR + return rune(0x22a1), true + case "dot": + // DOT ABOVE + return rune(0x02d9), true + case "doublebarwedge": + // PERSPECTIVE + return rune(0x2306), true + case "downarrow": + // DOWNWARDS ARROW + return rune(0x2193), true + case "downdownarrows": + // DOWNWARDS PAIRED ARROWS + return rune(0x21ca), true + case "downharpoonleft": + // DOWNWARDS HARPOON WITH BARB LEFTWARDS + return rune(0x21c3), true + case "downharpoonright": + // DOWNWARDS HARPOON WITH BARB RIGHTWARDS + return rune(0x21c2), true + case "drarr": + // SOUTH EAST ARROW + return rune(0x2198), true + case "drbkarow": + // RIGHTWARDS TWO-HEADED TRIPLE DASH ARROW + return rune(0x2910), true + case "drcorn": + // BOTTOM RIGHT CORNER + return rune(0x231f), true + case "drcrop": + // BOTTOM RIGHT CROP + return rune(0x230c), true + case "drharb": + // DOWNWARDS HARPOON WITH BARB RIGHT TO BAR + return rune(0x2955), true + case "dscr": + // MATHEMATICAL SCRIPT SMALL D + return rune(0x01d4b9), true + case "dscy": + // CYRILLIC SMALL LETTER DZE + return rune(0x0455), true + case "dsol": + // SOLIDUS WITH OVERBAR + return rune(0x29f6), true + case "dstrok": + // LATIN SMALL LETTER D WITH STROKE + return rune(0x0111), true + case "dtdot": + // DOWN RIGHT DIAGONAL ELLIPSIS + return rune(0x22f1), true + case "dtrif": + // BLACK DOWN-POINTING SMALL TRIANGLE + return rune(0x25be), true + case "dtri": + // WHITE DOWN-POINTING SMALL TRIANGLE + return rune(0x25bf), true + case "dtrilf": + // DOWN-POINTING TRIANGLE WITH LEFT HALF BLACK + return rune(0x29e8), true + case "dtrirf": + // DOWN-POINTING TRIANGLE WITH RIGHT HALF BLACK + return rune(0x29e9), true + case "duarr": + // DOWNWARDS ARROW LEFTWARDS OF UPWARDS ARROW + return rune(0x21f5), true + case "duhar": + // DOWNWARDS HARPOON WITH BARB LEFT BESIDE UPWARDS HARPOON WITH BARB RIGHT + return rune(0x296f), true + case "dumap": + // DOUBLE-ENDED MULTIMAP + return rune(0x29df), true + case "dwangle": + // OBLIQUE ANGLE OPENING UP + return rune(0x29a6), true + case "dzcy": + // CYRILLIC SMALL LETTER DZHE + return rune(0x045f), true + case "dzigrarr": + // LONG RIGHTWARDS SQUIGGLE ARROW + return rune(0x27ff), true + } + + case 'e': + switch name { + case "eDDot": + // EQUALS SIGN WITH TWO DOTS ABOVE AND TWO DOTS BELOW + return rune(0x2a77), true + case "eDot": + // GEOMETRICALLY EQUAL TO + return rune(0x2251), true + case "eacgr": + // GREEK SMALL LETTER EPSILON WITH TONOS + return rune(0x03ad), true + case "eacute": + // LATIN SMALL LETTER E WITH ACUTE + return rune(0xe9), true + case "easter": + // EQUALS WITH ASTERISK + return rune(0x2a6e), true + case "ecaron": + // LATIN SMALL LETTER E WITH CARON + return rune(0x011b), true + case "ecir": + // RING IN EQUAL TO + return rune(0x2256), true + case "ecirc": + // LATIN SMALL LETTER E WITH CIRCUMFLEX + return rune(0xea), true + case "ecolon": + // EQUALS COLON + return rune(0x2255), true + case "ecy": + // CYRILLIC SMALL LETTER E + return rune(0x044d), true + case "edot": + // LATIN SMALL LETTER E WITH DOT ABOVE + return rune(0x0117), true + case "ee": + // DOUBLE-STRUCK ITALIC SMALL E + return rune(0x2147), true + case "eeacgr": + // GREEK SMALL LETTER ETA WITH TONOS + return rune(0x03ae), true + case "eegr": + // GREEK SMALL LETTER ETA + return rune(0x03b7), true + case "efDot": + // APPROXIMATELY EQUAL TO OR THE IMAGE OF + return rune(0x2252), true + case "efr": + // MATHEMATICAL FRAKTUR SMALL E + return rune(0x01d522), true + case "egr": + // GREEK SMALL LETTER EPSILON + return rune(0x03b5), true + case "egs": + // SLANTED EQUAL TO OR GREATER-THAN + return rune(0x2a96), true + case "egsdot": + // SLANTED EQUAL TO OR GREATER-THAN WITH DOT INSIDE + return rune(0x2a98), true + case "eg": + // DOUBLE-LINE EQUAL TO OR GREATER-THAN + return rune(0x2a9a), true + case "egrave": + // LATIN SMALL LETTER E WITH GRAVE + return rune(0xe8), true + case "elinters": + // ELECTRICAL INTERSECTION + return rune(0x23e7), true + case "ell": + // SCRIPT SMALL L + return rune(0x2113), true + case "els": + // SLANTED EQUAL TO OR LESS-THAN + return rune(0x2a95), true + case "elsdot": + // SLANTED EQUAL TO OR LESS-THAN WITH DOT INSIDE + return rune(0x2a97), true + case "el": + // DOUBLE-LINE EQUAL TO OR LESS-THAN + return rune(0x2a99), true + case "emacr": + // LATIN SMALL LETTER E WITH MACRON + return rune(0x0113), true + case "empty": + // EMPTY SET + return rune(0x2205), true + case "emptyset": + // EMPTY SET + return rune(0x2205), true + case "emptyv": + // EMPTY SET + return rune(0x2205), true + case "emsp13": + // THREE-PER-EM SPACE + return rune(0x2004), true + case "emsp14": + // FOUR-PER-EM SPACE + return rune(0x2005), true + case "emsp": + // EM SPACE + return rune(0x2003), true + case "eng": + // LATIN SMALL LETTER ENG + return rune(0x014b), true + case "ensp": + // EN SPACE + return rune(0x2002), true + case "eogon": + // LATIN SMALL LETTER E WITH OGONEK + return rune(0x0119), true + case "eopf": + // MATHEMATICAL DOUBLE-STRUCK SMALL E + return rune(0x01d556), true + case "epar": + // EQUAL AND PARALLEL TO + return rune(0x22d5), true + case "eparsl": + // EQUALS SIGN AND SLANTED PARALLEL + return rune(0x29e3), true + case "eplus": + // EQUALS SIGN ABOVE PLUS SIGN + return rune(0x2a71), true + case "epsilon": + // GREEK SMALL LETTER EPSILON + return rune(0x03b5), true + case "epsis": + // GREEK LUNATE EPSILON SYMBOL + return rune(0x03f5), true + case "epsiv": + // GREEK LUNATE EPSILON SYMBOL + return rune(0x03f5), true + case "epsi": + // GREEK SMALL LETTER EPSILON + return rune(0x03b5), true + case "eqcirc": + // RING IN EQUAL TO + return rune(0x2256), true + case "eqcolon": + // EQUALS COLON + return rune(0x2255), true + case "eqeq": + // TWO CONSECUTIVE EQUALS SIGNS + return rune(0x2a75), true + case "eqsim": + // MINUS TILDE + return rune(0x2242), true + case "eqslantgtr": + // SLANTED EQUAL TO OR GREATER-THAN + return rune(0x2a96), true + case "eqslantless": + // SLANTED EQUAL TO OR LESS-THAN + return rune(0x2a95), true + case "equals": + // EQUALS SIGN + return rune(0x3d), true + case "equest": + // QUESTIONED EQUAL TO + return rune(0x225f), true + case "equiv": + // IDENTICAL TO + return rune(0x2261), true + case "equivDD": + // EQUIVALENT WITH FOUR DOTS ABOVE + return rune(0x2a78), true + case "eqvparsl": + // IDENTICAL TO AND SLANTED PARALLEL + return rune(0x29e5), true + case "erDot": + // IMAGE OF OR APPROXIMATELY EQUAL TO + return rune(0x2253), true + case "erarr": + // EQUALS SIGN ABOVE RIGHTWARDS ARROW + return rune(0x2971), true + case "escr": + // SCRIPT SMALL E + return rune(0x212f), true + case "esdot": + // APPROACHES THE LIMIT + return rune(0x2250), true + case "esim": + // MINUS TILDE + return rune(0x2242), true + case "eta": + // GREEK SMALL LETTER ETA + return rune(0x03b7), true + case "eth": + // LATIN SMALL LETTER ETH + return rune(0xf0), true + case "euml": + // LATIN SMALL LETTER E WITH DIAERESIS + return rune(0xeb), true + case "euro": + // EURO SIGN + return rune(0x20ac), true + case "excl": + // EXCLAMATION MARK + return rune(0x21), true + case "exist": + // THERE EXISTS + return rune(0x2203), true + case "expectation": + // SCRIPT CAPITAL E + return rune(0x2130), true + case "exponentiale": + // DOUBLE-STRUCK ITALIC SMALL E + return rune(0x2147), true + } + + case 'f': + switch name { + case "fallingdotseq": + // APPROXIMATELY EQUAL TO OR THE IMAGE OF + return rune(0x2252), true + case "fbowtie": + // BLACK BOWTIE + return rune(0x29d3), true + case "fcy": + // CYRILLIC SMALL LETTER EF + return rune(0x0444), true + case "fdiag": + // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT + return rune(0x2572), true + case "fdiordi": + // FALLING DIAGONAL CROSSING RISING DIAGONAL + return rune(0x292c), true + case "fdonearr": + // FALLING DIAGONAL CROSSING NORTH EAST ARROW + return rune(0x292f), true + case "female": + // FEMALE SIGN + return rune(0x2640), true + case "ffilig": + // LATIN SMALL LIGATURE FFI + return rune(0xfb03), true + case "fflig": + // LATIN SMALL LIGATURE FF + return rune(0xfb00), true + case "ffllig": + // LATIN SMALL LIGATURE FFL + return rune(0xfb04), true + case "ffr": + // MATHEMATICAL FRAKTUR SMALL F + return rune(0x01d523), true + case "fhrglass": + // BLACK HOURGLASS + return rune(0x29d7), true + case "filig": + // LATIN SMALL LIGATURE FI + return rune(0xfb01), true + case "fjlig": + // fj ligature + return rune(0x66), true + case "flat": + // MUSIC FLAT SIGN + return rune(0x266d), true + case "fllig": + // LATIN SMALL LIGATURE FL + return rune(0xfb02), true + case "fltns": + // WHITE PARALLELOGRAM + return rune(0x25b1), true + case "fnof": + // LATIN SMALL LETTER F WITH HOOK + return rune(0x0192), true + case "fopf": + // MATHEMATICAL DOUBLE-STRUCK SMALL F + return rune(0x01d557), true + case "forall": + // FOR ALL + return rune(0x2200), true + case "fork": + // PITCHFORK + return rune(0x22d4), true + case "forkv": + // ELEMENT OF OPENING DOWNWARDS + return rune(0x2ad9), true + case "fpartint": + // FINITE PART INTEGRAL + return rune(0x2a0d), true + case "frac12": + // VULGAR FRACTION ONE HALF + return rune(0xbd), true + case "frac13": + // VULGAR FRACTION ONE THIRD + return rune(0x2153), true + case "frac14": + // VULGAR FRACTION ONE QUARTER + return rune(0xbc), true + case "frac15": + // VULGAR FRACTION ONE FIFTH + return rune(0x2155), true + case "frac16": + // VULGAR FRACTION ONE SIXTH + return rune(0x2159), true + case "frac18": + // VULGAR FRACTION ONE EIGHTH + return rune(0x215b), true + case "frac23": + // VULGAR FRACTION TWO THIRDS + return rune(0x2154), true + case "frac25": + // VULGAR FRACTION TWO FIFTHS + return rune(0x2156), true + case "frac34": + // VULGAR FRACTION THREE QUARTERS + return rune(0xbe), true + case "frac35": + // VULGAR FRACTION THREE FIFTHS + return rune(0x2157), true + case "frac38": + // VULGAR FRACTION THREE EIGHTHS + return rune(0x215c), true + case "frac45": + // VULGAR FRACTION FOUR FIFTHS + return rune(0x2158), true + case "frac56": + // VULGAR FRACTION FIVE SIXTHS + return rune(0x215a), true + case "frac58": + // VULGAR FRACTION FIVE EIGHTHS + return rune(0x215d), true + case "frac78": + // VULGAR FRACTION SEVEN EIGHTHS + return rune(0x215e), true + case "frasl": + // FRACTION SLASH + return rune(0x2044), true + case "frown": + // FROWN + return rune(0x2322), true + case "fscr": + // MATHEMATICAL SCRIPT SMALL F + return rune(0x01d4bb), true + } + + case 'g': + switch name { + case "gE": + // GREATER-THAN OVER EQUAL TO + return rune(0x2267), true + case "gEl": + // GREATER-THAN ABOVE DOUBLE-LINE EQUAL ABOVE LESS-THAN + return rune(0x2a8c), true + case "gacute": + // LATIN SMALL LETTER G WITH ACUTE + return rune(0x01f5), true + case "gammad": + // GREEK SMALL LETTER DIGAMMA + return rune(0x03dd), true + case "gamma": + // GREEK SMALL LETTER GAMMA + return rune(0x03b3), true + case "gap": + // GREATER-THAN OR APPROXIMATE + return rune(0x2a86), true + case "gbreve": + // LATIN SMALL LETTER G WITH BREVE + return rune(0x011f), true + case "gcedil": + // LATIN SMALL LETTER G WITH CEDILLA + return rune(0x0123), true + case "gcirc": + // LATIN SMALL LETTER G WITH CIRCUMFLEX + return rune(0x011d), true + case "gcy": + // CYRILLIC SMALL LETTER GHE + return rune(0x0433), true + case "gdot": + // LATIN SMALL LETTER G WITH DOT ABOVE + return rune(0x0121), true + case "ge": + // GREATER-THAN OR EQUAL TO + return rune(0x2265), true + case "gel": + // GREATER-THAN EQUAL TO OR LESS-THAN + return rune(0x22db), true + case "geq": + // GREATER-THAN OR EQUAL TO + return rune(0x2265), true + case "geqq": + // GREATER-THAN OVER EQUAL TO + return rune(0x2267), true + case "geqslant": + // GREATER-THAN OR SLANTED EQUAL TO + return rune(0x2a7e), true + case "gesl": + // GREATER-THAN slanted EQUAL TO OR LESS-THAN + return rune(0x22db), true + case "ges": + // GREATER-THAN OR SLANTED EQUAL TO + return rune(0x2a7e), true + case "gescc": + // GREATER-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL + return rune(0x2aa9), true + case "gesdot": + // GREATER-THAN OR SLANTED EQUAL TO WITH DOT INSIDE + return rune(0x2a80), true + case "gesdoto": + // GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE + return rune(0x2a82), true + case "gesdotol": + // GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE LEFT + return rune(0x2a84), true + case "gesles": + // GREATER-THAN ABOVE SLANTED EQUAL ABOVE LESS-THAN ABOVE SLANTED EQUAL + return rune(0x2a94), true + case "gfr": + // MATHEMATICAL FRAKTUR SMALL G + return rune(0x01d524), true + case "gg": + // MUCH GREATER-THAN + return rune(0x226b), true + case "ggg": + // VERY MUCH GREATER-THAN + return rune(0x22d9), true + case "ggr": + // GREEK SMALL LETTER GAMMA + return rune(0x03b3), true + case "gimel": + // GIMEL SYMBOL + return rune(0x2137), true + case "gjcy": + // CYRILLIC SMALL LETTER GJE + return rune(0x0453), true + case "gl": + // GREATER-THAN OR LESS-THAN + return rune(0x2277), true + case "glE": + // GREATER-THAN ABOVE LESS-THAN ABOVE DOUBLE-LINE EQUAL + return rune(0x2a92), true + case "gla": + // GREATER-THAN BESIDE LESS-THAN + return rune(0x2aa5), true + case "glj": + // GREATER-THAN OVERLAPPING LESS-THAN + return rune(0x2aa4), true + case "gnE": + // GREATER-THAN BUT NOT EQUAL TO + return rune(0x2269), true + case "gnap": + // GREATER-THAN AND NOT APPROXIMATE + return rune(0x2a8a), true + case "gnapprox": + // GREATER-THAN AND NOT APPROXIMATE + return rune(0x2a8a), true + case "gneqq": + // GREATER-THAN BUT NOT EQUAL TO + return rune(0x2269), true + case "gne": + // GREATER-THAN AND SINGLE-LINE NOT EQUAL TO + return rune(0x2a88), true + case "gneq": + // GREATER-THAN AND SINGLE-LINE NOT EQUAL TO + return rune(0x2a88), true + case "gnsim": + // GREATER-THAN BUT NOT EQUIVALENT TO + return rune(0x22e7), true + case "gopf": + // MATHEMATICAL DOUBLE-STRUCK SMALL G + return rune(0x01d558), true + case "grave": + // GRAVE ACCENT + return rune(0x60), true + case "gscr": + // SCRIPT SMALL G + return rune(0x210a), true + case "gsdot": + // GREATER-THAN WITH DOT + return rune(0x22d7), true + case "gsim": + // GREATER-THAN OR EQUIVALENT TO + return rune(0x2273), true + case "gsime": + // GREATER-THAN ABOVE SIMILAR OR EQUAL + return rune(0x2a8e), true + case "gsiml": + // GREATER-THAN ABOVE SIMILAR ABOVE LESS-THAN + return rune(0x2a90), true + case "gtcc": + // GREATER-THAN CLOSED BY CURVE + return rune(0x2aa7), true + case "gtcir": + // GREATER-THAN WITH CIRCLE INSIDE + return rune(0x2a7a), true + case "gtdot": + // GREATER-THAN WITH DOT + return rune(0x22d7), true + case "gtlPar": + // DOUBLE LEFT ARC GREATER-THAN BRACKET + return rune(0x2995), true + case "gtquest": + // GREATER-THAN WITH QUESTION MARK ABOVE + return rune(0x2a7c), true + case "gtrapprox": + // GREATER-THAN OR APPROXIMATE + return rune(0x2a86), true + case "gtrarr": + // GREATER-THAN ABOVE RIGHTWARDS ARROW + return rune(0x2978), true + case "gtrdot": + // GREATER-THAN WITH DOT + return rune(0x22d7), true + case "gtreqless": + // GREATER-THAN EQUAL TO OR LESS-THAN + return rune(0x22db), true + case "gtreqqless": + // GREATER-THAN ABOVE DOUBLE-LINE EQUAL ABOVE LESS-THAN + return rune(0x2a8c), true + case "gtrless": + // GREATER-THAN OR LESS-THAN + return rune(0x2277), true + case "gtrpar": + // SPHERICAL ANGLE OPENING LEFT + return rune(0x29a0), true + case "gtrsim": + // GREATER-THAN OR EQUIVALENT TO + return rune(0x2273), true + case "gt": + // GREATER-THAN SIGN + return rune(0x3e), true + case "gvertneqq": + // GREATER-THAN BUT NOT EQUAL TO - with vertical stroke + return rune(0x2269), true + case "gvnE": + // GREATER-THAN BUT NOT EQUAL TO - with vertical stroke + return rune(0x2269), true + } + + case 'h': + switch name { + case "hArr": + // LEFT RIGHT DOUBLE ARROW + return rune(0x21d4), true + case "hairsp": + // HAIR SPACE + return rune(0x200a), true + case "half": + // VULGAR FRACTION ONE HALF + return rune(0xbd), true + case "hamilt": + // SCRIPT CAPITAL H + return rune(0x210b), true + case "hardcy": + // CYRILLIC SMALL LETTER HARD SIGN + return rune(0x044a), true + case "harrw": + // LEFT RIGHT WAVE ARROW + return rune(0x21ad), true + case "harr": + // LEFT RIGHT ARROW + return rune(0x2194), true + case "harrcir": + // LEFT RIGHT ARROW THROUGH SMALL CIRCLE + return rune(0x2948), true + case "hbar": + // PLANCK CONSTANT OVER TWO PI + return rune(0x210f), true + case "hcirc": + // LATIN SMALL LETTER H WITH CIRCUMFLEX + return rune(0x0125), true + case "hearts": + // BLACK HEART SUIT + return rune(0x2665), true + case "heartsuit": + // BLACK HEART SUIT + return rune(0x2665), true + case "hellip": + // HORIZONTAL ELLIPSIS + return rune(0x2026), true + case "hercon": + // HERMITIAN CONJUGATE MATRIX + return rune(0x22b9), true + case "hfr": + // MATHEMATICAL FRAKTUR SMALL H + return rune(0x01d525), true + case "hksearow": + // SOUTH EAST ARROW WITH HOOK + return rune(0x2925), true + case "hkswarow": + // SOUTH WEST ARROW WITH HOOK + return rune(0x2926), true + case "hoarr": + // LEFT RIGHT OPEN-HEADED ARROW + return rune(0x21ff), true + case "homtht": + // HOMOTHETIC + return rune(0x223b), true + case "hookleftarrow": + // LEFTWARDS ARROW WITH HOOK + return rune(0x21a9), true + case "hookrightarrow": + // RIGHTWARDS ARROW WITH HOOK + return rune(0x21aa), true + case "hopf": + // MATHEMATICAL DOUBLE-STRUCK SMALL H + return rune(0x01d559), true + case "horbar": + // HORIZONTAL BAR + return rune(0x2015), true + case "hrglass": + // WHITE HOURGLASS + return rune(0x29d6), true + case "hscr": + // MATHEMATICAL SCRIPT SMALL H + return rune(0x01d4bd), true + case "hslash": + // PLANCK CONSTANT OVER TWO PI + return rune(0x210f), true + case "hstrok": + // LATIN SMALL LETTER H WITH STROKE + return rune(0x0127), true + case "htimes": + // VECTOR OR CROSS PRODUCT + return rune(0x2a2f), true + case "hybull": + // HYPHEN BULLET + return rune(0x2043), true + case "hyphen": + // HYPHEN + return rune(0x2010), true + } + + case 'i': + switch name { + case "iacgr": + // GREEK SMALL LETTER IOTA WITH TONOS + return rune(0x03af), true + case "iacute": + // LATIN SMALL LETTER I WITH ACUTE + return rune(0xed), true + case "ic": + // INVISIBLE SEPARATOR + return rune(0x2063), true + case "icirc": + // LATIN SMALL LETTER I WITH CIRCUMFLEX + return rune(0xee), true + case "icy": + // CYRILLIC SMALL LETTER I + return rune(0x0438), true + case "idiagr": + // GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS + return rune(0x0390), true + case "idigr": + // GREEK SMALL LETTER IOTA WITH DIALYTIKA + return rune(0x03ca), true + case "iecy": + // CYRILLIC SMALL LETTER IE + return rune(0x0435), true + case "iexcl": + // INVERTED EXCLAMATION MARK + return rune(0xa1), true + case "iff": + // LEFT RIGHT DOUBLE ARROW + return rune(0x21d4), true + case "ifr": + // MATHEMATICAL FRAKTUR SMALL I + return rune(0x01d526), true + case "igr": + // GREEK SMALL LETTER IOTA + return rune(0x03b9), true + case "igrave": + // LATIN SMALL LETTER I WITH GRAVE + return rune(0xec), true + case "iiint": + // TRIPLE INTEGRAL + return rune(0x222d), true + case "ii": + // DOUBLE-STRUCK ITALIC SMALL I + return rune(0x2148), true + case "iiiint": + // QUADRUPLE INTEGRAL OPERATOR + return rune(0x2a0c), true + case "iinfin": + // INCOMPLETE INFINITY + return rune(0x29dc), true + case "iiota": + // TURNED GREEK SMALL LETTER IOTA + return rune(0x2129), true + case "ijlig": + // LATIN SMALL LIGATURE IJ + return rune(0x0133), true + case "imacr": + // LATIN SMALL LETTER I WITH MACRON + return rune(0x012b), true + case "image": + // BLACK-LETTER CAPITAL I + return rune(0x2111), true + case "imagline": + // SCRIPT CAPITAL I + return rune(0x2110), true + case "imagpart": + // BLACK-LETTER CAPITAL I + return rune(0x2111), true + case "imath": + // LATIN SMALL LETTER DOTLESS I + return rune(0x0131), true + case "imof": + // IMAGE OF + return rune(0x22b7), true + case "imped": + // LATIN CAPITAL LETTER Z WITH STROKE + return rune(0x01b5), true + case "in": + // ELEMENT OF + return rune(0x2208), true + case "incare": + // CARE OF + return rune(0x2105), true + case "infin": + // INFINITY + return rune(0x221e), true + case "infintie": + // TIE OVER INFINITY + return rune(0x29dd), true + case "inodot": + // LATIN SMALL LETTER DOTLESS I + return rune(0x0131), true + case "int": + // INTEGRAL + return rune(0x222b), true + case "intcal": + // INTERCALATE + return rune(0x22ba), true + case "integers": + // DOUBLE-STRUCK CAPITAL Z + return rune(0x2124), true + case "intercal": + // INTERCALATE + return rune(0x22ba), true + case "intlarhk": + // INTEGRAL WITH LEFTWARDS ARROW WITH HOOK + return rune(0x2a17), true + case "intprod": + // INTERIOR PRODUCT + return rune(0x2a3c), true + case "iocy": + // CYRILLIC SMALL LETTER IO + return rune(0x0451), true + case "iogon": + // LATIN SMALL LETTER I WITH OGONEK + return rune(0x012f), true + case "iopf": + // MATHEMATICAL DOUBLE-STRUCK SMALL I + return rune(0x01d55a), true + case "iota": + // GREEK SMALL LETTER IOTA + return rune(0x03b9), true + case "iprod": + // INTERIOR PRODUCT + return rune(0x2a3c), true + case "iprodr": + // RIGHTHAND INTERIOR PRODUCT + return rune(0x2a3d), true + case "iquest": + // INVERTED QUESTION MARK + return rune(0xbf), true + case "iscr": + // MATHEMATICAL SCRIPT SMALL I + return rune(0x01d4be), true + case "isin": + // ELEMENT OF + return rune(0x2208), true + case "isinE": + // ELEMENT OF WITH TWO HORIZONTAL STROKES + return rune(0x22f9), true + case "isindot": + // ELEMENT OF WITH DOT ABOVE + return rune(0x22f5), true + case "isinsv": + // ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE + return rune(0x22f3), true + case "isins": + // SMALL ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE + return rune(0x22f4), true + case "isinv": + // ELEMENT OF + return rune(0x2208), true + case "isinvb": + // ELEMENT OF WITH UNDERBAR + return rune(0x22f8), true + case "it": + // INVISIBLE TIMES + return rune(0x2062), true + case "itilde": + // LATIN SMALL LETTER I WITH TILDE + return rune(0x0129), true + case "iukcy": + // CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I + return rune(0x0456), true + case "iuml": + // LATIN SMALL LETTER I WITH DIAERESIS + return rune(0xef), true + } + + case 'j': + switch name { + case "jcirc": + // LATIN SMALL LETTER J WITH CIRCUMFLEX + return rune(0x0135), true + case "jcy": + // CYRILLIC SMALL LETTER SHORT I + return rune(0x0439), true + case "jfr": + // MATHEMATICAL FRAKTUR SMALL J + return rune(0x01d527), true + case "jmath": + // LATIN SMALL LETTER DOTLESS J + return rune(0x0237), true + case "jnodot": + // LATIN SMALL LETTER DOTLESS J + return rune(0x0237), true + case "jopf": + // MATHEMATICAL DOUBLE-STRUCK SMALL J + return rune(0x01d55b), true + case "jscr": + // MATHEMATICAL SCRIPT SMALL J + return rune(0x01d4bf), true + case "jsercy": + // CYRILLIC SMALL LETTER JE + return rune(0x0458), true + case "jukcy": + // CYRILLIC SMALL LETTER UKRAINIAN IE + return rune(0x0454), true + } + + case 'k': + switch name { + case "kappav": + // GREEK KAPPA SYMBOL + return rune(0x03f0), true + case "kappa": + // GREEK SMALL LETTER KAPPA + return rune(0x03ba), true + case "kcedil": + // LATIN SMALL LETTER K WITH CEDILLA + return rune(0x0137), true + case "kcy": + // CYRILLIC SMALL LETTER KA + return rune(0x043a), true + case "kfr": + // MATHEMATICAL FRAKTUR SMALL K + return rune(0x01d528), true + case "kgr": + // GREEK SMALL LETTER KAPPA + return rune(0x03ba), true + case "kgreen": + // LATIN SMALL LETTER KRA + return rune(0x0138), true + case "khcy": + // CYRILLIC SMALL LETTER HA + return rune(0x0445), true + case "khgr": + // GREEK SMALL LETTER CHI + return rune(0x03c7), true + case "kjcy": + // CYRILLIC SMALL LETTER KJE + return rune(0x045c), true + case "kopf": + // MATHEMATICAL DOUBLE-STRUCK SMALL K + return rune(0x01d55c), true + case "koppa": + // GREEK LETTER KOPPA + return rune(0x03de), true + case "kscr": + // MATHEMATICAL SCRIPT SMALL K + return rune(0x01d4c0), true + } + + case 'l': + switch name { + case "lAarr": + // LEFTWARDS TRIPLE ARROW + return rune(0x21da), true + case "lArr": + // LEFTWARDS DOUBLE ARROW + return rune(0x21d0), true + case "lAtail": + // LEFTWARDS DOUBLE ARROW-TAIL + return rune(0x291b), true + case "lBarr": + // LEFTWARDS TRIPLE DASH ARROW + return rune(0x290e), true + case "lE": + // LESS-THAN OVER EQUAL TO + return rune(0x2266), true + case "lEg": + // LESS-THAN ABOVE DOUBLE-LINE EQUAL ABOVE GREATER-THAN + return rune(0x2a8b), true + case "lHar": + // LEFTWARDS HARPOON WITH BARB UP ABOVE LEFTWARDS HARPOON WITH BARB DOWN + return rune(0x2962), true + case "lacute": + // LATIN SMALL LETTER L WITH ACUTE + return rune(0x013a), true + case "laemptyv": + // EMPTY SET WITH LEFT ARROW ABOVE + return rune(0x29b4), true + case "lagran": + // SCRIPT CAPITAL L + return rune(0x2112), true + case "lambda": + // GREEK SMALL LETTER LAMDA + return rune(0x03bb), true + case "lang": + // MATHEMATICAL LEFT ANGLE BRACKET + return rune(0x27e8), true + case "langd": + // LEFT ANGLE BRACKET WITH DOT + return rune(0x2991), true + case "langle": + // MATHEMATICAL LEFT ANGLE BRACKET + return rune(0x27e8), true + case "lap": + // LESS-THAN OR APPROXIMATE + return rune(0x2a85), true + case "laquo": + // LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + return rune(0xab), true + case "larr2": + // LEFTWARDS PAIRED ARROWS + return rune(0x21c7), true + case "larrb": + // LEFTWARDS ARROW TO BAR + return rune(0x21e4), true + case "larrhk": + // LEFTWARDS ARROW WITH HOOK + return rune(0x21a9), true + case "larrlp": + // LEFTWARDS ARROW WITH LOOP + return rune(0x21ab), true + case "larrtl": + // LEFTWARDS ARROW WITH TAIL + return rune(0x21a2), true + case "larr": + // LEFTWARDS ARROW + return rune(0x2190), true + case "larrbfs": + // LEFTWARDS ARROW FROM BAR TO BLACK DIAMOND + return rune(0x291f), true + case "larrfs": + // LEFTWARDS ARROW TO BLACK DIAMOND + return rune(0x291d), true + case "larrpl": + // LEFT-SIDE ARC ANTICLOCKWISE ARROW + return rune(0x2939), true + case "larrsim": + // LEFTWARDS ARROW ABOVE TILDE OPERATOR + return rune(0x2973), true + case "latail": + // LEFTWARDS ARROW-TAIL + return rune(0x2919), true + case "lat": + // LARGER THAN + return rune(0x2aab), true + case "late": + // LARGER THAN OR EQUAL TO + return rune(0x2aad), true + case "lates": + // LARGER THAN OR slanted EQUAL + return rune(0x2aad), true + case "lbarr": + // LEFTWARDS DOUBLE DASH ARROW + return rune(0x290c), true + case "lbbrk": + // LIGHT LEFT TORTOISE SHELL BRACKET ORNAMENT + return rune(0x2772), true + case "lbrace": + // LEFT CURLY BRACKET + return rune(0x7b), true + case "lbrack": + // LEFT SQUARE BRACKET + return rune(0x5b), true + case "lbrke": + // LEFT SQUARE BRACKET WITH UNDERBAR + return rune(0x298b), true + case "lbrksld": + // LEFT SQUARE BRACKET WITH TICK IN BOTTOM CORNER + return rune(0x298f), true + case "lbrkslu": + // LEFT SQUARE BRACKET WITH TICK IN TOP CORNER + return rune(0x298d), true + case "lcaron": + // LATIN SMALL LETTER L WITH CARON + return rune(0x013e), true + case "lcedil": + // LATIN SMALL LETTER L WITH CEDILLA + return rune(0x013c), true + case "lceil": + // LEFT CEILING + return rune(0x2308), true + case "lcub": + // LEFT CURLY BRACKET + return rune(0x7b), true + case "lcy": + // CYRILLIC SMALL LETTER EL + return rune(0x043b), true + case "ldca": + // ARROW POINTING DOWNWARDS THEN CURVING LEFTWARDS + return rune(0x2936), true + case "ldharb": + // LEFTWARDS HARPOON WITH BARB DOWN TO BAR + return rune(0x2956), true + case "ldot": + // LESS-THAN WITH DOT + return rune(0x22d6), true + case "ldquor": + // DOUBLE LOW-9 QUOTATION MARK + return rune(0x201e), true + case "ldquo": + // LEFT DOUBLE QUOTATION MARK + return rune(0x201c), true + case "ldrdhar": + // LEFTWARDS HARPOON WITH BARB DOWN ABOVE RIGHTWARDS HARPOON WITH BARB DOWN + return rune(0x2967), true + case "ldrdshar": + // LEFT BARB DOWN RIGHT BARB DOWN HARPOON + return rune(0x2950), true + case "ldrushar": + // LEFT BARB DOWN RIGHT BARB UP HARPOON + return rune(0x294b), true + case "ldsh": + // DOWNWARDS ARROW WITH TIP LEFTWARDS + return rune(0x21b2), true + case "leftarrowtail": + // LEFTWARDS ARROW WITH TAIL + return rune(0x21a2), true + case "leftarrow": + // LEFTWARDS ARROW + return rune(0x2190), true + case "leftharpoondown": + // LEFTWARDS HARPOON WITH BARB DOWNWARDS + return rune(0x21bd), true + case "leftharpoonup": + // LEFTWARDS HARPOON WITH BARB UPWARDS + return rune(0x21bc), true + case "leftleftarrows": + // LEFTWARDS PAIRED ARROWS + return rune(0x21c7), true + case "leftrightarrows": + // LEFTWARDS ARROW OVER RIGHTWARDS ARROW + return rune(0x21c6), true + case "leftrightarrow": + // LEFT RIGHT ARROW + return rune(0x2194), true + case "leftrightharpoons": + // LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON + return rune(0x21cb), true + case "leftrightsquigarrow": + // LEFT RIGHT WAVE ARROW + return rune(0x21ad), true + case "le": + // LESS-THAN OR EQUAL TO + return rune(0x2264), true + case "leftthreetimes": + // LEFT SEMIDIRECT PRODUCT + return rune(0x22cb), true + case "leg": + // LESS-THAN EQUAL TO OR GREATER-THAN + return rune(0x22da), true + case "leq": + // LESS-THAN OR EQUAL TO + return rune(0x2264), true + case "leqq": + // LESS-THAN OVER EQUAL TO + return rune(0x2266), true + case "leqslant": + // LESS-THAN OR SLANTED EQUAL TO + return rune(0x2a7d), true + case "lesg": + // LESS-THAN slanted EQUAL TO OR GREATER-THAN + return rune(0x22da), true + case "lessdot": + // LESS-THAN WITH DOT + return rune(0x22d6), true + case "lesseqgtr": + // LESS-THAN EQUAL TO OR GREATER-THAN + return rune(0x22da), true + case "lessgtr": + // LESS-THAN OR GREATER-THAN + return rune(0x2276), true + case "lesssim": + // LESS-THAN OR EQUIVALENT TO + return rune(0x2272), true + case "les": + // LESS-THAN OR SLANTED EQUAL TO + return rune(0x2a7d), true + case "lescc": + // LESS-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL + return rune(0x2aa8), true + case "lesdot": + // LESS-THAN OR SLANTED EQUAL TO WITH DOT INSIDE + return rune(0x2a7f), true + case "lesdoto": + // LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE + return rune(0x2a81), true + case "lesdotor": + // LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE RIGHT + return rune(0x2a83), true + case "lesges": + // LESS-THAN ABOVE SLANTED EQUAL ABOVE GREATER-THAN ABOVE SLANTED EQUAL + return rune(0x2a93), true + case "lessapprox": + // LESS-THAN OR APPROXIMATE + return rune(0x2a85), true + case "lesseqqgtr": + // LESS-THAN ABOVE DOUBLE-LINE EQUAL ABOVE GREATER-THAN + return rune(0x2a8b), true + case "lfbowtie": + // BOWTIE WITH LEFT HALF BLACK + return rune(0x29d1), true + case "lfisht": + // LEFT FISH TAIL + return rune(0x297c), true + case "lfloor": + // LEFT FLOOR + return rune(0x230a), true + case "lfr": + // MATHEMATICAL FRAKTUR SMALL L + return rune(0x01d529), true + case "lftimes": + // TIMES WITH LEFT HALF BLACK + return rune(0x29d4), true + case "lg": + // LESS-THAN OR GREATER-THAN + return rune(0x2276), true + case "lgE": + // LESS-THAN ABOVE GREATER-THAN ABOVE DOUBLE-LINE EQUAL + return rune(0x2a91), true + case "lgr": + // GREEK SMALL LETTER LAMDA + return rune(0x03bb), true + case "lhard": + // LEFTWARDS HARPOON WITH BARB DOWNWARDS + return rune(0x21bd), true + case "lharu": + // LEFTWARDS HARPOON WITH BARB UPWARDS + return rune(0x21bc), true + case "lharul": + // LEFTWARDS HARPOON WITH BARB UP ABOVE LONG DASH + return rune(0x296a), true + case "lhblk": + // LOWER HALF BLOCK + return rune(0x2584), true + case "ljcy": + // CYRILLIC SMALL LETTER LJE + return rune(0x0459), true + case "llarr": + // LEFTWARDS PAIRED ARROWS + return rune(0x21c7), true + case "ll": + // MUCH LESS-THAN + return rune(0x226a), true + case "llcorner": + // BOTTOM LEFT CORNER + return rune(0x231e), true + case "llhard": + // LEFTWARDS HARPOON WITH BARB DOWN BELOW LONG DASH + return rune(0x296b), true + case "lltrif": + // BLACK LOWER LEFT TRIANGLE + return rune(0x25e3), true + case "lltri": + // LOWER LEFT TRIANGLE + return rune(0x25fa), true + case "lmidot": + // LATIN SMALL LETTER L WITH MIDDLE DOT + return rune(0x0140), true + case "lmoust": + // UPPER LEFT OR LOWER RIGHT CURLY BRACKET SECTION + return rune(0x23b0), true + case "lmoustache": + // UPPER LEFT OR LOWER RIGHT CURLY BRACKET SECTION + return rune(0x23b0), true + case "lnE": + // LESS-THAN BUT NOT EQUAL TO + return rune(0x2268), true + case "lnap": + // LESS-THAN AND NOT APPROXIMATE + return rune(0x2a89), true + case "lnapprox": + // LESS-THAN AND NOT APPROXIMATE + return rune(0x2a89), true + case "lneqq": + // LESS-THAN BUT NOT EQUAL TO + return rune(0x2268), true + case "lne": + // LESS-THAN AND SINGLE-LINE NOT EQUAL TO + return rune(0x2a87), true + case "lneq": + // LESS-THAN AND SINGLE-LINE NOT EQUAL TO + return rune(0x2a87), true + case "lnsim": + // LESS-THAN BUT NOT EQUIVALENT TO + return rune(0x22e6), true + case "loang": + // MATHEMATICAL LEFT WHITE TORTOISE SHELL BRACKET + return rune(0x27ec), true + case "loarr": + // LEFTWARDS OPEN-HEADED ARROW + return rune(0x21fd), true + case "lobrk": + // MATHEMATICAL LEFT WHITE SQUARE BRACKET + return rune(0x27e6), true + case "locub": + // LEFT WHITE CURLY BRACKET + return rune(0x2983), true + case "longleftarrow": + // LONG LEFTWARDS ARROW + return rune(0x27f5), true + case "longleftrightarrow": + // LONG LEFT RIGHT ARROW + return rune(0x27f7), true + case "longmapsto": + // LONG RIGHTWARDS ARROW FROM BAR + return rune(0x27fc), true + case "longrightarrow": + // LONG RIGHTWARDS ARROW + return rune(0x27f6), true + case "looparrowleft": + // LEFTWARDS ARROW WITH LOOP + return rune(0x21ab), true + case "looparrowright": + // RIGHTWARDS ARROW WITH LOOP + return rune(0x21ac), true + case "lopar": + // LEFT WHITE PARENTHESIS + return rune(0x2985), true + case "lopf": + // MATHEMATICAL DOUBLE-STRUCK SMALL L + return rune(0x01d55d), true + case "loplus": + // PLUS SIGN IN LEFT HALF CIRCLE + return rune(0x2a2d), true + case "lotimes": + // MULTIPLICATION SIGN IN LEFT HALF CIRCLE + return rune(0x2a34), true + case "lowast": + // LOW ASTERISK + return rune(0x204e), true + case "lowbar": + // LOW LINE + return rune(0x5f), true + case "lowint": + // INTEGRAL WITH UNDERBAR + return rune(0x2a1c), true + case "loz": + // LOZENGE + return rune(0x25ca), true + case "lozenge": + // LOZENGE + return rune(0x25ca), true + case "lozf": + // BLACK LOZENGE + return rune(0x29eb), true + case "lpargt": + // SPHERICAL ANGLE OPENING LEFT + return rune(0x29a0), true + case "lparlt": + // LEFT ARC LESS-THAN BRACKET + return rune(0x2993), true + case "lpar": + // LEFT PARENTHESIS + return rune(0x28), true + case "lrarr2": + // LEFTWARDS ARROW OVER RIGHTWARDS ARROW + return rune(0x21c6), true + case "lrarr": + // LEFTWARDS ARROW OVER RIGHTWARDS ARROW + return rune(0x21c6), true + case "lrcorner": + // BOTTOM RIGHT CORNER + return rune(0x231f), true + case "lrhar": + // LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON + return rune(0x21cb), true + case "lrhar2": + // LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON + return rune(0x21cb), true + case "lrhard": + // RIGHTWARDS HARPOON WITH BARB DOWN BELOW LONG DASH + return rune(0x296d), true + case "lrm": + // LEFT-TO-RIGHT MARK + return rune(0x200e), true + case "lrtri": + // RIGHT TRIANGLE + return rune(0x22bf), true + case "lsaquo": + // SINGLE LEFT-POINTING ANGLE QUOTATION MARK + return rune(0x2039), true + case "lscr": + // MATHEMATICAL SCRIPT SMALL L + return rune(0x01d4c1), true + case "lsh": + // UPWARDS ARROW WITH TIP LEFTWARDS + return rune(0x21b0), true + case "lsim": + // LESS-THAN OR EQUIVALENT TO + return rune(0x2272), true + case "lsime": + // LESS-THAN ABOVE SIMILAR OR EQUAL + return rune(0x2a8d), true + case "lsimg": + // LESS-THAN ABOVE SIMILAR ABOVE GREATER-THAN + return rune(0x2a8f), true + case "lsqb": + // LEFT SQUARE BRACKET + return rune(0x5b), true + case "lsquor": + // SINGLE LOW-9 QUOTATION MARK + return rune(0x201a), true + case "lsquo": + // LEFT SINGLE QUOTATION MARK + return rune(0x2018), true + case "lstrok": + // LATIN SMALL LETTER L WITH STROKE + return rune(0x0142), true + case "ltcc": + // LESS-THAN CLOSED BY CURVE + return rune(0x2aa6), true + case "ltcir": + // LESS-THAN WITH CIRCLE INSIDE + return rune(0x2a79), true + case "ltdot": + // LESS-THAN WITH DOT + return rune(0x22d6), true + case "lthree": + // LEFT SEMIDIRECT PRODUCT + return rune(0x22cb), true + case "ltimes": + // LEFT NORMAL FACTOR SEMIDIRECT PRODUCT + return rune(0x22c9), true + case "ltlarr": + // LESS-THAN ABOVE LEFTWARDS ARROW + return rune(0x2976), true + case "ltquest": + // LESS-THAN WITH QUESTION MARK ABOVE + return rune(0x2a7b), true + case "ltrPar": + // DOUBLE RIGHT ARC LESS-THAN BRACKET + return rune(0x2996), true + case "ltrie": + // NORMAL SUBGROUP OF OR EQUAL TO + return rune(0x22b4), true + case "ltrif": + // BLACK LEFT-POINTING SMALL TRIANGLE + return rune(0x25c2), true + case "ltri": + // WHITE LEFT-POINTING SMALL TRIANGLE + return rune(0x25c3), true + case "ltrivb": + // LEFT TRIANGLE BESIDE VERTICAL BAR + return rune(0x29cf), true + case "lt": + // LESS-THAN SIGN + return rune(0x3c), true + case "luharb": + // LEFTWARDS HARPOON WITH BARB UP TO BAR + return rune(0x2952), true + case "lurdshar": + // LEFT BARB UP RIGHT BARB DOWN HARPOON + return rune(0x294a), true + case "luruhar": + // LEFTWARDS HARPOON WITH BARB UP ABOVE RIGHTWARDS HARPOON WITH BARB UP + return rune(0x2966), true + case "lurushar": + // LEFT BARB UP RIGHT BARB UP HARPOON + return rune(0x294e), true + case "lvertneqq": + // LESS-THAN BUT NOT EQUAL TO - with vertical stroke + return rune(0x2268), true + case "lvnE": + // LESS-THAN BUT NOT EQUAL TO - with vertical stroke + return rune(0x2268), true + } + + case 'm': + switch name { + case "mDDot": + // GEOMETRIC PROPORTION + return rune(0x223a), true + case "macr": + // MACRON + return rune(0xaf), true + case "male": + // MALE SIGN + return rune(0x2642), true + case "malt": + // MALTESE CROSS + return rune(0x2720), true + case "maltese": + // MALTESE CROSS + return rune(0x2720), true + case "mapstodown": + // DOWNWARDS ARROW FROM BAR + return rune(0x21a7), true + case "mapsto": + // RIGHTWARDS ARROW FROM BAR + return rune(0x21a6), true + case "map": + // RIGHTWARDS ARROW FROM BAR + return rune(0x21a6), true + case "mapstoleft": + // LEFTWARDS ARROW FROM BAR + return rune(0x21a4), true + case "mapstoup": + // UPWARDS ARROW FROM BAR + return rune(0x21a5), true + case "marker": + // BLACK VERTICAL RECTANGLE + return rune(0x25ae), true + case "mcomma": + // MINUS SIGN WITH COMMA ABOVE + return rune(0x2a29), true + case "mcy": + // CYRILLIC SMALL LETTER EM + return rune(0x043c), true + case "mdash": + // EM DASH + return rune(0x2014), true + case "measuredangle": + // MEASURED ANGLE + return rune(0x2221), true + case "mfr": + // MATHEMATICAL FRAKTUR SMALL M + return rune(0x01d52a), true + case "mgr": + // GREEK SMALL LETTER MU + return rune(0x03bc), true + case "mho": + // INVERTED OHM SIGN + return rune(0x2127), true + case "micro": + // MICRO SIGN + return rune(0xb5), true + case "mid": + // DIVIDES + return rune(0x2223), true + case "midast": + // ASTERISK + return rune(0x2a), true + case "midcir": + // VERTICAL LINE WITH CIRCLE BELOW + return rune(0x2af0), true + case "middot": + // MIDDLE DOT + return rune(0xb7), true + case "minus": + // MINUS SIGN + return rune(0x2212), true + case "minusb": + // SQUARED MINUS + return rune(0x229f), true + case "minusd": + // DOT MINUS + return rune(0x2238), true + case "minusdu": + // MINUS SIGN WITH DOT BELOW + return rune(0x2a2a), true + case "mlcp": + // TRANSVERSAL INTERSECTION + return rune(0x2adb), true + case "mldr": + // HORIZONTAL ELLIPSIS + return rune(0x2026), true + case "mnplus": + // MINUS-OR-PLUS SIGN + return rune(0x2213), true + case "models": + // MODELS + return rune(0x22a7), true + case "mopf": + // MATHEMATICAL DOUBLE-STRUCK SMALL M + return rune(0x01d55e), true + case "mp": + // MINUS-OR-PLUS SIGN + return rune(0x2213), true + case "mscr": + // MATHEMATICAL SCRIPT SMALL M + return rune(0x01d4c2), true + case "mstpos": + // INVERTED LAZY S + return rune(0x223e), true + case "multimap": + // MULTIMAP + return rune(0x22b8), true + case "mumap": + // MULTIMAP + return rune(0x22b8), true + case "mu": + // GREEK SMALL LETTER MU + return rune(0x03bc), true + } + + case 'n': + switch name { + case "nGg": + // VERY MUCH GREATER-THAN with slash + return rune(0x22d9), true + case "nGtv": + // MUCH GREATER THAN with slash + return rune(0x226b), true + case "nGt": + // MUCH GREATER THAN with vertical line + return rune(0x226b), true + case "nLeftarrow": + // LEFTWARDS DOUBLE ARROW WITH STROKE + return rune(0x21cd), true + case "nLeftrightarrow": + // LEFT RIGHT DOUBLE ARROW WITH STROKE + return rune(0x21ce), true + case "nLl": + // VERY MUCH LESS-THAN with slash + return rune(0x22d8), true + case "nLtv": + // MUCH LESS THAN with slash + return rune(0x226a), true + case "nLt": + // MUCH LESS THAN with vertical line + return rune(0x226a), true + case "nRightarrow": + // RIGHTWARDS DOUBLE ARROW WITH STROKE + return rune(0x21cf), true + case "nVDash": + // NEGATED DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE + return rune(0x22af), true + case "nVdash": + // DOES NOT FORCE + return rune(0x22ae), true + case "nabla": + // NABLA + return rune(0x2207), true + case "nacute": + // LATIN SMALL LETTER N WITH ACUTE + return rune(0x0144), true + case "nang": + // ANGLE with vertical line + return rune(0x2220), true + case "nap": + // NOT ALMOST EQUAL TO + return rune(0x2249), true + case "napE": + // APPROXIMATELY EQUAL OR EQUAL TO with slash + return rune(0x2a70), true + case "napid": + // TRIPLE TILDE with slash + return rune(0x224b), true + case "napos": + // LATIN SMALL LETTER N PRECEDED BY APOSTROPHE + return rune(0x0149), true + case "napprox": + // NOT ALMOST EQUAL TO + return rune(0x2249), true + case "naturals": + // DOUBLE-STRUCK CAPITAL N + return rune(0x2115), true + case "natur": + // MUSIC NATURAL SIGN + return rune(0x266e), true + case "natural": + // MUSIC NATURAL SIGN + return rune(0x266e), true + case "nbsp": + // NO-BREAK SPACE + return rune(0xa0), true + case "nbump": + // GEOMETRICALLY EQUIVALENT TO with slash + return rune(0x224e), true + case "nbumpe": + // DIFFERENCE BETWEEN with slash + return rune(0x224f), true + case "ncap": + // INTERSECTION WITH OVERBAR + return rune(0x2a43), true + case "ncaron": + // LATIN SMALL LETTER N WITH CARON + return rune(0x0148), true + case "ncedil": + // LATIN SMALL LETTER N WITH CEDILLA + return rune(0x0146), true + case "ncong": + // NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO + return rune(0x2247), true + case "ncongdot": + // CONGRUENT WITH DOT ABOVE with slash + return rune(0x2a6d), true + case "ncup": + // UNION WITH OVERBAR + return rune(0x2a42), true + case "ncy": + // CYRILLIC SMALL LETTER EN + return rune(0x043d), true + case "ndash": + // EN DASH + return rune(0x2013), true + case "neArr": + // NORTH EAST DOUBLE ARROW + return rune(0x21d7), true + case "nearrow": + // NORTH EAST ARROW + return rune(0x2197), true + case "nearr": + // NORTH EAST ARROW + return rune(0x2197), true + case "nedot": + // APPROACHES THE LIMIT with slash + return rune(0x2250), true + case "nesim": + // MINUS TILDE with slash + return rune(0x2242), true + case "nexist": + // THERE DOES NOT EXIST + return rune(0x2204), true + case "nexists": + // THERE DOES NOT EXIST + return rune(0x2204), true + case "ne": + // NOT EQUAL TO + return rune(0x2260), true + case "nearhk": + // NORTH EAST ARROW WITH HOOK + return rune(0x2924), true + case "neonwarr": + // NORTH EAST ARROW CROSSING NORTH WEST ARROW + return rune(0x2931), true + case "neosearr": + // NORTH EAST ARROW CROSSING SOUTH EAST ARROW + return rune(0x292e), true + case "nequiv": + // NOT IDENTICAL TO + return rune(0x2262), true + case "nesear": + // NORTH EAST ARROW AND SOUTH EAST ARROW + return rune(0x2928), true + case "neswsarr": + // NORTH EAST AND SOUTH WEST ARROW + return rune(0x2922), true + case "nfr": + // MATHEMATICAL FRAKTUR SMALL N + return rune(0x01d52b), true + case "ngE": + // GREATER-THAN OVER EQUAL TO with slash + return rune(0x2267), true + case "ngeqq": + // GREATER-THAN OVER EQUAL TO with slash + return rune(0x2267), true + case "nge": + // NEITHER GREATER-THAN NOR EQUAL TO + return rune(0x2271), true + case "ngeq": + // NEITHER GREATER-THAN NOR EQUAL TO + return rune(0x2271), true + case "ngeqslant": + // GREATER-THAN OR SLANTED EQUAL TO with slash + return rune(0x2a7e), true + case "nges": + // GREATER-THAN OR SLANTED EQUAL TO with slash + return rune(0x2a7e), true + case "ngr": + // GREEK SMALL LETTER NU + return rune(0x03bd), true + case "ngsim": + // NEITHER GREATER-THAN NOR EQUIVALENT TO + return rune(0x2275), true + case "ngt": + // NOT GREATER-THAN + return rune(0x226f), true + case "ngtr": + // NOT GREATER-THAN + return rune(0x226f), true + case "nhArr": + // LEFT RIGHT DOUBLE ARROW WITH STROKE + return rune(0x21ce), true + case "nharr": + // LEFT RIGHT ARROW WITH STROKE + return rune(0x21ae), true + case "nhpar": + // PARALLEL WITH HORIZONTAL STROKE + return rune(0x2af2), true + case "niv": + // CONTAINS AS MEMBER + return rune(0x220b), true + case "ni": + // CONTAINS AS MEMBER + return rune(0x220b), true + case "nisd": + // CONTAINS WITH LONG HORIZONTAL STROKE + return rune(0x22fa), true + case "nis": + // SMALL CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE + return rune(0x22fc), true + case "njcy": + // CYRILLIC SMALL LETTER NJE + return rune(0x045a), true + case "nlArr": + // LEFTWARDS DOUBLE ARROW WITH STROKE + return rune(0x21cd), true + case "nlE": + // LESS-THAN OVER EQUAL TO with slash + return rune(0x2266), true + case "nlarr": + // LEFTWARDS ARROW WITH STROKE + return rune(0x219a), true + case "nldr": + // TWO DOT LEADER + return rune(0x2025), true + case "nleftarrow": + // LEFTWARDS ARROW WITH STROKE + return rune(0x219a), true + case "nleftrightarrow": + // LEFT RIGHT ARROW WITH STROKE + return rune(0x21ae), true + case "nleqq": + // LESS-THAN OVER EQUAL TO with slash + return rune(0x2266), true + case "nless": + // NOT LESS-THAN + return rune(0x226e), true + case "nle": + // NEITHER LESS-THAN NOR EQUAL TO + return rune(0x2270), true + case "nleq": + // NEITHER LESS-THAN NOR EQUAL TO + return rune(0x2270), true + case "nleqslant": + // LESS-THAN OR SLANTED EQUAL TO with slash + return rune(0x2a7d), true + case "nles": + // LESS-THAN OR SLANTED EQUAL TO with slash + return rune(0x2a7d), true + case "nlsim": + // NEITHER LESS-THAN NOR EQUIVALENT TO + return rune(0x2274), true + case "nlt": + // NOT LESS-THAN + return rune(0x226e), true + case "nltri": + // NOT NORMAL SUBGROUP OF + return rune(0x22ea), true + case "nltrie": + // NOT NORMAL SUBGROUP OF OR EQUAL TO + return rune(0x22ec), true + case "nltrivb": + // LEFT TRIANGLE BESIDE VERTICAL BAR with slash + return rune(0x29cf), true + case "nmid": + // DOES NOT DIVIDE + return rune(0x2224), true + case "nopf": + // MATHEMATICAL DOUBLE-STRUCK SMALL N + return rune(0x01d55f), true + case "notin": + // NOT AN ELEMENT OF + return rune(0x2209), true + case "notinE": + // ELEMENT OF WITH TWO HORIZONTAL STROKES with slash + return rune(0x22f9), true + case "notindot": + // ELEMENT OF WITH DOT ABOVE with slash + return rune(0x22f5), true + case "notinva": + // NOT AN ELEMENT OF + return rune(0x2209), true + case "notinvb": + // SMALL ELEMENT OF WITH OVERBAR + return rune(0x22f7), true + case "notinvc": + // ELEMENT OF WITH OVERBAR + return rune(0x22f6), true + case "notni": + // DOES NOT CONTAIN AS MEMBER + return rune(0x220c), true + case "notniva": + // DOES NOT CONTAIN AS MEMBER + return rune(0x220c), true + case "notnivb": + // SMALL CONTAINS WITH OVERBAR + return rune(0x22fe), true + case "notnivc": + // CONTAINS WITH OVERBAR + return rune(0x22fd), true + case "not": + // NOT SIGN + return rune(0xac), true + case "npart": + // PARTIAL DIFFERENTIAL with slash + return rune(0x2202), true + case "npar": + // NOT PARALLEL TO + return rune(0x2226), true + case "nparallel": + // NOT PARALLEL TO + return rune(0x2226), true + case "nparsl": + // DOUBLE SOLIDUS OPERATOR with reverse slash + return rune(0x2afd), true + case "npolint": + // LINE INTEGRATION NOT INCLUDING THE POLE + return rune(0x2a14), true + case "nprsim": + // PRECEDES OR EQUIVALENT TO with slash + return rune(0x227e), true + case "npr": + // DOES NOT PRECEDE + return rune(0x2280), true + case "nprcue": + // DOES NOT PRECEDE OR EQUAL + return rune(0x22e0), true + case "nprec": + // DOES NOT PRECEDE + return rune(0x2280), true + case "npre": + // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN with slash + return rune(0x2aaf), true + case "npreceq": + // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN with slash + return rune(0x2aaf), true + case "nrArr": + // RIGHTWARDS DOUBLE ARROW WITH STROKE + return rune(0x21cf), true + case "nrarrw": + // RIGHTWARDS WAVE ARROW with slash + return rune(0x219d), true + case "nrarr": + // RIGHTWARDS ARROW WITH STROKE + return rune(0x219b), true + case "nrarrc": + // WAVE ARROW POINTING DIRECTLY RIGHT with slash + return rune(0x2933), true + case "nrightarrow": + // RIGHTWARDS ARROW WITH STROKE + return rune(0x219b), true + case "nrtri": + // DOES NOT CONTAIN AS NORMAL SUBGROUP + return rune(0x22eb), true + case "nrtrie": + // DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL + return rune(0x22ed), true + case "nsGt": + // DOUBLE NESTED GREATER-THAN with slash + return rune(0x2aa2), true + case "nsLt": + // DOUBLE NESTED LESS-THAN with slash + return rune(0x2aa1), true + case "nscsim": + // SUCCEEDS OR EQUIVALENT TO with slash + return rune(0x227f), true + case "nsc": + // DOES NOT SUCCEED + return rune(0x2281), true + case "nsccue": + // DOES NOT SUCCEED OR EQUAL + return rune(0x22e1), true + case "nsce": + // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN with slash + return rune(0x2ab0), true + case "nscr": + // MATHEMATICAL SCRIPT SMALL N + return rune(0x01d4c3), true + case "nshortmid": + // DOES NOT DIVIDE + return rune(0x2224), true + case "nshortparallel": + // NOT PARALLEL TO + return rune(0x2226), true + case "nsim": + // NOT TILDE + return rune(0x2241), true + case "nsime": + // NOT ASYMPTOTICALLY EQUAL TO + return rune(0x2244), true + case "nsimeq": + // NOT ASYMPTOTICALLY EQUAL TO + return rune(0x2244), true + case "nsmid": + // DOES NOT DIVIDE + return rune(0x2224), true + case "nspar": + // NOT PARALLEL TO + return rune(0x2226), true + case "nsqsub": + // SQUARE IMAGE OF with slash + return rune(0x228f), true + case "nsqsube": + // NOT SQUARE IMAGE OF OR EQUAL TO + return rune(0x22e2), true + case "nsqsup": + // SQUARE ORIGINAL OF with slash + return rune(0x2290), true + case "nsqsupe": + // NOT SQUARE ORIGINAL OF OR EQUAL TO + return rune(0x22e3), true + case "nsubset": + // SUBSET OF with vertical line + return rune(0x2282), true + case "nsub": + // NOT A SUBSET OF + return rune(0x2284), true + case "nsubE": + // SUBSET OF ABOVE EQUALS SIGN with slash + return rune(0x2ac5), true + case "nsube": + // NEITHER A SUBSET OF NOR EQUAL TO + return rune(0x2288), true + case "nsubseteq": + // NEITHER A SUBSET OF NOR EQUAL TO + return rune(0x2288), true + case "nsubseteqq": + // SUBSET OF ABOVE EQUALS SIGN with slash + return rune(0x2ac5), true + case "nsucc": + // DOES NOT SUCCEED + return rune(0x2281), true + case "nsucceq": + // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN with slash + return rune(0x2ab0), true + case "nsupset": + // SUPERSET OF with vertical line + return rune(0x2283), true + case "nsup": + // NOT A SUPERSET OF + return rune(0x2285), true + case "nsupE": + // SUPERSET OF ABOVE EQUALS SIGN with slash + return rune(0x2ac6), true + case "nsupe": + // NEITHER A SUPERSET OF NOR EQUAL TO + return rune(0x2289), true + case "nsupseteq": + // NEITHER A SUPERSET OF NOR EQUAL TO + return rune(0x2289), true + case "nsupseteqq": + // SUPERSET OF ABOVE EQUALS SIGN with slash + return rune(0x2ac6), true + case "ntgl": + // NEITHER GREATER-THAN NOR LESS-THAN + return rune(0x2279), true + case "ntilde": + // LATIN SMALL LETTER N WITH TILDE + return rune(0xf1), true + case "ntlg": + // NEITHER LESS-THAN NOR GREATER-THAN + return rune(0x2278), true + case "ntriangleleft": + // NOT NORMAL SUBGROUP OF + return rune(0x22ea), true + case "ntrianglelefteq": + // NOT NORMAL SUBGROUP OF OR EQUAL TO + return rune(0x22ec), true + case "ntriangleright": + // DOES NOT CONTAIN AS NORMAL SUBGROUP + return rune(0x22eb), true + case "ntrianglerighteq": + // DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL + return rune(0x22ed), true + case "numero": + // NUMERO SIGN + return rune(0x2116), true + case "numsp": + // FIGURE SPACE + return rune(0x2007), true + case "nu": + // GREEK SMALL LETTER NU + return rune(0x03bd), true + case "num": + // NUMBER SIGN + return rune(0x23), true + case "nvDash": + // NOT TRUE + return rune(0x22ad), true + case "nvHarr": + // LEFT RIGHT DOUBLE ARROW WITH VERTICAL STROKE + return rune(0x2904), true + case "nvap": + // EQUIVALENT TO with vertical line + return rune(0x224d), true + case "nvbrtri": + // VERTICAL BAR BESIDE RIGHT TRIANGLE with slash + return rune(0x29d0), true + case "nvdash": + // DOES NOT PROVE + return rune(0x22ac), true + case "nvge": + // GREATER-THAN OR EQUAL TO with vertical line + return rune(0x2265), true + case "nvgt": + // GREATER-THAN SIGN with vertical line + return rune(0x3e), true + case "nvinfin": + // INFINITY NEGATED WITH VERTICAL BAR + return rune(0x29de), true + case "nvlArr": + // LEFTWARDS DOUBLE ARROW WITH VERTICAL STROKE + return rune(0x2902), true + case "nvle": + // LESS-THAN OR EQUAL TO with vertical line + return rune(0x2264), true + case "nvltrie": + // NORMAL SUBGROUP OF OR EQUAL TO with vertical line + return rune(0x22b4), true + case "nvlt": + // LESS-THAN SIGN with vertical line + return rune(0x3c), true + case "nvrArr": + // RIGHTWARDS DOUBLE ARROW WITH VERTICAL STROKE + return rune(0x2903), true + case "nvrtrie": + // CONTAINS AS NORMAL SUBGROUP OR EQUAL TO with vertical line + return rune(0x22b5), true + case "nvsim": + // TILDE OPERATOR with vertical line + return rune(0x223c), true + case "nwArr": + // NORTH WEST DOUBLE ARROW + return rune(0x21d6), true + case "nwarhk": + // NORTH WEST ARROW WITH HOOK + return rune(0x2923), true + case "nwarrow": + // NORTH WEST ARROW + return rune(0x2196), true + case "nwarr": + // NORTH WEST ARROW + return rune(0x2196), true + case "nwnear": + // NORTH WEST ARROW AND NORTH EAST ARROW + return rune(0x2927), true + case "nwonearr": + // NORTH WEST ARROW CROSSING NORTH EAST ARROW + return rune(0x2932), true + case "nwsesarr": + // NORTH WEST AND SOUTH EAST ARROW + return rune(0x2921), true + } + + case 'o': + switch name { + case "oS": + // CIRCLED LATIN CAPITAL LETTER S + return rune(0x24c8), true + case "oacgr": + // GREEK SMALL LETTER OMICRON WITH TONOS + return rune(0x03cc), true + case "oacute": + // LATIN SMALL LETTER O WITH ACUTE + return rune(0xf3), true + case "oast": + // CIRCLED ASTERISK OPERATOR + return rune(0x229b), true + case "obsol": + // CIRCLED REVERSE SOLIDUS + return rune(0x29b8), true + case "ocir": + // CIRCLED RING OPERATOR + return rune(0x229a), true + case "ocirc": + // LATIN SMALL LETTER O WITH CIRCUMFLEX + return rune(0xf4), true + case "ocy": + // CYRILLIC SMALL LETTER O + return rune(0x043e), true + case "odash": + // CIRCLED DASH + return rune(0x229d), true + case "odblac": + // LATIN SMALL LETTER O WITH DOUBLE ACUTE + return rune(0x0151), true + case "odiv": + // CIRCLED DIVISION SIGN + return rune(0x2a38), true + case "odot": + // CIRCLED DOT OPERATOR + return rune(0x2299), true + case "odsold": + // CIRCLED ANTICLOCKWISE-ROTATED DIVISION SIGN + return rune(0x29bc), true + case "oelig": + // LATIN SMALL LIGATURE OE + return rune(0x0153), true + case "ofcir": + // CIRCLED BULLET + return rune(0x29bf), true + case "ofr": + // MATHEMATICAL FRAKTUR SMALL O + return rune(0x01d52c), true + case "ogon": + // OGONEK + return rune(0x02db), true + case "ogr": + // GREEK SMALL LETTER OMICRON + return rune(0x03bf), true + case "ograve": + // LATIN SMALL LETTER O WITH GRAVE + return rune(0xf2), true + case "ogt": + // CIRCLED GREATER-THAN + return rune(0x29c1), true + case "ohacgr": + // GREEK SMALL LETTER OMEGA WITH TONOS + return rune(0x03ce), true + case "ohbar": + // CIRCLE WITH HORIZONTAL BAR + return rune(0x29b5), true + case "ohgr": + // GREEK SMALL LETTER OMEGA + return rune(0x03c9), true + case "ohm": + // GREEK CAPITAL LETTER OMEGA + return rune(0x03a9), true + case "oint": + // CONTOUR INTEGRAL + return rune(0x222e), true + case "olarr": + // ANTICLOCKWISE OPEN CIRCLE ARROW + return rune(0x21ba), true + case "olcir": + // CIRCLED WHITE BULLET + return rune(0x29be), true + case "olcross": + // CIRCLE WITH SUPERIMPOSED X + return rune(0x29bb), true + case "oline": + // OVERLINE + return rune(0x203e), true + case "olt": + // CIRCLED LESS-THAN + return rune(0x29c0), true + case "omacr": + // LATIN SMALL LETTER O WITH MACRON + return rune(0x014d), true + case "omega": + // GREEK SMALL LETTER OMEGA + return rune(0x03c9), true + case "omicron": + // GREEK SMALL LETTER OMICRON + return rune(0x03bf), true + case "omid": + // CIRCLED VERTICAL BAR + return rune(0x29b6), true + case "ominus": + // CIRCLED MINUS + return rune(0x2296), true + case "oopf": + // MATHEMATICAL DOUBLE-STRUCK SMALL O + return rune(0x01d560), true + case "opar": + // CIRCLED PARALLEL + return rune(0x29b7), true + case "operp": + // CIRCLED PERPENDICULAR + return rune(0x29b9), true + case "opfgamma": + // DOUBLE-STRUCK SMALL GAMMA + return rune(0x213d), true + case "opfpi": + // DOUBLE-STRUCK CAPITAL PI + return rune(0x213f), true + case "opfsum": + // DOUBLE-STRUCK N-ARY SUMMATION + return rune(0x2140), true + case "oplus": + // CIRCLED PLUS + return rune(0x2295), true + case "orarr": + // CLOCKWISE OPEN CIRCLE ARROW + return rune(0x21bb), true + case "or": + // LOGICAL OR + return rune(0x2228), true + case "orderof": + // SCRIPT SMALL O + return rune(0x2134), true + case "order": + // SCRIPT SMALL O + return rune(0x2134), true + case "ord": + // LOGICAL OR WITH HORIZONTAL DASH + return rune(0x2a5d), true + case "ordf": + // FEMININE ORDINAL INDICATOR + return rune(0xaa), true + case "ordm": + // MASCULINE ORDINAL INDICATOR + return rune(0xba), true + case "origof": + // ORIGINAL OF + return rune(0x22b6), true + case "oror": + // TWO INTERSECTING LOGICAL OR + return rune(0x2a56), true + case "orslope": + // SLOPING LARGE OR + return rune(0x2a57), true + case "orv": + // LOGICAL OR WITH MIDDLE STEM + return rune(0x2a5b), true + case "oscr": + // SCRIPT SMALL O + return rune(0x2134), true + case "oslash": + // LATIN SMALL LETTER O WITH STROKE + return rune(0xf8), true + case "osol": + // CIRCLED DIVISION SLASH + return rune(0x2298), true + case "otilde": + // LATIN SMALL LETTER O WITH TILDE + return rune(0xf5), true + case "otimes": + // CIRCLED TIMES + return rune(0x2297), true + case "otimesas": + // CIRCLED MULTIPLICATION SIGN WITH CIRCUMFLEX ACCENT + return rune(0x2a36), true + case "ouml": + // LATIN SMALL LETTER O WITH DIAERESIS + return rune(0xf6), true + case "ovbar": + // APL FUNCTIONAL SYMBOL CIRCLE STILE + return rune(0x233d), true + case "ovrbrk": + // TOP SQUARE BRACKET + return rune(0x23b4), true + case "ovrcub": + // TOP CURLY BRACKET + return rune(0x23de), true + case "ovrpar": + // TOP PARENTHESIS + return rune(0x23dc), true + case "oxuarr": + // UP ARROW THROUGH CIRCLE + return rune(0x29bd), true + } + + case 'p': + switch name { + case "part": + // PARTIAL DIFFERENTIAL + return rune(0x2202), true + case "par": + // PARALLEL TO + return rune(0x2225), true + case "parallel": + // PARALLEL TO + return rune(0x2225), true + case "para": + // PILCROW SIGN + return rune(0xb6), true + case "parsim": + // PARALLEL WITH TILDE OPERATOR + return rune(0x2af3), true + case "parsl": + // DOUBLE SOLIDUS OPERATOR + return rune(0x2afd), true + case "pcy": + // CYRILLIC SMALL LETTER PE + return rune(0x043f), true + case "percnt": + // PERCENT SIGN + return rune(0x25), true + case "period": + // FULL STOP + return rune(0x2e), true + case "permil": + // PER MILLE SIGN + return rune(0x2030), true + case "perp": + // UP TACK + return rune(0x22a5), true + case "pertenk": + // PER TEN THOUSAND SIGN + return rune(0x2031), true + case "pfr": + // MATHEMATICAL FRAKTUR SMALL P + return rune(0x01d52d), true + case "pgr": + // GREEK SMALL LETTER PI + return rune(0x03c0), true + case "phgr": + // GREEK SMALL LETTER PHI + return rune(0x03c6), true + case "phis": + // GREEK PHI SYMBOL + return rune(0x03d5), true + case "phiv": + // GREEK PHI SYMBOL + return rune(0x03d5), true + case "phi": + // GREEK SMALL LETTER PHI + return rune(0x03c6), true + case "phmmat": + // SCRIPT CAPITAL M + return rune(0x2133), true + case "phone": + // BLACK TELEPHONE + return rune(0x260e), true + case "pitchfork": + // PITCHFORK + return rune(0x22d4), true + case "piv": + // GREEK PI SYMBOL + return rune(0x03d6), true + case "pi": + // GREEK SMALL LETTER PI + return rune(0x03c0), true + case "planck": + // PLANCK CONSTANT OVER TWO PI + return rune(0x210f), true + case "planckh": + // PLANCK CONSTANT + return rune(0x210e), true + case "plankv": + // PLANCK CONSTANT OVER TWO PI + return rune(0x210f), true + case "plusacir": + // PLUS SIGN WITH CIRCUMFLEX ACCENT ABOVE + return rune(0x2a23), true + case "plusb": + // SQUARED PLUS + return rune(0x229e), true + case "pluscir": + // PLUS SIGN WITH SMALL CIRCLE ABOVE + return rune(0x2a22), true + case "plusdo": + // DOT PLUS + return rune(0x2214), true + case "plusdu": + // PLUS SIGN WITH DOT BELOW + return rune(0x2a25), true + case "pluse": + // PLUS SIGN ABOVE EQUALS SIGN + return rune(0x2a72), true + case "plusmn": + // PLUS-MINUS SIGN + return rune(0xb1), true + case "plussim": + // PLUS SIGN WITH TILDE BELOW + return rune(0x2a26), true + case "plustrif": + // PLUS SIGN WITH BLACK TRIANGLE + return rune(0x2a28), true + case "plustwo": + // PLUS SIGN WITH SUBSCRIPT TWO + return rune(0x2a27), true + case "plus": + // PLUS SIGN + return rune(0x2b), true + case "pm": + // PLUS-MINUS SIGN + return rune(0xb1), true + case "pointint": + // INTEGRAL AROUND A POINT OPERATOR + return rune(0x2a15), true + case "popf": + // MATHEMATICAL DOUBLE-STRUCK SMALL P + return rune(0x01d561), true + case "pound": + // POUND SIGN + return rune(0xa3), true + case "prod": + // N-ARY PRODUCT + return rune(0x220f), true + case "prop": + // PROPORTIONAL TO + return rune(0x221d), true + case "propto": + // PROPORTIONAL TO + return rune(0x221d), true + case "pr": + // PRECEDES + return rune(0x227a), true + case "prE": + // PRECEDES ABOVE EQUALS SIGN + return rune(0x2ab3), true + case "prap": + // PRECEDES ABOVE ALMOST EQUAL TO + return rune(0x2ab7), true + case "prcue": + // PRECEDES OR EQUAL TO + return rune(0x227c), true + case "prec": + // PRECEDES + return rune(0x227a), true + case "preccurlyeq": + // PRECEDES OR EQUAL TO + return rune(0x227c), true + case "precnsim": + // PRECEDES BUT NOT EQUIVALENT TO + return rune(0x22e8), true + case "precsim": + // PRECEDES OR EQUIVALENT TO + return rune(0x227e), true + case "pre": + // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN + return rune(0x2aaf), true + case "precapprox": + // PRECEDES ABOVE ALMOST EQUAL TO + return rune(0x2ab7), true + case "preceq": + // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN + return rune(0x2aaf), true + case "precnapprox": + // PRECEDES ABOVE NOT ALMOST EQUAL TO + return rune(0x2ab9), true + case "precneqq": + // PRECEDES ABOVE NOT EQUAL TO + return rune(0x2ab5), true + case "primes": + // DOUBLE-STRUCK CAPITAL P + return rune(0x2119), true + case "prime": + // PRIME + return rune(0x2032), true + case "prnE": + // PRECEDES ABOVE NOT EQUAL TO + return rune(0x2ab5), true + case "prnap": + // PRECEDES ABOVE NOT ALMOST EQUAL TO + return rune(0x2ab9), true + case "prnsim": + // PRECEDES BUT NOT EQUIVALENT TO + return rune(0x22e8), true + case "profalar": + // ALL AROUND-PROFILE + return rune(0x232e), true + case "profline": + // ARC + return rune(0x2312), true + case "profsurf": + // SEGMENT + return rune(0x2313), true + case "prsim": + // PRECEDES OR EQUIVALENT TO + return rune(0x227e), true + case "prurel": + // PRECEDES UNDER RELATION + return rune(0x22b0), true + case "pscr": + // MATHEMATICAL SCRIPT SMALL P + return rune(0x01d4c5), true + case "psgr": + // GREEK SMALL LETTER PSI + return rune(0x03c8), true + case "psi": + // GREEK SMALL LETTER PSI + return rune(0x03c8), true + case "puncsp": + // PUNCTUATION SPACE + return rune(0x2008), true + } + + case 'q': + switch name { + case "qfr": + // MATHEMATICAL FRAKTUR SMALL Q + return rune(0x01d52e), true + case "qint": + // QUADRUPLE INTEGRAL OPERATOR + return rune(0x2a0c), true + case "qopf": + // MATHEMATICAL DOUBLE-STRUCK SMALL Q + return rune(0x01d562), true + case "qprime": + // QUADRUPLE PRIME + return rune(0x2057), true + case "qscr": + // MATHEMATICAL SCRIPT SMALL Q + return rune(0x01d4c6), true + case "quaternions": + // DOUBLE-STRUCK CAPITAL H + return rune(0x210d), true + case "quatint": + // QUATERNION INTEGRAL OPERATOR + return rune(0x2a16), true + case "questeq": + // QUESTIONED EQUAL TO + return rune(0x225f), true + case "quest": + // QUESTION MARK + return rune(0x3f), true + case "quot": + // QUOTATION MARK + return rune(0x22), true + } + + case 'r': + switch name { + case "rAarr": + // RIGHTWARDS TRIPLE ARROW + return rune(0x21db), true + case "rArr": + // RIGHTWARDS DOUBLE ARROW + return rune(0x21d2), true + case "rAtail": + // RIGHTWARDS DOUBLE ARROW-TAIL + return rune(0x291c), true + case "rBarr": + // RIGHTWARDS TRIPLE DASH ARROW + return rune(0x290f), true + case "rHar": + // RIGHTWARDS HARPOON WITH BARB UP ABOVE RIGHTWARDS HARPOON WITH BARB DOWN + return rune(0x2964), true + case "race": + // REVERSED TILDE with underline + return rune(0x223d), true + case "racute": + // LATIN SMALL LETTER R WITH ACUTE + return rune(0x0155), true + case "radic": + // SQUARE ROOT + return rune(0x221a), true + case "raemptyv": + // EMPTY SET WITH RIGHT ARROW ABOVE + return rune(0x29b3), true + case "rang": + // MATHEMATICAL RIGHT ANGLE BRACKET + return rune(0x27e9), true + case "rangd": + // RIGHT ANGLE BRACKET WITH DOT + return rune(0x2992), true + case "range": + // REVERSED ANGLE WITH UNDERBAR + return rune(0x29a5), true + case "rangle": + // MATHEMATICAL RIGHT ANGLE BRACKET + return rune(0x27e9), true + case "raquo": + // RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK + return rune(0xbb), true + case "rarr2": + // RIGHTWARDS PAIRED ARROWS + return rune(0x21c9), true + case "rarr3": + // THREE RIGHTWARDS ARROWS + return rune(0x21f6), true + case "rarrb": + // RIGHTWARDS ARROW TO BAR + return rune(0x21e5), true + case "rarrhk": + // RIGHTWARDS ARROW WITH HOOK + return rune(0x21aa), true + case "rarrlp": + // RIGHTWARDS ARROW WITH LOOP + return rune(0x21ac), true + case "rarrtl": + // RIGHTWARDS ARROW WITH TAIL + return rune(0x21a3), true + case "rarrw": + // RIGHTWARDS WAVE ARROW + return rune(0x219d), true + case "rarr": + // RIGHTWARDS ARROW + return rune(0x2192), true + case "rarrap": + // RIGHTWARDS ARROW ABOVE ALMOST EQUAL TO + return rune(0x2975), true + case "rarrbfs": + // RIGHTWARDS ARROW FROM BAR TO BLACK DIAMOND + return rune(0x2920), true + case "rarrc": + // WAVE ARROW POINTING DIRECTLY RIGHT + return rune(0x2933), true + case "rarrfs": + // RIGHTWARDS ARROW TO BLACK DIAMOND + return rune(0x291e), true + case "rarrpl": + // RIGHTWARDS ARROW WITH PLUS BELOW + return rune(0x2945), true + case "rarrsim": + // RIGHTWARDS ARROW ABOVE TILDE OPERATOR + return rune(0x2974), true + case "rarrx": + // RIGHTWARDS ARROW THROUGH X + return rune(0x2947), true + case "ratail": + // RIGHTWARDS ARROW-TAIL + return rune(0x291a), true + case "ratio": + // RATIO + return rune(0x2236), true + case "rationals": + // DOUBLE-STRUCK CAPITAL Q + return rune(0x211a), true + case "rbarr": + // RIGHTWARDS DOUBLE DASH ARROW + return rune(0x290d), true + case "rbbrk": + // LIGHT RIGHT TORTOISE SHELL BRACKET ORNAMENT + return rune(0x2773), true + case "rbrace": + // RIGHT CURLY BRACKET + return rune(0x7d), true + case "rbrack": + // RIGHT SQUARE BRACKET + return rune(0x5d), true + case "rbrke": + // RIGHT SQUARE BRACKET WITH UNDERBAR + return rune(0x298c), true + case "rbrksld": + // RIGHT SQUARE BRACKET WITH TICK IN BOTTOM CORNER + return rune(0x298e), true + case "rbrkslu": + // RIGHT SQUARE BRACKET WITH TICK IN TOP CORNER + return rune(0x2990), true + case "rcaron": + // LATIN SMALL LETTER R WITH CARON + return rune(0x0159), true + case "rcedil": + // LATIN SMALL LETTER R WITH CEDILLA + return rune(0x0157), true + case "rceil": + // RIGHT CEILING + return rune(0x2309), true + case "rcub": + // RIGHT CURLY BRACKET + return rune(0x7d), true + case "rcy": + // CYRILLIC SMALL LETTER ER + return rune(0x0440), true + case "rdca": + // ARROW POINTING DOWNWARDS THEN CURVING RIGHTWARDS + return rune(0x2937), true + case "rdharb": + // RIGHTWARDS HARPOON WITH BARB DOWN TO BAR + return rune(0x2957), true + case "rdiag": + // BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT + return rune(0x2571), true + case "rdiofdi": + // RISING DIAGONAL CROSSING FALLING DIAGONAL + return rune(0x292b), true + case "rdldhar": + // RIGHTWARDS HARPOON WITH BARB DOWN ABOVE LEFTWARDS HARPOON WITH BARB DOWN + return rune(0x2969), true + case "rdosearr": + // RISING DIAGONAL CROSSING SOUTH EAST ARROW + return rune(0x2930), true + case "rdquor": + // RIGHT DOUBLE QUOTATION MARK + return rune(0x201d), true + case "rdquo": + // RIGHT DOUBLE QUOTATION MARK + return rune(0x201d), true + case "rdsh": + // DOWNWARDS ARROW WITH TIP RIGHTWARDS + return rune(0x21b3), true + case "realpart": + // BLACK-LETTER CAPITAL R + return rune(0x211c), true + case "reals": + // DOUBLE-STRUCK CAPITAL R + return rune(0x211d), true + case "real": + // BLACK-LETTER CAPITAL R + return rune(0x211c), true + case "realine": + // SCRIPT CAPITAL R + return rune(0x211b), true + case "rect": + // WHITE RECTANGLE + return rune(0x25ad), true + case "reg": + // REGISTERED SIGN + return rune(0xae), true + case "rfbowtie": + // BOWTIE WITH RIGHT HALF BLACK + return rune(0x29d2), true + case "rfisht": + // RIGHT FISH TAIL + return rune(0x297d), true + case "rfloor": + // RIGHT FLOOR + return rune(0x230b), true + case "rfr": + // MATHEMATICAL FRAKTUR SMALL R + return rune(0x01d52f), true + case "rftimes": + // TIMES WITH RIGHT HALF BLACK + return rune(0x29d5), true + case "rgr": + // GREEK SMALL LETTER RHO + return rune(0x03c1), true + case "rhard": + // RIGHTWARDS HARPOON WITH BARB DOWNWARDS + return rune(0x21c1), true + case "rharu": + // RIGHTWARDS HARPOON WITH BARB UPWARDS + return rune(0x21c0), true + case "rharul": + // RIGHTWARDS HARPOON WITH BARB UP ABOVE LONG DASH + return rune(0x296c), true + case "rhov": + // GREEK RHO SYMBOL + return rune(0x03f1), true + case "rho": + // GREEK SMALL LETTER RHO + return rune(0x03c1), true + case "rightarrowtail": + // RIGHTWARDS ARROW WITH TAIL + return rune(0x21a3), true + case "rightarrow": + // RIGHTWARDS ARROW + return rune(0x2192), true + case "rightharpoondown": + // RIGHTWARDS HARPOON WITH BARB DOWNWARDS + return rune(0x21c1), true + case "rightharpoonup": + // RIGHTWARDS HARPOON WITH BARB UPWARDS + return rune(0x21c0), true + case "rightleftarrows": + // RIGHTWARDS ARROW OVER LEFTWARDS ARROW + return rune(0x21c4), true + case "rightleftharpoons": + // RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON + return rune(0x21cc), true + case "rightrightarrows": + // RIGHTWARDS PAIRED ARROWS + return rune(0x21c9), true + case "rightsquigarrow": + // RIGHTWARDS WAVE ARROW + return rune(0x219d), true + case "rightthreetimes": + // RIGHT SEMIDIRECT PRODUCT + return rune(0x22cc), true + case "rimply": + // RIGHT DOUBLE ARROW WITH ROUNDED HEAD + return rune(0x2970), true + case "ring": + // RING ABOVE + return rune(0x02da), true + case "risingdotseq": + // IMAGE OF OR APPROXIMATELY EQUAL TO + return rune(0x2253), true + case "rlarr2": + // RIGHTWARDS ARROW OVER LEFTWARDS ARROW + return rune(0x21c4), true + case "rlarr": + // RIGHTWARDS ARROW OVER LEFTWARDS ARROW + return rune(0x21c4), true + case "rlhar": + // RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON + return rune(0x21cc), true + case "rlhar2": + // RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON + return rune(0x21cc), true + case "rlm": + // RIGHT-TO-LEFT MARK + return rune(0x200f), true + case "rmoust": + // UPPER RIGHT OR LOWER LEFT CURLY BRACKET SECTION + return rune(0x23b1), true + case "rmoustache": + // UPPER RIGHT OR LOWER LEFT CURLY BRACKET SECTION + return rune(0x23b1), true + case "rnmid": + // DOES NOT DIVIDE WITH REVERSED NEGATION SLASH + return rune(0x2aee), true + case "roang": + // MATHEMATICAL RIGHT WHITE TORTOISE SHELL BRACKET + return rune(0x27ed), true + case "roarr": + // RIGHTWARDS OPEN-HEADED ARROW + return rune(0x21fe), true + case "robrk": + // MATHEMATICAL RIGHT WHITE SQUARE BRACKET + return rune(0x27e7), true + case "rocub": + // RIGHT WHITE CURLY BRACKET + return rune(0x2984), true + case "ropar": + // RIGHT WHITE PARENTHESIS + return rune(0x2986), true + case "ropf": + // MATHEMATICAL DOUBLE-STRUCK SMALL R + return rune(0x01d563), true + case "roplus": + // PLUS SIGN IN RIGHT HALF CIRCLE + return rune(0x2a2e), true + case "rotimes": + // MULTIPLICATION SIGN IN RIGHT HALF CIRCLE + return rune(0x2a35), true + case "rpargt": + // RIGHT ARC GREATER-THAN BRACKET + return rune(0x2994), true + case "rpar": + // RIGHT PARENTHESIS + return rune(0x29), true + case "rppolint": + // LINE INTEGRATION WITH RECTANGULAR PATH AROUND POLE + return rune(0x2a12), true + case "rrarr": + // RIGHTWARDS PAIRED ARROWS + return rune(0x21c9), true + case "rsaquo": + // SINGLE RIGHT-POINTING ANGLE QUOTATION MARK + return rune(0x203a), true + case "rscr": + // MATHEMATICAL SCRIPT SMALL R + return rune(0x01d4c7), true + case "rsh": + // UPWARDS ARROW WITH TIP RIGHTWARDS + return rune(0x21b1), true + case "rsolbar": + // REVERSE SOLIDUS WITH HORIZONTAL STROKE + return rune(0x29f7), true + case "rsqb": + // RIGHT SQUARE BRACKET + return rune(0x5d), true + case "rsquor": + // RIGHT SINGLE QUOTATION MARK + return rune(0x2019), true + case "rsquo": + // RIGHT SINGLE QUOTATION MARK + return rune(0x2019), true + case "rthree": + // RIGHT SEMIDIRECT PRODUCT + return rune(0x22cc), true + case "rtimes": + // RIGHT NORMAL FACTOR SEMIDIRECT PRODUCT + return rune(0x22ca), true + case "rtrie": + // CONTAINS AS NORMAL SUBGROUP OR EQUAL TO + return rune(0x22b5), true + case "rtrif": + // BLACK RIGHT-POINTING SMALL TRIANGLE + return rune(0x25b8), true + case "rtri": + // WHITE RIGHT-POINTING SMALL TRIANGLE + return rune(0x25b9), true + case "rtriltri": + // RIGHT TRIANGLE ABOVE LEFT TRIANGLE + return rune(0x29ce), true + case "ruharb": + // RIGHTWARDS HARPOON WITH BARB UP TO BAR + return rune(0x2953), true + case "ruluhar": + // RIGHTWARDS HARPOON WITH BARB UP ABOVE LEFTWARDS HARPOON WITH BARB UP + return rune(0x2968), true + case "rx": + // PRESCRIPTION TAKE + return rune(0x211e), true + } + + case 's': + switch name { + case "sacute": + // LATIN SMALL LETTER S WITH ACUTE + return rune(0x015b), true + case "samalg": + // N-ARY COPRODUCT + return rune(0x2210), true + case "sampi": + // GREEK LETTER SAMPI + return rune(0x03e0), true + case "sbquo": + // SINGLE LOW-9 QUOTATION MARK + return rune(0x201a), true + case "sbsol": + // SMALL REVERSE SOLIDUS + return rune(0xfe68), true + case "sc": + // SUCCEEDS + return rune(0x227b), true + case "scE": + // SUCCEEDS ABOVE EQUALS SIGN + return rune(0x2ab4), true + case "scap": + // SUCCEEDS ABOVE ALMOST EQUAL TO + return rune(0x2ab8), true + case "scaron": + // LATIN SMALL LETTER S WITH CARON + return rune(0x0161), true + case "sccue": + // SUCCEEDS OR EQUAL TO + return rune(0x227d), true + case "scedil": + // LATIN SMALL LETTER S WITH CEDILLA + return rune(0x015f), true + case "sce": + // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN + return rune(0x2ab0), true + case "scirc": + // LATIN SMALL LETTER S WITH CIRCUMFLEX + return rune(0x015d), true + case "scnE": + // SUCCEEDS ABOVE NOT EQUAL TO + return rune(0x2ab6), true + case "scnap": + // SUCCEEDS ABOVE NOT ALMOST EQUAL TO + return rune(0x2aba), true + case "scnsim": + // SUCCEEDS BUT NOT EQUIVALENT TO + return rune(0x22e9), true + case "scpolint": + // LINE INTEGRATION WITH SEMICIRCULAR PATH AROUND POLE + return rune(0x2a13), true + case "scsim": + // SUCCEEDS OR EQUIVALENT TO + return rune(0x227f), true + case "scy": + // CYRILLIC SMALL LETTER ES + return rune(0x0441), true + case "sdotb": + // SQUARED DOT OPERATOR + return rune(0x22a1), true + case "sdot": + // DOT OPERATOR + return rune(0x22c5), true + case "sdote": + // EQUALS SIGN WITH DOT BELOW + return rune(0x2a66), true + case "seArr": + // SOUTH EAST DOUBLE ARROW + return rune(0x21d8), true + case "searhk": + // SOUTH EAST ARROW WITH HOOK + return rune(0x2925), true + case "searrow": + // SOUTH EAST ARROW + return rune(0x2198), true + case "searr": + // SOUTH EAST ARROW + return rune(0x2198), true + case "sect": + // SECTION SIGN + return rune(0xa7), true + case "semi": + // SEMICOLON + return rune(0x3b), true + case "seonearr": + // SOUTH EAST ARROW CROSSING NORTH EAST ARROW + return rune(0x292d), true + case "seswar": + // SOUTH EAST ARROW AND SOUTH WEST ARROW + return rune(0x2929), true + case "setminus": + // SET MINUS + return rune(0x2216), true + case "setmn": + // SET MINUS + return rune(0x2216), true + case "sext": + // SIX POINTED BLACK STAR + return rune(0x2736), true + case "sfgr": + // GREEK SMALL LETTER FINAL SIGMA + return rune(0x03c2), true + case "sfrown": + // FROWN + return rune(0x2322), true + case "sfr": + // MATHEMATICAL FRAKTUR SMALL S + return rune(0x01d530), true + case "sgr": + // GREEK SMALL LETTER SIGMA + return rune(0x03c3), true + case "sharp": + // MUSIC SHARP SIGN + return rune(0x266f), true + case "shchcy": + // CYRILLIC SMALL LETTER SHCHA + return rune(0x0449), true + case "shcy": + // CYRILLIC SMALL LETTER SHA + return rune(0x0448), true + case "shortmid": + // DIVIDES + return rune(0x2223), true + case "shortparallel": + // PARALLEL TO + return rune(0x2225), true + case "shuffle": + // SHUFFLE PRODUCT + return rune(0x29e2), true + case "shy": + // SOFT HYPHEN + return rune(0xad), true + case "sigma": + // GREEK SMALL LETTER SIGMA + return rune(0x03c3), true + case "sigmaf": + // GREEK SMALL LETTER FINAL SIGMA + return rune(0x03c2), true + case "sigmav": + // GREEK SMALL LETTER FINAL SIGMA + return rune(0x03c2), true + case "sim": + // TILDE OPERATOR + return rune(0x223c), true + case "simdot": + // TILDE OPERATOR WITH DOT ABOVE + return rune(0x2a6a), true + case "sime": + // ASYMPTOTICALLY EQUAL TO + return rune(0x2243), true + case "simeq": + // ASYMPTOTICALLY EQUAL TO + return rune(0x2243), true + case "simg": + // SIMILAR OR GREATER-THAN + return rune(0x2a9e), true + case "simgE": + // SIMILAR ABOVE GREATER-THAN ABOVE EQUALS SIGN + return rune(0x2aa0), true + case "siml": + // SIMILAR OR LESS-THAN + return rune(0x2a9d), true + case "simlE": + // SIMILAR ABOVE LESS-THAN ABOVE EQUALS SIGN + return rune(0x2a9f), true + case "simne": + // APPROXIMATELY BUT NOT ACTUALLY EQUAL TO + return rune(0x2246), true + case "simplus": + // PLUS SIGN WITH TILDE ABOVE + return rune(0x2a24), true + case "simrarr": + // TILDE OPERATOR ABOVE RIGHTWARDS ARROW + return rune(0x2972), true + case "slarr": + // LEFTWARDS ARROW + return rune(0x2190), true + case "slint": + // INTEGRAL AVERAGE WITH SLASH + return rune(0x2a0f), true + case "smallsetminus": + // SET MINUS + return rune(0x2216), true + case "smashp": + // SMASH PRODUCT + return rune(0x2a33), true + case "smeparsl": + // EQUALS SIGN AND SLANTED PARALLEL WITH TILDE ABOVE + return rune(0x29e4), true + case "smid": + // DIVIDES + return rune(0x2223), true + case "smile": + // SMILE + return rune(0x2323), true + case "smt": + // SMALLER THAN + return rune(0x2aaa), true + case "smte": + // SMALLER THAN OR EQUAL TO + return rune(0x2aac), true + case "smtes": + // SMALLER THAN OR slanted EQUAL + return rune(0x2aac), true + case "softcy": + // CYRILLIC SMALL LETTER SOFT SIGN + return rune(0x044c), true + case "solbar": + // APL FUNCTIONAL SYMBOL SLASH BAR + return rune(0x233f), true + case "solb": + // SQUARED RISING DIAGONAL SLASH + return rune(0x29c4), true + case "sol": + // SOLIDUS + return rune(0x2f), true + case "sopf": + // MATHEMATICAL DOUBLE-STRUCK SMALL S + return rune(0x01d564), true + case "spades": + // BLACK SPADE SUIT + return rune(0x2660), true + case "spadesuit": + // BLACK SPADE SUIT + return rune(0x2660), true + case "spar": + // PARALLEL TO + return rune(0x2225), true + case "sqcap": + // SQUARE CAP + return rune(0x2293), true + case "sqcaps": + // SQUARE CAP with serifs + return rune(0x2293), true + case "sqcup": + // SQUARE CUP + return rune(0x2294), true + case "sqcups": + // SQUARE CUP with serifs + return rune(0x2294), true + case "sqsub": + // SQUARE IMAGE OF + return rune(0x228f), true + case "sqsube": + // SQUARE IMAGE OF OR EQUAL TO + return rune(0x2291), true + case "sqsubset": + // SQUARE IMAGE OF + return rune(0x228f), true + case "sqsubseteq": + // SQUARE IMAGE OF OR EQUAL TO + return rune(0x2291), true + case "sqsup": + // SQUARE ORIGINAL OF + return rune(0x2290), true + case "sqsupe": + // SQUARE ORIGINAL OF OR EQUAL TO + return rune(0x2292), true + case "sqsupset": + // SQUARE ORIGINAL OF + return rune(0x2290), true + case "sqsupseteq": + // SQUARE ORIGINAL OF OR EQUAL TO + return rune(0x2292), true + case "squ": + // WHITE SQUARE + return rune(0x25a1), true + case "square": + // WHITE SQUARE + return rune(0x25a1), true + case "squarf": + // BLACK SMALL SQUARE + return rune(0x25aa), true + case "squb": + // SQUARED SQUARE + return rune(0x29c8), true + case "squerr": + // ERROR-BARRED WHITE SQUARE + return rune(0x29ee), true + case "squf": + // BLACK SMALL SQUARE + return rune(0x25aa), true + case "squferr": + // ERROR-BARRED BLACK SQUARE + return rune(0x29ef), true + case "srarr": + // RIGHTWARDS ARROW + return rune(0x2192), true + case "sscr": + // MATHEMATICAL SCRIPT SMALL S + return rune(0x01d4c8), true + case "ssetmn": + // SET MINUS + return rune(0x2216), true + case "ssmile": + // SMILE + return rune(0x2323), true + case "sstarf": + // STAR OPERATOR + return rune(0x22c6), true + case "starf": + // BLACK STAR + return rune(0x2605), true + case "star": + // WHITE STAR + return rune(0x2606), true + case "stigma": + // GREEK LETTER STIGMA + return rune(0x03da), true + case "straightepsilon": + // GREEK LUNATE EPSILON SYMBOL + return rune(0x03f5), true + case "straightphi": + // GREEK PHI SYMBOL + return rune(0x03d5), true + case "strns": + // MACRON + return rune(0xaf), true + case "sub": + // SUBSET OF + return rune(0x2282), true + case "subE": + // SUBSET OF ABOVE EQUALS SIGN + return rune(0x2ac5), true + case "subdot": + // SUBSET WITH DOT + return rune(0x2abd), true + case "sube": + // SUBSET OF OR EQUAL TO + return rune(0x2286), true + case "subedot": + // SUBSET OF OR EQUAL TO WITH DOT ABOVE + return rune(0x2ac3), true + case "submult": + // SUBSET WITH MULTIPLICATION SIGN BELOW + return rune(0x2ac1), true + case "subnE": + // SUBSET OF ABOVE NOT EQUAL TO + return rune(0x2acb), true + case "subne": + // SUBSET OF WITH NOT EQUAL TO + return rune(0x228a), true + case "subplus": + // SUBSET WITH PLUS SIGN BELOW + return rune(0x2abf), true + case "subrarr": + // SUBSET ABOVE RIGHTWARDS ARROW + return rune(0x2979), true + case "subset": + // SUBSET OF + return rune(0x2282), true + case "subseteq": + // SUBSET OF OR EQUAL TO + return rune(0x2286), true + case "subseteqq": + // SUBSET OF ABOVE EQUALS SIGN + return rune(0x2ac5), true + case "subsetneq": + // SUBSET OF WITH NOT EQUAL TO + return rune(0x228a), true + case "subsetneqq": + // SUBSET OF ABOVE NOT EQUAL TO + return rune(0x2acb), true + case "subsim": + // SUBSET OF ABOVE TILDE OPERATOR + return rune(0x2ac7), true + case "subsub": + // SUBSET ABOVE SUBSET + return rune(0x2ad5), true + case "subsup": + // SUBSET ABOVE SUPERSET + return rune(0x2ad3), true + case "succ": + // SUCCEEDS + return rune(0x227b), true + case "succapprox": + // SUCCEEDS ABOVE ALMOST EQUAL TO + return rune(0x2ab8), true + case "succcurlyeq": + // SUCCEEDS OR EQUAL TO + return rune(0x227d), true + case "succeq": + // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN + return rune(0x2ab0), true + case "succnapprox": + // SUCCEEDS ABOVE NOT ALMOST EQUAL TO + return rune(0x2aba), true + case "succneqq": + // SUCCEEDS ABOVE NOT EQUAL TO + return rune(0x2ab6), true + case "succnsim": + // SUCCEEDS BUT NOT EQUIVALENT TO + return rune(0x22e9), true + case "succsim": + // SUCCEEDS OR EQUIVALENT TO + return rune(0x227f), true + case "sum": + // N-ARY SUMMATION + return rune(0x2211), true + case "sumint": + // SUMMATION WITH INTEGRAL + return rune(0x2a0b), true + case "sung": + // EIGHTH NOTE + return rune(0x266a), true + case "sup": + // SUPERSET OF + return rune(0x2283), true + case "sup1": + // SUPERSCRIPT ONE + return rune(0xb9), true + case "sup2": + // SUPERSCRIPT TWO + return rune(0xb2), true + case "sup3": + // SUPERSCRIPT THREE + return rune(0xb3), true + case "supE": + // SUPERSET OF ABOVE EQUALS SIGN + return rune(0x2ac6), true + case "supdot": + // SUPERSET WITH DOT + return rune(0x2abe), true + case "supdsub": + // SUPERSET BESIDE AND JOINED BY DASH WITH SUBSET + return rune(0x2ad8), true + case "supe": + // SUPERSET OF OR EQUAL TO + return rune(0x2287), true + case "supedot": + // SUPERSET OF OR EQUAL TO WITH DOT ABOVE + return rune(0x2ac4), true + case "suphsol": + // SUPERSET PRECEDING SOLIDUS + return rune(0x27c9), true + case "suphsub": + // SUPERSET BESIDE SUBSET + return rune(0x2ad7), true + case "suplarr": + // SUPERSET ABOVE LEFTWARDS ARROW + return rune(0x297b), true + case "supmult": + // SUPERSET WITH MULTIPLICATION SIGN BELOW + return rune(0x2ac2), true + case "supnE": + // SUPERSET OF ABOVE NOT EQUAL TO + return rune(0x2acc), true + case "supne": + // SUPERSET OF WITH NOT EQUAL TO + return rune(0x228b), true + case "supplus": + // SUPERSET WITH PLUS SIGN BELOW + return rune(0x2ac0), true + case "supset": + // SUPERSET OF + return rune(0x2283), true + case "supseteq": + // SUPERSET OF OR EQUAL TO + return rune(0x2287), true + case "supseteqq": + // SUPERSET OF ABOVE EQUALS SIGN + return rune(0x2ac6), true + case "supsetneq": + // SUPERSET OF WITH NOT EQUAL TO + return rune(0x228b), true + case "supsetneqq": + // SUPERSET OF ABOVE NOT EQUAL TO + return rune(0x2acc), true + case "supsim": + // SUPERSET OF ABOVE TILDE OPERATOR + return rune(0x2ac8), true + case "supsub": + // SUPERSET ABOVE SUBSET + return rune(0x2ad4), true + case "supsup": + // SUPERSET ABOVE SUPERSET + return rune(0x2ad6), true + case "swArr": + // SOUTH WEST DOUBLE ARROW + return rune(0x21d9), true + case "swarhk": + // SOUTH WEST ARROW WITH HOOK + return rune(0x2926), true + case "swarrow": + // SOUTH WEST ARROW + return rune(0x2199), true + case "swarr": + // SOUTH WEST ARROW + return rune(0x2199), true + case "swnwar": + // SOUTH WEST ARROW AND NORTH WEST ARROW + return rune(0x292a), true + case "szlig": + // LATIN SMALL LETTER SHARP S + return rune(0xdf), true + } + + case 't': + switch name { + case "target": + // POSITION INDICATOR + return rune(0x2316), true + case "tau": + // GREEK SMALL LETTER TAU + return rune(0x03c4), true + case "tbrk": + // TOP SQUARE BRACKET + return rune(0x23b4), true + case "tcaron": + // LATIN SMALL LETTER T WITH CARON + return rune(0x0165), true + case "tcedil": + // LATIN SMALL LETTER T WITH CEDILLA + return rune(0x0163), true + case "tcy": + // CYRILLIC SMALL LETTER TE + return rune(0x0442), true + case "tdot": + // COMBINING THREE DOTS ABOVE + return rune(0x20db), true + case "telrec": + // TELEPHONE RECORDER + return rune(0x2315), true + case "tfr": + // MATHEMATICAL FRAKTUR SMALL T + return rune(0x01d531), true + case "tgr": + // GREEK SMALL LETTER TAU + return rune(0x03c4), true + case "there4": + // THEREFORE + return rune(0x2234), true + case "therefore": + // THEREFORE + return rune(0x2234), true + case "thermod": + // THERMODYNAMIC + return rune(0x29e7), true + case "thetasym": + // GREEK THETA SYMBOL + return rune(0x03d1), true + case "thetas": + // GREEK SMALL LETTER THETA + return rune(0x03b8), true + case "thetav": + // GREEK THETA SYMBOL + return rune(0x03d1), true + case "theta": + // GREEK SMALL LETTER THETA + return rune(0x03b8), true + case "thgr": + // GREEK SMALL LETTER THETA + return rune(0x03b8), true + case "thickapprox": + // ALMOST EQUAL TO + return rune(0x2248), true + case "thicksim": + // TILDE OPERATOR + return rune(0x223c), true + case "thinsp": + // THIN SPACE + return rune(0x2009), true + case "thkap": + // ALMOST EQUAL TO + return rune(0x2248), true + case "thksim": + // TILDE OPERATOR + return rune(0x223c), true + case "thorn": + // LATIN SMALL LETTER THORN + return rune(0xfe), true + case "tilde": + // SMALL TILDE + return rune(0x02dc), true + case "timeint": + // INTEGRAL WITH TIMES SIGN + return rune(0x2a18), true + case "timesb": + // SQUARED TIMES + return rune(0x22a0), true + case "timesbar": + // MULTIPLICATION SIGN WITH UNDERBAR + return rune(0x2a31), true + case "timesd": + // MULTIPLICATION SIGN WITH DOT ABOVE + return rune(0x2a30), true + case "times": + // MULTIPLICATION SIGN + return rune(0xd7), true + case "tint": + // TRIPLE INTEGRAL + return rune(0x222d), true + case "toea": + // NORTH EAST ARROW AND SOUTH EAST ARROW + return rune(0x2928), true + case "top": + // DOWN TACK + return rune(0x22a4), true + case "topbot": + // APL FUNCTIONAL SYMBOL I-BEAM + return rune(0x2336), true + case "topcir": + // DOWN TACK WITH CIRCLE BELOW + return rune(0x2af1), true + case "topfork": + // PITCHFORK WITH TEE TOP + return rune(0x2ada), true + case "topf": + // MATHEMATICAL DOUBLE-STRUCK SMALL T + return rune(0x01d565), true + case "tosa": + // SOUTH EAST ARROW AND SOUTH WEST ARROW + return rune(0x2929), true + case "tprime": + // TRIPLE PRIME + return rune(0x2034), true + case "trade": + // TRADE MARK SIGN + return rune(0x2122), true + case "triS": + // S IN TRIANGLE + return rune(0x29cc), true + case "trianglelefteq": + // NORMAL SUBGROUP OF OR EQUAL TO + return rune(0x22b4), true + case "triangleq": + // DELTA EQUAL TO + return rune(0x225c), true + case "trianglerighteq": + // CONTAINS AS NORMAL SUBGROUP OR EQUAL TO + return rune(0x22b5), true + case "triangle": + // WHITE UP-POINTING SMALL TRIANGLE + return rune(0x25b5), true + case "triangledown": + // WHITE DOWN-POINTING SMALL TRIANGLE + return rune(0x25bf), true + case "triangleleft": + // WHITE LEFT-POINTING SMALL TRIANGLE + return rune(0x25c3), true + case "triangleright": + // WHITE RIGHT-POINTING SMALL TRIANGLE + return rune(0x25b9), true + case "tribar": + // TRIANGLE WITH UNDERBAR + return rune(0x29cb), true + case "tridot": + // WHITE UP-POINTING TRIANGLE WITH DOT + return rune(0x25ec), true + case "tridoto": + // TRIANGLE WITH DOT ABOVE + return rune(0x29ca), true + case "trie": + // DELTA EQUAL TO + return rune(0x225c), true + case "triminus": + // MINUS SIGN IN TRIANGLE + return rune(0x2a3a), true + case "triplus": + // PLUS SIGN IN TRIANGLE + return rune(0x2a39), true + case "trisb": + // TRIANGLE WITH SERIFS AT BOTTOM + return rune(0x29cd), true + case "tritime": + // MULTIPLICATION SIGN IN TRIANGLE + return rune(0x2a3b), true + case "trpezium": + // WHITE TRAPEZIUM + return rune(0x23e2), true + case "tscr": + // MATHEMATICAL SCRIPT SMALL T + return rune(0x01d4c9), true + case "tscy": + // CYRILLIC SMALL LETTER TSE + return rune(0x0446), true + case "tshcy": + // CYRILLIC SMALL LETTER TSHE + return rune(0x045b), true + case "tstrok": + // LATIN SMALL LETTER T WITH STROKE + return rune(0x0167), true + case "tverbar": + // TRIPLE VERTICAL BAR DELIMITER + return rune(0x2980), true + case "twixt": + // BETWEEN + return rune(0x226c), true + case "twoheadleftarrow": + // LEFTWARDS TWO HEADED ARROW + return rune(0x219e), true + case "twoheadrightarrow": + // RIGHTWARDS TWO HEADED ARROW + return rune(0x21a0), true + } + + case 'u': + switch name { + case "uAarr": + // UPWARDS TRIPLE ARROW + return rune(0x290a), true + case "uArr": + // UPWARDS DOUBLE ARROW + return rune(0x21d1), true + case "uHar": + // UPWARDS HARPOON WITH BARB LEFT BESIDE UPWARDS HARPOON WITH BARB RIGHT + return rune(0x2963), true + case "uacgr": + // GREEK SMALL LETTER UPSILON WITH TONOS + return rune(0x03cd), true + case "uacute": + // LATIN SMALL LETTER U WITH ACUTE + return rune(0xfa), true + case "uarr2": + // UPWARDS PAIRED ARROWS + return rune(0x21c8), true + case "uarr": + // UPWARDS ARROW + return rune(0x2191), true + case "uarrb": + // UPWARDS ARROW TO BAR + return rune(0x2912), true + case "uarrln": + // UPWARDS ARROW WITH HORIZONTAL STROKE + return rune(0x2909), true + case "ubrcy": + // CYRILLIC SMALL LETTER SHORT U + return rune(0x045e), true + case "ubreve": + // LATIN SMALL LETTER U WITH BREVE + return rune(0x016d), true + case "ucirc": + // LATIN SMALL LETTER U WITH CIRCUMFLEX + return rune(0xfb), true + case "ucy": + // CYRILLIC SMALL LETTER U + return rune(0x0443), true + case "udarr": + // UPWARDS ARROW LEFTWARDS OF DOWNWARDS ARROW + return rune(0x21c5), true + case "udblac": + // LATIN SMALL LETTER U WITH DOUBLE ACUTE + return rune(0x0171), true + case "udhar": + // UPWARDS HARPOON WITH BARB LEFT BESIDE DOWNWARDS HARPOON WITH BARB RIGHT + return rune(0x296e), true + case "udiagr": + // GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS + return rune(0x03b0), true + case "udigr": + // GREEK SMALL LETTER UPSILON WITH DIALYTIKA + return rune(0x03cb), true + case "udrbrk": + // BOTTOM SQUARE BRACKET + return rune(0x23b5), true + case "udrcub": + // BOTTOM CURLY BRACKET + return rune(0x23df), true + case "udrpar": + // BOTTOM PARENTHESIS + return rune(0x23dd), true + case "ufisht": + // UP FISH TAIL + return rune(0x297e), true + case "ufr": + // MATHEMATICAL FRAKTUR SMALL U + return rune(0x01d532), true + case "ugr": + // GREEK SMALL LETTER UPSILON + return rune(0x03c5), true + case "ugrave": + // LATIN SMALL LETTER U WITH GRAVE + return rune(0xf9), true + case "uharl": + // UPWARDS HARPOON WITH BARB LEFTWARDS + return rune(0x21bf), true + case "uharr": + // UPWARDS HARPOON WITH BARB RIGHTWARDS + return rune(0x21be), true + case "uhblk": + // UPPER HALF BLOCK + return rune(0x2580), true + case "ulcorn": + // TOP LEFT CORNER + return rune(0x231c), true + case "ulcorner": + // TOP LEFT CORNER + return rune(0x231c), true + case "ulcrop": + // TOP LEFT CROP + return rune(0x230f), true + case "uldlshar": + // UP BARB LEFT DOWN BARB LEFT HARPOON + return rune(0x2951), true + case "ulharb": + // UPWARDS HARPOON WITH BARB LEFT TO BAR + return rune(0x2958), true + case "ultri": + // UPPER LEFT TRIANGLE + return rune(0x25f8), true + case "umacr": + // LATIN SMALL LETTER U WITH MACRON + return rune(0x016b), true + case "uml": + // DIAERESIS + return rune(0xa8), true + case "uogon": + // LATIN SMALL LETTER U WITH OGONEK + return rune(0x0173), true + case "uopf": + // MATHEMATICAL DOUBLE-STRUCK SMALL U + return rune(0x01d566), true + case "uparrow": + // UPWARDS ARROW + return rune(0x2191), true + case "updownarrow": + // UP DOWN ARROW + return rune(0x2195), true + case "upharpoonleft": + // UPWARDS HARPOON WITH BARB LEFTWARDS + return rune(0x21bf), true + case "upharpoonright": + // UPWARDS HARPOON WITH BARB RIGHTWARDS + return rune(0x21be), true + case "upint": + // INTEGRAL WITH OVERBAR + return rune(0x2a1b), true + case "uplus": + // MULTISET UNION + return rune(0x228e), true + case "upsih": + // GREEK UPSILON WITH HOOK SYMBOL + return rune(0x03d2), true + case "upsilon": + // GREEK SMALL LETTER UPSILON + return rune(0x03c5), true + case "upsi": + // GREEK SMALL LETTER UPSILON + return rune(0x03c5), true + case "upuparrows": + // UPWARDS PAIRED ARROWS + return rune(0x21c8), true + case "urcorn": + // TOP RIGHT CORNER + return rune(0x231d), true + case "urcorner": + // TOP RIGHT CORNER + return rune(0x231d), true + case "urcrop": + // TOP RIGHT CROP + return rune(0x230e), true + case "urdrshar": + // UP BARB RIGHT DOWN BARB RIGHT HARPOON + return rune(0x294f), true + case "urharb": + // UPWARDS HARPOON WITH BARB RIGHT TO BAR + return rune(0x2954), true + case "uring": + // LATIN SMALL LETTER U WITH RING ABOVE + return rune(0x016f), true + case "urtrif": + // BLACK UPPER RIGHT TRIANGLE + return rune(0x25e5), true + case "urtri": + // UPPER RIGHT TRIANGLE + return rune(0x25f9), true + case "uscr": + // MATHEMATICAL SCRIPT SMALL U + return rune(0x01d4ca), true + case "utdot": + // UP RIGHT DIAGONAL ELLIPSIS + return rune(0x22f0), true + case "utilde": + // LATIN SMALL LETTER U WITH TILDE + return rune(0x0169), true + case "utrif": + // BLACK UP-POINTING SMALL TRIANGLE + return rune(0x25b4), true + case "utri": + // WHITE UP-POINTING SMALL TRIANGLE + return rune(0x25b5), true + case "uuarr": + // UPWARDS PAIRED ARROWS + return rune(0x21c8), true + case "uuml": + // LATIN SMALL LETTER U WITH DIAERESIS + return rune(0xfc), true + case "uwangle": + // OBLIQUE ANGLE OPENING DOWN + return rune(0x29a7), true + } + + case 'v': + switch name { + case "vArr": + // UP DOWN DOUBLE ARROW + return rune(0x21d5), true + case "vBar": + // SHORT UP TACK WITH UNDERBAR + return rune(0x2ae8), true + case "vBarv": + // SHORT UP TACK ABOVE SHORT DOWN TACK + return rune(0x2ae9), true + case "vDash": + // TRUE + return rune(0x22a8), true + case "vDdash": + // VERTICAL BAR TRIPLE RIGHT TURNSTILE + return rune(0x2ae2), true + case "vangrt": + // RIGHT ANGLE VARIANT WITH SQUARE + return rune(0x299c), true + case "varepsilon": + // GREEK LUNATE EPSILON SYMBOL + return rune(0x03f5), true + case "varkappa": + // GREEK KAPPA SYMBOL + return rune(0x03f0), true + case "varnothing": + // EMPTY SET + return rune(0x2205), true + case "varphi": + // GREEK PHI SYMBOL + return rune(0x03d5), true + case "varpi": + // GREEK PI SYMBOL + return rune(0x03d6), true + case "varpropto": + // PROPORTIONAL TO + return rune(0x221d), true + case "varr": + // UP DOWN ARROW + return rune(0x2195), true + case "varrho": + // GREEK RHO SYMBOL + return rune(0x03f1), true + case "varsigma": + // GREEK SMALL LETTER FINAL SIGMA + return rune(0x03c2), true + case "varsubsetneq": + // SUBSET OF WITH NOT EQUAL TO - variant with stroke through bottom members + return rune(0x228a), true + case "varsubsetneqq": + // SUBSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members + return rune(0x2acb), true + case "varsupsetneq": + // SUPERSET OF WITH NOT EQUAL TO - variant with stroke through bottom members + return rune(0x228b), true + case "varsupsetneqq": + // SUPERSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members + return rune(0x2acc), true + case "vartheta": + // GREEK THETA SYMBOL + return rune(0x03d1), true + case "vartriangleleft": + // NORMAL SUBGROUP OF + return rune(0x22b2), true + case "vartriangleright": + // CONTAINS AS NORMAL SUBGROUP + return rune(0x22b3), true + case "vbrtri": + // VERTICAL BAR BESIDE RIGHT TRIANGLE + return rune(0x29d0), true + case "vcy": + // CYRILLIC SMALL LETTER VE + return rune(0x0432), true + case "vdash": + // RIGHT TACK + return rune(0x22a2), true + case "vee": + // LOGICAL OR + return rune(0x2228), true + case "veeBar": + // LOGICAL OR WITH DOUBLE UNDERBAR + return rune(0x2a63), true + case "veebar": + // XOR + return rune(0x22bb), true + case "veeeq": + // EQUIANGULAR TO + return rune(0x225a), true + case "vellip": + // VERTICAL ELLIPSIS + return rune(0x22ee), true + case "vellip4": + // DOTTED FENCE + return rune(0x2999), true + case "vellipv": + // TRIPLE COLON OPERATOR + return rune(0x2af6), true + case "verbar": + // VERTICAL LINE + return rune(0x7c), true + case "vert3": + // TRIPLE VERTICAL BAR BINARY RELATION + return rune(0x2af4), true + case "vert": + // VERTICAL LINE + return rune(0x7c), true + case "vfr": + // MATHEMATICAL FRAKTUR SMALL V + return rune(0x01d533), true + case "vldash": + // LEFT SQUARE BRACKET LOWER CORNER + return rune(0x23a3), true + case "vltri": + // NORMAL SUBGROUP OF + return rune(0x22b2), true + case "vnsub": + // SUBSET OF with vertical line + return rune(0x2282), true + case "vnsup": + // SUPERSET OF with vertical line + return rune(0x2283), true + case "vopf": + // MATHEMATICAL DOUBLE-STRUCK SMALL V + return rune(0x01d567), true + case "vprime": + // PRIME + return rune(0x2032), true + case "vprop": + // PROPORTIONAL TO + return rune(0x221d), true + case "vrtri": + // CONTAINS AS NORMAL SUBGROUP + return rune(0x22b3), true + case "vscr": + // MATHEMATICAL SCRIPT SMALL V + return rune(0x01d4cb), true + case "vsubnE": + // SUBSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members + return rune(0x2acb), true + case "vsubne": + // SUBSET OF WITH NOT EQUAL TO - variant with stroke through bottom members + return rune(0x228a), true + case "vsupnE": + // SUPERSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members + return rune(0x2acc), true + case "vsupne": + // SUPERSET OF WITH NOT EQUAL TO - variant with stroke through bottom members + return rune(0x228b), true + case "vzigzag": + // VERTICAL ZIGZAG LINE + return rune(0x299a), true + } + + case 'w': + switch name { + case "wcirc": + // LATIN SMALL LETTER W WITH CIRCUMFLEX + return rune(0x0175), true + case "wedbar": + // LOGICAL AND WITH UNDERBAR + return rune(0x2a5f), true + case "wedge": + // LOGICAL AND + return rune(0x2227), true + case "wedgeq": + // ESTIMATES + return rune(0x2259), true + case "weierp": + // SCRIPT CAPITAL P + return rune(0x2118), true + case "wfr": + // MATHEMATICAL FRAKTUR SMALL W + return rune(0x01d534), true + case "wopf": + // MATHEMATICAL DOUBLE-STRUCK SMALL W + return rune(0x01d568), true + case "wp": + // SCRIPT CAPITAL P + return rune(0x2118), true + case "wreath": + // WREATH PRODUCT + return rune(0x2240), true + case "wr": + // WREATH PRODUCT + return rune(0x2240), true + case "wscr": + // MATHEMATICAL SCRIPT SMALL W + return rune(0x01d4cc), true + } + + case 'x': + switch name { + case "xandand": + // TWO LOGICAL AND OPERATOR + return rune(0x2a07), true + case "xbsol": + // BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT + return rune(0x2571), true + case "xcap": + // N-ARY INTERSECTION + return rune(0x22c2), true + case "xcirc": + // LARGE CIRCLE + return rune(0x25ef), true + case "xcup": + // N-ARY UNION + return rune(0x22c3), true + case "xcupdot": + // N-ARY UNION OPERATOR WITH DOT + return rune(0x2a03), true + case "xdtri": + // WHITE DOWN-POINTING TRIANGLE + return rune(0x25bd), true + case "xfr": + // MATHEMATICAL FRAKTUR SMALL X + return rune(0x01d535), true + case "xgr": + // GREEK SMALL LETTER XI + return rune(0x03be), true + case "xhArr": + // LONG LEFT RIGHT DOUBLE ARROW + return rune(0x27fa), true + case "xharr": + // LONG LEFT RIGHT ARROW + return rune(0x27f7), true + case "xi": + // GREEK SMALL LETTER XI + return rune(0x03be), true + case "xlArr": + // LONG LEFTWARDS DOUBLE ARROW + return rune(0x27f8), true + case "xlarr": + // LONG LEFTWARDS ARROW + return rune(0x27f5), true + case "xmap": + // LONG RIGHTWARDS ARROW FROM BAR + return rune(0x27fc), true + case "xnis": + // CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE + return rune(0x22fb), true + case "xodot": + // N-ARY CIRCLED DOT OPERATOR + return rune(0x2a00), true + case "xopf": + // MATHEMATICAL DOUBLE-STRUCK SMALL X + return rune(0x01d569), true + case "xoplus": + // N-ARY CIRCLED PLUS OPERATOR + return rune(0x2a01), true + case "xoror": + // TWO LOGICAL OR OPERATOR + return rune(0x2a08), true + case "xotime": + // N-ARY CIRCLED TIMES OPERATOR + return rune(0x2a02), true + case "xrArr": + // LONG RIGHTWARDS DOUBLE ARROW + return rune(0x27f9), true + case "xrarr": + // LONG RIGHTWARDS ARROW + return rune(0x27f6), true + case "xscr": + // MATHEMATICAL SCRIPT SMALL X + return rune(0x01d4cd), true + case "xsol": + // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT + return rune(0x2572), true + case "xsqcap": + // N-ARY SQUARE INTERSECTION OPERATOR + return rune(0x2a05), true + case "xsqcup": + // N-ARY SQUARE UNION OPERATOR + return rune(0x2a06), true + case "xsqu": + // WHITE MEDIUM SQUARE + return rune(0x25fb), true + case "xsquf": + // BLACK MEDIUM SQUARE + return rune(0x25fc), true + case "xtimes": + // N-ARY TIMES OPERATOR + return rune(0x2a09), true + case "xuplus": + // N-ARY UNION OPERATOR WITH PLUS + return rune(0x2a04), true + case "xutri": + // WHITE UP-POINTING TRIANGLE + return rune(0x25b3), true + case "xvee": + // N-ARY LOGICAL OR + return rune(0x22c1), true + case "xwedge": + // N-ARY LOGICAL AND + return rune(0x22c0), true + } + + case 'y': + switch name { + case "yacute": + // LATIN SMALL LETTER Y WITH ACUTE + return rune(0xfd), true + case "yacy": + // CYRILLIC SMALL LETTER YA + return rune(0x044f), true + case "ycirc": + // LATIN SMALL LETTER Y WITH CIRCUMFLEX + return rune(0x0177), true + case "ycy": + // CYRILLIC SMALL LETTER YERU + return rune(0x044b), true + case "yen": + // YEN SIGN + return rune(0xa5), true + case "yfr": + // MATHEMATICAL FRAKTUR SMALL Y + return rune(0x01d536), true + case "yicy": + // CYRILLIC SMALL LETTER YI + return rune(0x0457), true + case "yopf": + // MATHEMATICAL DOUBLE-STRUCK SMALL Y + return rune(0x01d56a), true + case "yscr": + // MATHEMATICAL SCRIPT SMALL Y + return rune(0x01d4ce), true + case "yucy": + // CYRILLIC SMALL LETTER YU + return rune(0x044e), true + case "yuml": + // LATIN SMALL LETTER Y WITH DIAERESIS + return rune(0xff), true + } + + case 'z': + switch name { + case "zacute": + // LATIN SMALL LETTER Z WITH ACUTE + return rune(0x017a), true + case "zcaron": + // LATIN SMALL LETTER Z WITH CARON + return rune(0x017e), true + case "zcy": + // CYRILLIC SMALL LETTER ZE + return rune(0x0437), true + case "zdot": + // LATIN SMALL LETTER Z WITH DOT ABOVE + return rune(0x017c), true + case "zeetrf": + // BLACK-LETTER CAPITAL Z + return rune(0x2128), true + case "zeta": + // GREEK SMALL LETTER ZETA + return rune(0x03b6), true + case "zfr": + // MATHEMATICAL FRAKTUR SMALL Z + return rune(0x01d537), true + case "zgr": + // GREEK SMALL LETTER ZETA + return rune(0x03b6), true + case "zhcy": + // CYRILLIC SMALL LETTER ZHE + return rune(0x0436), true + case "zigrarr": + // RIGHTWARDS SQUIGGLE ARROW + return rune(0x21dd), true + case "zopf": + // MATHEMATICAL DOUBLE-STRUCK SMALL Z + return rune(0x01d56b), true + case "zscr": + // MATHEMATICAL SCRIPT SMALL Z + return rune(0x01d4cf), true + case "zwj": + // ZERO WIDTH JOINER + return rune(0x200d), true + case "zwnj": + // ZERO WIDTH NON-JOINER + return rune(0x200c), true + } + } + return -1, false +} + +/* + ------ GENERATED ------ DO NOT EDIT ------ GENERATED ------ DO NOT EDIT ------ GENERATED ------ +*/ diff --git a/core/encoding/json/parser.odin b/core/encoding/json/parser.odin index c682ec9bd..7bf88c565 100644 --- a/core/encoding/json/parser.odin +++ b/core/encoding/json/parser.odin @@ -354,6 +354,12 @@ unquote_string :: proc(token: Token, spec: Specification, allocator := context.a b := bytes_make(len(s) + 2*utf8.UTF_MAX, 1, allocator) or_return w := copy(b, s[0:i]) + + if len(b) == 0 && allocator.data == nil { + // `unmarshal_count_array` calls us with a nil allocator + return string(b[:w]), nil + } + loop: for i < len(s) { c := s[i] switch { diff --git a/core/encoding/varint/doc.odin b/core/encoding/varint/doc.odin index dd068b261..5e4708a59 100644 --- a/core/encoding/varint/doc.odin +++ b/core/encoding/varint/doc.odin @@ -4,24 +4,25 @@ Author of this Odin package: Jeroen van Rijn Example: - ```odin - import "core:encoding/varint" - import "core:fmt" + ```odin + import "core:encoding/varint" + import "core:fmt" - main :: proc() { - buf: [varint.LEB128_MAX_BYTES]u8 + main :: proc() { + buf: [varint.LEB128_MAX_BYTES]u8 - value := u128(42) + value := u128(42) - encode_size, encode_err := varint.encode_uleb128(buf[:], value) - assert(encode_size == 1 && encode_err == .None) + encode_size, encode_err := varint.encode_uleb128(buf[:], value) + assert(encode_size == 1 && encode_err == .None) - fmt.println(buf[:encode_size]) + fmt.printf("Encoded as %v\n", buf[:encode_size]) + decoded_val, decode_size, decode_err := varint.decode_uleb128(buf[:]) - decoded_val, decode_size, decode_err := varint.decode_uleb128(buf[:encode_size]) - assert(decoded_val == value && decode_size == encode_size && decode_err == .None) - } - ``` + assert(decoded_val == value && decode_size == encode_size && decode_err == .None) + fmt.printf("Decoded as %v, using %v byte%v\n", decoded_val, decode_size, "" if decode_size == 1 else "s") + } + ``` */ package varint
\ No newline at end of file diff --git a/core/encoding/varint/leb128.odin b/core/encoding/varint/leb128.odin index 476b9c2c9..1cdbb81b0 100644 --- a/core/encoding/varint/leb128.odin +++ b/core/encoding/varint/leb128.odin @@ -10,12 +10,10 @@ // the LEB128 format as used by DWARF debug info, Android .dex and other file formats. package varint -import "core:fmt" - // In theory we should use the bigint package. In practice, varints bigger than this indicate a corrupted file. // Instead we'll set limits on the values we'll encode/decode // 18 * 7 bits = 126, which means that a possible 19th byte may at most be `0b0000_0011`. -LEB128_MAX_BYTES :: 19 +LEB128_MAX_BYTES :: 19 Error :: enum { None = 0, @@ -25,60 +23,89 @@ Error :: enum { // Decode a slice of bytes encoding an unsigned LEB128 integer into value and number of bytes used. // Returns `size` == 0 for an invalid value, empty slice, or a varint > 18 bytes. -decode_uleb128 :: proc(buf: []u8) -> (val: u128, size: int, err: Error) { - more := true - - for v, i in buf { - size = i + 1 +decode_uleb128_buffer :: proc(buf: []u8) -> (val: u128, size: int, err: Error) { + if len(buf) == 0 { + return 0, 0, .Buffer_Too_Small + } - // 18 * 7 bits = 126, which means that a possible 19th byte may at most be 0b0000_0011. - if size > LEB128_MAX_BYTES || size == LEB128_MAX_BYTES && v > 0b0000_0011 { - return 0, 0, .Value_Too_Large + for v in buf { + val, size, err = decode_uleb128_byte(v, size, val) + if err != .Buffer_Too_Small { + return } + } - val |= u128(v & 0x7f) << uint(i * 7) + if err == .Buffer_Too_Small { + val, size = 0, 0 + } + return +} - if v < 128 { - more = false - break - } +// Decodes an unsigned LEB128 integer into value a byte at a time. +// Returns `.None` when decoded properly, `.Value_Too_Large` when they value +// exceeds the limits of a u128, and `.Buffer_Too_Small` when it's not yet fully decoded. +decode_uleb128_byte :: proc(input: u8, offset: int, accumulator: u128) -> (val: u128, size: int, err: Error) { + size = offset + 1 + + // 18 * 7 bits = 126, which means that a possible 19th byte may at most be 0b0000_0011. + if size > LEB128_MAX_BYTES || size == LEB128_MAX_BYTES && input > 0b0000_0011 { + return 0, 0, .Value_Too_Large } - // If the buffer runs out before the number ends, return an error. - if more { - return 0, 0, .Buffer_Too_Small + val = accumulator | u128(input & 0x7f) << uint(offset * 7) + + if input < 128 { + // We're done + return } - return + + // If the buffer runs out before the number ends, return an error. + return val, size, .Buffer_Too_Small } +decode_uleb128 :: proc {decode_uleb128_buffer, decode_uleb128_byte} // Decode a slice of bytes encoding a signed LEB128 integer into value and number of bytes used. // Returns `size` == 0 for an invalid value, empty slice, or a varint > 18 bytes. -decode_ileb128 :: proc(buf: []u8) -> (val: i128, size: int, err: Error) { - shift: uint - +decode_ileb128_buffer :: proc(buf: []u8) -> (val: i128, size: int, err: Error) { if len(buf) == 0 { return 0, 0, .Buffer_Too_Small } for v in buf { - size += 1 - - // 18 * 7 bits = 126, which including sign means we can have a 19th byte. - if size > LEB128_MAX_BYTES || size == LEB128_MAX_BYTES && v > 0x7f { - return 0, 0, .Value_Too_Large + val, size, err = decode_ileb128_byte(v, size, val) + if err != .Buffer_Too_Small { + return } + } + + if err == .Buffer_Too_Small { + val, size = 0, 0 + } + return +} - val |= i128(v & 0x7f) << shift - shift += 7 +// Decode a a signed LEB128 integer into value and number of bytes used, one byte at a time. +// Returns `size` == 0 for an invalid value, empty slice, or a varint > 18 bytes. +decode_ileb128_byte :: proc(input: u8, offset: int, accumulator: i128) -> (val: i128, size: int, err: Error) { + size = offset + 1 + shift := uint(offset * 7) - if v < 128 { break } + // 18 * 7 bits = 126, which including sign means we can have a 19th byte. + if size > LEB128_MAX_BYTES || size == LEB128_MAX_BYTES && input > 0x7f { + return 0, 0, .Value_Too_Large } - if buf[size - 1] & 0x40 == 0x40 { - val |= max(i128) << shift + val = accumulator | i128(input & 0x7f) << shift + + if input < 128 { + if input & 0x40 == 0x40 { + val |= max(i128) << (shift + 7) + } + return val, size, .None } - return + return val, size, .Buffer_Too_Small } +decode_ileb128 :: proc{decode_ileb128_buffer, decode_ileb128_byte} // Encode `val` into `buf` as an unsigned LEB128 encoded series of bytes. // `buf` must be appropriately sized. @@ -89,7 +116,6 @@ encode_uleb128 :: proc(buf: []u8, val: u128) -> (size: int, err: Error) { size += 1 if size > len(buf) { - fmt.println(val, buf[:size - 1]) return 0, .Buffer_Too_Small } @@ -106,14 +132,12 @@ encode_uleb128 :: proc(buf: []u8, val: u128) -> (size: int, err: Error) { return } -@(private) -SIGN_MASK :: (i128(1) << 121) // sign extend mask - // Encode `val` into `buf` as a signed LEB128 encoded series of bytes. // `buf` must be appropriately sized. encode_ileb128 :: proc(buf: []u8, val: i128) -> (size: int, err: Error) { - val := val - more := true + SIGN_MASK :: i128(1) << 121 // sign extend mask + + val, more := val, true for more { size += 1 diff --git a/core/encoding/xml/debug_print.odin b/core/encoding/xml/debug_print.odin new file mode 100644 index 000000000..e9a1cb160 --- /dev/null +++ b/core/encoding/xml/debug_print.odin @@ -0,0 +1,86 @@ +/* + An XML 1.0 / 1.1 parser + + Copyright 2021-2022 Jeroen van Rijn <nom@duclavier.com>. + Made available under Odin's BSD-3 license. + + A from-scratch XML implementation, loosely modeled on the [spec](https://www.w3.org/TR/2006/REC-xml11-20060816). + + List of contributors: + Jeroen van Rijn: Initial implementation. +*/ +package xml + +import "core:io" +import "core:fmt" + +/* + Just for debug purposes. +*/ +print :: proc(writer: io.Writer, doc: ^Document) -> (written: int, err: io.Error) { + if doc == nil { return } + using fmt + + written += wprintf(writer, "[XML Prolog]\n") + + for attr in doc.prologue { + written += wprintf(writer, "\t%v: %v\n", attr.key, attr.val) + } + + written += wprintf(writer, "[Encoding] %v\n", doc.encoding) + + if len(doc.doctype.ident) > 0 { + written += wprintf(writer, "[DOCTYPE] %v\n", doc.doctype.ident) + + if len(doc.doctype.rest) > 0 { + wprintf(writer, "\t%v\n", doc.doctype.rest) + } + } + + for comment in doc.comments { + written += wprintf(writer, "[Pre-root comment] %v\n", comment) + } + + if len(doc.elements) > 0 { + wprintln(writer, " --- ") + print_element(writer, doc, 0) + wprintln(writer, " --- ") + } + + return written, .None +} + +print_element :: proc(writer: io.Writer, doc: ^Document, element_id: Element_ID, indent := 0) -> (written: int, err: io.Error) { + using fmt + + tab :: proc(writer: io.Writer, indent: int) { + for _ in 0..=indent { + wprintf(writer, "\t") + } + } + + tab(writer, indent) + + element := doc.elements[element_id] + + if element.kind == .Element { + wprintf(writer, "<%v>\n", element.ident) + if len(element.value) > 0 { + tab(writer, indent + 1) + wprintf(writer, "[Value] %v\n", element.value) + } + + for attr in element.attribs { + tab(writer, indent + 1) + wprintf(writer, "[Attr] %v: %v\n", attr.key, attr.val) + } + + for child in element.children { + print_element(writer, doc, child, indent + 1) + } + } else if element.kind == .Comment { + wprintf(writer, "[COMMENT] %v\n", element.value) + } + + return written, .None +}
\ No newline at end of file diff --git a/core/encoding/xml/example/xml_example.odin b/core/encoding/xml/example/xml_example.odin new file mode 100644 index 000000000..f7e74840e --- /dev/null +++ b/core/encoding/xml/example/xml_example.odin @@ -0,0 +1,112 @@ +package xml_example + +import "core:encoding/xml" +import "core:mem" +import "core:fmt" +import "core:time" +import "core:strings" +import "core:hash" + +N :: 1 + +example :: proc() { + using fmt + + docs: [N]^xml.Document + errs: [N]xml.Error + times: [N]time.Duration + + defer for round in 0..<N { + xml.destroy(docs[round]) + } + + DOC :: #load("../../../../tests/core/assets/XML/unicode.xml") + input := DOC + + for round in 0..<N { + start := time.tick_now() + + docs[round], errs[round] = xml.parse(input, xml.Options{ + flags={.Ignore_Unsupported}, + expected_doctype = "", + }) + + end := time.tick_now() + times[round] = time.tick_diff(start, end) + } + + fastest := max(time.Duration) + slowest := time.Duration(0) + total := time.Duration(0) + + for round in 0..<N { + fastest = min(fastest, times[round]) + slowest = max(slowest, times[round]) + total += times[round] + } + + fastest_ms := time.duration_milliseconds(fastest) + slowest_ms := time.duration_milliseconds(slowest) + average_ms := time.duration_milliseconds(time.Duration(f64(total) / f64(N))) + + fastest_speed := (f64(1000.0) / fastest_ms) * f64(len(DOC)) / 1_024.0 / 1_024.0 + slowest_speed := (f64(1000.0) / slowest_ms) * f64(len(DOC)) / 1_024.0 / 1_024.0 + average_speed := (f64(1000.0) / average_ms) * f64(len(DOC)) / 1_024.0 / 1_024.0 + + fmt.printf("N = %v\n", N) + fmt.printf("[Fastest]: %v bytes in %.2f ms (%.2f MiB/s).\n", len(input), fastest_ms, fastest_speed) + fmt.printf("[Slowest]: %v bytes in %.2f ms (%.2f MiB/s).\n", len(input), slowest_ms, slowest_speed) + fmt.printf("[Average]: %v bytes in %.2f ms (%.2f MiB/s).\n", len(input), average_ms, average_speed) + + if errs[0] != .None { + printf("Load/Parse error: %v\n", errs[0]) + if errs[0] == .File_Error { + println("\"unicode.xml\" not found. Did you run \"tests\\download_assets.py\"?") + } + return + } + + charlist, charlist_ok := xml.find_child_by_ident(docs[0], 0, "charlist") + if !charlist_ok { + eprintln("Could not locate top-level `<charlist>` tag.") + return + } + + printf("Found `<charlist>` with %v children, %v elements total\n", len(docs[0].elements[charlist].children), docs[0].element_count) + + crc32 := doc_hash(docs[0]) + printf("[%v] CRC32: 0x%08x\n", "🎉" if crc32 == 0xcaa042b9 else "🤬", crc32) + + for round in 0..<N { + defer xml.destroy(docs[round]) + } +} + +doc_hash :: proc(doc: ^xml.Document, print := false) -> (crc32: u32) { + buf: strings.Builder + defer strings.destroy_builder(&buf) + w := strings.to_writer(&buf) + + xml.print(w, doc) + tree := strings.to_string(buf) + if print { fmt.println(tree) } + return hash.crc32(transmute([]u8)tree) +} + +main :: proc() { + using fmt + + track: mem.Tracking_Allocator + mem.tracking_allocator_init(&track, context.allocator) + context.allocator = mem.tracking_allocator(&track) + + example() + + if len(track.allocation_map) > 0 { + println() + for _, v in track.allocation_map { + printf("%v Leaked %v bytes.\n", v.location, v.size) + } + } + println("Done and cleaned up!") +}
\ No newline at end of file diff --git a/core/encoding/xml/helpers.odin b/core/encoding/xml/helpers.odin new file mode 100644 index 000000000..48f058334 --- /dev/null +++ b/core/encoding/xml/helpers.odin @@ -0,0 +1,45 @@ +/* + An XML 1.0 / 1.1 parser + + Copyright 2021-2022 Jeroen van Rijn <nom@duclavier.com>. + Made available under Odin's BSD-3 license. + + This file contains helper functions. +*/ +package xml + +// Find parent's nth child with a given ident. +find_child_by_ident :: proc(doc: ^Document, parent_id: Element_ID, ident: string, nth := 0) -> (res: Element_ID, found: bool) { + tag := doc.elements[parent_id] + + count := 0 + for child_id in tag.children { + child := doc.elements[child_id] + /* + Skip commments. They have no name. + */ + if child.kind != .Element { continue } + + /* + If the ident matches and it's the nth such child, return it. + */ + if child.ident == ident { + if count == nth { return child_id, true } + count += 1 + } + } + return 0, false +} + +// Find an attribute by key. +find_attribute_val_by_key :: proc(doc: ^Document, parent_id: Element_ID, key: string) -> (val: string, found: bool) { + tag := doc.elements[parent_id] + + for attr in tag.attribs { + /* + If the ident matches, we're done. There can only ever be one attribute with the same name. + */ + if attr.key == key { return attr.val, true } + } + return "", false +}
\ No newline at end of file diff --git a/core/encoding/xml/tokenizer.odin b/core/encoding/xml/tokenizer.odin new file mode 100644 index 000000000..c3fece76e --- /dev/null +++ b/core/encoding/xml/tokenizer.odin @@ -0,0 +1,436 @@ +/* + An XML 1.0 / 1.1 parser + + Copyright 2021-2022 Jeroen van Rijn <nom@duclavier.com>. + Made available under Odin's BSD-3 license. + + A from-scratch XML implementation, loosely modeled on the [spec](https://www.w3.org/TR/2006/REC-xml11-20060816). + + List of contributors: + Jeroen van Rijn: Initial implementation. +*/ +package xml + +import "core:fmt" +import "core:unicode" +import "core:unicode/utf8" + +Error_Handler :: #type proc(pos: Pos, fmt: string, args: ..any) + +Token :: struct { + kind: Token_Kind, + text: string, + pos: Pos, +} + +Pos :: struct { + file: string, + offset: int, // starting at 0 + line: int, // starting at 1 + column: int, // starting at 1 +} + +Token_Kind :: enum { + Invalid, + + Ident, + Literal, + Rune, + String, + + Double_Quote, // " + Single_Quote, // ' + Colon, // : + + Eq, // = + Lt, // < + Gt, // > + Exclaim, // ! + Question, // ? + Hash, // # + Slash, // / + Dash, // - + + Open_Bracket, // [ + Close_Bracket, // ] + + EOF, +} + +CDATA_START :: "<![CDATA[" +CDATA_END :: "]]>" + +COMMENT_START :: "<!--" +COMMENT_END :: "-->" + +Tokenizer :: struct { + // Immutable data + path: string, + src: string, + err: Error_Handler, + + // Tokenizing state + ch: rune, + offset: int, + read_offset: int, + line_offset: int, + line_count: int, + + // Mutable data + error_count: int, +} + +init :: proc(t: ^Tokenizer, src: string, path: string, err: Error_Handler = default_error_handler) { + t.src = src + t.err = err + t.ch = ' ' + t.offset = 0 + t.read_offset = 0 + t.line_offset = 0 + t.line_count = len(src) > 0 ? 1 : 0 + t.error_count = 0 + t.path = path + + advance_rune(t) + if t.ch == utf8.RUNE_BOM { + advance_rune(t) + } +} + +@(private) +offset_to_pos :: proc(t: ^Tokenizer, offset: int) -> Pos { + line := t.line_count + column := offset - t.line_offset + 1 + + return Pos { + file = t.path, + offset = offset, + line = line, + column = column, + } +} + +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") +} + +error :: 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 +} + +@(optimization_mode="speed") +advance_rune :: proc(using t: ^Tokenizer) { + #no_bounds_check { + /* + Already bounds-checked here. + */ + if read_offset < len(src) { + offset = read_offset + if ch == '\n' { + line_offset = offset + line_count += 1 + } + r, w := rune(src[read_offset]), 1 + switch { + case r == 0: + error(t, t.offset, "illegal character NUL") + case r >= utf8.RUNE_SELF: + r, w = #force_inline utf8.decode_rune_in_string(src[read_offset:]) + if r == utf8.RUNE_ERROR && w == 1 { + error(t, t.offset, "illegal UTF-8 encoding") + } else if r == utf8.RUNE_BOM && offset > 0 { + error(t, t.offset, "illegal byte order mark") + } + } + read_offset += w + ch = r + } else { + offset = len(src) + if ch == '\n' { + line_offset = offset + line_count += 1 + } + ch = -1 + } + } +} + +peek_byte :: proc(t: ^Tokenizer, offset := 0) -> byte { + if t.read_offset+offset < len(t.src) { + #no_bounds_check return t.src[t.read_offset+offset] + } + return 0 +} + +@(optimization_mode="speed") +skip_whitespace :: proc(t: ^Tokenizer) { + for { + switch t.ch { + case ' ', '\t', '\r', '\n': + advance_rune(t) + case: + return + } + } +} + +@(optimization_mode="speed") +is_letter :: proc(r: rune) -> bool { + if r < utf8.RUNE_SELF { + switch r { + case '_': + return true + case 'A'..='Z', 'a'..='z': + return true + } + } + return unicode.is_letter(r) +} + +is_valid_identifier_rune :: proc(r: rune) -> bool { + if r < utf8.RUNE_SELF { + switch r { + case '_', '-', ':': return true + case 'A'..='Z', 'a'..='z': return true + case '0'..'9': return true + case -1: return false + } + } + + if unicode.is_letter(r) || unicode.is_digit(r) { + return true + } + return false +} + +scan_identifier :: proc(t: ^Tokenizer) -> string { + offset := t.offset + namespaced := false + + for is_valid_identifier_rune(t.ch) { + advance_rune(t) + if t.ch == ':' { + /* + A namespaced attr can have at most two parts, `namespace:ident`. + */ + if namespaced { + break + } + namespaced = true + } + } + return string(t.src[offset : t.offset]) +} + +/* + A comment ends when we see -->, preceded by a character that's not a dash. + "For compatibility, the string "--" (double-hyphen) must not occur within comments." + + See: https://www.w3.org/TR/2006/REC-xml11-20060816/#dt-comment + + Thanks to the length (4) of the comment start, we also have enough lookback, + and the peek at the next byte asserts that there's at least one more character + that's a `>`. +*/ +scan_comment :: proc(t: ^Tokenizer) -> (comment: string, err: Error) { + offset := t.offset + + for { + advance_rune(t) + ch := t.ch + + if ch < 0 { + error(t, offset, "[parse] Comment was not terminated\n") + return "", .Unclosed_Comment + } + + if string(t.src[t.offset - 1:][:2]) == "--" { + if peek_byte(t) == '>' { + break + } else { + error(t, t.offset - 1, "Invalid -- sequence in comment.\n") + return "", .Invalid_Sequence_In_Comment + } + } + } + + expect(t, .Dash) + expect(t, .Gt) + + return string(t.src[offset : t.offset - 1]), .None +} + +/* + Skip CDATA +*/ +skip_cdata :: proc(t: ^Tokenizer) -> (err: Error) { + if t.read_offset + len(CDATA_START) >= len(t.src) { + /* + Can't be the start of a CDATA tag. + */ + return .None + } + + if string(t.src[t.offset:][:len(CDATA_START)]) == CDATA_START { + t.read_offset += len(CDATA_START) + offset := t.offset + + cdata_scan: for { + advance_rune(t) + if t.ch < 0 { + error(t, offset, "[scan_string] CDATA was not terminated\n") + return .Premature_EOF + } + + /* + Scan until the end of a CDATA tag. + */ + if t.read_offset + len(CDATA_END) < len(t.src) { + if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END { + t.read_offset += len(CDATA_END) + break cdata_scan + } + } + } + } + return +} + +@(optimization_mode="speed") +scan_string :: proc(t: ^Tokenizer, offset: int, close: rune = '<', consume_close := false, multiline := true) -> (value: string, err: Error) { + err = .None + + loop: for { + ch := t.ch + + switch ch { + case -1: + error(t, t.offset, "[scan_string] Premature end of file.\n") + return "", .Premature_EOF + + case '<': + if peek_byte(t) == '!' { + if peek_byte(t, 1) == '[' { + /* + Might be the start of a CDATA tag. + */ + skip_cdata(t) or_return + } else if peek_byte(t, 1) == '-' && peek_byte(t, 2) == '-' { + /* + Comment start. Eat comment. + */ + t.read_offset += 3 + _ = scan_comment(t) or_return + } + } + + case '\n': + if !multiline { + error(t, offset, string(t.src[offset : t.offset])) + error(t, offset, "[scan_string] Not terminated\n") + err = .Invalid_Tag_Value + break loop + } + } + + if t.ch == close { + /* + If it's not a CDATA or comment, it's the end of this body. + */ + break loop + } + advance_rune(t) + } + + /* + Strip trailing whitespace. + */ + lit := string(t.src[offset : t.offset]) + + end := len(lit) + eat: for ; end > 0; end -= 1 { + ch := lit[end - 1] + switch ch { + case ' ', '\t', '\r', '\n': + case: + break eat + } + } + lit = lit[:end] + + if consume_close { + advance_rune(t) + } + + /* + TODO: Handle decoding escape characters and unboxing CDATA. + */ + + return lit, err +} + +peek :: proc(t: ^Tokenizer) -> (token: Token) { + old := t^ + token = scan(t) + t^ = old + return token +} + +scan :: proc(t: ^Tokenizer) -> Token { + skip_whitespace(t) + + offset := t.offset + + kind: Token_Kind + err: Error + lit: string + pos := offset_to_pos(t, offset) + + switch ch := t.ch; true { + case is_letter(ch): + lit = scan_identifier(t) + kind = .Ident + + case: + advance_rune(t) + switch ch { + case -1: + kind = .EOF + + case '<': kind = .Lt + case '>': kind = .Gt + case '!': kind = .Exclaim + case '?': kind = .Question + case '=': kind = .Eq + case '#': kind = .Hash + case '/': kind = .Slash + case '-': kind = .Dash + case ':': kind = .Colon + + case '"', '\'': + kind = .Invalid + + lit, err = scan_string(t, t.offset, ch, true, false) + if err == .None { + kind = .String + } + + case '\n': + lit = "\n" + + case: + kind = .Invalid + } + } + + if kind != .String && lit == "" { + lit = string(t.src[offset : t.offset]) + } + return Token{kind, lit, pos} +}
\ No newline at end of file diff --git a/core/encoding/xml/xml_reader.odin b/core/encoding/xml/xml_reader.odin new file mode 100644 index 000000000..b77ae97b3 --- /dev/null +++ b/core/encoding/xml/xml_reader.odin @@ -0,0 +1,713 @@ +/* + An XML 1.0 / 1.1 parser + + Copyright 2021-2022 Jeroen van Rijn <nom@duclavier.com>. + Made available under Odin's BSD-3 license. + + A from-scratch XML implementation, loosely modelled on the [spec](https://www.w3.org/TR/2006/REC-xml11-20060816). + + Features: + - Supports enough of the XML 1.0/1.1 spec to handle the 99.9% of XML documents in common current usage. + - Simple to understand and use. Small. + + Caveats: + - We do NOT support HTML in this package, as that may or may not be valid XML. + If it works, great. If it doesn't, that's not considered a bug. + + - We do NOT support UTF-16. If you have a UTF-16 XML file, please convert it to UTF-8 first. Also, our condolences. + - <[!ELEMENT and <[!ATTLIST are not supported, and will be either ignored or return an error depending on the parser options. + + MAYBE: + - XML writer? + - Serialize/deserialize Odin types? + + List of contributors: + Jeroen van Rijn: Initial implementation. +*/ +package xml +// An XML 1.0 / 1.1 parser + +import "core:bytes" +import "core:encoding/entity" +import "core:intrinsics" +import "core:mem" +import "core:os" +import "core:strings" + +likely :: intrinsics.expect + +DEFAULT_OPTIONS :: Options{ + flags = {.Ignore_Unsupported}, + expected_doctype = "", +} + +Option_Flag :: enum { + /* + If the caller says that input may be modified, we can perform in-situ parsing. + If this flag isn't provided, the XML parser first duplicates the input so that it can. + */ + Input_May_Be_Modified, + + /* + Document MUST start with `<?xml` prologue. + */ + Must_Have_Prolog, + + /* + Document MUST have a `<!DOCTYPE`. + */ + Must_Have_DocType, + + /* + By default we skip comments. Use this option to intern a comment on a parented Element. + */ + Intern_Comments, + + /* + How to handle unsupported parts of the specification, like <! other than <!DOCTYPE and <![CDATA[ + */ + Error_on_Unsupported, + Ignore_Unsupported, + + /* + By default CDATA tags are passed-through as-is. + This option unwraps them when encountered. + */ + Unbox_CDATA, + + /* + By default SGML entities like `>`, ` ` and ` ` are passed-through as-is. + This option decodes them when encountered. + */ + Decode_SGML_Entities, + + /* + If a tag body has a comment, it will be stripped unless this option is given. + */ + Keep_Tag_Body_Comments, +} +Option_Flags :: bit_set[Option_Flag; u16] + +Document :: struct { + elements: [dynamic]Element, + element_count: Element_ID, + + prologue: Attributes, + encoding: Encoding, + + doctype: struct { + /* + We only scan the <!DOCTYPE IDENT part and skip the rest. + */ + ident: string, + rest: string, + }, + + /* + If we encounter comments before the root node, and the option to intern comments is given, this is where they'll live. + Otherwise they'll be in the element tree. + */ + comments: [dynamic]string, + + /* + Internal + */ + tokenizer: ^Tokenizer, + allocator: mem.Allocator, + + /* + Input. Either the original buffer, or a copy if `.Input_May_Be_Modified` isn't specified. + */ + input: []u8, + strings_to_free: [dynamic]string, +} + +Element :: struct { + ident: string, + value: string, + attribs: Attributes, + + kind: enum { + Element = 0, + Comment, + }, + + parent: Element_ID, + children: [dynamic]Element_ID, +} + +Attribute :: struct { + key: string, + val: string, +} + +Attributes :: [dynamic]Attribute + +Options :: struct { + flags: Option_Flags, + expected_doctype: string, +} + +Encoding :: enum { + Unknown, + + UTF_8, + ISO_8859_1, + + /* + Aliases + */ + LATIN_1 = ISO_8859_1, +} + +Error :: enum { + /* + General return values. + */ + None = 0, + General_Error, + Unexpected_Token, + Invalid_Token, + + /* + Couldn't find, open or read file. + */ + File_Error, + + /* + File too short. + */ + Premature_EOF, + + /* + XML-specific errors. + */ + No_Prolog, + Invalid_Prolog, + Too_Many_Prologs, + + No_DocType, + Too_Many_DocTypes, + DocType_Must_Preceed_Elements, + + /* + If a DOCTYPE is present _or_ the caller + asked for a specific DOCTYPE and the DOCTYPE + and root tag don't match, we return `.Invalid_DocType`. + */ + Invalid_DocType, + + Invalid_Tag_Value, + Mismatched_Closing_Tag, + + Unclosed_Comment, + Comment_Before_Root_Element, + Invalid_Sequence_In_Comment, + + Unsupported_Version, + Unsupported_Encoding, + + /* + <!FOO are usually skipped. + */ + Unhandled_Bang, + + Duplicate_Attribute, + Conflicting_Options, +} + +/* + Implementation starts here. +*/ +parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_handler := default_error_handler, allocator := context.allocator) -> (doc: ^Document, err: Error) { + data := data + context.allocator = allocator + + opts := validate_options(options) or_return + + /* + If `.Input_May_Be_Modified` is not specified, we duplicate the input so that we can modify it in-place. + */ + if .Input_May_Be_Modified not_in opts.flags { + data = bytes.clone(data) + } + + t := &Tokenizer{} + init(t, string(data), path, error_handler) + + doc = new(Document) + doc.allocator = allocator + doc.tokenizer = t + doc.input = data + + doc.elements = make([dynamic]Element, 1024, 1024, allocator) + + // strings.intern_init(&doc.intern, allocator, allocator) + + err = .Unexpected_Token + element, parent: Element_ID + + tag_is_open := false + first_element := true + open: Token + + /* + If a DOCTYPE is present, the root tag has to match. + If an expected DOCTYPE is given in options (i.e. it's non-empty), the DOCTYPE (if present) and root tag have to match. + */ + expected_doctype := options.expected_doctype + + loop: for { + skip_whitespace(t) + // NOTE(Jeroen): This is faster as a switch. + switch t.ch { + case '<': + /* + Consume peeked `<` + */ + advance_rune(t) + + open = scan(t) + // NOTE(Jeroen): We're not using a switch because this if-else chain ordered by likelihood is 2.5% faster at -o:size and -o:speed. + if likely(open.kind, Token_Kind.Ident) == .Ident { + /* + e.g. <odin - Start of new element. + */ + element = new_element(doc) + tag_is_open = true + + if first_element { + /* + First element. + */ + parent = element + first_element = false + } else { + append(&doc.elements[parent].children, element) + } + + doc.elements[element].parent = parent + doc.elements[element].ident = open.text + + parse_attributes(doc, &doc.elements[element].attribs) or_return + + /* + If a DOCTYPE is present _or_ the caller + asked for a specific DOCTYPE and the DOCTYPE + and root tag don't match, we return .Invalid_Root_Tag. + */ + if element == 0 { // Root tag? + if len(expected_doctype) > 0 && expected_doctype != open.text { + error(t, t.offset, "Root Tag doesn't match DOCTYPE. Expected: %v, got: %v\n", expected_doctype, open.text) + return doc, .Invalid_DocType + } + } + + /* + One of these should follow: + - `>`, which means we've just opened this tag and expect a later element to close it. + - `/>`, which means this is an 'empty' or self-closing tag. + */ + end_token := scan(t) + #partial switch end_token.kind { + case .Gt: + /* + We're now the new parent. + */ + parent = element + + case .Slash: + /* + Empty tag. Close it. + */ + expect(t, .Gt) or_return + parent = doc.elements[element].parent + element = parent + tag_is_open = false + + case: + error(t, t.offset, "Expected close tag, got: %#v\n", end_token) + return + } + + } else if open.kind == .Slash { + /* + Close tag. + */ + ident := expect(t, .Ident) or_return + _ = expect(t, .Gt) or_return + + if doc.elements[element].ident != ident.text { + error(t, t.offset, "Mismatched Closing Tag. Expected %v, got %v\n", doc.elements[element].ident, ident.text) + return doc, .Mismatched_Closing_Tag + } + parent = doc.elements[element].parent + element = parent + tag_is_open = false + + } else if open.kind == .Exclaim { + /* + <! + */ + next := scan(t) + #partial switch next.kind { + case .Ident: + switch next.text { + case "DOCTYPE": + if len(doc.doctype.ident) > 0 { + return doc, .Too_Many_DocTypes + } + if doc.element_count > 0 { + return doc, .DocType_Must_Preceed_Elements + } + parse_doctype(doc) or_return + + if len(expected_doctype) > 0 && expected_doctype != doc.doctype.ident { + error(t, t.offset, "Invalid DOCTYPE. Expected: %v, got: %v\n", expected_doctype, doc.doctype.ident) + return doc, .Invalid_DocType + } + expected_doctype = doc.doctype.ident + + case: + if .Error_on_Unsupported in opts.flags { + error(t, t.offset, "Unhandled: <!%v\n", next.text) + return doc, .Unhandled_Bang + } + skip_element(t) or_return + } + + case .Dash: + /* + Comment: <!-- -->. + The grammar does not allow a comment to end in ---> + */ + expect(t, .Dash) + comment := scan_comment(t) or_return + + if .Intern_Comments in opts.flags { + if len(doc.elements) == 0 { + append(&doc.comments, comment) + } else { + el := new_element(doc) + doc.elements[el].parent = element + doc.elements[el].kind = .Comment + doc.elements[el].value = comment + append(&doc.elements[element].children, el) + } + } + + case: + error(t, t.offset, "Invalid Token after <!. Expected .Ident, got %#v\n", next) + return + } + + } else if open.kind == .Question { + /* + <?xml + */ + next := scan(t) + #partial switch next.kind { + case .Ident: + if len(next.text) == 3 && strings.to_lower(next.text, context.temp_allocator) == "xml" { + parse_prologue(doc) or_return + } else if len(doc.prologue) > 0 { + /* + We've already seen a prologue. + */ + return doc, .Too_Many_Prologs + } else { + /* + Could be `<?xml-stylesheet`, etc. Ignore it. + */ + skip_element(t) or_return + } + case: + error(t, t.offset, "Expected \"<?xml\", got \"<?%v\".", next.text) + return + } + + } else { + error(t, t.offset, "Invalid Token after <: %#v\n", open) + return + } + + case -1: + /* + End of file. + */ + if tag_is_open { + return doc, .Premature_EOF + } + break loop + + case: + /* + This should be a tag's body text. + */ + body_text := scan_string(t, t.offset) or_return + needs_processing := .Unbox_CDATA in opts.flags + needs_processing |= .Decode_SGML_Entities in opts.flags + + if !needs_processing { + doc.elements[element].value = body_text + continue + } + + decode_opts := entity.XML_Decode_Options{} + if .Keep_Tag_Body_Comments not_in opts.flags { + decode_opts += { .Comment_Strip } + } + + if .Decode_SGML_Entities not_in opts.flags { + decode_opts += { .No_Entity_Decode } + } + + if .Unbox_CDATA in opts.flags { + decode_opts += { .Unbox_CDATA } + if .Decode_SGML_Entities in opts.flags { + decode_opts += { .Decode_CDATA } + } + } + + decoded, decode_err := entity.decode_xml(body_text, decode_opts) + if decode_err == .None { + doc.elements[element].value = decoded + append(&doc.strings_to_free, decoded) + } else { + doc.elements[element].value = body_text + } + } + } + + if .Must_Have_Prolog in opts.flags && len(doc.prologue) == 0 { + return doc, .No_Prolog + } + + if .Must_Have_DocType in opts.flags && len(doc.doctype.ident) == 0 { + return doc, .No_DocType + } + + resize(&doc.elements, int(doc.element_count)) + return doc, .None +} + +parse_string :: proc(data: string, options := DEFAULT_OPTIONS, path := "", error_handler := default_error_handler, allocator := context.allocator) -> (doc: ^Document, err: Error) { + _data := transmute([]u8)data + + return parse_bytes(_data, options, path, error_handler, allocator) +} + +parse :: proc { parse_string, parse_bytes } + +// Load an XML file +load_from_file :: proc(filename: string, options := DEFAULT_OPTIONS, error_handler := default_error_handler, allocator := context.allocator) -> (doc: ^Document, err: Error) { + context.allocator = allocator + options := options + + data, data_ok := os.read_entire_file(filename) + if !data_ok { return {}, .File_Error } + + options.flags += { .Input_May_Be_Modified } + + return parse_bytes(data, options, filename, error_handler, allocator) +} + +destroy :: proc(doc: ^Document) { + if doc == nil { return } + + for el in doc.elements { + delete(el.attribs) + delete(el.children) + } + delete(doc.elements) + + delete(doc.prologue) + delete(doc.comments) + delete(doc.input) + + for s in doc.strings_to_free { + delete(s) + } + delete(doc.strings_to_free) + + free(doc) +} + +/* + Helpers. +*/ + +validate_options :: proc(options: Options) -> (validated: Options, err: Error) { + validated = options + + if .Error_on_Unsupported in validated.flags && .Ignore_Unsupported in validated.flags { + return options, .Conflicting_Options + } + return validated, .None +} + +expect :: proc(t: ^Tokenizer, kind: Token_Kind) -> (tok: Token, err: Error) { + tok = scan(t) + if tok.kind == kind { return tok, .None } + + error(t, t.offset, "Expected \"%v\", got \"%v\".", kind, tok.kind) + return tok, .Unexpected_Token +} + +parse_attribute :: proc(doc: ^Document) -> (attr: Attribute, offset: int, err: Error) { + assert(doc != nil) + context.allocator = doc.allocator + t := doc.tokenizer + + key := expect(t, .Ident) or_return + offset = t.offset - len(key.text) + + _ = expect(t, .Eq) or_return + value := expect(t, .String) or_return + + attr.key = key.text + attr.val = value.text + + err = .None + return +} + +check_duplicate_attributes :: proc(t: ^Tokenizer, attribs: Attributes, attr: Attribute, offset: int) -> (err: Error) { + for a in attribs { + if attr.key == a.key { + error(t, offset, "Duplicate attribute: %v\n", attr.key) + return .Duplicate_Attribute + } + } + return .None +} + +parse_attributes :: proc(doc: ^Document, attribs: ^Attributes) -> (err: Error) { + assert(doc != nil) + context.allocator = doc.allocator + t := doc.tokenizer + + for peek(t).kind == .Ident { + attr, offset := parse_attribute(doc) or_return + check_duplicate_attributes(t, attribs^, attr, offset) or_return + append(attribs, attr) + } + skip_whitespace(t) + return .None +} + +parse_prologue :: proc(doc: ^Document) -> (err: Error) { + assert(doc != nil) + context.allocator = doc.allocator + t := doc.tokenizer + + offset := t.offset + parse_attributes(doc, &doc.prologue) or_return + + for attr in doc.prologue { + switch attr.key { + case "version": + switch attr.val { + case "1.0", "1.1": + case: + error(t, offset, "[parse_prologue] Warning: Unhandled XML version: %v\n", attr.val) + } + + case "encoding": + switch strings.to_lower(attr.val, context.temp_allocator) { + case "utf-8", "utf8": + doc.encoding = .UTF_8 + + case "latin-1", "latin1", "iso-8859-1": + doc.encoding = .LATIN_1 + + case: + /* + Unrecognized encoding, assume UTF-8. + */ + error(t, offset, "[parse_prologue] Warning: Unrecognized encoding: %v\n", attr.val) + } + + case: + // Ignored. + } + } + + _ = expect(t, .Question) or_return + _ = expect(t, .Gt) or_return + + return .None +} + +skip_element :: proc(t: ^Tokenizer) -> (err: Error) { + close := 1 + + loop: for { + tok := scan(t) + #partial switch tok.kind { + case .EOF: + error(t, t.offset, "[skip_element] Premature EOF\n") + return .Premature_EOF + + case .Lt: + close += 1 + + case .Gt: + close -= 1 + if close == 0 { + break loop + } + + case: + + } + } + return .None +} + +parse_doctype :: proc(doc: ^Document) -> (err: Error) { + /* + <!DOCTYPE greeting SYSTEM "hello.dtd"> + + <!DOCTYPE greeting [ + <!ELEMENT greeting (#PCDATA)> + ]> + */ + assert(doc != nil) + context.allocator = doc.allocator + t := doc.tokenizer + + tok := expect(t, .Ident) or_return + doc.doctype.ident = tok.text + + skip_whitespace(t) + offset := t.offset + skip_element(t) or_return + + /* + -1 because the current offset is that of the closing tag, so the rest of the DOCTYPE tag ends just before it. + */ + doc.doctype.rest = string(t.src[offset : t.offset - 1]) + return .None +} + +Element_ID :: u32 + +new_element :: proc(doc: ^Document) -> (id: Element_ID) { + element_space := len(doc.elements) + + // Need to resize + if int(doc.element_count) + 1 > element_space { + if element_space < 65536 { + element_space *= 2 + } else { + element_space += 65536 + } + resize(&doc.elements, element_space) + } + + cur := doc.element_count + doc.element_count += 1 + + return cur +}
\ No newline at end of file diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index d006d0ef8..4f78cfcce 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -119,17 +119,17 @@ tprintf :: proc(fmt: string, args: ..any) -> string { // bprint procedures return a string using a buffer from an array bprint :: proc(buf: []byte, args: ..any, sep := " ") -> string { - sb := strings.builder_from_slice(buf[0:len(buf)]) + sb := strings.builder_from_bytes(buf[0:len(buf)]) return sbprint(buf=&sb, args=args, sep=sep) } // bprintln procedures return a string using a buffer from an array bprintln :: proc(buf: []byte, args: ..any, sep := " ") -> string { - sb := strings.builder_from_slice(buf[0:len(buf)]) + sb := strings.builder_from_bytes(buf[0:len(buf)]) return sbprintln(buf=&sb, args=args, sep=sep) } // bprintf procedures return a string using a buffer from an array bprintf :: proc(buf: []byte, fmt: string, args: ..any) -> string { - sb := strings.builder_from_slice(buf[0:len(buf)]) + sb := strings.builder_from_bytes(buf[0:len(buf)]) return sbprintf(&sb, fmt, ..args) } diff --git a/core/hash/xxhash/streaming.odin b/core/hash/xxhash/streaming.odin index d6df1089f..6f630b042 100644 --- a/core/hash/xxhash/streaming.odin +++ b/core/hash/xxhash/streaming.odin @@ -52,9 +52,6 @@ XXH3_128_reset_with_seed :: proc(state: ^XXH3_state, seed: XXH64_hash) -> (err: XXH3_64_reset_with_seed :: XXH3_128_reset_with_seed XXH3_128_update :: proc(state: ^XXH3_state, input: []u8) -> (err: Error) { - if len(input) < XXH3_MIDSIZE_MAX { - return .Error - } return XXH3_update(state, input, XXH3_accumulate_512, XXH3_scramble_accumulator) } XXH3_64_update :: XXH3_128_update @@ -127,6 +124,7 @@ XXH3_create_state :: proc(allocator := context.allocator) -> (res: ^XXH3_state, err = nil if mem_error == nil else .Error XXH3_init_state(state) + XXH3_128_reset(state) return state, nil } @@ -213,7 +211,9 @@ XXH3_update :: #force_inline proc( length := len(input) secret := state.custom_secret[:] if len(state.external_secret) == 0 else state.external_secret[:] - assert(len(input) > 0) + if len(input) == 0 { + return + } state.total_length += u64(length) assert(state.buffered_size <= XXH3_INTERNAL_BUFFER_SIZE) @@ -234,7 +234,9 @@ XXH3_update :: #force_inline proc( */ if state.buffered_size > 0 { load_size := int(XXH3_INTERNAL_BUFFER_SIZE - state.buffered_size) - mem_copy(&state.buffer[state.buffered_size], &input[0], load_size) + + state_ptr := rawptr(uintptr(raw_data(state.buffer[:])) + uintptr(state.buffered_size)) + mem_copy(state_ptr, raw_data(input), load_size) input = input[load_size:] XXH3_consume_stripes( diff --git a/core/hash/xxhash/xxhash_32.odin b/core/hash/xxhash/xxhash_32.odin index e63d998dd..5bc87c2c0 100644 --- a/core/hash/xxhash/xxhash_32.odin +++ b/core/hash/xxhash/xxhash_32.odin @@ -197,6 +197,7 @@ XXH32 :: proc(input: []u8, seed := XXH32_DEFAULT_SEED) -> (digest: XXH32_hash) { */ XXH32_create_state :: proc(allocator := context.allocator) -> (res: ^XXH32_state, err: Error) { state := new(XXH32_state, allocator) + XXH32_reset_state(state) return state, .None if state != nil else .Error } @@ -258,7 +259,7 @@ XXH32_update :: proc(state: ^XXH32_state, input: []u8) -> (err: Error) { v3 := state.v3 v4 := state.v4 - for len(buf) >= 15 { + for len(buf) >= 16 { #no_bounds_check v1 = XXH32_round(v1, XXH32_read32(buf, .Unaligned)); buf = buf[4:] #no_bounds_check v2 = XXH32_round(v2, XXH32_read32(buf, .Unaligned)); buf = buf[4:] #no_bounds_check v3 = XXH32_round(v3, XXH32_read32(buf, .Unaligned)); buf = buf[4:] diff --git a/core/hash/xxhash/xxhash_64.odin b/core/hash/xxhash/xxhash_64.odin index e95842168..9280e9c59 100644 --- a/core/hash/xxhash/xxhash_64.odin +++ b/core/hash/xxhash/xxhash_64.odin @@ -163,6 +163,7 @@ XXH64 :: proc(input: []u8, seed := XXH64_DEFAULT_SEED) -> (digest: XXH64_hash) { */ XXH64_create_state :: proc(allocator := context.allocator) -> (res: ^XXH64_state, err: Error) { state := new(XXH64_state, allocator) + XXH64_reset_state(state) return state, .None if state != nil else .Error } diff --git a/core/image/common.odin b/core/image/common.odin index adaa094d8..baacd64d9 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -15,26 +15,56 @@ import "core:mem" import "core:compress" import "core:runtime" +/* + 67_108_864 pixels max by default. + + For QOI, the Worst case scenario means all pixels will be encoded as RGBA literals, costing 5 bytes each. + This caps memory usage at 320 MiB. + + The tunable is limited to 4_294_836_225 pixels maximum, or 4 GiB per 8-bit channel. + It is not advised to tune it this large. + + The 64 Megapixel default is considered to be a decent upper bound you won't run into in practice, + except in very specific circumstances. + +*/ +MAX_DIMENSIONS :: min(#config(MAX_DIMENSIONS, 8192 * 8192), 65535 * 65535) + +// Color +RGB_Pixel :: [3]u8 +RGBA_Pixel :: [4]u8 +RGB_Pixel_16 :: [3]u16 +RGBA_Pixel_16 :: [4]u16 +// Grayscale +G_Pixel :: [1]u8 +GA_Pixel :: [2]u8 +G_Pixel_16 :: [1]u16 +GA_Pixel_16 :: [2]u16 + Image :: struct { width: int, height: int, channels: int, - depth: int, + depth: int, // Channel depth in bits, typically 8 or 16 pixels: bytes.Buffer, /* Some image loaders/writers can return/take an optional background color. For convenience, we return them as u16 so we don't need to switch on the type in our viewer, and can just test against nil. */ - background: Maybe([3]u16), - + background: Maybe(RGB_Pixel_16), metadata: Image_Metadata, + which: Which_File_Type, } -Image_Metadata :: union { +Image_Metadata :: union #shared_nil { + ^Netpbm_Info, ^PNG_Info, + ^QOI_Info, } + + /* IMPORTANT: `.do_not_expand_*` options currently skip handling of the `alpha_*` options, therefore Gray+Alpha will be returned as such even if you add `.alpha_drop_if_present`, @@ -46,13 +76,13 @@ Image_Metadata :: union { /* Image_Option: `.info` - This option behaves as `.return_ihdr` and `.do_not_decompress_image` and can be used + This option behaves as `.return_metadata` and `.do_not_decompress_image` and can be used to gather an image's dimensions and color information. `.return_header` - Fill out img.sidecar.header with the image's format-specific header struct. + Fill out img.metadata.header with the image's format-specific header struct. If we only care about the image specs, we can set `.return_header` + - `.do_not_decompress_image`, or `.info`, which works as if both of these were set. + `.do_not_decompress_image`, or `.info`. `.return_metadata` Returns all chunks not needed to decode the data. @@ -88,7 +118,7 @@ Image_Option: `.alpha_premultiply` If the image has an alpha channel, returns image data as follows: - RGB *= A, Gray = Gray *= A + RGB *= A, Gray = Gray *= A `.blend_background` If a bKGD chunk is present in a PNG, we normally just set `img.background` @@ -103,24 +133,31 @@ Image_Option: */ Option :: enum { + // LOAD OPTIONS info = 0, do_not_decompress_image, return_header, return_metadata, - alpha_add_if_missing, - alpha_drop_if_present, - alpha_premultiply, - blend_background, + alpha_add_if_missing, // Ignored for QOI. Always returns RGBA8. + alpha_drop_if_present, // Unimplemented for QOI. Returns error. + alpha_premultiply, // Unimplemented for QOI. Returns error. + blend_background, // Ignored for non-PNG formats + // Unimplemented do_not_expand_grayscale, do_not_expand_indexed, do_not_expand_channels, + + // SAVE OPTIONS + qoi_all_channels_linear, // QOI, informative only. If not set, defaults to sRGB with linear alpha. } Options :: distinct bit_set[Option] Error :: union #shared_nil { General_Image_Error, + Netpbm_Error, PNG_Error, + QOI_Error, compress.Error, compress.General_Error, @@ -131,14 +168,76 @@ Error :: union #shared_nil { General_Image_Error :: enum { None = 0, - Invalid_Image_Dimensions, + // File I/O + Unable_To_Read_File, + Unable_To_Write_File, + + // Invalid + Unsupported_Format, + Invalid_Signature, + Invalid_Input_Image, Image_Dimensions_Too_Large, + Invalid_Image_Dimensions, + Invalid_Number_Of_Channels, Image_Does_Not_Adhere_to_Spec, + Invalid_Image_Depth, + Invalid_Bit_Depth, + Invalid_Color_Space, + + // More data than pixels to decode into, for example. + Corrupt, + + // Output buffer is the wrong size + Invalid_Output, + + // Allocation + Unable_To_Allocate_Or_Resize, +} + +/* + Netpbm-specific definitions +*/ +Netpbm_Format :: enum { + P1, P2, P3, P4, P5, P6, P7, Pf, PF, +} + +Netpbm_Header :: struct { + format: Netpbm_Format, + width: int, + height: int, + channels: int, + depth: int, + maxval: int, + tupltype: string, + scale: f32, + little_endian: bool, } +Netpbm_Info :: struct { + header: Netpbm_Header, +} + +Netpbm_Error :: enum { + None = 0, + + // reading + Invalid_Header_Token_Character, + Incomplete_Header, + Invalid_Header_Value, + Duplicate_Header_Field, + Buffer_Too_Small, + Invalid_Buffer_ASCII_Token, + Invalid_Buffer_Value, + + // writing + Invalid_Format, +} + +/* + PNG-specific definitions +*/ PNG_Error :: enum { None = 0, - Invalid_PNG_Signature, IHDR_Not_First_Chunk, IHDR_Corrupt, IDAT_Missing, @@ -147,7 +246,9 @@ PNG_Error :: enum { IDAT_Size_Too_Large, PLTE_Encountered_Unexpectedly, PLTE_Invalid_Length, + PLTE_Missing, TRNS_Encountered_Unexpectedly, + TNRS_Invalid_Length, BKGD_Invalid_Length, Unknown_Color_Type, Invalid_Color_Bit_Depth_Combo, @@ -158,9 +259,6 @@ PNG_Error :: enum { Invalid_Chunk_Length, } -/* - PNG-specific structs -*/ PNG_Info :: struct { header: PNG_IHDR, chunks: [dynamic]PNG_Chunk, @@ -223,7 +321,7 @@ PNG_Chunk_Type :: enum u32be { */ iDOT = 'i' << 24 | 'D' << 16 | 'O' << 8 | 'T', - CbGI = 'C' << 24 | 'b' << 16 | 'H' << 8 | 'I', + CgBI = 'C' << 24 | 'g' << 16 | 'B' << 8 | 'I', } PNG_IHDR :: struct #packed { @@ -251,16 +349,53 @@ PNG_Interlace_Method :: enum u8 { } /* - Functions to help with image buffer calculations + QOI-specific definitions */ +QOI_Error :: enum { + None = 0, + Missing_Or_Corrupt_Trailer, // Image seemed to have decoded okay, but trailer is missing or corrupt. +} + +QOI_Magic :: u32be(0x716f6966) // "qoif" + +QOI_Color_Space :: enum u8 { + sRGB = 0, + Linear = 1, +} + +QOI_Header :: struct #packed { + magic: u32be, + width: u32be, + height: u32be, + channels: u8, + color_space: QOI_Color_Space, +} +#assert(size_of(QOI_Header) == 14) + +QOI_Info :: struct { + header: QOI_Header, +} + +TGA_Header :: struct #packed { + id_length: u8, + color_map_type: u8, + data_type_code: u8, + color_map_origin: u16le, + color_map_length: u16le, + color_map_depth: u8, + origin: [2]u16le, + dimensions: [2]u16le, + bits_per_pixel: u8, + image_descriptor: u8, +} +#assert(size_of(TGA_Header) == 18) + +// Function to help with image buffer calculations compute_buffer_size :: proc(width, height, channels, depth: int, extra_row_bytes := int(0)) -> (size: int) { size = ((((channels * width * depth) + 7) >> 3) + extra_row_bytes) * height return } -/* - For when you have an RGB(A) image, but want a particular channel. -*/ Channel :: enum u8 { R = 1, G = 2, @@ -268,7 +403,13 @@ Channel :: enum u8 { A = 4, } +// When you have an RGB(A) image, but want a particular channel. return_single_channel :: proc(img: ^Image, channel: Channel) -> (res: ^Image, ok: bool) { + // Were we actually given a valid image? + if img == nil { + return nil, false + } + ok = false t: bytes.Buffer @@ -298,7 +439,7 @@ return_single_channel :: proc(img: ^Image, channel: Channel) -> (res: ^Image, ok o = o[1:] } case 16: - buffer_size := compute_buffer_size(img.width, img.height, 2, 8) + buffer_size := compute_buffer_size(img.width, img.height, 1, 16) t = bytes.Buffer{} resize(&t.buf, buffer_size) @@ -326,3 +467,724 @@ return_single_channel :: proc(img: ^Image, channel: Channel) -> (res: ^Image, ok return res, true } + +// Does the image have 1 or 2 channels, a valid bit depth (8 or 16), +// Is the pointer valid, are the dimenions valid? +is_valid_grayscale_image :: proc(img: ^Image) -> (ok: bool) { + // Were we actually given a valid image? + if img == nil { + return false + } + + // Are we a Gray or Gray + Alpha image? + if img.channels != 1 && img.channels != 2 { + return false + } + + // Do we have an acceptable bit depth? + if img.depth != 8 && img.depth != 16 { + return false + } + + // This returns 0 if any of the inputs is zero. + bytes_expected := compute_buffer_size(img.width, img.height, img.channels, img.depth) + + // If the dimenions are invalid or the buffer size doesn't match the image characteristics, bail. + if bytes_expected == 0 || bytes_expected != len(img.pixels.buf) || img.width * img.height > MAX_DIMENSIONS { + return false + } + + return true +} + +// Does the image have 3 or 4 channels, a valid bit depth (8 or 16), +// Is the pointer valid, are the dimenions valid? +is_valid_color_image :: proc(img: ^Image) -> (ok: bool) { + // Were we actually given a valid image? + if img == nil { + return false + } + + // Are we an RGB or RGBA image? + if img.channels != 3 && img.channels != 4 { + return false + } + + // Do we have an acceptable bit depth? + if img.depth != 8 && img.depth != 16 { + return false + } + + // This returns 0 if any of the inputs is zero. + bytes_expected := compute_buffer_size(img.width, img.height, img.channels, img.depth) + + // If the dimenions are invalid or the buffer size doesn't match the image characteristics, bail. + if bytes_expected == 0 || bytes_expected != len(img.pixels.buf) || img.width * img.height > MAX_DIMENSIONS { + return false + } + + return true +} + +// Does the image have 1..4 channels, a valid bit depth (8 or 16), +// Is the pointer valid, are the dimenions valid? +is_valid_image :: proc(img: ^Image) -> (ok: bool) { + // Were we actually given a valid image? + if img == nil { + return false + } + + return is_valid_color_image(img) || is_valid_grayscale_image(img) +} + +Alpha_Key :: union { + GA_Pixel, + RGBA_Pixel, + GA_Pixel_16, + RGBA_Pixel_16, +} + +/* + Add alpha channel if missing, in-place. + + Expects 1..4 channels (Gray, Gray + Alpha, RGB, RGBA). + Any other number of channels will be considered an error, returning `false` without modifying the image. + If the input image already has an alpha channel, it'll return `true` early (without considering optional keyed alpha). + + If an image doesn't already have an alpha channel: + If the optional `alpha_key` is provided, it will be resolved as follows: + - For RGB, if pix = key.rgb -> pix = {0, 0, 0, key.a} + - For Gray, if pix = key.r -> pix = {0, key.g} + Otherwise, an opaque alpha channel will be added. +*/ +alpha_add_if_missing :: proc(img: ^Image, alpha_key := Alpha_Key{}, allocator := context.allocator) -> (ok: bool) { + context.allocator = allocator + + if !is_valid_image(img) { + return false + } + + // We should now have a valid Image with 1..4 channels. Do we already have alpha? + if img.channels == 2 || img.channels == 4 { + // We're done. + return true + } + + channels := img.channels + 1 + bytes_wanted := compute_buffer_size(img.width, img.height, channels, img.depth) + + buf := bytes.Buffer{} + + // Can we allocate the return buffer? + if !resize(&buf.buf, bytes_wanted) { + delete(buf.buf) + return false + } + + switch img.depth { + case 8: + switch channels { + case 2: + // Turn Gray into Gray + Alpha + inp := mem.slice_data_cast([]G_Pixel, img.pixels.buf[:]) + out := mem.slice_data_cast([]GA_Pixel, buf.buf[:]) + + if key, key_ok := alpha_key.(GA_Pixel); key_ok { + // We have keyed alpha. + o: GA_Pixel + for p in inp { + if p == key.r { + o = GA_Pixel{0, key.g} + } else { + o = GA_Pixel{p.r, 255} + } + out[0] = o + out = out[1:] + } + } else { + // No keyed alpha, just make all pixels opaque. + o := GA_Pixel{0, 255} + for p in inp { + o.r = p.r + out[0] = o + out = out[1:] + } + } + + case 4: + // Turn RGB into RGBA + inp := mem.slice_data_cast([]RGB_Pixel, img.pixels.buf[:]) + out := mem.slice_data_cast([]RGBA_Pixel, buf.buf[:]) + + if key, key_ok := alpha_key.(RGBA_Pixel); key_ok { + // We have keyed alpha. + o: RGBA_Pixel + for p in inp { + if p == key.rgb { + o = RGBA_Pixel{0, 0, 0, key.a} + } else { + o = RGBA_Pixel{p.r, p.g, p.b, 255} + } + out[0] = o + out = out[1:] + } + } else { + // No keyed alpha, just make all pixels opaque. + o := RGBA_Pixel{0, 0, 0, 255} + for p in inp { + o.rgb = p + out[0] = o + out = out[1:] + } + } + case: + // We shouldn't get here. + unreachable() + } + case 16: + switch channels { + case 2: + // Turn Gray into Gray + Alpha + inp := mem.slice_data_cast([]G_Pixel_16, img.pixels.buf[:]) + out := mem.slice_data_cast([]GA_Pixel_16, buf.buf[:]) + + if key, key_ok := alpha_key.(GA_Pixel_16); key_ok { + // We have keyed alpha. + o: GA_Pixel_16 + for p in inp { + if p == key.r { + o = GA_Pixel_16{0, key.g} + } else { + o = GA_Pixel_16{p.r, 65535} + } + out[0] = o + out = out[1:] + } + } else { + // No keyed alpha, just make all pixels opaque. + o := GA_Pixel_16{0, 65535} + for p in inp { + o.r = p.r + out[0] = o + out = out[1:] + } + } + + case 4: + // Turn RGB into RGBA + inp := mem.slice_data_cast([]RGB_Pixel_16, img.pixels.buf[:]) + out := mem.slice_data_cast([]RGBA_Pixel_16, buf.buf[:]) + + if key, key_ok := alpha_key.(RGBA_Pixel_16); key_ok { + // We have keyed alpha. + o: RGBA_Pixel_16 + for p in inp { + if p == key.rgb { + o = RGBA_Pixel_16{0, 0, 0, key.a} + } else { + o = RGBA_Pixel_16{p.r, p.g, p.b, 65535} + } + out[0] = o + out = out[1:] + } + } else { + // No keyed alpha, just make all pixels opaque. + o := RGBA_Pixel_16{0, 0, 0, 65535} + for p in inp { + o.rgb = p + out[0] = o + out = out[1:] + } + } + case: + // We shouldn't get here. + unreachable() + } + } + + // If we got here, that means we've now got a buffer with the alpha channel added. + // Destroy the old pixel buffer and replace it with the new one, and update the channel count. + bytes.buffer_destroy(&img.pixels) + img.pixels = buf + img.channels = channels + return true +} +alpha_apply_keyed_alpha :: alpha_add_if_missing + +/* + Drop alpha channel if present, in-place. + + Expects 1..4 channels (Gray, Gray + Alpha, RGB, RGBA). + Any other number of channels will be considered an error, returning `false` without modifying the image. + + Of the `options`, the following are considered: + `.alpha_premultiply` + If the image has an alpha channel, returns image data as follows: + RGB *= A, Gray = Gray *= A + + `.blend_background` + If `img.background` is set, it'll be blended in like this: + RGB = (1 - A) * Background + A * RGB + + If an image has 1 (Gray) or 3 (RGB) channels, it'll return early without modifying the image, + with one exception: `alpha_key` and `img.background` are present, and `.blend_background` is set. + + In this case a keyed alpha pixel will be replaced with the background color. +*/ +alpha_drop_if_present :: proc(img: ^Image, options := Options{}, alpha_key := Alpha_Key{}, allocator := context.allocator) -> (ok: bool) { + context.allocator = allocator + + if !is_valid_image(img) { + return false + } + + // Do we have a background to blend? + will_it_blend := false + switch v in img.background { + case RGB_Pixel_16: will_it_blend = true if .blend_background in options else false + } + + // Do we have keyed alpha? + keyed := false + switch v in alpha_key { + case GA_Pixel: keyed = true if img.channels == 1 && img.depth == 8 else false + case RGBA_Pixel: keyed = true if img.channels == 3 && img.depth == 8 else false + case GA_Pixel_16: keyed = true if img.channels == 1 && img.depth == 16 else false + case RGBA_Pixel_16: keyed = true if img.channels == 3 && img.depth == 16 else false + } + + // We should now have a valid Image with 1..4 channels. Do we have alpha? + if img.channels == 1 || img.channels == 3 { + if !(will_it_blend && keyed) { + // We're done + return true + } + } + + // # of destination channels + channels := 1 if img.channels < 3 else 3 + + bytes_wanted := compute_buffer_size(img.width, img.height, channels, img.depth) + buf := bytes.Buffer{} + + // Can we allocate the return buffer? + if !resize(&buf.buf, bytes_wanted) { + delete(buf.buf) + return false + } + + switch img.depth { + case 8: + switch img.channels { + case 1: // Gray to Gray, but we should have keyed alpha + background. + inp := mem.slice_data_cast([]G_Pixel, img.pixels.buf[:]) + out := mem.slice_data_cast([]G_Pixel, buf.buf[:]) + + key := alpha_key.(GA_Pixel).r + bg := G_Pixel{} + if temp_bg, temp_bg_ok := img.background.(RGB_Pixel_16); temp_bg_ok { + // Background is RGB 16-bit, take just the red channel's topmost byte. + bg = u8(temp_bg.r >> 8) + } + + for p in inp { + out[0] = bg if p == key else p + out = out[1:] + } + + case 2: // Gray + Alpha to Gray, no keyed alpha but we can have a background. + inp := mem.slice_data_cast([]GA_Pixel, img.pixels.buf[:]) + out := mem.slice_data_cast([]G_Pixel, buf.buf[:]) + + if will_it_blend { + // Blend with background "color", then drop alpha. + bg := f32(0.0) + if temp_bg, temp_bg_ok := img.background.(RGB_Pixel_16); temp_bg_ok { + // Background is RGB 16-bit, take just the red channel's topmost byte. + bg = f32(temp_bg.r >> 8) + } + + for p in inp { + a := f32(p.g) / 255.0 + c := ((1.0 - a) * bg + a * f32(p.r)) + out[0] = u8(c) + out = out[1:] + } + + } else if .alpha_premultiply in options { + // Premultiply component with alpha, then drop alpha. + for p in inp { + a := f32(p.g) / 255.0 + c := f32(p.r) * a + out[0] = u8(c) + out = out[1:] + } + } else { + // Just drop alpha on the floor. + for p in inp { + out[0] = p.r + out = out[1:] + } + } + + case 3: // RGB to RGB, but we should have keyed alpha + background. + inp := mem.slice_data_cast([]RGB_Pixel, img.pixels.buf[:]) + out := mem.slice_data_cast([]RGB_Pixel, buf.buf[:]) + + key := alpha_key.(RGBA_Pixel) + bg := RGB_Pixel{} + if temp_bg, temp_bg_ok := img.background.(RGB_Pixel_16); temp_bg_ok { + // Background is RGB 16-bit, squash down to 8 bits. + bg = {u8(temp_bg.r >> 8), u8(temp_bg.g >> 8), u8(temp_bg.b >> 8)} + } + + for p in inp { + out[0] = bg if p == key.rgb else p + out = out[1:] + } + + case 4: // RGBA to RGB, no keyed alpha but we can have a background or need to premultiply. + inp := mem.slice_data_cast([]RGBA_Pixel, img.pixels.buf[:]) + out := mem.slice_data_cast([]RGB_Pixel, buf.buf[:]) + + if will_it_blend { + // Blend with background "color", then drop alpha. + bg := [3]f32{} + if temp_bg, temp_bg_ok := img.background.(RGB_Pixel_16); temp_bg_ok { + // Background is RGB 16-bit, take just the red channel's topmost byte. + bg = {f32(temp_bg.r >> 8), f32(temp_bg.g >> 8), f32(temp_bg.b >> 8)} + } + + for p in inp { + a := f32(p.a) / 255.0 + rgb := [3]f32{f32(p.r), f32(p.g), f32(p.b)} + c := ((1.0 - a) * bg + a * rgb) + + out[0] = {u8(c.r), u8(c.g), u8(c.b)} + out = out[1:] + } + + } else if .alpha_premultiply in options { + // Premultiply component with alpha, then drop alpha. + for p in inp { + a := f32(p.a) / 255.0 + rgb := [3]f32{f32(p.r), f32(p.g), f32(p.b)} + c := rgb * a + + out[0] = {u8(c.r), u8(c.g), u8(c.b)} + out = out[1:] + } + } else { + // Just drop alpha on the floor. + for p in inp { + out[0] = p.rgb + out = out[1:] + } + } + } + + case 16: + switch img.channels { + case 1: // Gray to Gray, but we should have keyed alpha + background. + inp := mem.slice_data_cast([]G_Pixel_16, img.pixels.buf[:]) + out := mem.slice_data_cast([]G_Pixel_16, buf.buf[:]) + + key := alpha_key.(GA_Pixel_16).r + bg := G_Pixel_16{} + if temp_bg, temp_bg_ok := img.background.(RGB_Pixel_16); temp_bg_ok { + // Background is RGB 16-bit, take just the red channel. + bg = temp_bg.r + } + + for p in inp { + out[0] = bg if p == key else p + out = out[1:] + } + + case 2: // Gray + Alpha to Gray, no keyed alpha but we can have a background. + inp := mem.slice_data_cast([]GA_Pixel_16, img.pixels.buf[:]) + out := mem.slice_data_cast([]G_Pixel_16, buf.buf[:]) + + if will_it_blend { + // Blend with background "color", then drop alpha. + bg := f32(0.0) + if temp_bg, temp_bg_ok := img.background.(RGB_Pixel_16); temp_bg_ok { + // Background is RGB 16-bit, take just the red channel. + bg = f32(temp_bg.r) + } + + for p in inp { + a := f32(p.g) / 65535.0 + c := ((1.0 - a) * bg + a * f32(p.r)) + out[0] = u16(c) + out = out[1:] + } + + } else if .alpha_premultiply in options { + // Premultiply component with alpha, then drop alpha. + for p in inp { + a := f32(p.g) / 65535.0 + c := f32(p.r) * a + out[0] = u16(c) + out = out[1:] + } + } else { + // Just drop alpha on the floor. + for p in inp { + out[0] = p.r + out = out[1:] + } + } + + case 3: // RGB to RGB, but we should have keyed alpha + background. + inp := mem.slice_data_cast([]RGB_Pixel_16, img.pixels.buf[:]) + out := mem.slice_data_cast([]RGB_Pixel_16, buf.buf[:]) + + key := alpha_key.(RGBA_Pixel_16) + bg := img.background.(RGB_Pixel_16) + + for p in inp { + out[0] = bg if p == key.rgb else p + out = out[1:] + } + + case 4: // RGBA to RGB, no keyed alpha but we can have a background or need to premultiply. + inp := mem.slice_data_cast([]RGBA_Pixel_16, img.pixels.buf[:]) + out := mem.slice_data_cast([]RGB_Pixel_16, buf.buf[:]) + + if will_it_blend { + // Blend with background "color", then drop alpha. + bg := [3]f32{} + if temp_bg, temp_bg_ok := img.background.(RGB_Pixel_16); temp_bg_ok { + // Background is RGB 16-bit, convert to [3]f32 to blend. + bg = {f32(temp_bg.r), f32(temp_bg.g), f32(temp_bg.b)} + } + + for p in inp { + a := f32(p.a) / 65535.0 + rgb := [3]f32{f32(p.r), f32(p.g), f32(p.b)} + c := ((1.0 - a) * bg + a * rgb) + + out[0] = {u16(c.r), u16(c.g), u16(c.b)} + out = out[1:] + } + + } else if .alpha_premultiply in options { + // Premultiply component with alpha, then drop alpha. + for p in inp { + a := f32(p.a) / 65535.0 + rgb := [3]f32{f32(p.r), f32(p.g), f32(p.b)} + c := rgb * a + + out[0] = {u16(c.r), u16(c.g), u16(c.b)} + out = out[1:] + } + } else { + // Just drop alpha on the floor. + for p in inp { + out[0] = p.rgb + out = out[1:] + } + } + } + + case: + unreachable() + } + + // If we got here, that means we've now got a buffer with the alpha channel dropped. + // Destroy the old pixel buffer and replace it with the new one, and update the channel count. + bytes.buffer_destroy(&img.pixels) + img.pixels = buf + img.channels = channels + return true +} + +// Apply palette to 8-bit single-channel image and return an 8-bit RGB image, in-place. +// If the image given is not a valid 8-bit single channel image, the procedure will return `false` early. +apply_palette_rgb :: proc(img: ^Image, palette: [256]RGB_Pixel, allocator := context.allocator) -> (ok: bool) { + context.allocator = allocator + + if img == nil || img.channels != 1 || img.depth != 8 { + return false + } + + bytes_expected := compute_buffer_size(img.width, img.height, 1, 8) + if bytes_expected == 0 || bytes_expected != len(img.pixels.buf) || img.width * img.height > MAX_DIMENSIONS { + return false + } + + // Can we allocate the return buffer? + buf := bytes.Buffer{} + bytes_wanted := compute_buffer_size(img.width, img.height, 3, 8) + if !resize(&buf.buf, bytes_wanted) { + delete(buf.buf) + return false + } + + out := mem.slice_data_cast([]RGB_Pixel, buf.buf[:]) + + // Apply the palette + for p, i in img.pixels.buf { + out[i] = palette[p] + } + + // If we got here, that means we've now got a buffer with the alpha channel dropped. + // Destroy the old pixel buffer and replace it with the new one, and update the channel count. + bytes.buffer_destroy(&img.pixels) + img.pixels = buf + img.channels = 3 + return true +} + +// Apply palette to 8-bit single-channel image and return an 8-bit RGBA image, in-place. +// If the image given is not a valid 8-bit single channel image, the procedure will return `false` early. +apply_palette_rgba :: proc(img: ^Image, palette: [256]RGBA_Pixel, allocator := context.allocator) -> (ok: bool) { + context.allocator = allocator + + if img == nil || img.channels != 1 || img.depth != 8 { + return false + } + + bytes_expected := compute_buffer_size(img.width, img.height, 1, 8) + if bytes_expected == 0 || bytes_expected != len(img.pixels.buf) || img.width * img.height > MAX_DIMENSIONS { + return false + } + + // Can we allocate the return buffer? + buf := bytes.Buffer{} + bytes_wanted := compute_buffer_size(img.width, img.height, 4, 8) + if !resize(&buf.buf, bytes_wanted) { + delete(buf.buf) + return false + } + + out := mem.slice_data_cast([]RGBA_Pixel, buf.buf[:]) + + // Apply the palette + for p, i in img.pixels.buf { + out[i] = palette[p] + } + + // If we got here, that means we've now got a buffer with the alpha channel dropped. + // Destroy the old pixel buffer and replace it with the new one, and update the channel count. + bytes.buffer_destroy(&img.pixels) + img.pixels = buf + img.channels = 4 + return true +} +apply_palette :: proc{apply_palette_rgb, apply_palette_rgba} + + +// Replicates grayscale values into RGB(A) 8- or 16-bit images as appropriate. +// Returns early with `false` if already an RGB(A) image. +expand_grayscale :: proc(img: ^Image, allocator := context.allocator) -> (ok: bool) { + context.allocator = allocator + + if !is_valid_grayscale_image(img) { + return false + } + + // We should have 1 or 2 channels of 8- or 16 bits now. We need to turn that into 3 or 4. + // Can we allocate the return buffer? + buf := bytes.Buffer{} + bytes_wanted := compute_buffer_size(img.width, img.height, img.channels + 2, img.depth) + if !resize(&buf.buf, bytes_wanted) { + delete(buf.buf) + return false + } + + switch img.depth { + case 8: + switch img.channels { + case 1: // Turn Gray into RGB + out := mem.slice_data_cast([]RGB_Pixel, buf.buf[:]) + + for p in img.pixels.buf { + out[0] = p // Broadcast gray value into RGB components. + out = out[1:] + } + + case 2: // Turn Gray + Alpha into RGBA + inp := mem.slice_data_cast([]GA_Pixel, img.pixels.buf[:]) + out := mem.slice_data_cast([]RGBA_Pixel, buf.buf[:]) + + for p in inp { + out[0].rgb = p.r // Gray component. + out[0].a = p.g // Alpha component. + } + + case: + unreachable() + } + + case 16: + switch img.channels { + case 1: // Turn Gray into RGB + inp := mem.slice_data_cast([]u16, img.pixels.buf[:]) + out := mem.slice_data_cast([]RGB_Pixel_16, buf.buf[:]) + + for p in inp { + out[0] = p // Broadcast gray value into RGB components. + out = out[1:] + } + + case 2: // Turn Gray + Alpha into RGBA + inp := mem.slice_data_cast([]GA_Pixel_16, img.pixels.buf[:]) + out := mem.slice_data_cast([]RGBA_Pixel_16, buf.buf[:]) + + for p in inp { + out[0].rgb = p.r // Gray component. + out[0].a = p.g // Alpha component. + } + + case: + unreachable() + } + + case: + unreachable() + } + + + // If we got here, that means we've now got a buffer with the extra alpha channel. + // Destroy the old pixel buffer and replace it with the new one, and update the channel count. + bytes.buffer_destroy(&img.pixels) + img.pixels = buf + img.channels += 2 + return true +} + +/* + Helper functions to read and write data from/to a Context, etc. +*/ +@(optimization_mode="speed") +read_data :: proc(z: $C, $T: typeid) -> (res: T, err: compress.General_Error) { + if r, e := compress.read_data(z, T); e != .None { + return {}, .Stream_Too_Short + } else { + return r, nil + } +} + +@(optimization_mode="speed") +read_u8 :: proc(z: $C) -> (res: u8, err: compress.General_Error) { + if r, e := compress.read_u8(z); e != .None { + return {}, .Stream_Too_Short + } else { + return r, nil + } +} + +write_bytes :: proc(buf: ^bytes.Buffer, data: []u8) -> (err: compress.General_Error) { + if len(data) == 0 { + return nil + } else if len(data) == 1 { + if bytes.buffer_write_byte(buf, data[0]) != nil { + return .Resize_Failed + } + } else if n, _ := bytes.buffer_write(buf, data); n != len(data) { + return .Resize_Failed + } + return nil +} diff --git a/core/image/general_loader.odin b/core/image/general_loader.odin new file mode 100644 index 000000000..36629c39e --- /dev/null +++ b/core/image/general_loader.odin @@ -0,0 +1,61 @@ +package image + +import "core:mem" +import "core:os" +import "core:bytes" + +Loader_Proc :: #type proc(data: []byte, options: Options, allocator: mem.Allocator) -> (img: ^Image, err: Error) +Destroy_Proc :: #type proc(img: ^Image) + +@(private) +_internal_loaders: [Which_File_Type]Loader_Proc +_internal_destroyers: [Which_File_Type]Destroy_Proc + +register :: proc(kind: Which_File_Type, loader: Loader_Proc, destroyer: Destroy_Proc) { + assert(loader != nil) + assert(destroyer != nil) + assert(_internal_loaders[kind] == nil) + _internal_loaders[kind] = loader + + assert(_internal_destroyers[kind] == nil) + _internal_destroyers[kind] = destroyer +} + +load :: proc{ + load_from_bytes, + load_from_file, +} + +load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { + loader := _internal_loaders[which(data)] + if loader == nil { + return nil, .Unsupported_Format + } + return loader(data, options, allocator) +} + + +load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { + data, ok := os.read_entire_file(filename, allocator) + defer delete(data, allocator) + if ok { + return load_from_bytes(data, options, allocator) + } else { + return nil, .Unable_To_Read_File + } +} + +destroy :: proc(img: ^Image, allocator := context.allocator) { + if img == nil { + return + } + context.allocator = allocator + destroyer := _internal_destroyers[img.which] + if destroyer != nil { + destroyer(img) + } else { + assert(img.metadata == nil) + bytes.buffer_destroy(&img.pixels) + free(img) + } +}
\ No newline at end of file diff --git a/core/image/netpbm/doc.odin b/core/image/netpbm/doc.odin new file mode 100644 index 000000000..1b5b46856 --- /dev/null +++ b/core/image/netpbm/doc.odin @@ -0,0 +1,33 @@ +/* +Formats: + PBM (P1, P4): Portable Bit Map, stores black and white images (1 channel) + PGM (P2, P5): Portable Gray Map, stores greyscale images (1 channel, 1 or 2 bytes per value) + PPM (P3, P6): Portable Pixel Map, stores colour images (3 channel, 1 or 2 bytes per value) + PAM (P7 ): Portable Arbitrary Map, stores arbitrary channel images (1 or 2 bytes per value) + PFM (Pf, PF): Portable Float Map, stores floating-point images (Pf: 1 channel, PF: 3 channel) + +Reading: + All formats fill out header fields `format`, `width`, `height`, `channels`, `depth` + Specific formats use more fields + PGM, PPM, and PAM set `maxval` (maximum of 65535) + PAM sets `tupltype` if there is one, and can set `channels` to any value (not just 1 or 3) + PFM sets `scale` (float equivalent of `maxval`) and `little_endian` (endianness of stored floats) + Currently doesn't support reading multiple images from one binary-format file + +Writing: + You can use your own `Netpbm_Info` struct to control how images are written + All formats require the header field `format` to be specified + Additional header fields are required for specific formats + PGM, PPM, and PAM require `maxval` (maximum of 65535) + PAM also uses `tupltype`, though it may be left as default (empty or nil string) + PFM requires `scale`, and optionally `little_endian` + +Some syntax differences from the specifications: + `channels` stores the number of values per pixel, what the PAM specification calls `depth` + `depth` instead is the number of bits for a single value (32 for PFM, 16 or 8 otherwise) + `scale` and `little_endian` are separated, so the `header` will always store a positive `scale` + `little_endian` will only be true for a negative `scale` PFM, every other format will be false + `little_endian` only describes the netpbm data being read/written, the image buffer will be native +*/ + +package netpbm diff --git a/core/image/netpbm/helpers.odin b/core/image/netpbm/helpers.odin new file mode 100644 index 000000000..016f9453e --- /dev/null +++ b/core/image/netpbm/helpers.odin @@ -0,0 +1,27 @@ +package netpbm + +import "core:bytes" +import "core:image" + +destroy :: proc(img: ^image.Image) -> bool { + if img == nil do return false + + defer free(img) + bytes.buffer_destroy(&img.pixels) + + info, ok := img.metadata.(^image.Netpbm_Info) + if !ok do return false + + header_destroy(&info.header) + free(info) + img.metadata = nil + + return true +} + +header_destroy :: proc(using header: ^Header) { + if format == .P7 && tupltype != "" { + delete(tupltype) + tupltype = "" + } +} diff --git a/core/image/netpbm/netpbm.odin b/core/image/netpbm/netpbm.odin new file mode 100644 index 000000000..5a504cd7c --- /dev/null +++ b/core/image/netpbm/netpbm.odin @@ -0,0 +1,763 @@ +package netpbm + +import "core:bytes" +import "core:fmt" +import "core:image" +import "core:mem" +import "core:os" +import "core:strconv" +import "core:strings" +import "core:unicode" + +Image :: image.Image +Format :: image.Netpbm_Format +Header :: image.Netpbm_Header +Info :: image.Netpbm_Info +Error :: image.Error +Format_Error :: image.Netpbm_Error + +Formats :: bit_set[Format] +PBM :: Formats{.P1, .P4} +PGM :: Formats{.P2, .P5} +PPM :: Formats{.P3, .P6} +PNM :: PBM + PGM + PPM +PAM :: Formats{.P7} +PFM :: Formats{.Pf, .PF} +ASCII :: Formats{.P1, .P2, .P3} +BINARY :: Formats{.P4, .P5, .P6} + PAM + PFM + +load :: proc { + load_from_file, + load_from_bytes, +} + +load_from_file :: proc(filename: string, allocator := context.allocator) -> (img: ^Image, err: Error) { + context.allocator = allocator + + data, ok := os.read_entire_file(filename); defer delete(data) + if !ok { + err = .Unable_To_Read_File + return + } + + return load_from_bytes(data) +} + +load_from_bytes :: proc(data: []byte, allocator := context.allocator) -> (img: ^Image, err: Error) { + context.allocator = allocator + + img = new(Image) + img.which = .NetPBM + + header: Header; defer header_destroy(&header) + header_size: int + header, header_size = parse_header(data) or_return + + img_data := data[header_size:] + decode_image(img, header, img_data) or_return + + info := new(Info) + info.header = header + if header.format == .P7 && header.tupltype != "" { + info.header.tupltype = strings.clone(header.tupltype) + } + img.metadata = info + + return img, nil +} + +save :: proc { + save_to_file, + save_to_buffer, +} + +save_to_file :: proc(filename: string, img: ^Image, custom_info: Info = {}, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + data: []byte; defer delete(data) + data = save_to_buffer(img, custom_info) or_return + + if ok := os.write_entire_file(filename, data); !ok { + return .Unable_To_Write_File + } + + return Format_Error.None +} + +save_to_buffer :: proc(img: ^Image, custom_info: Info = {}, allocator := context.allocator) -> (buffer: []byte, err: Error) { + context.allocator = allocator + + info: Info = {} + if custom_info.header.width > 0 { + // Custom info has been set, use it. + info = custom_info + } else { + img_info, ok := img.metadata.(^image.Netpbm_Info) + if !ok { + // image doesn't have .Netpbm info, guess it + auto_info, auto_info_found := autoselect_pbm_format_from_image(img) + if auto_info_found { + info = auto_info + } else { + return {}, .Invalid_Input_Image + } + } else { + // use info as stored on image + info = img_info^ + } + } + + // using info so we can just talk about the header + using info + + // validation + if header.format in (PBM + PGM + Formats{.Pf}) && img.channels != 1 \ + || header.format in (PPM + Formats{.PF}) && img.channels != 3 { + err = .Invalid_Number_Of_Channels + return + } + + if header.format in (PNM + PAM) { + if header.maxval <= int(max(u8)) && img.depth != 8 \ + || header.maxval > int(max(u8)) && header.maxval <= int(max(u16)) && img.depth != 16 { + err = .Invalid_Image_Depth + return + } + } else if header.format in PFM && img.depth != 32 { + err = .Invalid_Image_Depth + return + } + + // we will write to a string builder + data: strings.Builder + strings.init_builder(&data) + + // all PNM headers start with the format + fmt.sbprintf(&data, "%s\n", header.format) + if header.format in PNM { + fmt.sbprintf(&data, "%i %i\n", img.width, img.height) + if header.format not_in PBM { + fmt.sbprintf(&data, "%i\n", header.maxval) + } + } else if header.format in PAM { + if len(header.tupltype) > 0 { + fmt.sbprintf(&data, "WIDTH %i\nHEIGHT %i\nMAXVAL %i\nDEPTH %i\nTUPLTYPE %s\nENDHDR\n", + img.width, img.height, header.maxval, img.channels, header.tupltype) + } else { + fmt.sbprintf(&data, "WIDTH %i\nHEIGHT %i\nMAXVAL %i\nDEPTH %i\nENDHDR\n", + img.width, img.height, header.maxval, img.channels) + } + + } else if header.format in PFM { + scale := -header.scale if header.little_endian else header.scale + fmt.sbprintf(&data, "%i %i\n%f\n", img.width, img.height, scale) + } + + switch header.format { + // Compressed binary + case .P4: + header_buf := data.buf[:] + pixels := img.pixels.buf[:] + + p4_buffer_size := (img.width / 8 + 1) * img.height + reserve(&data.buf, len(header_buf) + p4_buffer_size) + + // we build up a byte value until it is completely filled + // or we reach the end the row + for y in 0 ..< img.height { + b: byte + + for x in 0 ..< img.width { + i := y * img.width + x + bit := byte(7 - (x % 8)) + v : byte = 0 if pixels[i] == 0 else 1 + b |= (v << bit) + + if bit == 0 { + append(&data.buf, b) + b = 0 + } + } + + if b != 0 { + append(&data.buf, b) + b = 0 + } + } + + // Simple binary + case .P5, .P6, .P7, .Pf, .PF: + header_buf := data.buf[:] + pixels := img.pixels.buf[:] + + resize(&data.buf, len(header_buf) + len(pixels)) + mem.copy(raw_data(data.buf[len(header_buf):]), raw_data(pixels), len(pixels)) + + // convert from native endianness + if img.depth == 16 { + pixels := mem.slice_data_cast([]u16be, data.buf[len(header_buf):]) + for p in &pixels { + p = u16be(transmute(u16) p) + } + } else if header.format in PFM { + if header.little_endian { + pixels := mem.slice_data_cast([]f32le, data.buf[len(header_buf):]) + for p in &pixels { + p = f32le(transmute(f32) p) + } + } else { + pixels := mem.slice_data_cast([]f32be, data.buf[len(header_buf):]) + for p in &pixels { + p = f32be(transmute(f32) p) + } + } + } + + // If-it-looks-like-a-bitmap ASCII + case .P1: + pixels := img.pixels.buf[:] + for y in 0 ..< img.height { + for x in 0 ..< img.width { + i := y * img.width + x + append(&data.buf, '0' if pixels[i] == 0 else '1') + } + append(&data.buf, '\n') + } + + // Token ASCII + case .P2, .P3: + switch img.depth { + case 8: + pixels := img.pixels.buf[:] + for y in 0 ..< img.height { + for x in 0 ..< img.width { + i := y * img.width + x + for c in 0 ..< img.channels { + i := i * img.channels + c + fmt.sbprintf(&data, "%i ", pixels[i]) + } + fmt.sbprint(&data, "\n") + } + fmt.sbprint(&data, "\n") + } + + case 16: + pixels := mem.slice_data_cast([]u16, img.pixels.buf[:]) + for y in 0 ..< img.height { + for x in 0 ..< img.width { + i := y * img.width + x + for c in 0 ..< img.channels { + i := i * img.channels + c + fmt.sbprintf(&data, "%i ", pixels[i]) + } + fmt.sbprint(&data, "\n") + } + fmt.sbprint(&data, "\n") + } + + case: + return data.buf[:], .Invalid_Image_Depth + } + + case: + return data.buf[:], .Invalid_Format + } + + return data.buf[:], Format_Error.None +} + +parse_header :: proc(data: []byte, allocator := context.allocator) -> (header: Header, length: int, err: Error) { + context.allocator = allocator + + // we need the signature and a space + if len(data) < 3 { + err = Format_Error.Incomplete_Header + return + } + + if data[0] == 'P' { + switch data[1] { + case '1' ..= '6': + return _parse_header_pnm(data) + case '7': + return _parse_header_pam(data, allocator) + case 'F', 'f': + return _parse_header_pfm(data) + } + } + + err = .Invalid_Signature + return +} + +@(private) +_parse_header_pnm :: proc(data: []byte) -> (header: Header, length: int, err: Error) { + SIG_LENGTH :: 2 + + { + header_formats := []Format{.P1, .P2, .P3, .P4, .P5, .P6} + header.format = header_formats[data[1] - '0' - 1] + } + + // have a list of fielda for easy iteration + header_fields: []^int + if header.format in PBM { + header_fields = {&header.width, &header.height} + header.maxval = 1 // we know maxval for a bitmap + } else { + header_fields = {&header.width, &header.height, &header.maxval} + } + + // we're keeping track of the header byte length + length = SIG_LENGTH + + // loop state + in_comment := false + already_in_space := true + current_field := 0 + current_value := header_fields[0] + + parse_loop: for d, i in data[SIG_LENGTH:] { + length += 1 + + // handle comments + if in_comment { + switch d { + // comments only go up to next carriage return or line feed + case '\r', '\n': + in_comment = false + } + continue + } else if d == '#' { + in_comment = true + continue + } + + // handle whitespace + in_space := unicode.is_white_space(rune(d)) + if in_space { + if already_in_space { + continue + } + already_in_space = true + + // switch to next value + current_field += 1 + if current_field == len(header_fields) { + // header byte length is 1-index so we'll increment again + length += 1 + break parse_loop + } + current_value = header_fields[current_field] + } else { + already_in_space = false + + if !unicode.is_digit(rune(d)) { + err = Format_Error.Invalid_Header_Token_Character + return + } + + val := int(d - '0') + current_value^ = current_value^ * 10 + val + } + } + + // set extra info + header.channels = 3 if header.format in PPM else 1 + header.depth = 16 if header.maxval > int(max(u8)) else 8 + + // limit checking + if current_field < len(header_fields) { + err = Format_Error.Incomplete_Header + return + } + + if header.width < 1 \ + || header.height < 1 \ + || header.maxval < 1 || header.maxval > int(max(u16)) { + fmt.printf("[pnm] Header: {{width = %v, height = %v, maxval: %v}}\n", header.width, header.height, header.maxval) + err = .Invalid_Header_Value + return + } + + length -= 1 + err = Format_Error.None + return +} + +@(private) +_parse_header_pam :: proc(data: []byte, allocator := context.allocator) -> (header: Header, length: int, err: Error) { + context.allocator = allocator + + // the spec needs the newline apparently + if string(data[0:3]) != "P7\n" { + err = .Invalid_Signature + return + } + header.format = .P7 + + SIGNATURE_LENGTH :: 3 + HEADER_END :: "ENDHDR\n" + + // we can already work out the size of the header + header_end_index := strings.index(string(data), HEADER_END) + if header_end_index == -1 { + err = Format_Error.Incomplete_Header + return + } + length = header_end_index + len(HEADER_END) + + // string buffer for the tupltype + tupltype: strings.Builder + strings.init_builder(&tupltype, context.temp_allocator); defer strings.destroy_builder(&tupltype) + fmt.sbprint(&tupltype, "") + + // PAM uses actual lines, so we can iterate easily + line_iterator := string(data[SIGNATURE_LENGTH : header_end_index]) + parse_loop: for line in strings.split_lines_iterator(&line_iterator) { + line := line + + if len(line) == 0 || line[0] == '#' { + continue + } + + field, ok := strings.fields_iterator(&line) + value := strings.trim_space(line) + + // the field will change, but the logic stays the same + current_field: ^int + + switch field { + case "WIDTH": current_field = &header.width + case "HEIGHT": current_field = &header.height + case "DEPTH": current_field = &header.channels + case "MAXVAL": current_field = &header.maxval + + case "TUPLTYPE": + if len(value) == 0 { + err = .Invalid_Header_Value + return + } + + if len(tupltype.buf) == 0 { + fmt.sbprint(&tupltype, value) + } else { + fmt.sbprint(&tupltype, "", value) + } + + continue + + case: + continue + } + + if current_field^ != 0 { + err = Format_Error.Duplicate_Header_Field + return + } + current_field^, ok = strconv.parse_int(value) + if !ok { + err = Format_Error.Invalid_Header_Value + return + } + } + + // extra info + header.depth = 16 if header.maxval > int(max(u8)) else 8 + + // limit checking + if header.width < 1 \ + || header.height < 1 \ + || header.maxval < 1 \ + || header.maxval > int(max(u16)) { + fmt.printf("[pam] Header: {{width = %v, height = %v, maxval: %v}}\n", header.width, header.height, header.maxval) + err = Format_Error.Invalid_Header_Value + return + } + + header.tupltype = strings.clone(strings.to_string(tupltype)) + err = Format_Error.None + return +} + +@(private) +_parse_header_pfm :: proc(data: []byte) -> (header: Header, length: int, err: Error) { + // we can just cycle through tokens for PFM + field_iterator := string(data) + field, ok := strings.fields_iterator(&field_iterator) + + switch field { + case "Pf": + header.format = .Pf + header.channels = 1 + case "PF": + header.format = .PF + header.channels = 3 + case: + err = .Invalid_Signature + return + } + + // floating point + header.depth = 32 + + // width + field, ok = strings.fields_iterator(&field_iterator) + if !ok { + err = Format_Error.Incomplete_Header + return + } + header.width, ok = strconv.parse_int(field) + if !ok { + err = Format_Error.Invalid_Header_Value + return + } + + // height + field, ok = strings.fields_iterator(&field_iterator) + if !ok { + err = Format_Error.Incomplete_Header + return + } + header.height, ok = strconv.parse_int(field) + if !ok { + err = Format_Error.Invalid_Header_Value + return + } + + // scale (sign is endianness) + field, ok = strings.fields_iterator(&field_iterator) + if !ok { + err = Format_Error.Incomplete_Header + return + } + header.scale, ok = strconv.parse_f32(field) + if !ok { + err = Format_Error.Invalid_Header_Value + return + } + + if header.scale < 0.0 { + header.little_endian = true + header.scale = -header.scale + } + + // pointer math to get header size + length = int((uintptr(raw_data(field_iterator)) + 1) - uintptr(raw_data(data))) + + // limit checking + if header.width < 1 \ + || header.height < 1 \ + || header.scale == 0.0 { + fmt.printf("[pfm] Header: {{width = %v, height = %v, scale: %v}}\n", header.width, header.height, header.scale) + err = .Invalid_Header_Value + return + } + + err = Format_Error.None + return +} + +decode_image :: proc(img: ^Image, header: Header, data: []byte, allocator := context.allocator) -> (err: Error) { + assert(img != nil) + context.allocator = allocator + + img.width = header.width + img.height = header.height + img.channels = header.channels + img.depth = header.depth + + buffer_size := image.compute_buffer_size(img.width, img.height, img.channels, img.depth) + + // we can check data size for binary formats + if header.format in BINARY { + if len(data) < buffer_size { + fmt.printf("len(data): %v, buffer size: %v\n", len(data), buffer_size) + return .Buffer_Too_Small + } + } + + // for ASCII and P4, we use length for the termination condition, so start at 0 + // BINARY will be a simple memcopy so the buffer length should also be initialised + if header.format in ASCII || header.format == .P4 { + bytes.buffer_init_allocator(&img.pixels, 0, buffer_size) + } else { + bytes.buffer_init_allocator(&img.pixels, buffer_size, buffer_size) + } + + switch header.format { + // Compressed binary + case .P4: + for d in data { + for b in 1 ..= 8 { + bit := byte(8 - b) + pix := (d >> bit) & 1 + bytes.buffer_write_byte(&img.pixels, pix) + if len(img.pixels.buf) % img.width == 0 { + break + } + } + + if len(img.pixels.buf) == cap(img.pixels.buf) { + break + } + } + + // Simple binary + case .P5, .P6, .P7, .Pf, .PF: + copy(img.pixels.buf[:], data[:]) + + // convert to native endianness + if header.format in PFM { + pixels := mem.slice_data_cast([]f32, img.pixels.buf[:]) + if header.little_endian { + for p in &pixels { + p = f32(transmute(f32le) p) + } + } else { + for p in &pixels { + p = f32(transmute(f32be) p) + } + } + } else { + if img.depth == 16 { + pixels := mem.slice_data_cast([]u16, img.pixels.buf[:]) + for p in &pixels { + p = u16(transmute(u16be) p) + } + } + } + + // If-it-looks-like-a-bitmap ASCII + case .P1: + for c in data { + switch c { + case '0', '1': + bytes.buffer_write_byte(&img.pixels, c - '0') + } + + if len(img.pixels.buf) == cap(img.pixels.buf) { + break + } + } + + if len(img.pixels.buf) < cap(img.pixels.buf) { + err = Format_Error.Buffer_Too_Small + return + } + + // Token ASCII + case .P2, .P3: + field_iterator := string(data) + for field in strings.fields_iterator(&field_iterator) { + value, ok := strconv.parse_int(field) + if !ok { + err = Format_Error.Invalid_Buffer_ASCII_Token + return + } + + //? do we want to enforce the maxval, the limit, or neither + if value > int(max(u16)) /*header.maxval*/ { + err = Format_Error.Invalid_Buffer_Value + return + } + + switch img.depth { + case 8: + bytes.buffer_write_byte(&img.pixels, u8(value)) + case 16: + vb := transmute([2]u8) u16(value) + bytes.buffer_write(&img.pixels, vb[:]) + } + + if len(img.pixels.buf) == cap(img.pixels.buf) { + break + } + } + + if len(img.pixels.buf) < cap(img.pixels.buf) { + err = Format_Error.Buffer_Too_Small + return + } + } + + err = Format_Error.None + return +} + +// Automatically try to select an appropriate format to save to based on `img.channel` and `img.depth` +autoselect_pbm_format_from_image :: proc(img: ^Image, prefer_binary := true, force_black_and_white := false, pfm_scale := f32(1.0)) -> (res: Info, ok: bool) { + /* + PBM (P1, P4): Portable Bit Map, stores black and white images (1 channel) + PGM (P2, P5): Portable Gray Map, stores greyscale images (1 channel, 1 or 2 bytes per value) + PPM (P3, P6): Portable Pixel Map, stores colour images (3 channel, 1 or 2 bytes per value) + PAM (P7 ): Portable Arbitrary Map, stores arbitrary channel images (1 or 2 bytes per value) + PFM (Pf, PF): Portable Float Map, stores floating-point images (Pf: 1 channel, PF: 3 channel) + + ASCII :: Formats{.P1, .P2, .P3} + */ + using res.header + + width = img.width + height = img.height + channels = img.channels + depth = img.depth + maxval = 255 if img.depth == 8 else 65535 + little_endian = true if ODIN_ENDIAN == .Little else false + + // Assume we'll find a suitable format + ok = true + + switch img.channels { + case 1: + // Must be Portable Float Map + if img.depth == 32 { + format = .Pf + return + } + + if force_black_and_white { + // Portable Bit Map + format = .P4 if prefer_binary else .P1 + maxval = 1 + return + } else { + // Portable Gray Map + format = .P5 if prefer_binary else .P2 + return + } + + case 3: + // Must be Portable Float Map + if img.depth == 32 { + format = .PF + return + } + + // Portable Pixel Map + format = .P6 if prefer_binary else .P3 + return + + case: + // Portable Arbitrary Map + if img.depth == 8 || img.depth == 16 { + format = .P7 + scale = pfm_scale + return + } + } + + // We couldn't find a suitable format + return {}, false +} + +@(init, private) +_register :: proc() { + loader :: proc(data: []byte, options: image.Options, allocator: mem.Allocator) -> (img: ^Image, err: Error) { + return load_from_bytes(data, allocator) + } + destroyer :: proc(img: ^Image) { + _ = destroy(img) + } + image.register(.NetPBM, loader, destroyer) +}
\ No newline at end of file diff --git a/core/image/png/helpers.odin b/core/image/png/helpers.odin index c64e6f471..0ebf0b20b 100644 --- a/core/image/png/helpers.odin +++ b/core/image/png/helpers.odin @@ -242,17 +242,16 @@ srgb :: proc(c: image.PNG_Chunk) -> (res: sRGB, ok: bool) { } plte :: proc(c: image.PNG_Chunk) -> (res: PLTE, ok: bool) { - if c.header.type != .PLTE { + if c.header.type != .PLTE || c.header.length % 3 != 0 || c.header.length > 768 { return {}, false } - i := 0; j := 0; ok = true - for j < int(c.header.length) { - res.entries[i] = {c.data[j], c.data[j+1], c.data[j+2]} - i += 1; j += 3 + plte := mem.slice_data_cast([]image.RGB_Pixel, c.data[:]) + for color, i in plte { + res.entries[i] = color } - res.used = u16(i) - return + res.used = u16(len(plte)) + return res, true } splt :: proc(c: image.PNG_Chunk) -> (res: sPLT, ok: bool) { diff --git a/core/image/png/png.odin b/core/image/png/png.odin index bff0afde3..35fdb58d8 100644 --- a/core/image/png/png.odin +++ b/core/image/png/png.odin @@ -18,23 +18,16 @@ import "core:compress/zlib" import "core:image" import "core:os" -import "core:strings" import "core:hash" import "core:bytes" import "core:io" import "core:mem" import "core:intrinsics" -/* - 67_108_864 pixels max by default. - Maximum allowed dimensions are capped at 65535 * 65535. -*/ -MAX_DIMENSIONS :: min(#config(PNG_MAX_DIMENSIONS, 8192 * 8192), 65535 * 65535) +// Limit chunk sizes. +// By default: IDAT = 8k x 8k x 16-bits + 8k filter bytes. +// The total number of pixels defaults to 64 Megapixel and can be tuned in image/common.odin. -/* - Limit chunk sizes. - By default: IDAT = 8k x 8k x 16-bits + 8k filter bytes. -*/ _MAX_IDAT_DEFAULT :: ( 8192 /* Width */ * 8192 /* Height */ * 2 /* 16-bit */) + 8192 /* Filter bytes */ _MAX_IDAT :: (65535 /* Width */ * 65535 /* Height */ * 2 /* 16-bit */) + 65535 /* Filter bytes */ @@ -64,7 +57,7 @@ Row_Filter :: enum u8 { Paeth = 4, } -PLTE_Entry :: [3]u8 +PLTE_Entry :: image.RGB_Pixel PLTE :: struct #packed { entries: [256]PLTE_Entry, @@ -244,7 +237,7 @@ append_chunk :: proc(list: ^[dynamic]image.PNG_Chunk, src: image.PNG_Chunk, allo append(list, c) if len(list) != length + 1 { // Resize during append failed. - return mem.Allocator_Error.Out_Of_Memory + return .Unable_To_Allocate_Or_Resize } return @@ -259,7 +252,7 @@ read_header :: proc(ctx: ^$C) -> (image.PNG_IHDR, Error) { header := (^image.PNG_IHDR)(raw_data(c.data))^ // Validate IHDR using header - if width == 0 || height == 0 || u128(width) * u128(height) > MAX_DIMENSIONS { + if width == 0 || height == 0 || u128(width) * u128(height) > image.MAX_DIMENSIONS { return {}, .Invalid_Image_Dimensions } @@ -324,13 +317,12 @@ read_header :: proc(ctx: ^$C) -> (image.PNG_IHDR, Error) { } chunk_type_to_name :: proc(type: ^image.PNG_Chunk_Type) -> string { - t := transmute(^u8)type - return strings.string_from_ptr(t, 4) + return string(([^]u8)(type)[:4]) } -load_from_slice :: proc(slice: []u8, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { +load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { ctx := &compress.Context_Memory_Input{ - input_data = slice, + input_data = data, } /* @@ -350,10 +342,9 @@ load_from_file :: proc(filename: string, options := Options{}, allocator := cont defer delete(data) if ok { - return load_from_slice(data, options) + return load_from_bytes(data, options) } else { - img = new(Image) - return img, compress.General_Error.File_Not_Found + return nil, .Unable_To_Read_File } } @@ -366,6 +357,10 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a options -= {.info} } + if .return_header in options && .return_metadata in options { + options -= {.return_header} + } + if .alpha_drop_if_present in options && .alpha_add_if_missing in options { return {}, compress.General_Error.Incompatible_Options } @@ -377,13 +372,14 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a if img == nil { img = new(Image) } + img.which = .PNG info := new(image.PNG_Info) img.metadata = info signature, io_error := compress.read_data(ctx, Signature) if io_error != .None || signature != .PNG { - return img, .Invalid_PNG_Signature + return img, .Invalid_Signature } idat: []u8 @@ -392,7 +388,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a idat_length := u64(0) - c: image.PNG_Chunk + c: image.PNG_Chunk ch: image.PNG_Chunk_Header e: io.Error @@ -473,6 +469,10 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a } info.header = h + if .return_header in options && .return_metadata not_in options && .do_not_decompress_image not_in options { + return img, nil + } + case .PLTE: seen_plte = true // PLTE must appear before IDAT and can't appear for color types 0, 4. @@ -540,9 +540,6 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a seen_iend = true case .bKGD: - - // TODO: Make sure that 16-bit bKGD + tRNS chunks return u16 instead of u16be - c = read_chunk(ctx) or_return seen_bkgd = true if .return_metadata in options { @@ -594,23 +591,36 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a */ final_image_channels += 1 - seen_trns = true + + if .Paletted in header.color_type { + if len(c.data) > 256 { + return img, .TNRS_Invalid_Length + } + } else if .Color in header.color_type { + if len(c.data) != 6 { + return img, .TNRS_Invalid_Length + } + } else if len(c.data) != 2 { + return img, .TNRS_Invalid_Length + } + if info.header.bit_depth < 8 && .Paletted not_in info.header.color_type { // Rescale tRNS data so key matches intensity - dsc := depth_scale_table + dsc := depth_scale_table scale := dsc[info.header.bit_depth] if scale != 1 { key := mem.slice_data_cast([]u16be, c.data)[0] * u16be(scale) c.data = []u8{0, u8(key & 255)} } } + trns = c - case .iDOT, .CbGI: + case .iDOT, .CgBI: /* iPhone PNG bastardization that doesn't adhere to spec with broken IDAT chunk. - We're not going to add support for it. If you have the misfortunte of coming + We're not going to add support for it. If you have the misfortune of coming across one of these files, use a utility to defry it. */ return img, .Image_Does_Not_Adhere_to_Spec @@ -635,6 +645,10 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a return img, .IDAT_Missing } + if .Paletted in header.color_type && !seen_plte { + return img, .PLTE_Missing + } + /* Calculate the expected output size, to help `inflate` make better decisions about the output buffer. We'll also use it to check the returned buffer size is what we expected it to be. @@ -683,15 +697,6 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a return {}, defilter_error } - /* - Now we'll handle the relocoring of paletted images, handling of tRNS chunks, - and we'll expand grayscale images to RGB(A). - - For the sake of convenience we return only RGB(A) images. In the future we - may supply an option to return Gray/Gray+Alpha as-is, in which case RGB(A) - will become the default. - */ - if .Paletted in header.color_type && .do_not_expand_indexed in options { return img, nil } @@ -699,7 +704,10 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a return img, nil } - + /* + Now we're going to optionally apply various post-processing stages, + to for example expand grayscale, apply a palette, premultiply alpha, etc. + */ raw_image_channels := img.channels out_image_channels := 3 @@ -737,7 +745,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a dest_raw_size := compute_buffer_size(int(header.width), int(header.height), out_image_channels, 8) t := bytes.Buffer{} if !resize(&t.buf, dest_raw_size) { - return {}, mem.Allocator_Error.Out_Of_Memory + return {}, .Unable_To_Allocate_Or_Resize } i := 0; j := 0 @@ -818,7 +826,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a dest_raw_size := compute_buffer_size(int(header.width), int(header.height), out_image_channels, 16) t := bytes.Buffer{} if !resize(&t.buf, dest_raw_size) { - return {}, mem.Allocator_Error.Out_Of_Memory + return {}, .Unable_To_Allocate_Or_Resize } p16 := mem.slice_data_cast([]u16, temp.buf[:]) @@ -1017,7 +1025,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a dest_raw_size := compute_buffer_size(int(header.width), int(header.height), out_image_channels, 8) t := bytes.Buffer{} if !resize(&t.buf, dest_raw_size) { - return {}, mem.Allocator_Error.Out_Of_Memory + return {}, .Unable_To_Allocate_Or_Resize } p := mem.slice_data_cast([]u8, temp.buf[:]) @@ -1204,7 +1212,6 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a return img, nil } - filter_paeth :: #force_inline proc(left, up, up_left: u8) -> u8 { aa, bb, cc := i16(left), i16(up), i16(up_left) p := aa + bb - cc @@ -1526,7 +1533,7 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IH num_bytes := compute_buffer_size(width, height, channels, depth == 16 ? 16 : 8) if !resize(&img.pixels.buf, num_bytes) { - return mem.Allocator_Error.Out_Of_Memory + return .Unable_To_Allocate_Or_Resize } filter_ok: bool @@ -1568,7 +1575,7 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IH temp: bytes.Buffer temp_len := compute_buffer_size(x, y, channels, depth == 16 ? 16 : 8) if !resize(&temp.buf, temp_len) { - return mem.Allocator_Error.Out_Of_Memory + return .Unable_To_Allocate_Or_Resize } params := Filter_Params{ @@ -1630,4 +1637,10 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IH return nil } -load :: proc{load_from_file, load_from_slice, load_from_context} +load :: proc{load_from_file, load_from_bytes, load_from_context} + + +@(init, private) +_register :: proc() { + image.register(.PNG, load_from_bytes, destroy) +}
\ No newline at end of file diff --git a/core/image/qoi/qoi.odin b/core/image/qoi/qoi.odin new file mode 100644 index 000000000..29a17d4f4 --- /dev/null +++ b/core/image/qoi/qoi.odin @@ -0,0 +1,411 @@ +/*
+ Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
+ Made available under Odin's BSD-3 license.
+
+ List of contributors:
+ Jeroen van Rijn: Initial implementation.
+*/
+
+
+// package qoi implements a QOI image reader
+//
+// The QOI specification is at https://qoiformat.org.
+package qoi
+
+import "core:image"
+import "core:compress"
+import "core:bytes"
+import "core:os"
+
+Error :: image.Error
+Image :: image.Image
+Options :: image.Options
+
+RGB_Pixel :: image.RGB_Pixel
+RGBA_Pixel :: image.RGBA_Pixel
+
+save_to_memory :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
+ context.allocator = allocator
+
+ if img == nil {
+ return .Invalid_Input_Image
+ }
+
+ if output == nil {
+ return .Invalid_Output
+ }
+
+ pixels := img.width * img.height
+ if pixels == 0 || pixels > image.MAX_DIMENSIONS {
+ return .Invalid_Input_Image
+ }
+
+ // QOI supports only 8-bit images with 3 or 4 channels.
+ if img.depth != 8 || img.channels < 3 || img.channels > 4 {
+ return .Invalid_Input_Image
+ }
+
+ if img.channels * pixels != len(img.pixels.buf) {
+ return .Invalid_Input_Image
+ }
+
+ written := 0
+
+ // Calculate and allocate maximum size. We'll reclaim space to actually written output at the end.
+ max_size := pixels * (img.channels + 1) + size_of(image.QOI_Header) + size_of(u64be)
+
+ if !resize(&output.buf, max_size) {
+ return .Unable_To_Allocate_Or_Resize
+ }
+
+ header := image.QOI_Header{
+ magic = image.QOI_Magic,
+ width = u32be(img.width),
+ height = u32be(img.height),
+ channels = u8(img.channels),
+ color_space = .Linear if .qoi_all_channels_linear in options else .sRGB,
+ }
+ header_bytes := transmute([size_of(image.QOI_Header)]u8)header
+
+ copy(output.buf[written:], header_bytes[:])
+ written += size_of(image.QOI_Header)
+
+ /*
+ Encode loop starts here.
+ */
+ seen: [64]RGBA_Pixel
+ pix := RGBA_Pixel{0, 0, 0, 255}
+ prev := pix
+
+ seen[qoi_hash(pix)] = pix
+
+ input := img.pixels.buf[:]
+ run := u8(0)
+
+ for len(input) > 0 {
+ if img.channels == 4 {
+ pix = (^RGBA_Pixel)(raw_data(input))^
+ } else {
+ pix.rgb = (^RGB_Pixel)(raw_data(input))^
+ }
+ input = input[img.channels:]
+
+ if pix == prev {
+ run += 1
+ // As long as the pixel matches the last one, accumulate the run total.
+ // If we reach the max run length or the end of the image, write the run.
+ if run == 62 || len(input) == 0 {
+ // Encode and write run
+ output.buf[written] = u8(QOI_Opcode_Tag.RUN) | (run - 1)
+ written += 1
+ run = 0
+ }
+ } else {
+ if run > 0 {
+ // The pixel differs from the previous one, but we still need to write the pending run.
+ // Encode and write run
+ output.buf[written] = u8(QOI_Opcode_Tag.RUN) | (run - 1)
+ written += 1
+ run = 0
+ }
+
+ index := qoi_hash(pix)
+
+ if seen[index] == pix {
+ // Write indexed pixel
+ output.buf[written] = u8(QOI_Opcode_Tag.INDEX) | index
+ written += 1
+ } else {
+ // Add pixel to index
+ seen[index] = pix
+
+ // If the alpha matches the previous pixel's alpha, we don't need to write a full RGBA literal.
+ if pix.a == prev.a {
+ // Delta
+ d := pix.rgb - prev.rgb
+
+ // DIFF, biased and modulo 256
+ _d := d + 2
+
+ // LUMA, biased and modulo 256
+ _l := RGB_Pixel{ d.r - d.g + 8, d.g + 32, d.b - d.g + 8 }
+
+ if _d.r < 4 && _d.g < 4 && _d.b < 4 {
+ // Delta is between -2 and 1 inclusive
+ output.buf[written] = u8(QOI_Opcode_Tag.DIFF) | _d.r << 4 | _d.g << 2 | _d.b
+ written += 1
+ } else if _l.r < 16 && _l.g < 64 && _l.b < 16 {
+ // Biased luma is between {-8..7, -32..31, -8..7}
+ output.buf[written ] = u8(QOI_Opcode_Tag.LUMA) | _l.g
+ output.buf[written + 1] = _l.r << 4 | _l.b
+ written += 2
+ } else {
+ // Write RGB literal
+ output.buf[written] = u8(QOI_Opcode_Tag.RGB)
+ pix_bytes := transmute([4]u8)pix
+ copy(output.buf[written + 1:], pix_bytes[:3])
+ written += 4
+ }
+ } else {
+ // Write RGBA literal
+ output.buf[written] = u8(QOI_Opcode_Tag.RGBA)
+ pix_bytes := transmute([4]u8)pix
+ copy(output.buf[written + 1:], pix_bytes[:])
+ written += 5
+ }
+ }
+ }
+ prev = pix
+ }
+
+ trailer := []u8{0, 0, 0, 0, 0, 0, 0, 1}
+ copy(output.buf[written:], trailer[:])
+ written += len(trailer)
+
+ resize(&output.buf, written)
+ return nil
+}
+
+save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
+ context.allocator = allocator
+
+ out := &bytes.Buffer{}
+ defer bytes.buffer_destroy(out)
+
+ save_to_memory(out, img, options) or_return
+ write_ok := os.write_entire_file(output, out.buf[:])
+
+ return nil if write_ok else .Unable_To_Write_File
+}
+
+save :: proc{save_to_memory, save_to_file}
+
+load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
+ ctx := &compress.Context_Memory_Input{
+ input_data = data,
+ }
+
+ img, err = load_from_context(ctx, options, allocator)
+ return img, err
+}
+
+load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
+ context.allocator = allocator
+
+ data, ok := os.read_entire_file(filename)
+ defer delete(data)
+
+ if ok {
+ return load_from_bytes(data, options)
+ } else {
+ return nil, .Unable_To_Read_File
+ }
+}
+
+@(optimization_mode="speed")
+load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
+ context.allocator = allocator
+ options := options
+
+ if .info in options {
+ options |= {.return_metadata, .do_not_decompress_image}
+ options -= {.info}
+ }
+
+ if .return_header in options && .return_metadata in options {
+ options -= {.return_header}
+ }
+
+ header := image.read_data(ctx, image.QOI_Header) or_return
+ if header.magic != image.QOI_Magic {
+ return img, .Invalid_Signature
+ }
+
+ if img == nil {
+ img = new(Image)
+ }
+ img.which = .QOI
+
+ if .return_metadata in options {
+ info := new(image.QOI_Info)
+ info.header = header
+ img.metadata = info
+ }
+
+ if header.channels != 3 && header.channels != 4 {
+ return img, .Invalid_Number_Of_Channels
+ }
+
+ if header.color_space != .sRGB && header.color_space != .Linear {
+ return img, .Invalid_Color_Space
+ }
+
+ if header.width == 0 || header.height == 0 {
+ return img, .Invalid_Image_Dimensions
+ }
+
+ total_pixels := header.width * header.height
+ if total_pixels > image.MAX_DIMENSIONS {
+ return img, .Image_Dimensions_Too_Large
+ }
+
+ img.width = int(header.width)
+ img.height = int(header.height)
+ img.channels = 4 if .alpha_add_if_missing in options else int(header.channels)
+ img.depth = 8
+
+ if .do_not_decompress_image in options {
+ img.channels = int(header.channels)
+ return
+ }
+
+ bytes_needed := image.compute_buffer_size(int(header.width), int(header.height), img.channels, 8)
+
+ if !resize(&img.pixels.buf, bytes_needed) {
+ return img, .Unable_To_Allocate_Or_Resize
+ }
+
+ /*
+ Decode loop starts here.
+ */
+ seen: [64]RGBA_Pixel
+ pix := RGBA_Pixel{0, 0, 0, 255}
+ seen[qoi_hash(pix)] = pix
+ pixels := img.pixels.buf[:]
+
+ decode: for len(pixels) > 0 {
+ data := image.read_u8(ctx) or_return
+
+ tag := QOI_Opcode_Tag(data)
+ #partial switch tag {
+ case .RGB:
+ pix.rgb = image.read_data(ctx, RGB_Pixel) or_return
+
+ #no_bounds_check {
+ seen[qoi_hash(pix)] = pix
+ }
+
+ case .RGBA:
+ pix = image.read_data(ctx, RGBA_Pixel) or_return
+
+ #no_bounds_check {
+ seen[qoi_hash(pix)] = pix
+ }
+
+ case:
+ // 2-bit tag
+ tag = QOI_Opcode_Tag(data & QOI_Opcode_Mask)
+ #partial switch tag {
+ case .INDEX:
+ pix = seen[data & 63]
+
+ case .DIFF:
+ diff_r := ((data >> 4) & 3) - 2
+ diff_g := ((data >> 2) & 3) - 2
+ diff_b := ((data >> 0) & 3) - 2
+
+ pix += {diff_r, diff_g, diff_b, 0}
+
+ #no_bounds_check {
+ seen[qoi_hash(pix)] = pix
+ }
+
+ case .LUMA:
+ data2 := image.read_u8(ctx) or_return
+
+ diff_g := (data & 63) - 32
+ diff_r := diff_g - 8 + ((data2 >> 4) & 15)
+ diff_b := diff_g - 8 + (data2 & 15)
+
+ pix += {diff_r, diff_g, diff_b, 0}
+
+ #no_bounds_check {
+ seen[qoi_hash(pix)] = pix
+ }
+
+ case .RUN:
+ if length := int(data & 63) + 1; (length * img.channels) > len(pixels) {
+ return img, .Corrupt
+ } else {
+ #no_bounds_check for in 0..<length {
+ copy(pixels, pix[:img.channels])
+ pixels = pixels[img.channels:]
+ }
+ }
+
+ continue decode
+
+ case:
+ unreachable()
+ }
+ }
+
+ #no_bounds_check {
+ copy(pixels, pix[:img.channels])
+ pixels = pixels[img.channels:]
+ }
+ }
+
+ // The byte stream's end is marked with 7 0x00 bytes followed by a single 0x01 byte.
+ trailer, trailer_err := compress.read_data(ctx, u64be)
+ if trailer_err != nil || trailer != 0x1 {
+ return img, .Missing_Or_Corrupt_Trailer
+ }
+
+ if .alpha_premultiply in options && !image.alpha_drop_if_present(img, options) {
+ return img, .Post_Processing_Error
+ }
+
+ return
+}
+
+load :: proc{load_from_file, load_from_bytes, load_from_context}
+
+/*
+ Cleanup of image-specific data.
+*/
+destroy :: proc(img: ^Image) {
+ if img == nil {
+ /*
+ Nothing to do.
+ Load must've returned with an error.
+ */
+ return
+ }
+
+ bytes.buffer_destroy(&img.pixels)
+
+ if v, ok := img.metadata.(^image.QOI_Info); ok {
+ free(v)
+ }
+ free(img)
+}
+
+QOI_Opcode_Tag :: enum u8 {
+ // 2-bit tags
+ INDEX = 0b0000_0000, // 6-bit index into color array follows
+ DIFF = 0b0100_0000, // 3x (RGB) 2-bit difference follows (-2..1), bias of 2.
+ LUMA = 0b1000_0000, // Luma difference
+ RUN = 0b1100_0000, // Run length encoding, bias -1
+
+ // 8-bit tags
+ RGB = 0b1111_1110, // Raw RGB pixel follows
+ RGBA = 0b1111_1111, // Raw RGBA pixel follows
+}
+
+QOI_Opcode_Mask :: 0b1100_0000
+QOI_Data_Mask :: 0b0011_1111
+
+qoi_hash :: #force_inline proc(pixel: RGBA_Pixel) -> (index: u8) {
+ i1 := u16(pixel.r) * 3
+ i2 := u16(pixel.g) * 5
+ i3 := u16(pixel.b) * 7
+ i4 := u16(pixel.a) * 11
+
+ return u8((i1 + i2 + i3 + i4) & 63)
+}
+
+@(init, private)
+_register :: proc() {
+ image.register(.QOI, load_from_bytes, destroy)
+}
\ No newline at end of file diff --git a/core/image/tga/tga.odin b/core/image/tga/tga.odin new file mode 100644 index 000000000..67a088eb5 --- /dev/null +++ b/core/image/tga/tga.odin @@ -0,0 +1,101 @@ +/* + Copyright 2022 Jeroen van Rijn <nom@duclavier.com>. + Made available under Odin's BSD-3 license. + + List of contributors: + Jeroen van Rijn: Initial implementation. +*/ + + +// package tga implements a TGA image writer for 8-bit RGB and RGBA images. +package tga + +import "core:mem" +import "core:image" +import "core:bytes" +import "core:os" + +Error :: image.Error +Image :: image.Image +Options :: image.Options + +RGB_Pixel :: image.RGB_Pixel +RGBA_Pixel :: image.RGBA_Pixel + +save_to_memory :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + if img == nil { + return .Invalid_Input_Image + } + + if output == nil { + return .Invalid_Output + } + + pixels := img.width * img.height + if pixels == 0 || pixels > image.MAX_DIMENSIONS || img.width > 65535 || img.height > 65535 { + return .Invalid_Input_Image + } + + // Our TGA writer supports only 8-bit images with 3 or 4 channels. + if img.depth != 8 || img.channels < 3 || img.channels > 4 { + return .Invalid_Input_Image + } + + if img.channels * pixels != len(img.pixels.buf) { + return .Invalid_Input_Image + } + + written := 0 + + // Calculate and allocate necessary space. + necessary := pixels * img.channels + size_of(image.TGA_Header) + + if !resize(&output.buf, necessary) { + return .Unable_To_Allocate_Or_Resize + } + + header := image.TGA_Header{ + data_type_code = 0x02, // Color, uncompressed. + dimensions = {u16le(img.width), u16le(img.height)}, + bits_per_pixel = u8(img.depth * img.channels), + image_descriptor = 1 << 5, // Origin is top left. + } + header_bytes := transmute([size_of(image.TGA_Header)]u8)header + + copy(output.buf[written:], header_bytes[:]) + written += size_of(image.TGA_Header) + + /* + Encode loop starts here. + */ + if img.channels == 3 { + pix := mem.slice_data_cast([]RGB_Pixel, img.pixels.buf[:]) + out := mem.slice_data_cast([]RGB_Pixel, output.buf[written:]) + for p, i in pix { + out[i] = p.bgr + } + } else if img.channels == 4 { + pix := mem.slice_data_cast([]RGBA_Pixel, img.pixels.buf[:]) + out := mem.slice_data_cast([]RGBA_Pixel, output.buf[written:]) + for p, i in pix { + out[i] = p.bgra + } + } + return nil +} + +save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + out := &bytes.Buffer{} + defer bytes.buffer_destroy(out) + + save_to_memory(out, img, options) or_return + write_ok := os.write_entire_file(output, out.buf[:]) + + return nil if write_ok else .Unable_To_Write_File +} + +save :: proc{save_to_memory, save_to_file}
\ No newline at end of file diff --git a/core/image/which.odin b/core/image/which.odin new file mode 100644 index 000000000..ab608174f --- /dev/null +++ b/core/image/which.odin @@ -0,0 +1,179 @@ +package image + +import "core:os" + +Which_File_Type :: enum { + Unknown, + + BMP, + DjVu, // AT&T DjVu file format + EXR, + FLIF, + GIF, + HDR, // Radiance RGBE HDR + ICNS, // Apple Icon Image + JPEG, + JPEG_2000, + JPEG_XL, + NetPBM, // NetPBM family + PIC, // Softimage PIC + PNG, // Portable Network Graphics + PSD, // Photoshop PSD + QOI, // Quite Okay Image + SGI_RGB, // Silicon Graphics Image RGB file format + Sun_Rast, // Sun Raster Graphic + TGA, // Targa Truevision + TIFF, // Tagged Image File Format + WebP, + XBM, // X BitMap +} + +which :: proc{ + which_bytes, + which_file, +} + +which_bytes :: proc(data: []byte) -> Which_File_Type { + test_tga :: proc(s: string) -> bool { + get8 :: #force_inline proc(s: ^string) -> u8 { + v := s[0] + s^ = s[1:] + return v + } + get16le :: #force_inline proc(s: ^string) -> u16 { + v := u16(s[0]) | u16(s[1])<<16 + s^ = s[2:] + return v + } + s := s + s = s[1:] // skip offset + + color_type := get8(&s) + if color_type > 1 { + return false + } + image_type := get8(&s) // image type + if color_type == 1 { // Colormap (Paletted) Image + if image_type != 1 && image_type != 9 { // color type requires 1 or 9 + return false + } + s = s[4:] // skip index of first colormap + bpcme := get8(&s) // check bits per colormap entry + if bpcme != 8 && bpcme != 15 && bpcme != 16 && bpcme != 24 && bpcme != 32 { + return false + } + s = s[4:] // skip image origin (x, y) + } else { // Normal image without colormap + if image_type != 2 && image_type != 3 && image_type != 10 && image_type != 11 { + return false + } + s = s[9:] // skip colormap specification + } + if get16le(&s) < 1 || get16le(&s) < 1 { // test width and height + return false + } + bpp := get8(&s) // bits per pixel + if color_type == 1 && bpp != 8 && bpp != 16 { + return false + } + if bpp != 8 && bpp != 15 && bpp != 16 && bpp != 24 && bpp != 32 { + return false + } + return true + } + + header: [128]byte + copy(header[:], data) + s := string(header[:]) + + switch { + case s[:2] == "BM": + return .BMP + case s[:8] == "AT&TFORM": + switch s[12:16] { + case "DJVU", "DJVM": + return .DjVu + } + case s[:4] == "\x76\x2f\x31\x01": + return .EXR + case s[:6] == "GIF87a", s[:6] == "GIF89a": + return .GIF + case s[6:10] == "JFIF", s[6:10] == "Exif": + return .JPEG + case s[:3] == "\xff\xd8\xff": + switch s[4] { + case 0xdb, 0xee, 0xe1, 0xe0: + return .JPEG + } + switch { + case s[:12] == "\xff\xd8\xff\xe0\x00\x10\x4a\x46\x49\x46\x00\x01": + return .JPEG + } + case s[:4] == "\xff\x4f\xff\x51", s[:12] == "\x00\x00\x00\x0c\x6a\x50\x20\x20\x0d\x0a\x87\x0a": + return .JPEG_2000 + case s[:12] == "\x00\x00\x00\x0c\x4a\x58\x4c\x20\x0d\x0a\x87\x0a": + return .JPEG_XL + case s[0] == 'P': + switch s[2] { + case '\t', '\n', '\r': + switch s[1] { + case '1', '4': // PBM + return .NetPBM + case '2', '5': // PGM + return .NetPBM + case '3', '6': // PPM + return .NetPBM + case '7': // PAM + return .NetPBM + case 'F', 'f': // PFM + return .NetPBM + } + } + case s[:8] == "\x89PNG\r\n\x1a\n": + return .PNG + case s[:4] == "qoif": + return .QOI + case s[:2] == "\x01\xda": + return .SGI_RGB + case s[:4] == "\x59\xA6\x6A\x95": + return .Sun_Rast + case s[:4] == "MM\x2a\x00", s[:4] == "II\x00\x2A": + return .TIFF + case s[:4] == "RIFF" && s[8:12] == "WEBP": + return .WebP + case s[:8] == "#define ": + return .XBM + + case s[:11] == "#?RADIANCE\n", s[:7] == "#?RGBE\n": + return .HDR + case s[:4] == "\x38\x42\x50\x53": + return .PSD + case s[:4] != "\x53\x80\xF6\x34" && s[88:92] == "PICT": + return .PIC + case s[:4] == "\x69\x63\x6e\x73": + return .ICNS + case s[:4] == "\x46\x4c\x49\x46": + return .FLIF + case: + // More complex formats + if test_tga(s) { + return .TGA + } + + + } + return .Unknown +} + + +which_file :: proc(path: string) -> Which_File_Type { + f, err := os.open(path) + if err != 0 { + return .Unknown + } + header: [128]byte + os.read(f, header[:]) + file_type := which_bytes(header[:]) + os.close(f) + return file_type +}
\ No newline at end of file diff --git a/core/intrinsics/intrinsics.odin b/core/intrinsics/intrinsics.odin index a25e9783d..85859e8c3 100644 --- a/core/intrinsics/intrinsics.odin +++ b/core/intrinsics/intrinsics.odin @@ -41,6 +41,10 @@ mem_copy_non_overlapping :: proc(dst, src: rawptr, len: int) --- mem_zero :: proc(ptr: rawptr, len: int) --- mem_zero_volatile :: proc(ptr: rawptr, len: int) --- +// prefer [^]T operations if possible +ptr_offset :: proc(ptr: ^$T, offset: int) -> ^T --- +ptr_sub :: proc(a, b: ^$T) -> int --- + unaligned_load :: proc(src: ^$T) -> T --- unaligned_store :: proc(dst: ^$T, val: T) -> T --- @@ -82,20 +86,21 @@ atomic_store_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) --- atomic_load :: proc(dst: ^$T) -> T --- atomic_load_explicit :: proc(dst: ^$T, order: Atomic_Memory_Order) -> T --- -atomic_add :: proc(dst; ^$T, val: T) -> T --- -atomic_add_explicit :: proc(dst; ^$T, val: T, order: Atomic_Memory_Order) -> T --- -atomic_sub :: proc(dst; ^$T, val: T) -> T --- -atomic_sub_explicit :: proc(dst; ^$T, val: T, order: Atomic_Memory_Order) -> T --- -atomic_and :: proc(dst; ^$T, val: T) -> T --- -atomic_and_explicit :: proc(dst; ^$T, val: T, order: Atomic_Memory_Order) -> T --- -atomic_nand :: proc(dst; ^$T, val: T) -> T --- -atomic_nand_explicit :: proc(dst; ^$T, val: T, order: Atomic_Memory_Order) -> T --- -atomic_or :: proc(dst; ^$T, val: T) -> T --- -atomic_or_explicit :: proc(dst; ^$T, val: T, order: Atomic_Memory_Order) -> T --- -atomic_xor :: proc(dst; ^$T, val: T) -> T --- -atomic_xor_explicit :: proc(dst; ^$T, val: T, order: Atomic_Memory_Order) -> T --- -atomic_exchange :: proc(dst; ^$T, val: T) -> T --- -atomic_exchange_explicit :: proc(dst; ^$T, val: T, order: Atomic_Memory_Order) -> T --- +// fetch then operator +atomic_add :: proc(dst: ^$T, val: T) -> T --- +atomic_add_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) -> T --- +atomic_sub :: proc(dst: ^$T, val: T) -> T --- +atomic_sub_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) -> T --- +atomic_and :: proc(dst: ^$T, val: T) -> T --- +atomic_and_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) -> T --- +atomic_nand :: proc(dst: ^$T, val: T) -> T --- +atomic_nand_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) -> T --- +atomic_or :: proc(dst: ^$T, val: T) -> T --- +atomic_or_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) -> T --- +atomic_xor :: proc(dst: ^$T, val: T) -> T --- +atomic_xor_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) -> T --- +atomic_exchange :: proc(dst: ^$T, val: T) -> T --- +atomic_exchange_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) -> T --- atomic_compare_exchange_strong :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok --- atomic_compare_exchange_strong_explicit :: proc(dst: ^$T, old, new: T, success, failure: Atomic_Memory_Order) -> (T, bool) #optional_ok --- @@ -119,22 +124,24 @@ type_is_string :: proc($T: typeid) -> bool --- type_is_typeid :: proc($T: typeid) -> bool --- type_is_any :: proc($T: typeid) -> bool --- -type_is_endian_platform :: proc($T: typeid) -> bool --- -type_is_endian_little :: proc($T: typeid) -> bool --- -type_is_endian_big :: proc($T: typeid) -> bool --- -type_is_unsigned :: proc($T: typeid) -> bool --- -type_is_numeric :: proc($T: typeid) -> bool --- -type_is_ordered :: proc($T: typeid) -> bool --- -type_is_ordered_numeric :: proc($T: typeid) -> bool --- -type_is_indexable :: proc($T: typeid) -> bool --- -type_is_sliceable :: proc($T: typeid) -> bool --- -type_is_comparable :: proc($T: typeid) -> bool --- -type_is_simple_compare :: proc($T: typeid) -> bool --- // easily compared using memcmp (== and !=) -type_is_dereferenceable :: proc($T: typeid) -> bool --- -type_is_valid_map_key :: proc($T: typeid) -> bool --- +type_is_endian_platform :: proc($T: typeid) -> bool --- +type_is_endian_little :: proc($T: typeid) -> bool --- +type_is_endian_big :: proc($T: typeid) -> bool --- +type_is_unsigned :: proc($T: typeid) -> bool --- +type_is_numeric :: proc($T: typeid) -> bool --- +type_is_ordered :: proc($T: typeid) -> bool --- +type_is_ordered_numeric :: proc($T: typeid) -> bool --- +type_is_indexable :: proc($T: typeid) -> bool --- +type_is_sliceable :: proc($T: typeid) -> bool --- +type_is_comparable :: proc($T: typeid) -> bool --- +type_is_simple_compare :: proc($T: typeid) -> bool --- // easily compared using memcmp (== and !=) +type_is_dereferenceable :: proc($T: typeid) -> bool --- +type_is_valid_map_key :: proc($T: typeid) -> bool --- +type_is_valid_matrix_elements :: proc($T: typeid) -> bool --- type_is_named :: proc($T: typeid) -> bool --- type_is_pointer :: proc($T: typeid) -> bool --- +type_is_multi_pointer :: proc($T: typeid) -> bool --- type_is_array :: proc($T: typeid) -> bool --- type_is_enumerated_array :: proc($T: typeid) -> bool --- type_is_slice :: proc($T: typeid) -> bool --- @@ -146,6 +153,7 @@ type_is_enum :: proc($T: typeid) -> bool --- type_is_proc :: proc($T: typeid) -> bool --- type_is_bit_set :: proc($T: typeid) -> bool --- type_is_simd_vector :: proc($T: typeid) -> bool --- +type_is_matrix :: proc($T: typeid) -> bool --- type_has_nil :: proc($T: typeid) -> bool --- @@ -153,6 +161,7 @@ type_is_specialization_of :: proc($T, $S: typeid) -> bool --- type_is_variant_of :: proc($U, $V: typeid) -> bool where type_is_union(U) --- type_has_field :: proc($T: typeid, $name: string) -> bool --- +type_field_type :: proc($T: typeid, $name: string) -> typeid --- type_proc_parameter_count :: proc($T: typeid) -> int where type_is_proc(T) --- type_proc_return_count :: proc($T: typeid) -> int where type_is_proc(T) --- @@ -160,20 +169,41 @@ type_proc_return_count :: proc($T: typeid) -> int where type_is_proc(T) --- type_proc_parameter_type :: proc($T: typeid, index: int) -> typeid where type_is_proc(T) --- type_proc_return_type :: proc($T: typeid, index: int) -> typeid where type_is_proc(T) --- +type_struct_field_count :: proc($T: typeid) -> int where type_is_struct(T) --- + type_polymorphic_record_parameter_count :: proc($T: typeid) -> typeid --- type_polymorphic_record_parameter_value :: proc($T: typeid, index: int) -> $V --- +type_is_specialized_polymorphic_record :: proc($T: typeid) -> bool --- +type_is_unspecialized_polymorphic_record :: proc($T: typeid) -> bool --- + +type_is_subtype_of :: proc($T, $U: typeid) -> bool --- type_field_index_of :: proc($T: typeid, $name: string) -> uintptr --- type_equal_proc :: proc($T: typeid) -> (equal: proc "contextless" (rawptr, rawptr) -> bool) where type_is_comparable(T) --- type_hasher_proc :: proc($T: typeid) -> (hasher: proc "contextless" (data: rawptr, seed: uintptr) -> uintptr) where type_is_comparable(T) --- +constant_utf16_cstring :: proc($literal: string) -> [^]u16 --- // WASM targets only wasm_memory_grow :: proc(index, delta: uintptr) -> int --- wasm_memory_size :: proc(index: uintptr) -> int --- + +// Darwin targets only +objc_object :: struct{} +objc_selector :: struct{} +objc_class :: struct{} +objc_id :: ^objc_object +objc_SEL :: ^objc_selector +objc_Class :: ^objc_class + +objc_find_selector :: proc($name: string) -> objc_SEL --- +objc_register_selector :: proc($name: string) -> objc_SEL --- +objc_find_class :: proc($name: string) -> objc_Class --- +objc_register_class :: proc($name: string) -> objc_Class --- + // Internal compiler use only __entry_point :: proc() ---
\ No newline at end of file diff --git a/core/io/io.odin b/core/io/io.odin index e9d839efb..3ad34d607 100644 --- a/core/io/io.odin +++ b/core/io/io.odin @@ -4,7 +4,6 @@ package io import "core:intrinsics" -import "core:runtime" import "core:unicode/utf8" // Seek whence values @@ -254,11 +253,7 @@ read_at :: proc(r: Reader_At, p: []byte, offset: i64, n_read: ^int = nil) -> (n: return 0, .Empty } - curr_offset: i64 - curr_offset, err = r->impl_seek(offset, .Current) - if err != nil { - return 0, err - } + curr_offset := r->impl_seek(offset, .Current) or_return n, err = r->impl_read(p) _, err1 := r->impl_seek(curr_offset, .Start) @@ -552,7 +547,7 @@ _copy_buffer :: proc(dst: Writer, src: Reader, buf: []byte) -> (written: i64, er } } // NOTE(bill): alloca is fine here - buf = transmute([]byte)runtime.Raw_Slice{intrinsics.alloca(size, 2*align_of(rawptr)), size} + buf = intrinsics.alloca(size, 2*align_of(rawptr))[:size] } for { nr, er := read(src, buf) diff --git a/core/log/file_console_logger.odin b/core/log/file_console_logger.odin index cc019617f..7f0d3b07a 100644 --- a/core/log/file_console_logger.odin +++ b/core/log/file_console_logger.odin @@ -67,7 +67,7 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string h = data.file_handle } backing: [1024]byte //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths. - buf := strings.builder_from_slice(backing[:]) + buf := strings.builder_from_bytes(backing[:]) do_level_header(options, level, &buf) diff --git a/core/math/linalg/specific.odin b/core/math/linalg/specific.odin index cb007bd91..a4aaeb012 100644 --- a/core/math/linalg/specific.odin +++ b/core/math/linalg/specific.odin @@ -479,21 +479,21 @@ angle_from_quaternion_f16 :: proc(q: Quaternionf16) -> f16 { return math.asin(q.x*q.x + q.y*q.y + q.z*q.z) * 2 } - return math.cos(q.x) * 2 + return math.acos(q.w) * 2 } angle_from_quaternion_f32 :: proc(q: Quaternionf32) -> f32 { if abs(q.w) > math.SQRT_THREE*0.5 { return math.asin(q.x*q.x + q.y*q.y + q.z*q.z) * 2 } - return math.cos(q.x) * 2 + return math.acos(q.w) * 2 } angle_from_quaternion_f64 :: proc(q: Quaternionf64) -> f64 { if abs(q.w) > math.SQRT_THREE*0.5 { return math.asin(q.x*q.x + q.y*q.y + q.z*q.z) * 2 } - return math.cos(q.x) * 2 + return math.acos(q.w) * 2 } angle_from_quaternion :: proc{ angle_from_quaternion_f16, diff --git a/core/math/rand/distributions.odin b/core/math/rand/distributions.odin new file mode 100644 index 000000000..ada89afad --- /dev/null +++ b/core/math/rand/distributions.odin @@ -0,0 +1,312 @@ +package rand + +import "core:math" + +float64_uniform :: float64_range +float32_uniform :: float32_range + +// Triangular Distribution +// See: http://wikipedia.org/wiki/Triangular_distribution +float64_triangular :: proc(lo, hi: f64, mode: Maybe(f64), r: ^Rand = nil) -> f64 { + if hi-lo == 0 { + return lo + } + lo, hi := lo, hi + u := float64(r) + c := f64(0.5) if mode == nil else clamp((mode.?-lo) / (hi-lo), 0, 1) + if u > c { + u = 1-u + c = 1-c + lo, hi = hi, lo + } + return lo + (hi - lo) * math.sqrt(u * c) + +} +// Triangular Distribution +// See: http://wikipedia.org/wiki/Triangular_distribution +float32_triangular :: proc(lo, hi: f32, mode: Maybe(f32), r: ^Rand = nil) -> f32 { + if hi-lo == 0 { + return lo + } + lo, hi := lo, hi + u := float32(r) + c := f32(0.5) if mode == nil else clamp((mode.?-lo) / (hi-lo), 0, 1) + if u > c { + u = 1-u + c = 1-c + lo, hi = hi, lo + } + return lo + (hi - lo) * math.sqrt(u * c) +} + + +// Normal/Gaussian Distribution +float64_normal :: proc(mean, stddev: f64, r: ^Rand = nil) -> f64 { + return norm_float64(r) * stddev + mean +} +// Normal/Gaussian Distribution +float32_normal :: proc(mean, stddev: f32, r: ^Rand = nil) -> f32 { + return f32(float64_normal(f64(mean), f64(stddev), r)) +} + + +// Log Normal Distribution +float64_log_normal :: proc(mean, stddev: f64, r: ^Rand = nil) -> f64 { + return math.exp(float64_normal(mean, stddev, r)) +} +// Log Normal Distribution +float32_log_normal :: proc(mean, stddev: f32, r: ^Rand = nil) -> f32 { + return f32(float64_log_normal(f64(mean), f64(stddev), r)) +} + + +// Exponential Distribution +// `lambda` is 1.0/(desired mean). It should be non-zero. +// Return values range from +// 0 to positive infinity if lambda > 0 +// negative infinity to 0 if lambda <= 0 +float64_exponential :: proc(lambda: f64, r: ^Rand = nil) -> f64 { + return - math.ln(1 - float64(r)) / lambda +} +// Exponential Distribution +// `lambda` is 1.0/(desired mean). It should be non-zero. +// Return values range from +// 0 to positive infinity if lambda > 0 +// negative infinity to 0 if lambda <= 0 +float32_exponential :: proc(lambda: f32, r: ^Rand = nil) -> f32 { + return f32(float64_exponential(f64(lambda), r)) +} + + +// Gamma Distribution (NOT THE GAMMA FUNCTION) +// +// Required: alpha > 0 and beta > 0 +// +// math.pow(x, alpha-1) * math.exp(-x / beta) +// pdf(x) = -------------------------------------------- +// math.gamma(alpha) * math.pow(beta, alpha) +// +// mean is alpha*beta, variance is math.pow(alpha*beta, 2) +float64_gamma :: proc(alpha, beta: f64, r: ^Rand = nil) -> f64 { + if alpha <= 0 || beta <= 0 { + panic(#procedure + ": alpha and beta must be > 0.0") + } + + LOG4 :: 1.3862943611198906188344642429163531361510002687205105082413600189 + SG_MAGIC_CONST :: 2.5040773967762740733732583523868748412194809812852436493487 + + switch { + case alpha > 1: + // R.C.H. Cheng, "The generation of Gamma variables with non-integral shape parameters", Applied Statistics, (1977), 26, No. 1, p71-74 + + ainv := math.sqrt(2 * alpha - 1) + bbb := alpha - LOG4 + ccc := alpha + ainv + for { + u1 := float64(r) + if !(1e-7 < u1 && u1 < 0.9999999) { + continue + } + u2 := 1 - float64(r) + v := math.ln(u1 / (1 - u1)) / ainv + x := alpha * math.exp(v) + z := u1 * u1 * u2 + t := bbb + ccc*v - x + if t + SG_MAGIC_CONST - 4.5 * z >= 0 || t >= math.ln(z) { + return x * beta + } + } + case alpha == 1: + // float64_exponential(1/beta) + return -math.ln(1 - float64(r)) * beta + case: + // ALGORITHM GS of Statistical Computing - Kennedy & Gentle + x: f64 + for { + u := float64(r) + b := (math.e + alpha) / math.e + p := b * u + if p <= 1 { + x = math.pow(p, 1/alpha) + } else { + x = -math.ln((b - p) / alpha) + } + u1 := float64(r) + if p > 1 { + if u1 <= math.pow(x, alpha-1) { + break + } + } else if u1 <= math.exp(-x) { + break + } + } + return x * beta + } +} +// Gamma Distribution (NOT THE GAMMA FUNCTION) +// +// Required: alpha > 0 and beta > 0 +// +// math.pow(x, alpha-1) * math.exp(-x / beta) +// pdf(x) = -------------------------------------------- +// math.gamma(alpha) * math.pow(beta, alpha) +// +// mean is alpha*beta, variance is math.pow(alpha*beta, 2) +float32_gamma :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 { + return f32(float64_gamma(f64(alpha), f64(beta), r)) +} + + +// Beta Distribution +// +// Required: alpha > 0 and beta > 0 +// +// Return values range between 0 and 1 +float64_beta :: proc(alpha, beta: f64, r: ^Rand = nil) -> f64 { + if alpha <= 0 || beta <= 0 { + panic(#procedure + ": alpha and beta must be > 0.0") + } + // Knuth Vol 2 Ed 3 pg 134 "the beta distribution" + y := float64_gamma(alpha, 1.0, r) + if y != 0 { + return y / (y + float64_gamma(beta, 1.0, r)) + } + return 0 +} +// Beta Distribution +// +// Required: alpha > 0 and beta > 0 +// +// Return values range between 0 and 1 +float32_beta :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 { + return f32(float64_beta(f64(alpha), f64(beta), r)) +} + + +// Pareto distribution, `alpha` is the shape parameter. +// https://wikipedia.org/wiki/Pareto_distribution +float64_pareto :: proc(alpha: f64, r: ^Rand = nil) -> f64 { + return math.pow(1 - float64(r), -1.0 / alpha) +} +// Pareto distribution, `alpha` is the shape parameter. +// https://wikipedia.org/wiki/Pareto_distribution +float32_pareto :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 { + return f32(float64_pareto(f64(alpha), r)) +} + + +// Weibull distribution, `alpha` is the scale parameter, `beta` is the shape parameter. +float64_weibull :: proc(alpha, beta: f64, r: ^Rand = nil) -> f64 { + u := 1 - float64(r) + return alpha * math.pow(-math.ln(u), 1.0/beta) +} +// Weibull distribution, `alpha` is the scale parameter, `beta` is the shape parameter. +float32_weibull :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 { + return f32(float64_weibull(f64(alpha), f64(beta), r)) +} + + +// Circular Data (von Mises) Distribution +// `mean_angle` is the in mean angle between 0 and 2pi radians +// `kappa` is the concentration parameter which must be >= 0 +// When `kappa` is zero, the Distribution is a uniform Distribution over the range 0 to 2pi +float64_von_mises :: proc(mean_angle, kappa: f64, r: ^Rand = nil) -> f64 { + // Fisher, N.I., "Statistical Analysis of Circular Data", Cambridge University Press, 1993. + + mu := mean_angle + if kappa <= 1e-6 { + return math.TAU * float64(r) + } + + s := 0.5 / kappa + t := s + math.sqrt(1 + s*s) + z: f64 + for { + u1 := float64(r) + z = math.cos(math.TAU * 0.5 * u1) + + d := z / (t + z) + u2 := float64(r) + if u2 < 1 - d*d || u2 <= (1-d)*math.exp(d) { + break + } + } + + q := 1.0 / t + f := (q + z) / (1 + q*z) + u3 := float64(r) + if u3 > 0.5 { + return math.mod(mu + math.acos(f), math.TAU) + } else { + return math.mod(mu - math.acos(f), math.TAU) + } +} +// Circular Data (von Mises) Distribution +// `mean_angle` is the in mean angle between 0 and 2pi radians +// `kappa` is the concentration parameter which must be >= 0 +// When `kappa` is zero, the Distribution is a uniform Distribution over the range 0 to 2pi +float32_von_mises :: proc(mean_angle, kappa: f32, r: ^Rand = nil) -> f32 { + return f32(float64_von_mises(f64(mean_angle), f64(kappa), r)) +} + + +// Cauchy-Lorentz Distribution +// `x_0` is the location, `gamma` is the scale where `gamma` > 0 +float64_cauchy_lorentz :: proc(x_0, gamma: f64, r: ^Rand = nil) -> f64 { + assert(gamma > 0) + + // Calculated from the inverse CDF + + return math.tan(math.PI * (float64(r) - 0.5))*gamma + x_0 +} +// Cauchy-Lorentz Distribution +// `x_0` is the location, `gamma` is the scale where `gamma` > 0 +float32_cauchy_lorentz :: proc(x_0, gamma: f32, r: ^Rand = nil) -> f32 { + return f32(float64_cauchy_lorentz(f64(x_0), f64(gamma), r)) +} + + +// Log Cauchy-Lorentz Distribution +// `x_0` is the location, `gamma` is the scale where `gamma` > 0 +float64_log_cauchy_lorentz :: proc(x_0, gamma: f64, r: ^Rand = nil) -> f64 { + assert(gamma > 0) + return math.exp(math.tan(math.PI * (float64(r) - 0.5))*gamma + x_0) +} +// Log Cauchy-Lorentz Distribution +// `x_0` is the location, `gamma` is the scale where `gamma` > 0 +float32_log_cauchy_lorentz :: proc(x_0, gamma: f32, r: ^Rand = nil) -> f32 { + return f32(float64_log_cauchy_lorentz(f64(x_0), f64(gamma), r)) +} + + +// Laplace Distribution +// `b` is the scale where `b` > 0 +float64_laplace :: proc(mean, b: f64, r: ^Rand = nil) -> f64 { + assert(b > 0) + p := float64(r)-0.5 + return -math.sign(p)*math.ln(1 - 2*abs(p))*b + mean +} +// Laplace Distribution +// `b` is the scale where `b` > 0 +float32_laplace :: proc(mean, b: f32, r: ^Rand = nil) -> f32 { + return f32(float64_laplace(f64(mean), f64(b), r)) +} + + +// Gompertz Distribution +// `eta` is the shape, `b` is the scale +// Both `eta` and `b` must be > 0 +float64_gompertz :: proc(eta, b: f64, r: ^Rand = nil) -> f64 { + if eta <= 0 || b <= 0 { + panic(#procedure + ": eta and b must be > 0.0") + } + + p := float64(r) + return math.ln(1 - math.ln(1 - p)/eta)/b +} +// Gompertz Distribution +// `eta` is the shape, `b` is the scale +// Both `eta` and `b` must be > 0 +float32_gompertz :: proc(eta, b: f32, r: ^Rand = nil) -> f32 { + return f32(float64_gompertz(f64(eta), f64(b), r)) +} diff --git a/core/math/rand/rand.odin b/core/math/rand/rand.odin index 19e475835..f7dfcb3b8 100644 --- a/core/math/rand/rand.odin +++ b/core/math/rand/rand.odin @@ -5,6 +5,7 @@ import "core:intrinsics" Rand :: struct { state: u64, inc: u64, + is_system: bool, } @@ -29,6 +30,16 @@ init :: proc(r: ^Rand, seed: u64) { _random(r) } +init_as_system :: proc(r: ^Rand) { + if !#defined(_system_random) { + panic(#procedure + " is not supported on this platform yet") + } + r.state = 0 + r.inc = 0 + r.is_system = true +} + +@(private) _random :: proc(r: ^Rand) -> u32 { r := r if r == nil { @@ -36,6 +47,12 @@ _random :: proc(r: ^Rand) -> u32 { // enforce the global random state if necessary with `nil` r = &global_rand } + when #defined(_system_random) { + if r.is_system { + return _system_random() + } + } + old_state := r.state r.state = old_state * 6364136223846793005 + (r.inc|1) xor_shifted := u32(((old_state>>18) ~ old_state) >> 27) @@ -119,13 +136,14 @@ int_max :: proc(n: int, r: ^Rand = nil) -> int { } } +// Uniform random distribution [0, 1) float64 :: proc(r: ^Rand = nil) -> f64 { return f64(int63_max(1<<53, r)) / (1 << 53) } +// Uniform random distribution [0, 1) float32 :: proc(r: ^Rand = nil) -> f32 { return f32(float64(r)) } float64_range :: proc(lo, hi: f64, r: ^Rand = nil) -> f64 { return (hi-lo)*float64(r) + lo } float32_range :: proc(lo, hi: f32, r: ^Rand = nil) -> f32 { return (hi-lo)*float32(r) + lo } - read :: proc(p: []byte, r: ^Rand = nil) -> (n: int) { pos := i8(0) val := i64(0) diff --git a/core/math/rand/system_darwin.odin b/core/math/rand/system_darwin.odin new file mode 100644 index 000000000..f51e4473e --- /dev/null +++ b/core/math/rand/system_darwin.odin @@ -0,0 +1,21 @@ +package rand + +import "core:sys/darwin" + +_system_random :: proc() -> u32 { + for { + value: u32 + ret := darwin.syscall_getentropy(([^]u8)(&value), 4) + if ret < 0 { + switch ret { + case -4: // EINTR + continue + case -78: // ENOSYS + panic("getentropy not available in kernel") + case: + panic("getentropy failed") + } + } + return value + } +}
\ No newline at end of file diff --git a/core/math/rand/system_linux.odin b/core/math/rand/system_linux.odin new file mode 100644 index 000000000..bfdc8872b --- /dev/null +++ b/core/math/rand/system_linux.odin @@ -0,0 +1,27 @@ +package rand + +import "core:sys/unix" + +_system_random :: proc() -> u32 { + for { + value: u32 + ret := unix.sys_getrandom(([^]u8)(&value), 4, 0) + if ret < 0 { + switch ret { + case -4: // EINTR + // Call interupted by a signal handler, just retry the request. + continue + case -38: // ENOSYS + // The kernel is apparently prehistoric (< 3.17 circa 2014) + // and does not support getrandom. + panic("getrandom not available in kernel") + case: + // All other failures are things that should NEVER happen + // unless the kernel interface changes (ie: the Linux + // developers break userland). + panic("getrandom failed") + } + } + return value + } +}
\ No newline at end of file diff --git a/core/math/rand/system_windows.odin b/core/math/rand/system_windows.odin new file mode 100644 index 000000000..ee9cd0294 --- /dev/null +++ b/core/math/rand/system_windows.odin @@ -0,0 +1,12 @@ +package rand + +import win32 "core:sys/windows" + +_system_random :: proc() -> u32 { + value: u32 + status := win32.BCryptGenRandom(nil, ([^]u8)(&value), 4, win32.BCRYPT_USE_SYSTEM_PREFERRED_RNG) + if status < 0 { + panic("BCryptGenRandom failed") + } + return value +}
\ No newline at end of file diff --git a/core/mem/allocators.odin b/core/mem/allocators.odin index 4954122ed..d006e4574 100644 --- a/core/mem/allocators.odin +++ b/core/mem/allocators.odin @@ -6,24 +6,7 @@ import "core:runtime" nil_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, size, alignment: int, old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { - switch mode { - case .Alloc: - return nil, .Out_Of_Memory - case .Free: - return nil, .None - case .Free_All: - return nil, .Mode_Not_Implemented - case .Resize: - if size == 0 { - return nil, .None - } - return nil, .Out_Of_Memory - case .Query_Features: - return nil, .Mode_Not_Implemented - case .Query_Info: - return nil, .Mode_Not_Implemented - } - return nil, .None + return nil, nil } nil_allocator :: proc() -> Allocator { @@ -679,6 +662,7 @@ dynamic_pool_destroy :: proc(using pool: ^Dynamic_Pool) { dynamic_pool_free_all(pool) delete(unused_blocks) delete(used_blocks) + delete(out_band_allocations) zero(pool, size_of(pool^)) } @@ -763,6 +747,8 @@ dynamic_pool_reset :: proc(using pool: ^Dynamic_Pool) { free(a, block_allocator) } clear(&out_band_allocations) + + bytes_left = 0 // Make new allocations call `cycle_new_block` again. } dynamic_pool_free_all :: proc(using pool: ^Dynamic_Pool) { @@ -872,7 +858,7 @@ tracking_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, result: []byte err: Allocator_Error - if mode == .Free && old_memory not_in data.allocation_map { + if mode == .Free && old_memory != nil && old_memory not_in data.allocation_map { append(&data.bad_free_array, Tracking_Allocator_Bad_Free_Entry{ memory = old_memory, location = loc, diff --git a/core/mem/mem.odin b/core/mem/mem.odin index 817c0ea5f..b33f8ba13 100644 --- a/core/mem/mem.odin +++ b/core/mem/mem.odin @@ -3,6 +3,12 @@ package mem import "core:runtime" import "core:intrinsics" +Byte :: 1 +Kilobyte :: 1024 * Byte +Megabyte :: 1024 * Kilobyte +Gigabyte :: 1024 * Megabyte +Terabyte :: 1024 * Gigabyte + set :: proc "contextless" (data: rawptr, value: byte, len: int) -> rawptr { return runtime.memset(data, i32(value), len) } @@ -166,7 +172,7 @@ slice_data_cast :: proc "contextless" ($T: typeid/[]$A, slice: $S/[]$B) -> T { slice_to_components :: proc "contextless" (slice: $E/[]$T) -> (data: ^T, len: int) { s := transmute(Raw_Slice)slice - return s.data, s.len + return (^T)(s.data), s.len } buffer_from_slice :: proc "contextless" (backing: $T/[]$E) -> [dynamic]E { @@ -192,11 +198,6 @@ any_to_bytes :: proc "contextless" (val: any) -> []byte { } -kilobytes :: proc "contextless" (x: int) -> int { return (x) * 1024 } -megabytes :: proc "contextless" (x: int) -> int { return kilobytes(x) * 1024 } -gigabytes :: proc "contextless" (x: int) -> int { return megabytes(x) * 1024 } -terabytes :: proc "contextless" (x: int) -> int { return gigabytes(x) * 1024 } - is_power_of_two :: proc "contextless" (x: uintptr) -> bool { if x <= 0 { return false diff --git a/core/mem/virtual/virtual.odin b/core/mem/virtual/virtual.odin index 2035171a7..21ab5ef21 100644 --- a/core/mem/virtual/virtual.odin +++ b/core/mem/virtual/virtual.odin @@ -120,7 +120,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: int) do_commit_if_necessary :: proc(block: ^Memory_Block, size: uint) -> (err: Allocator_Error) { if block.committed - block.used < size { pmblock := (^Platform_Memory_Block)(block) - base_offset := uint(uintptr(block) - uintptr(pmblock)) + base_offset := uint(uintptr(pmblock.block.base) - uintptr(pmblock)) platform_total_commit := base_offset + block.used + size assert(pmblock.committed <= pmblock.reserved) diff --git a/core/odin/printer/printer.odin b/core/odin/printer/printer.odin index abda44fa2..807cc99bd 100644 --- a/core/odin/printer/printer.odin +++ b/core/odin/printer/printer.odin @@ -151,7 +151,7 @@ print :: proc(p: ^Printer, file: ^ast.File) -> string { fix_lines(p)
- builder := strings.make_builder(0, mem.megabytes(5), p.allocator)
+ builder := strings.make_builder(0, 5 * mem.Megabyte, p.allocator)
last_line := 0
diff --git a/core/os/dir_windows.odin b/core/os/dir_windows.odin index 3261b8cb3..89a09d403 100644 --- a/core/os/dir_windows.odin +++ b/core/os/dir_windows.odin @@ -13,7 +13,7 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 { return } - path := strings.concatenate({base_path, `\`, win32.utf16_to_utf8(d.cFileName[:])}) + path := strings.concatenate({base_path, `\`, win32.utf16_to_utf8(d.cFileName[:]) or_else ""}) fi.fullpath = path fi.name = basename(path) fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) diff --git a/core/os/env_windows.odin b/core/os/env_windows.odin index 74981bc6e..9a33a0611 100644 --- a/core/os/env_windows.odin +++ b/core/os/env_windows.odin @@ -22,7 +22,7 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin } if n <= u32(len(b)) { - value = win32.utf16_to_utf8(b[:n], allocator) + value, _ = win32.utf16_to_utf8(b[:n], allocator) found = true return } @@ -76,7 +76,7 @@ environ :: proc(allocator := context.allocator) -> []string { if i <= from { break } - append(&r, win32.utf16_to_utf8(envs[from:i], allocator)) + append(&r, win32.utf16_to_utf8(envs[from:i], allocator) or_else "") from = i + 1 } } diff --git a/core/os/file_windows.odin b/core/os/file_windows.odin index a9f78070f..daabe60f0 100644 --- a/core/os/file_windows.odin +++ b/core/os/file_windows.odin @@ -365,7 +365,7 @@ get_current_directory :: proc(allocator := context.allocator) -> string { win32.ReleaseSRWLockExclusive(&cwd_lock) - return win32.utf16_to_utf8(dir_buf_wstr, allocator) + return win32.utf16_to_utf8(dir_buf_wstr, allocator) or_else "" } set_current_directory :: proc(path: string) -> (err: Errno) { diff --git a/core/os/os.odin b/core/os/os.odin index e880ec21e..5e71e720e 100644 --- a/core/os/os.odin +++ b/core/os/os.odin @@ -9,6 +9,10 @@ OS :: ODIN_OS ARCH :: ODIN_ARCH ENDIAN :: ODIN_ENDIAN +SEEK_SET :: 0 +SEEK_CUR :: 1 +SEEK_END :: 2 + write_string :: proc(fd: Handle, str: string) -> (int, Errno) { return write(fd, transmute([]byte)str) } diff --git a/core/os/os2/env_windows.odin b/core/os/os2/env_windows.odin index 1e1ffba4d..f58922fac 100644 --- a/core/os/os2/env_windows.odin +++ b/core/os/os2/env_windows.odin @@ -1,33 +1,35 @@ //+private package os2 -//import "core:runtime" -//import "core:mem" import win32 "core:sys/windows" +import "core:runtime" -_get_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { +_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { if key == "" { return } wkey := win32.utf8_to_wstring(key) - // https://docs.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-getenvironmentvariablew - buf_len := win32.GetEnvironmentVariableW(wkey, nil, 0) - if buf_len == 0 { - return + n := win32.GetEnvironmentVariableW(wkey, nil, 0) + if n == 0 { + err := win32.GetLastError() + if err == win32.ERROR_ENVVAR_NOT_FOUND { + return "", false + } + return "", true } - buf := make([dynamic]u16, buf_len, context.temp_allocator) - n := win32.GetEnvironmentVariableW(wkey, raw_data(buf), buf_len) + b := make([]u16, n+1, _temp_allocator()) + + n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b))) if n == 0 { - if win32.GetLastError() == win32.ERROR_ENVVAR_NOT_FOUND { + err := win32.GetLastError() + if err == win32.ERROR_ENVVAR_NOT_FOUND { return "", false } - value = "" - found = true - return + return "", false } - value = win32.utf16_to_utf8(buf[:n], allocator) + value = win32.utf16_to_utf8(b[:n], allocator) or_else "" found = true return } @@ -36,21 +38,18 @@ _set_env :: proc(key, value: string) -> bool { k := win32.utf8_to_wstring(key) v := win32.utf8_to_wstring(value) - // https://docs.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-setenvironmentvariablew return bool(win32.SetEnvironmentVariableW(k, v)) } _unset_env :: proc(key: string) -> bool { k := win32.utf8_to_wstring(key) - - // https://docs.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-setenvironmentvariablew return bool(win32.SetEnvironmentVariableW(k, nil)) } _clear_env :: proc() { - envs := environ(context.temp_allocator) + envs := environ(_temp_allocator()) for env in envs { - #no_bounds_check for j in 1..<len(env) { + for j in 1..<len(env) { if env[j] == '=' { unset_env(env[0:j]) break @@ -59,34 +58,23 @@ _clear_env :: proc() { } } -_environ :: proc(allocator := context.allocator) -> []string { - envs := ([^]u16)(win32.GetEnvironmentStringsW()) +_environ :: proc(allocator: runtime.Allocator) -> []string { + envs := win32.GetEnvironmentStringsW() if envs == nil { return nil } defer win32.FreeEnvironmentStringsW(envs) - length := 0 - n := 0 - count_loop: for { - if envs[length] == 0 { - n += 1 - if envs[length+1] == 0 { - break count_loop - } - } - - length += 1 - } - - r := make([dynamic]string, 0, n, allocator) - for offset, i := 0, 0; i < length && len(r) < n; i += 1 { - c := envs[i] + r := make([dynamic]string, 0, 50, allocator) + for from, i, p := 0, 0, envs; true; i += 1 { + c := ([^]u16)(p)[i] if c == 0 { - wstr := envs[offset:i] - append(&r, win32.utf16_to_utf8(wstr, allocator)) - i += 1 - offset = i + if i <= from { + break + } + w := ([^]u16)(p)[from:i] + append(&r, win32.utf16_to_utf8(w, allocator) or_else "") + from = i + 1 } } diff --git a/core/os/os2/errors.odin b/core/os/os2/errors.odin index 906d73e12..2cff73ebd 100644 --- a/core/os/os2/errors.odin +++ b/core/os/os2/errors.odin @@ -1,9 +1,10 @@ package os2 import "core:io" +import "core:runtime" General_Error :: enum u32 { - Invalid_Argument, + None, Permission_Denied, Exist, @@ -11,79 +12,78 @@ General_Error :: enum u32 { Closed, Timeout, -} -Platform_Error :: struct { - err: i32, + Invalid_File, + Invalid_Dir, + Invalid_Path, + + Unsupported, } -Error :: union { +Platform_Error :: enum i32 {None=0} + +Error :: union #shared_nil { General_Error, io.Error, + runtime.Allocator_Error, Platform_Error, } #assert(size_of(Error) == size_of(u64)) -Link_Error :: struct { - op: string, - old: string, - new: string, - err: Error, -} - -link_error_delete :: proc(lerr: Maybe(Link_Error)) { - if err, ok := lerr.?; ok { - context.allocator = error_allocator() - delete(err.op) - delete(err.old) - delete(err.new) - } -} - is_platform_error :: proc(ferr: Error) -> (err: i32, ok: bool) { v := ferr.(Platform_Error) or_else {} - return v.err, v.err != 0 + return i32(v), i32(v) != 0 } error_string :: proc(ferr: Error) -> string { - @static general_error_strings := [General_Error]string{ - .Invalid_Argument = "invalid argument", - .Permission_Denied = "permission denied", - .Exist = "file already exists", - .Not_Exist = "file does not exist", - .Closed = "file already closed", - .Timeout = "i/o timeout", - } - - @static io_error_strings := [io.Error]string{ - .None = "", - .EOF = "eof", - .Unexpected_EOF = "unexpected eof", - .Short_Write = "short write", - .Invalid_Write = "invalid write result", - .Short_Buffer = "short buffer", - .No_Progress = "multiple read calls return no data or error", - .Invalid_Whence = "invalid whence", - .Invalid_Offset = "invalid offset", - .Invalid_Unread = "invalid unread", - .Negative_Read = "negative read", - .Negative_Write = "negative write", - .Negative_Count = "negative count", - .Buffer_Full = "buffer full", - .Unknown = "unknown i/o error", - .Empty = "empty i/o error", - } if ferr == nil { return "" } - - switch err in ferr { - case General_Error: return general_error_strings[err] - case io.Error: return io_error_strings[err] - case Platform_Error: return _error_string(err.err) + switch e in ferr { + case General_Error: + switch e { + case .None: return "" + case .Permission_Denied: return "permission denied" + case .Exist: return "file already exists" + case .Not_Exist: return "file does not exist" + case .Closed: return "file already closed" + case .Timeout: return "i/o timeout" + case .Invalid_File: return "invalid file" + case .Invalid_Dir: return "invalid directory" + case .Invalid_Path: return "invalid path" + case .Unsupported: return "unsupported" + } + case io.Error: + switch e { + case .None: return "" + case .EOF: return "eof" + case .Unexpected_EOF: return "unexpected eof" + case .Short_Write: return "short write" + case .Invalid_Write: return "invalid write result" + case .Short_Buffer: return "short buffer" + case .No_Progress: return "multiple read calls return no data or error" + case .Invalid_Whence: return "invalid whence" + case .Invalid_Offset: return "invalid offset" + case .Invalid_Unread: return "invalid unread" + case .Negative_Read: return "negative read" + case .Negative_Write: return "negative write" + case .Negative_Count: return "negative count" + case .Buffer_Full: return "buffer full" + case .Unknown, .Empty: // + } + case runtime.Allocator_Error: + switch e { + case .None: return "" + case .Out_Of_Memory: return "out of memory" + case .Invalid_Pointer: return "invalid allocator pointer" + case .Invalid_Argument: return "invalid allocator argument" + case .Mode_Not_Implemented: return "allocator mode not implemented" + } + case Platform_Error: + return _error_string(i32(e)) } return "unknown error" diff --git a/core/os/os2/errors_linux.odin b/core/os/os2/errors_linux.odin index d9056bd6b..053256fbd 100644 --- a/core/os/os2/errors_linux.odin +++ b/core/os/os2/errors_linux.odin @@ -130,7 +130,7 @@ EHWPOISON :: 133 /* Memory page has hardware error */ _get_platform_error :: proc(res: int) -> Error { errno := unix.get_errno(res) - return Platform_Error{i32(errno)} + return Platform_Error(i32(errno)) } _ok_or_error :: proc(res: int) -> Error { diff --git a/core/os/os2/errors_windows.odin b/core/os/os2/errors_windows.odin index f9dd3fdca..27c16e72e 100644 --- a/core/os/os2/errors_windows.odin +++ b/core/os/os2/errors_windows.odin @@ -12,3 +12,49 @@ _error_string :: proc(errno: i32) -> string { // FormatMessageW return "" } + +_get_platform_error :: proc() -> Error { + err := win32.GetLastError() + if err == 0 { + return nil + } + switch err { + case win32.ERROR_ACCESS_DENIED, win32.ERROR_SHARING_VIOLATION: + return .Permission_Denied + + case win32.ERROR_FILE_EXISTS, win32.ERROR_ALREADY_EXISTS: + return .Exist + + case win32.ERROR_FILE_NOT_FOUND, win32.ERROR_PATH_NOT_FOUND: + return .Not_Exist + + case win32.ERROR_NO_DATA: + return .Closed + + case win32.ERROR_TIMEOUT, win32.WAIT_TIMEOUT: + return .Timeout + + case win32.ERROR_NOT_SUPPORTED: + return .Unsupported + + case + win32.ERROR_BAD_ARGUMENTS, + win32.ERROR_INVALID_PARAMETER, + win32.ERROR_NOT_ENOUGH_MEMORY, + win32.ERROR_INVALID_HANDLE, + win32.ERROR_NO_MORE_FILES, + win32.ERROR_LOCK_VIOLATION, + win32.ERROR_HANDLE_EOF, + win32.ERROR_BROKEN_PIPE, + win32.ERROR_CALL_NOT_IMPLEMENTED, + win32.ERROR_INSUFFICIENT_BUFFER, + win32.ERROR_INVALID_NAME, + win32.ERROR_LOCK_FAILED, + win32.ERROR_ENVVAR_NOT_FOUND, + win32.ERROR_OPERATION_ABORTED, + win32.ERROR_IO_PENDING, + win32.ERROR_NO_UNICODE_TRANSLATION: + // fallthrough + } + return Platform_Error(err) +}
\ No newline at end of file diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index 707df37a2..eb6d9e366 100644 --- a/core/os/os2/file.odin +++ b/core/os/os2/file.odin @@ -2,10 +2,11 @@ package os2 import "core:io" import "core:time" +import "core:runtime" -Handle :: distinct uintptr - -INVALID_HANDLE :: ~Handle(0) +File :: struct { + impl: _File, +} Seek_From :: enum { Start = 0, // seek relative to the origin of the file @@ -20,106 +21,109 @@ File_Mode_Device :: File_Mode(1<<18) File_Mode_Char_Device :: File_Mode(1<<19) File_Mode_Sym_Link :: File_Mode(1<<20) - -File_Flag :: enum u32 { - Read = 0, - Write = 1, - Append = 2, - Create = 3, - Excl = 4, - Sync = 5, - Trunc = 6, - Close_On_Exec = 7, -} -File_Flags :: distinct bit_set[File_Flag; u32] - -O_RDONLY :: File_Flags{.Read} -O_WRONLY :: File_Flags{.Write} -O_RDWR :: File_Flags{.Read, .Write} -O_APPEND :: File_Flags{.Append} -O_CREATE :: File_Flags{.Create} -O_EXCL :: File_Flags{.Excl} -O_SYNC :: File_Flags{.Sync} -O_TRUNC :: File_Flags{.Trunc} +File_Mode_Perm :: File_Mode(0o777) // Unix permision bits + +File_Flags :: distinct bit_set[File_Flag; uint] +File_Flag :: enum { + Read, + Write, + Append, + Create, + Excl, + Sync, + Trunc, + Sparse, + Close_On_Exec, + + Unbuffered_IO, +} + +O_RDONLY :: File_Flags{.Read} +O_WRONLY :: File_Flags{.Write} +O_RDWR :: File_Flags{.Read, .Write} +O_APPEND :: File_Flags{.Append} +O_CREATE :: File_Flags{.Create} +O_EXCL :: File_Flags{.Excl} +O_SYNC :: File_Flags{.Sync} +O_TRUNC :: File_Flags{.Trunc} +O_SPARSE :: File_Flags{.Sparse} O_CLOEXEC :: File_Flags{.Close_On_Exec} -Std_Handle_Kind :: enum u8 { - stdin = 0, - stdout = 1, - stderr = 2, -} -stdin: Handle = std_handle(.stdin) -stdout: Handle = std_handle(.stdout) -stderr: Handle = std_handle(.stderr) -std_handle :: proc(kind: Std_Handle_Kind) -> Handle { - return _std_handle(kind) -} +stdin: ^File = nil // OS-Specific +stdout: ^File = nil // OS-Specific +stderr: ^File = nil // OS-Specific + -create :: proc(name: string, perm: File_Mode = 0) -> (Handle, Error) { - return open(name, {.Read, .Write, .Create}, perm) +create :: proc(name: string) -> (^File, Error) { + return open(name, {.Read, .Write, .Create}, File_Mode(0o777)) } -open :: proc(name: string, flags := File_Flags{.Read}, perm: File_Mode = 0) -> (Handle, Error) { - flags := flags - if .Write not_in flags { - flags += {.Read} - } +open :: proc(name: string, flags := File_Flags{.Read}, perm := File_Mode(0o777)) -> (^File, Error) { return _open(name, flags, perm) } -close :: proc(fd: Handle) -> Error { - return _close(fd) +new_file :: proc(handle: uintptr, name: string) -> ^File { + return _new_file(handle, name) +} + +fd :: proc(f: ^File) -> uintptr { + return _fd(f) +} + + +close :: proc(f: ^File) -> Error { + return _close(f) } -name :: proc(fd: Handle, allocator := context.allocator) -> string { - return _name(fd) +name :: proc(f: ^File) -> string { + return _name(f) } -seek :: proc(fd: Handle, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) { - return _seek(fd, offset, whence) +seek :: proc(f: ^File, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) { + return _seek(f, offset, whence) } -read :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) { - return _read(fd, p) +read :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { + return _read(f, p) } -read_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) { - return _read_at(fd, p, offset) +read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { + return _read_at(f, p, offset) } -read_from :: proc(fd: Handle, r: io.Reader) -> (n: i64, err: Error) { - return _read_from(fd, r) +read_from :: proc(f: ^File, r: io.Reader) -> (n: i64, err: Error) { + return _read_from(f, r) } -write :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) { - return _write(fd, p) +write :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { + return _write(f, p) } -write_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) { - return _write_at(fd, p, offset) +write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { + return _write_at(f, p, offset) } -write_to :: proc(fd: Handle, w: io.Writer) -> (n: i64, err: Error) { - return _write_to(fd, w) +write_to :: proc(f: ^File, w: io.Writer) -> (n: i64, err: Error) { + return _write_to(f, w) } -file_size :: proc(fd: Handle) -> (n: i64, err: Error) { - return _file_size(fd) +file_size :: proc(f: ^File) -> (n: i64, err: Error) { + return _file_size(f) } -sync :: proc(fd: Handle) -> Error { - return _sync(fd) +sync :: proc(f: ^File) -> Error { + return _sync(f) } -flush :: proc(fd: Handle) -> Error { - return _flush(fd) +flush :: proc(f: ^File) -> Error { + return _flush(f) } -truncate :: proc(fd: Handle, size: i64) -> Error { - return _truncate(fd, size) +truncate :: proc(f: ^File, size: i64) -> Error { + return _truncate(f, size) } remove :: proc(name: string) -> Error { @@ -139,29 +143,37 @@ symlink :: proc(old_name, new_name: string) -> Error { return _symlink(old_name, new_name) } -read_link :: proc(name: string) -> (string, Error) { - return _read_link(name) +read_link :: proc(name: string, allocator: runtime.Allocator) -> (string, Error) { + return _read_link(name,allocator) } -unlink :: proc(path: string) -> Error { - return _unlink(path) + +chdir :: proc(name: string) -> Error { + return _chdir(name) } +chmod :: proc(name: string, mode: File_Mode) -> Error { + return _chmod(name, mode) +} +chown :: proc(name: string, uid, gid: int) -> Error { + return _chown(name, uid, gid) +} -chdir :: proc(fd: Handle) -> Error { - return _chdir(fd) +fchdir :: proc(f: ^File) -> Error { + return _fchdir(f) } -chmod :: proc(fd: Handle, mode: File_Mode) -> Error { - return _chmod(fd, mode) +fchmod :: proc(f: ^File, mode: File_Mode) -> Error { + return _fchmod(f, mode) } -chown :: proc(fd: Handle, uid, gid: int) -> Error { - return _chown(fd, uid, gid) +fchown :: proc(f: ^File, uid, gid: int) -> Error { + return _fchown(f, uid, gid) } + lchown :: proc(name: string, uid, gid: int) -> Error { return _lchown(name, uid, gid) } @@ -170,16 +182,36 @@ lchown :: proc(name: string, uid, gid: int) -> Error { chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { return _chtimes(name, atime, mtime) } +fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { + return _fchtimes(f, atime, mtime) +} exists :: proc(path: string) -> bool { return _exists(path) } -is_file :: proc(fd: Handle) -> bool { - return _is_file(fd) +is_file :: proc(path: string) -> bool { + return _is_file(path) } -is_dir :: proc(fd: Handle) -> bool { - return _is_dir(fd) +is_dir :: proc(path: string) -> bool { + return _is_dir(path) } + +copy_file :: proc(dst_path, src_path: string) -> Error { + src := open(src_path) or_return + defer close(src) + + info := fstat(src, _file_allocator()) or_return + defer file_info_delete(info, _file_allocator()) + if info.is_dir { + return .Invalid_File + } + + dst := open(dst_path, {.Read, .Write, .Create, .Trunc}, info.mode & File_Mode_Perm) or_return + defer close(dst) + + _, err := io.copy(to_writer(dst), to_reader(src)) + return err +}
\ No newline at end of file diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 449eff1e2..8698ee54d 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -4,13 +4,10 @@ package os2 import "core:io" import "core:time" import "core:strings" -import "core:strconv" +import "core:runtime" import "core:sys/unix" - -_std_handle :: proc(kind: Std_Handle_Kind) -> Handle { - return Handle(kind) -} +INVALID_HANDLE :: -1 _O_RDONLY :: 0o0 _O_WRONLY :: 0o1 @@ -31,7 +28,17 @@ _AT_FDCWD :: -100 _CSTRING_NAME_HEAP_THRESHOLD :: 512 -_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (Handle, Error) { +_File :: struct { + name: string, + fd: int, + allocator: runtime.Allocator, +} + +_file_allocator :: proc() -> runtime.Allocator { + return heap_allocator() +} + +_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (^File, Error) { name_cstr, allocated := _name_to_cstring(name) defer if allocated { delete(name_cstr) @@ -51,63 +58,75 @@ _open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (Handle, Erro flags_i |= (_O_TRUNC * int(.Trunc in flags)) flags_i |= (_O_CLOEXEC * int(.Close_On_Exec in flags)) - handle_i := unix.sys_open(name_cstr, flags_i, int(perm)) - if handle_i < 0 { - return INVALID_HANDLE, _get_platform_error(handle_i) + fd := unix.sys_open(name_cstr, flags_i, int(perm)) + if fd < 0 { + return nil, _get_platform_error(fd) } - return Handle(handle_i), nil + return _new_file(uintptr(fd), name), nil } -_close :: proc(fd: Handle) -> Error { - res := unix.sys_close(int(fd)) - return _ok_or_error(res) +_new_file :: proc(fd: uintptr, _: string) -> ^File { + file := new(File, _file_allocator()) + file.impl.fd = int(fd) + file.impl.allocator = _file_allocator() + file.impl.name = _get_full_path(file.impl.fd, file.impl.allocator) + return file } -_name :: proc(fd: Handle, allocator := context.allocator) -> string { - // NOTE: Not sure how portable this really is - PROC_FD_PATH :: "/proc/self/fd/" +_destroy :: proc(f: ^File) -> Error { + if f == nil { + return nil + } + delete(f.impl.name, f.impl.allocator) + free(f, f.impl.allocator) + return nil +} - buf: [32]u8 - copy(buf[:], PROC_FD_PATH) - strconv.itoa(buf[len(PROC_FD_PATH):], int(fd)) +_close :: proc(f: ^File) -> Error { + res := unix.sys_close(f.impl.fd) + return _ok_or_error(res) +} - realpath: string - err: Error - if realpath, err = _read_link_cstr(cstring(&buf[0]), allocator); err != nil || realpath[0] != '/' { - return "" +_fd :: proc(f: ^File) -> uintptr { + if f == nil { + return ~uintptr(0) } - return realpath + return uintptr(f.impl.fd) +} + +_name :: proc(f: ^File) -> string { + return f.impl.name if f != nil else "" } -_seek :: proc(fd: Handle, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) { - res := unix.sys_lseek(int(fd), offset, int(whence)) +_seek :: proc(f: ^File, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) { + res := unix.sys_lseek(f.impl.fd, offset, int(whence)) if res < 0 { return -1, _get_platform_error(int(res)) } return res, nil } -_read :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) { +_read :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { if len(p) == 0 { return 0, nil } - n = unix.sys_read(int(fd), &p[0], len(p)) + n = unix.sys_read(f.impl.fd, &p[0], len(p)) if n < 0 { - return -1, _get_platform_error(int(unix.get_errno(n))) + return -1, _get_platform_error(n) } return n, nil } -_read_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) { +_read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { if offset < 0 { return 0, .Invalid_Offset } b, offset := p, offset for len(b) > 0 { - m := unix.sys_pread(int(fd), &b[0], len(b), offset) + m := unix.sys_pread(f.impl.fd, &b[0], len(b), offset) if m < 0 { return -1, _get_platform_error(m) } @@ -118,30 +137,30 @@ _read_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) { return } -_read_from :: proc(fd: Handle, r: io.Reader) -> (n: i64, err: Error) { +_read_from :: proc(f: ^File, r: io.Reader) -> (n: i64, err: Error) { //TODO return } -_write :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) { +_write :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { if len(p) == 0 { return 0, nil } - n = unix.sys_write(int(fd), &p[0], uint(len(p))) + n = unix.sys_write(f.impl.fd, &p[0], uint(len(p))) if n < 0 { return -1, _get_platform_error(n) } return int(n), nil } -_write_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) { +_write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { if offset < 0 { return 0, .Invalid_Offset } b, offset := p, offset for len(b) > 0 { - m := unix.sys_pwrite(int(fd), &b[0], len(b), offset) + m := unix.sys_pwrite(f.impl.fd, &b[0], len(b), offset) if m < 0 { return -1, _get_platform_error(m) } @@ -152,30 +171,30 @@ _write_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) { return } -_write_to :: proc(fd: Handle, w: io.Writer) -> (n: i64, err: Error) { +_write_to :: proc(f: ^File, w: io.Writer) -> (n: i64, err: Error) { //TODO return } -_file_size :: proc(fd: Handle) -> (n: i64, err: Error) { - s: OS_Stat = --- - res := unix.sys_fstat(int(fd), &s) +_file_size :: proc(f: ^File) -> (n: i64, err: Error) { + s: _Stat = --- + res := unix.sys_fstat(f.impl.fd, &s) if res < 0 { return -1, _get_platform_error(res) } return s.size, nil } -_sync :: proc(fd: Handle) -> Error { - return _ok_or_error(unix.sys_fsync(int(fd))) +_sync :: proc(f: ^File) -> Error { + return _ok_or_error(unix.sys_fsync(f.impl.fd)) } -_flush :: proc(fd: Handle) -> Error { - return _ok_or_error(unix.sys_fsync(int(fd))) +_flush :: proc(f: ^File) -> Error { + return _ok_or_error(unix.sys_fsync(f.impl.fd)) } -_truncate :: proc(fd: Handle, size: i64) -> Error { - return _ok_or_error(unix.sys_ftruncate(int(fd), size)) +_truncate :: proc(f: ^File, size: i64) -> Error { + return _ok_or_error(unix.sys_ftruncate(f.impl.fd, size)) } _remove :: proc(name: string) -> Error { @@ -184,13 +203,13 @@ _remove :: proc(name: string) -> Error { delete(name_cstr) } - handle_i := unix.sys_open(name_cstr, int(File_Flags.Read)) - if handle_i < 0 { - return _get_platform_error(handle_i) + fd := unix.sys_open(name_cstr, int(File_Flags.Read)) + if fd < 0 { + return _get_platform_error(fd) } - defer unix.sys_close(handle_i) + defer unix.sys_close(fd) - if _is_dir(Handle(handle_i)) { + if _is_dir_fd(fd) { return _ok_or_error(unix.sys_rmdir(name_cstr)) } return _ok_or_error(unix.sys_unlink(name_cstr)) @@ -242,7 +261,7 @@ _read_link_cstr :: proc(name_cstr: cstring, allocator := context.allocator) -> ( rc := unix.sys_readlink(name_cstr, &(buf[0]), bufsz) if rc < 0 { delete(buf) - return "", _get_platform_error(int(unix.get_errno(rc))) + return "", _get_platform_error(rc) } else if rc == int(bufsz) { bufsz *= 2 delete(buf) @@ -269,18 +288,40 @@ _unlink :: proc(name: string) -> Error { return _ok_or_error(unix.sys_unlink(name_cstr)) } -_chdir :: proc(fd: Handle) -> Error { - return _ok_or_error(unix.sys_fchdir(int(fd))) +_chdir :: proc(name: string) -> Error { + name_cstr, allocated := _name_to_cstring(name) + defer if allocated { + delete(name_cstr) + } + return _ok_or_error(unix.sys_chdir(name_cstr)) +} + +_fchdir :: proc(f: ^File) -> Error { + return _ok_or_error(unix.sys_fchdir(f.impl.fd)) +} + +_chmod :: proc(name: string, mode: File_Mode) -> Error { + name_cstr, allocated := _name_to_cstring(name) + defer if allocated { + delete(name_cstr) + } + return _ok_or_error(unix.sys_chmod(name_cstr, int(mode))) } -_chmod :: proc(fd: Handle, mode: File_Mode) -> Error { - return _ok_or_error(unix.sys_fchmod(int(fd), int(mode))) +_fchmod :: proc(f: ^File, mode: File_Mode) -> Error { + return _ok_or_error(unix.sys_fchmod(f.impl.fd, int(mode))) } -_chown :: proc(fd: Handle, uid, gid: int) -> Error { - return _ok_or_error(unix.sys_fchown(int(fd), uid, gid)) +// NOTE: will throw error without super user priviledges +_chown :: proc(name: string, uid, gid: int) -> Error { + name_cstr, allocated := _name_to_cstring(name) + defer if allocated { + delete(name_cstr) + } + return _ok_or_error(unix.sys_chown(name_cstr, uid, gid)) } +// NOTE: will throw error without super user priviledges _lchown :: proc(name: string, uid, gid: int) -> Error { name_cstr, allocated := _name_to_cstring(name) defer if allocated { @@ -289,6 +330,11 @@ _lchown :: proc(name: string, uid, gid: int) -> Error { return _ok_or_error(unix.sys_lchown(name_cstr, uid, gid)) } +// NOTE: will throw error without super user priviledges +_fchown :: proc(f: ^File, uid, gid: int) -> Error { + return _ok_or_error(unix.sys_fchown(f.impl.fd, uid, gid)) +} + _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { name_cstr, allocated := _name_to_cstring(name) defer if allocated { @@ -301,6 +347,14 @@ _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { return _ok_or_error(unix.sys_utimensat(_AT_FDCWD, name_cstr, ×, 0)) } +_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { + times := [2]Unix_File_Time { + { atime._nsec, 0 }, + { mtime._nsec, 0 }, + } + return _ok_or_error(unix.sys_utimensat(f.impl.fd, nil, ×, 0)) +} + _exists :: proc(name: string) -> bool { name_cstr, allocated := _name_to_cstring(name) defer if allocated { @@ -309,18 +363,38 @@ _exists :: proc(name: string) -> bool { return unix.sys_access(name_cstr, F_OK) == 0 } -_is_file :: proc(fd: Handle) -> bool { - s: OS_Stat - res := unix.sys_fstat(int(fd), &s) +_is_file :: proc(name: string) -> bool { + name_cstr, allocated := _name_to_cstring(name) + defer if allocated { + delete(name_cstr) + } + s: _Stat + res := unix.sys_stat(name_cstr, &s) + return S_ISREG(s.mode) +} + +_is_file_fd :: proc(fd: int) -> bool { + s: _Stat + res := unix.sys_fstat(fd, &s) if res < 0 { // error return false } return S_ISREG(s.mode) } -_is_dir :: proc(fd: Handle) -> bool { - s: OS_Stat - res := unix.sys_fstat(int(fd), &s) +_is_dir :: proc(name: string) -> bool { + name_cstr, allocated := _name_to_cstring(name) + defer if allocated { + delete(name_cstr) + } + s: _Stat + res := unix.sys_stat(name_cstr, &s) + return S_ISDIR(s.mode) +} + +_is_dir_fd :: proc(fd: int) -> bool { + s: _Stat + res := unix.sys_fstat(fd, &s) if res < 0 { // error return false } @@ -330,7 +404,7 @@ _is_dir :: proc(fd: Handle) -> bool { // Ideally we want to use the temp_allocator. PATH_MAX on Linux is commonly // defined as 512, however, it is well known that paths can exceed that limit. // So, in theory you could have a path larger than the entire temp_allocator's -// buffer. Therefor any large paths will use context.allocator. +// buffer. Therefor, any large paths will use context.allocator. _name_to_cstring :: proc(name: string) -> (cname: cstring, allocated: bool) { if len(name) > _CSTRING_NAME_HEAP_THRESHOLD { cname = strings.clone_to_cstring(name) diff --git a/core/os/os2/file_stream.odin b/core/os/os2/file_stream.odin index 0962ed59c..5e3db9370 100644 --- a/core/os/os2/file_stream.odin +++ b/core/os/os2/file_stream.odin @@ -2,12 +2,20 @@ package os2 import "core:io" -file_to_stream :: proc(fd: Handle) -> (s: io.Stream) { - s.stream_data = rawptr(uintptr(fd)) +to_stream :: proc(f: ^File) -> (s: io.Stream) { + s.stream_data = f s.stream_vtable = _file_stream_vtable return } +to_writer :: proc(f: ^File) -> (s: io.Writer) { + return {to_stream(f)} +} +to_reader :: proc(f: ^File) -> (s: io.Reader) { + return {to_stream(f)} +} + + @(private) error_to_io_error :: proc(ferr: Error) -> io.Error { if ferr == nil { @@ -20,66 +28,66 @@ error_to_io_error :: proc(ferr: Error) -> io.Error { @(private) _file_stream_vtable := &io.Stream_VTable{ impl_read = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) { - fd := Handle(uintptr(s.stream_data)) + f := (^File)(s.stream_data) ferr: Error - n, ferr = read(fd, p) + n, ferr = read(f, p) err = error_to_io_error(ferr) return }, impl_read_at = proc(s: io.Stream, p: []byte, offset: i64) -> (n: int, err: io.Error) { - fd := Handle(uintptr(s.stream_data)) + f := (^File)(s.stream_data) ferr: Error - n, ferr = read_at(fd, p, offset) + n, ferr = read_at(f, p, offset) err = error_to_io_error(ferr) return }, impl_write_to = proc(s: io.Stream, w: io.Writer) -> (n: i64, err: io.Error) { - fd := Handle(uintptr(s.stream_data)) + f := (^File)(s.stream_data) ferr: Error - n, ferr = write_to(fd, w) + n, ferr = write_to(f, w) err = error_to_io_error(ferr) return }, impl_write = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) { - fd := Handle(uintptr(s.stream_data)) + f := (^File)(s.stream_data) ferr: Error - n, ferr = write(fd, p) + n, ferr = write(f, p) err = error_to_io_error(ferr) return }, impl_write_at = proc(s: io.Stream, p: []byte, offset: i64) -> (n: int, err: io.Error) { - fd := Handle(uintptr(s.stream_data)) + f := (^File)(s.stream_data) ferr: Error - n, ferr = write_at(fd, p, offset) + n, ferr = write_at(f, p, offset) err = error_to_io_error(ferr) return }, impl_read_from = proc(s: io.Stream, r: io.Reader) -> (n: i64, err: io.Error) { - fd := Handle(uintptr(s.stream_data)) + f := (^File)(s.stream_data) ferr: Error - n, ferr = read_from(fd, r) + n, ferr = read_from(f, r) err = error_to_io_error(ferr) return }, impl_seek = proc(s: io.Stream, offset: i64, whence: io.Seek_From) -> (i64, io.Error) { - fd := Handle(uintptr(s.stream_data)) - n, ferr := seek(fd, offset, Seek_From(whence)) + f := (^File)(s.stream_data) + n, ferr := seek(f, offset, Seek_From(whence)) err := error_to_io_error(ferr) return n, err }, impl_size = proc(s: io.Stream) -> i64 { - fd := Handle(uintptr(s.stream_data)) - sz, _ := file_size(fd) + f := (^File)(s.stream_data) + sz, _ := file_size(f) return sz }, impl_flush = proc(s: io.Stream) -> io.Error { - fd := Handle(uintptr(s.stream_data)) - ferr := flush(fd) + f := (^File)(s.stream_data) + ferr := flush(f) return error_to_io_error(ferr) }, impl_close = proc(s: io.Stream) -> io.Error { - fd := Handle(uintptr(s.stream_data)) - ferr := close(fd) + f := (^File)(s.stream_data) + ferr := close(f) return error_to_io_error(ferr) }, } diff --git a/core/os/os2/file_util.odin b/core/os/os2/file_util.odin index 285f24154..9f9064244 100644 --- a/core/os/os2/file_util.odin +++ b/core/os/os2/file_util.odin @@ -4,25 +4,25 @@ import "core:mem" import "core:strconv" import "core:unicode/utf8" -write_string :: proc(fd: Handle, s: string) -> (n: int, err: Error) { - return write(fd, transmute([]byte)s) +write_string :: proc(f: ^File, s: string) -> (n: int, err: Error) { + return write(f, transmute([]byte)s) } -write_byte :: proc(fd: Handle, b: byte) -> (n: int, err: Error) { - return write(fd, []byte{b}) +write_byte :: proc(f: ^File, b: byte) -> (n: int, err: Error) { + return write(f, []byte{b}) } -write_rune :: proc(fd: Handle, r: rune) -> (n: int, err: Error) { +write_rune :: proc(f: ^File, r: rune) -> (n: int, err: Error) { if r < utf8.RUNE_SELF { - return write_byte(fd, byte(r)) + return write_byte(f, byte(r)) } b: [4]byte b, n = utf8.encode_rune(r) - return write(fd, b[:n]) + return write(f, b[:n]) } -write_encoded_rune :: proc(fd: Handle, r: rune) -> (n: int, err: Error) { +write_encoded_rune :: proc(f: ^File, r: rune) -> (n: int, err: Error) { wrap :: proc(m: int, merr: Error, n: ^int, err: ^Error) -> bool { n^ += m if merr != nil { @@ -32,102 +32,78 @@ write_encoded_rune :: proc(fd: Handle, r: rune) -> (n: int, err: Error) { return false } - if wrap(write_byte(fd, '\''), &n, &err) { return } + if wrap(write_byte(f, '\''), &n, &err) { return } switch r { - case '\a': if wrap(write_string(fd, "\\a"), &n, &err) { return } - case '\b': if wrap(write_string(fd, "\\b"), &n, &err) { return } - case '\e': if wrap(write_string(fd, "\\e"), &n, &err) { return } - case '\f': if wrap(write_string(fd, "\\f"), &n, &err) { return } - case '\n': if wrap(write_string(fd, "\\n"), &n, &err) { return } - case '\r': if wrap(write_string(fd, "\\r"), &n, &err) { return } - case '\t': if wrap(write_string(fd, "\\t"), &n, &err) { return } - case '\v': if wrap(write_string(fd, "\\v"), &n, &err) { return } + case '\a': if wrap(write_string(f, "\\a"), &n, &err) { return } + case '\b': if wrap(write_string(f, "\\b"), &n, &err) { return } + case '\e': if wrap(write_string(f, "\\e"), &n, &err) { return } + case '\f': if wrap(write_string(f, "\\f"), &n, &err) { return } + case '\n': if wrap(write_string(f, "\\n"), &n, &err) { return } + case '\r': if wrap(write_string(f, "\\r"), &n, &err) { return } + case '\t': if wrap(write_string(f, "\\t"), &n, &err) { return } + case '\v': if wrap(write_string(f, "\\v"), &n, &err) { return } case: if r < 32 { - if wrap(write_string(fd, "\\x"), &n, &err) { return } + if wrap(write_string(f, "\\x"), &n, &err) { return } b: [2]byte s := strconv.append_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil) switch len(s) { - case 0: if wrap(write_string(fd, "00"), &n, &err) { return } - case 1: if wrap(write_rune(fd, '0'), &n, &err) { return } - case 2: if wrap(write_string(fd, s), &n, &err) { return } + case 0: if wrap(write_string(f, "00"), &n, &err) { return } + case 1: if wrap(write_rune(f, '0'), &n, &err) { return } + case 2: if wrap(write_string(f, s), &n, &err) { return } } } else { - if wrap(write_rune(fd, r), &n, &err) { return } + if wrap(write_rune(f, r), &n, &err) { return } } } - _ = wrap(write_byte(fd, '\''), &n, &err) + _ = wrap(write_byte(f, '\''), &n, &err) return } -write_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (n: int, err: Error) { - s := transmute([]byte)mem.Raw_Slice{data, len} - return write(fd, s) -} -read_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (n: int, err: Error) { +write_ptr :: proc(f: ^File, data: rawptr, len: int) -> (n: int, err: Error) { s := transmute([]byte)mem.Raw_Slice{data, len} - return read(fd, s) -} - - -read_at_least :: proc(fd: Handle, buf: []byte, min: int) -> (n: int, err: Error) { - if len(buf) < min { - return 0, .Short_Buffer - } - for n < min && err == nil { - nn: int - nn, err = read(fd, buf[n:]) - n += nn - } - if n >= min { - err = nil - } - return -} - -read_full :: proc(fd: Handle, buf: []byte) -> (n: int, err: Error) { - return read_at_least(fd, buf, len(buf)) + return write(f, s) } -file_size_from_path :: proc(path: string) -> (length: i64, err: Error) { - fd := open(path, O_RDONLY, 0) or_return - defer close(fd) - return file_size(fd) -} - -read_entire_file :: proc{ - read_entire_file_from_path, - read_entire_file_from_handle, +read_ptr :: proc(f: ^File, data: rawptr, len: int) -> (n: int, err: Error) { + s := transmute([]byte)mem.Raw_Slice{data, len} + return read(f, s) } -read_entire_file_from_path :: proc(name: string, allocator := context.allocator) -> (data: []byte, err: Error) { - fd := open(name, {.Read}) or_return - defer close(fd) - return read_entire_file_from_handle(fd, allocator) -} -read_entire_file_from_handle :: proc(fd: Handle, allocator := context.allocator) -> (data: []byte, err: Error) { - length := file_size(fd) or_return - if length <= 0 { - return nil, nil - } - if i64(int(length)) != length { - return nil, .Short_Buffer +read_entire_file :: proc(name: string, allocator := context.allocator) -> (data: []byte, err: Error) { + f, ferr := open(name) + if ferr != nil { + return nil, ferr } + defer close(f) - data = make([]byte, int(length), allocator) - if data == nil { - return nil, .Short_Buffer + size: int + if size64, err := file_size(f); err == nil { + if i64(int(size64)) != size64 { + size = int(size64) + } } - defer if err != nil { - delete(data, allocator) + size += 1 // for EOF + + // TODO(bill): Is this correct logic? + total: int + data = make([]byte, size, allocator) or_return + for { + n: int + n, err = read(f, data[total:]) + total += n + if err != nil { + if err == .EOF { + err = nil + } + data = data[:total] + return + } } - - bytes_read := read_full(fd, data) or_return - return data[:bytes_read], nil } write_entire_file :: proc(name: string, data: []byte, perm: File_Mode, truncate := true) -> Error { @@ -135,9 +111,11 @@ write_entire_file :: proc(name: string, data: []byte, perm: File_Mode, truncate if truncate { flags |= O_TRUNC } - f := open(name, flags, perm) or_return - - _, err := write(f, data) + f, err := open(name, flags, perm) + if err != nil { + return err + } + _, err = write(f, data) if cerr := close(f); cerr != nil && err == nil { err = cerr } diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 9fdbd9a5a..7589ed799 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -2,323 +2,455 @@ package os2 import "core:io" +import "core:mem" +import "core:sync" +import "core:runtime" +import "core:strings" import "core:time" import "core:unicode/utf16" import win32 "core:sys/windows" -_get_platform_error :: proc() -> Error { - // TODO(bill): map some of these errors correctly - err := win32.GetLastError() - if err == 0 { - return nil - } - return Platform_Error{i32(err)} +INVALID_HANDLE :: ~uintptr(0) + +S_IWRITE :: 0o200 +_ERROR_BAD_NETPATH :: 53 +MAX_RW :: 1<<30 + +_file_allocator :: proc() -> runtime.Allocator { + return heap_allocator() } -_ok_or_error :: proc(ok: win32.BOOL) -> Error { - return nil if ok else _get_platform_error() +_temp_allocator :: proc() -> runtime.Allocator { + // TODO(bill): make this not depend on the context allocator + return context.temp_allocator } -_std_handle :: proc(kind: Std_Handle_Kind) -> Handle { - get_handle :: proc(h: win32.DWORD) -> Handle { - fd := win32.GetStdHandle(h) - when size_of(uintptr) == 8 { - win32.SetHandleInformation(fd, win32.HANDLE_FLAG_INHERIT, 0) - } - return Handle(fd) - } - switch kind { - case .stdin: return get_handle(win32.STD_INPUT_HANDLE) - case .stdout: return get_handle(win32.STD_OUTPUT_HANDLE) - case .stderr: return get_handle(win32.STD_ERROR_HANDLE) - } - unreachable() +_File_Kind :: enum u8 { + File, + Console, + Pipe, +} + +_File :: struct { + fd: rawptr, + name: string, + wname: win32.wstring, + kind: _File_Kind, + + allocator: runtime.Allocator, + + rw_mutex: sync.RW_Mutex, // read write calls + p_mutex: sync.Mutex, // pread pwrite calls } -_open :: proc(path: string, flags: File_Flags, perm: File_Mode) -> (handle: Handle, err: Error) { - handle = INVALID_HANDLE - if len(path) == 0 { +_handle :: proc(f: ^File) -> win32.HANDLE { + return win32.HANDLE(_fd(f)) +} + +_open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (handle: uintptr, err: Error) { + if len(name) == 0 { err = .Not_Exist return } + + path := _fix_long_path(name) access: u32 - switch flags & 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 + switch flags & {.Read, .Write} { + case {.Read}: access = win32.FILE_GENERIC_READ + case {.Write}: access = win32.FILE_GENERIC_WRITE + case {.Read, .Write}: access = win32.FILE_GENERIC_READ | win32.FILE_GENERIC_WRITE } - if .Append in flags { - access &~= win32.FILE_GENERIC_WRITE - access |= win32.FILE_APPEND_DATA - } if .Create in flags { access |= win32.FILE_GENERIC_WRITE } - - 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 .Close_On_Exec in flags { - sa = &sa_inherit + if .Append in flags { + access &~= win32.FILE_GENERIC_WRITE + access |= win32.FILE_APPEND_DATA + } + share_mode := u32(win32.FILE_SHARE_READ | win32.FILE_SHARE_WRITE) + sa: ^win32.SECURITY_ATTRIBUTES + if .Close_On_Exec not_in flags { + sa = &win32.SECURITY_ATTRIBUTES{} + sa.nLength = size_of(win32.SECURITY_ATTRIBUTES) + sa.bInheritHandle = true } - create_mode: u32 + create_mode: u32 = win32.OPEN_EXISTING switch { - case flags&(O_CREATE|O_EXCL) == (O_CREATE | O_EXCL): + case flags & {.Create, .Excl} == {.Create, .Excl}: create_mode = win32.CREATE_NEW - case flags&(O_CREATE|O_TRUNC) == (O_CREATE | O_TRUNC): + case flags & {.Create, .Trunc} == {.Create, .Trunc}: create_mode = win32.CREATE_ALWAYS - case flags&O_CREATE == O_CREATE: + case flags & {.Create} == {.Create}: create_mode = win32.OPEN_ALWAYS - case flags&O_TRUNC == O_TRUNC: + case flags & {.Trunc} == {.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 { - err = _get_platform_error() + + attrs: u32 = win32.FILE_ATTRIBUTE_NORMAL + if perm & S_IWRITE == 0 { + attrs = win32.FILE_ATTRIBUTE_READONLY + if create_mode == win32.CREATE_ALWAYS { + // NOTE(bill): Open has just asked to create a file in read-only mode. + // If the file already exists, to make it akin to a *nix open call, + // the call preserves the existing permissions. + h := win32.CreateFileW(path, access, share_mode, sa, win32.TRUNCATE_EXISTING, win32.FILE_ATTRIBUTE_NORMAL, nil) + if h == win32.INVALID_HANDLE { + switch e := win32.GetLastError(); e { + case win32.ERROR_FILE_NOT_FOUND, _ERROR_BAD_NETPATH, win32.ERROR_PATH_NOT_FOUND: + // file does not exist, create the file + case 0: + return uintptr(h), nil + case: + return 0, Platform_Error(e) + } + } + } } - return + h := win32.CreateFileW(path, access, share_mode, sa, create_mode, attrs, nil) + if h == win32.INVALID_HANDLE { + return 0, _get_platform_error() + } + return uintptr(h), nil } -_close :: proc(fd: Handle) -> Error { - if fd == 0 { - return .Invalid_Argument + +_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (f: ^File, err: Error) { + flags := flags if flags != nil else {.Read} + handle := _open_internal(name, flags + {.Close_On_Exec}, perm) or_return + return _new_file(handle, name), nil +} + +_new_file :: proc(handle: uintptr, name: string) -> ^File { + if handle == INVALID_HANDLE { + return nil } - hnd := win32.HANDLE(fd) + f := new(File, _file_allocator()) - file_info: win32.BY_HANDLE_FILE_INFORMATION - _ok_or_error(win32.GetFileInformationByHandle(hnd, &file_info)) or_return + f.impl.allocator = _file_allocator() + f.impl.fd = rawptr(fd) + f.impl.name = strings.clone(name, f.impl.allocator) + f.impl.wname = win32.utf8_to_wstring(name, f.impl.allocator) + + handle := _handle(f) + kind := _File_Kind.File + if m: u32; win32.GetConsoleMode(handle, &m) { + kind = .Console + } + if win32.GetFileType(handle) == win32.FILE_TYPE_PIPE { + kind = .Pipe + } + f.impl.kind = kind - if file_info.dwFileAttributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { + return f +} + +_fd :: proc(f: ^File) -> uintptr { + if f == nil { + return INVALID_HANDLE + } + return uintptr(f.impl.fd) +} + +_destroy :: proc(f: ^File) -> Error { + if f == nil { return nil } - return _ok_or_error(win32.CloseHandle(hnd)) + a := f.impl.allocator + free(f.impl.wname, a) + delete(f.impl.name, a) + free(f, a) + return nil } -_name :: proc(fd: Handle, allocator := context.allocator) -> string { - FILE_NAME_NORMALIZED :: 0x0 - handle := win32.HANDLE(fd) - buf_len := win32.GetFinalPathNameByHandleW(handle, nil, 0, FILE_NAME_NORMALIZED) - if buf_len == 0 { - return "" + +_close :: proc(f: ^File) -> Error { + if f == nil { + return nil + } + if !win32.CloseHandle(win32.HANDLE(f.impl.fd)) { + return .Closed } - buf := make([]u16, buf_len, context.temp_allocator) - n := win32.GetFinalPathNameByHandleW(handle, raw_data(buf), buf_len, FILE_NAME_NORMALIZED) - return win32.utf16_to_utf8(buf[:n], allocator) + return _destroy(f) } -_seek :: proc(fd: Handle, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) { - new_offset: win32.LARGE_INTEGER - move_method: win32.DWORD +_name :: proc(f: ^File) -> string { + return f.impl.name if f != nil else "" +} + +_seek :: proc(f: ^File, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) { + handle := _handle(f) + if handle == win32.INVALID_HANDLE { + return 0, .Invalid_File + } + if f.impl.kind == .Pipe { + return 0, .Invalid_File + } + + sync.guard(&f.impl.rw_mutex) + + w: u32 switch whence { - case .Start: move_method = win32.FILE_BEGIN - case .Current: move_method = win32.FILE_CURRENT - case .End: move_method = win32.FILE_END + case .Start: w = win32.FILE_BEGIN + case .Current: w = win32.FILE_CURRENT + case .End: w = win32.FILE_END } - ok := win32.SetFilePointerEx(win32.HANDLE(fd), win32.LARGE_INTEGER(offset), &new_offset, move_method) - ret = i64(new_offset) - if !ok { - err = .Invalid_Whence + hi := i32(offset>>32) + lo := i32(offset) + + dw_ptr := win32.SetFilePointer(handle, lo, &hi, w) + if dw_ptr == win32.INVALID_SET_FILE_POINTER { + return 0, _get_platform_error() } - return + return i64(hi)<<32 + i64(dw_ptr), nil } -MAX_RW :: 1<<30 +_read :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { + read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) { + if len(b) == 0 { + return 0, nil + } -@(private="file") -_read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) { - if len(b) == 0 { - return 0, nil - } + // TODO(bill): should this be moved to `_File` instead? + BUF_SIZE :: 386 + buf16: [BUF_SIZE]u16 + buf8: [4*BUF_SIZE]u8 - 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 + } - for n < len(b) && err == nil { - max_read := u32(min(BUF_SIZE, len(b)/4)) + single_read_length: u32 + ok := win32.ReadConsoleW(handle, &buf16[0], max_read, &single_read_length, nil) + if !ok { + err = _get_platform_error() + } - single_read_length: u32 - err = _ok_or_error(win32.ReadConsoleW(handle, &buf16[0], max_read, &single_read_length, nil)) + buf8_len := utf16.decode_to_utf8(buf8[:], buf16[:single_read_length]) + src := buf8[:buf8_len] - 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+i < 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 + } - ctrl_z := false - for i := 0; i < len(src) && n+i < len(b); i += 1 { - x := src[i] - if x == 0x1a { // ctrl-z - ctrl_z = true + // 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 } - b[n] = x - n += 1 - } - if ctrl_z || single_read_length < len(buf16) { - break } - } - - return -} -_read :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) { - if len(p) == 0 { - return 0, nil + return } - handle := win32.HANDLE(fd) - - m: u32 - is_console := win32.GetConsoleMode(handle, &m) + handle := _handle(f) single_read_length: win32.DWORD total_read: int length := len(p) - to_read := min(win32.DWORD(length), MAX_RW) + sync.shared_guard(&f.impl.rw_mutex) // multiple readers + + if sync.guard(&f.impl.p_mutex) { + to_read := min(win32.DWORD(length), MAX_RW) + ok: win32.BOOL + if f.impl.kind == .Console { + n, err := read_console(handle, p[total_read:][:to_read]) + total_read += n + if err != nil { + return int(total_read), err + } + } else { + ok = win32.ReadFile(handle, &p[total_read], to_read, &single_read_length, nil) + } - e: win32.BOOL - if is_console { - n, err := _read_console(handle, p[total_read:][:to_read]) - total_read += n - if err != nil { - return int(total_read), err + if single_read_length > 0 && ok { + total_read += int(single_read_length) + } else { + err = _get_platform_error() } - } else { - e = win32.ReadFile(handle, &p[total_read], to_read, &single_read_length, nil) - } - if single_read_length <= 0 || !e { - return int(total_read), _get_platform_error() } - total_read += int(single_read_length) return int(total_read), nil } +_read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { + pread :: proc(f: ^File, data: []byte, offset: i64) -> (n: int, err: Error) { + buf := data + if len(buf) > MAX_RW { + buf = buf[:MAX_RW] -_read_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) { - if offset < 0 { - return 0, .Invalid_Offset + } + curr_offset := seek(f, offset, .Current) or_return + defer seek(f, curr_offset, .Start) + + o := win32.OVERLAPPED{ + OffsetHigh = u32(offset>>32), + Offset = u32(offset), + } + + // TODO(bill): Determine the correct behaviour for consoles + + h := _handle(f) + done: win32.DWORD + if !win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o) { + err = _get_platform_error() + done = 0 + } + n = int(done) + return } - b, offset := p, offset - for len(b) > 0 { - m := _pread(fd, b, offset) or_return + sync.guard(&f.impl.p_mutex) + + p, offset := p, offset + for len(p) > 0 { + m := pread(f, p, offset) or_return n += m - b = b[m:] + p = p[m:] offset += i64(m) } return } -_read_from :: proc(fd: Handle, r: io.Reader) -> (n: i64, err: Error) { +_read_from :: proc(f: ^File, r: io.Reader) -> (n: i64, err: Error) { + // TODO(bill) return } - - -_pread :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - buf := data - if len(buf) > MAX_RW { - buf = buf[:MAX_RW] - - } - curr_offset := seek(fd, offset, .Current) or_return - defer seek(fd, curr_offset, .Start) - - o := win32.OVERLAPPED{ - OffsetHigh = u32(offset>>32), - Offset = u32(offset), +_write :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { + if len(p) == 0 { + return } - // TODO(bill): Determine the correct behaviour for consoles + single_write_length: win32.DWORD + total_write: i64 + length := i64(len(p)) - h := win32.HANDLE(fd) - done: win32.DWORD - _ok_or_error(win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o)) or_return - return int(done), nil -} + handle := _handle(f) -_pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - buf := data - if len(buf) > MAX_RW { - buf = buf[:MAX_RW] + sync.guard(&f.impl.rw_mutex) + for total_write < length { + remaining := length - total_write + to_write := win32.DWORD(min(i32(remaining), MAX_RW)) + e := win32.WriteFile(handle, &p[total_write], to_write, &single_write_length, nil) + if single_write_length <= 0 || !e { + n = int(total_write) + err = _get_platform_error() + return + } + total_write += i64(single_write_length) } - curr_offset := seek(fd, offset, .Current) or_return - defer seek(fd, curr_offset, .Start) + return int(total_write), nil +} - o := win32.OVERLAPPED{ - OffsetHigh = u32(offset>>32), - Offset = u32(offset), - } +_write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { + pwrite :: proc(f: ^File, data: []byte, offset: i64) -> (n: int, err: Error) { + buf := data + if len(buf) > MAX_RW { + buf = buf[:MAX_RW] - h := win32.HANDLE(fd) - done: win32.DWORD - _ok_or_error(win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o)) or_return - return int(done), nil -} + } + curr_offset := seek(f, offset, .Current) or_return + defer seek(f, curr_offset, .Start) -_write :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) { - return -} + o := win32.OVERLAPPED{ + OffsetHigh = u32(offset>>32), + Offset = u32(offset), + } -_write_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) { - if offset < 0 { - return 0, .Invalid_Offset + h := _handle(f) + done: win32.DWORD + if !win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o) { + err = _get_platform_error() + done = 0 + } + n = int(done) + return } - b, offset := p, offset - for len(b) > 0 { - m := _pwrite(fd, b, offset) or_return + sync.guard(&f.impl.p_mutex) + p, offset := p, offset + for len(p) > 0 { + m := pwrite(f, p, offset) or_return n += m - b = b[m:] + p = p[m:] offset += i64(m) } return } -_write_to :: proc(fd: Handle, w: io.Writer) -> (n: i64, err: Error) { +_write_to :: proc(f: ^File, w: io.Writer) -> (n: i64, err: Error) { + // TODO(bill) return } -_file_size :: proc(fd: Handle) -> (n: i64, err: Error) { +_file_size :: proc(f: ^File) -> (n: i64, err: Error) { length: win32.LARGE_INTEGER - err = _ok_or_error(win32.GetFileSizeEx(win32.HANDLE(fd), &length)) - return i64(length), err + handle := _handle(f) + if !win32.GetFileSizeEx(handle, &length) { + err = _get_platform_error() + } + n = i64(length) + return } -_sync :: proc(fd: Handle) -> Error { - return nil +_sync :: proc(f: ^File) -> Error { + return _flush(f) } -_flush :: proc(fd: Handle) -> Error { - return _ok_or_error(win32.FlushFileBuffers(win32.HANDLE(fd))) +_flush :: proc(f: ^File) -> Error { + handle := _handle(f) + if !win32.FlushFileBuffers(handle) { + return _get_platform_error() + } + return nil } -_truncate :: proc(fd: Handle, size: i64) -> Error { - offset := seek(fd, size, .Start) or_return - defer seek(fd, offset, .Start) - - return _ok_or_error(win32.SetEndOfFile(win32.HANDLE(fd))) +_truncate :: proc(f: ^File, size: i64) -> Error { + if f == nil { + return nil + } + curr_off := seek(f, 0, .Current) or_return + defer seek(f, curr_off, .Start) + seek(f, size, .Start) or_return + handle := _handle(f) + if !win32.SetEndOfFile(handle) { + return _get_platform_error() + } + return nil } _remove :: proc(name: string) -> Error { - p := win32.utf8_to_wstring(_fix_long_path(name)) - - err := _ok_or_error(win32.DeleteFileW(p)) + p := _fix_long_path(name) + err, err1: Error + if !win32.DeleteFileW(p) { + err = _get_platform_error() + } if err == nil { return nil } - err1 := _ok_or_error(win32.RemoveDirectoryW(p)) + if !win32.RemoveDirectoryW(p) { + err1 = _get_platform_error() + } if err1 == nil { return nil } @@ -332,7 +464,10 @@ _remove :: proc(name: string) -> Error { err = err1 } else if a & win32.FILE_ATTRIBUTE_READONLY != 0 { if win32.SetFileAttributesW(p, a &~ win32.FILE_ATTRIBUTE_READONLY) { - err = _ok_or_error(win32.DeleteFileW(p)) + err = nil + if !win32.DeleteFileW(p) { + err = _get_platform_error() + } } } } @@ -342,80 +477,253 @@ _remove :: proc(name: string) -> Error { } _rename :: proc(old_path, new_path: string) -> Error { - from := win32.utf8_to_wstring(old_path, context.temp_allocator) - to := win32.utf8_to_wstring(new_path, context.temp_allocator) - return _ok_or_error(win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING)) + from := _fix_long_path(old_path) + to := _fix_long_path(new_path) + if win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) { + return nil + } + return _get_platform_error() + } _link :: proc(old_name, new_name: string) -> Error { - n := win32.utf8_to_wstring(_fix_long_path(new_name)) - o := win32.utf8_to_wstring(_fix_long_path(old_name)) - return _ok_or_error(win32.CreateHardLinkW(n, o, nil)) + o := _fix_long_path(old_name) + n := _fix_long_path(new_name) + if win32.CreateHardLinkW(n, o, nil) { + return nil + } + return _get_platform_error() } _symlink :: proc(old_name, new_name: string) -> Error { - return nil + return .Unsupported } -_read_link :: proc(name: string) -> (string, Error) { - return "", nil +_open_sym_link :: proc(p: [^]u16) -> (handle: win32.HANDLE, err: Error) { + attrs := u32(win32.FILE_FLAG_BACKUP_SEMANTICS) + attrs |= win32.FILE_FLAG_OPEN_REPARSE_POINT + handle = win32.CreateFileW(p, 0, 0, nil, win32.OPEN_EXISTING, attrs, nil) + if handle == win32.INVALID_HANDLE { + return nil, _get_platform_error() + } + return + } -_unlink :: proc(path: string) -> Error { - wpath := win32.utf8_to_wstring(path, context.temp_allocator) - return _ok_or_error(win32.DeleteFileW(wpath)) +_normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: string, err: Error) { + has_prefix :: proc(p: []u16, str: string) -> bool { + if len(p) < len(str) { + return false + } + // assume ascii + for i in 0..<len(str) { + if p[i] != u16(str[i]) { + return false + } + } + return true + } + has_unc_prefix :: proc(p: []u16) -> bool { + return has_prefix(p, `\??\`) + } + + if !has_unc_prefix(p) { + return win32.utf16_to_utf8(p, allocator) + } + + ws := p[4:] + switch { + case len(ws) >= 2 && ws[1] == ':': + return win32.utf16_to_utf8(ws, allocator) + case has_prefix(ws, `UNC\`): + ws[3] = '\\' // override data in buffer + return win32.utf16_to_utf8(ws[3:], allocator) + } + + + handle := _open_sym_link(raw_data(p)) or_return + defer win32.CloseHandle(handle) + + n := win32.GetFinalPathNameByHandleW(handle, nil, 0, win32.VOLUME_NAME_DOS) + if n == 0 { + return "", _get_platform_error() + } + buf := make([]u16, n+1, _temp_allocator()) + n = win32.GetFinalPathNameByHandleW(handle, raw_data(buf), u32(len(buf)), win32.VOLUME_NAME_DOS) + if n == 0 { + return "", _get_platform_error() + } + + ws = buf[:n] + if has_unc_prefix(ws) { + ws = ws[4:] + if len(ws) > 3 && has_prefix(ws, `UNC`) { + ws[2] = '\\' + return win32.utf16_to_utf8(ws[2:], allocator) + } + return win32.utf16_to_utf8(ws, allocator) + } + return "", .Invalid_Path } +_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) { + MAXIMUM_REPARSE_DATA_BUFFER_SIZE :: 16 * 1024 + + @thread_local + rdb_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte -_chdir :: proc(fd: Handle) -> Error { + p := _fix_long_path(name) + handle := _open_sym_link(p) or_return + defer win32.CloseHandle(handle) + + bytes_returned: u32 + if !win32.DeviceIoControl(handle, win32.FSCTL_GET_REPARSE_POINT, nil, 0, &rdb_buf[0], len(rdb_buf)-1, &bytes_returned, nil) { + err = _get_platform_error() + return + } + mem.zero_slice(rdb_buf[:min(bytes_returned+1, len(rdb_buf))]) + + + rdb := (^win32.REPARSE_DATA_BUFFER)(&rdb_buf[0]) + switch rdb.ReparseTag { + case win32.IO_REPARSE_TAG_SYMLINK: + rb := (^win32.SYMBOLIC_LINK_REPARSE_BUFFER)(&rdb.rest) + pb := win32.wstring(&rb.PathBuffer) + pb[rb.SubstituteNameOffset+rb.SubstituteNameLength] = 0 + p := pb[rb.SubstituteNameOffset:][:rb.SubstituteNameLength] + if rb.Flags & win32.SYMLINK_FLAG_RELATIVE != 0 { + return win32.utf16_to_utf8(p, allocator) + } + return _normalize_link_path(p, allocator) + + case win32.IO_REPARSE_TAG_MOUNT_POINT: + rb := (^win32.MOUNT_POINT_REPARSE_BUFFER)(&rdb.rest) + pb := win32.wstring(&rb.PathBuffer) + pb[rb.SubstituteNameOffset+rb.SubstituteNameLength] = 0 + p := pb[rb.SubstituteNameOffset:][:rb.SubstituteNameLength] + return _normalize_link_path(p, allocator) + } + // Path wasn't a symlink/junction but another reparse point kind + return "", nil +} + + +_fchdir :: proc(f: ^File) -> Error { + if f == nil { + return nil + } + if !win32.SetCurrentDirectoryW(f.impl.wname) { + return _get_platform_error() + } return nil } -_chmod :: proc(fd: Handle, mode: File_Mode) -> Error { +_fchmod :: proc(f: ^File, mode: File_Mode) -> Error { + if f == nil { + return nil + } + d: win32.BY_HANDLE_FILE_INFORMATION + if !win32.GetFileInformationByHandle(_handle(f), &d) { + return _get_platform_error() + } + attrs := d.dwFileAttributes + if mode & S_IWRITE != 0 { + attrs &~= win32.FILE_ATTRIBUTE_READONLY + } else { + attrs |= win32.FILE_ATTRIBUTE_READONLY + } + + info: win32.FILE_BASIC_INFO + info.FileAttributes = attrs + if !win32.SetFileInformationByHandle(_handle(f), .FileBasicInfo, &info, size_of(d)) { + return _get_platform_error() + } return nil } -_chown :: proc(fd: Handle, uid, gid: int) -> Error { +_fchown :: proc(f: ^File, uid, gid: int) -> Error { + return .Unsupported +} + +_chdir :: proc(name: string) -> Error { + p := _fix_long_path(name) + if !win32.SetCurrentDirectoryW(p) { + return _get_platform_error() + } return nil } +_chmod :: proc(name: string, mode: File_Mode) -> Error { + f := open(name, {.Write}) or_return + defer close(f) + return _fchmod(f, mode) +} + +_chown :: proc(name: string, uid, gid: int) -> Error { + return .Unsupported +} _lchown :: proc(name: string, uid, gid: int) -> Error { - return nil + return .Unsupported } _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { + f := open(name, {.Write}) or_return + defer close(f) + return _fchtimes(f, atime, mtime) +} +_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { + if f == nil { + return nil + } + d: win32.BY_HANDLE_FILE_INFORMATION + if !win32.GetFileInformationByHandle(_handle(f), &d) { + return _get_platform_error() + } + + to_windows_time :: #force_inline proc(t: time.Time) -> win32.LARGE_INTEGER { + // a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC) + return win32.LARGE_INTEGER(time.time_to_unix_nano(t) * 100 + 116444736000000000) + } + + atime, mtime := atime, mtime + if time.time_to_unix_nano(atime) < time.time_to_unix_nano(mtime) { + atime = mtime + } + + info: win32.FILE_BASIC_INFO + info.LastAccessTime = to_windows_time(atime) + info.LastWriteTime = to_windows_time(mtime) + if !win32.SetFileInformationByHandle(_handle(f), .FileBasicInfo, &info, size_of(d)) { + return _get_platform_error() + } return nil } + _exists :: proc(path: string) -> bool { - wpath := win32.utf8_to_wstring(path, context.temp_allocator) - return bool(win32.PathFileExistsW(wpath)) + wpath := _fix_long_path(path) + attribs := win32.GetFileAttributesW(wpath) + return i32(attribs) != win32.INVALID_FILE_ATTRIBUTES } -_is_file :: proc(fd: Handle) -> bool { - hnd := win32.HANDLE(fd) - - file_info: win32.BY_HANDLE_FILE_INFORMATION - if ok := win32.GetFileInformationByHandle(hnd, &file_info); !ok { - return false +_is_file :: proc(path: string) -> bool { + wpath := _fix_long_path(path) + attribs := win32.GetFileAttributesW(wpath) + if i32(attribs) != win32.INVALID_FILE_ATTRIBUTES { + return attribs & win32.FILE_ATTRIBUTE_DIRECTORY == 0 } - no_flags :: win32.FILE_ATTRIBUTE_DIRECTORY | win32.FILE_ATTRIBUTE_DEVICE - yes_flags :: win32.FILE_ATTRIBUTE_NORMAL - return (file_info.dwFileAttributes & no_flags == 0) && (file_info.dwFileAttributes & yes_flags != 0) + return false } -_is_dir :: proc(fd: Handle) -> bool { - hnd := win32.HANDLE(fd) - - file_info: win32.BY_HANDLE_FILE_INFORMATION - if ok := win32.GetFileInformationByHandle(hnd, &file_info); !ok { - return false +_is_dir :: proc(path: string) -> bool { + wpath := _fix_long_path(path) + attribs := win32.GetFileAttributesW(wpath) + if i32(attribs) != win32.INVALID_FILE_ATTRIBUTES { + return attribs & win32.FILE_ATTRIBUTE_DIRECTORY != 0 } - return file_info.dwFileAttributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 + return false } - - diff --git a/core/os/os2/heap_linux.odin b/core/os/os2/heap_linux.odin index c470d2007..5fdad5329 100644 --- a/core/os/os2/heap_linux.odin +++ b/core/os/os2/heap_linux.odin @@ -102,7 +102,6 @@ MMAP_PROT :: unix.PROT_READ | unix.PROT_WRITE @thread_local _local_region: ^Region -//_local_region: ^Region global_regions: ^Region diff --git a/core/os/os2/path.odin b/core/os/os2/path.odin index eca8b518f..c27015862 100644 --- a/core/os/os2/path.odin +++ b/core/os/os2/path.odin @@ -1,5 +1,7 @@ package os2 +import "core:runtime" + Path_Separator :: _Path_Separator // OS-Specific Path_List_Separator :: _Path_List_Separator // OS-Specific @@ -21,7 +23,7 @@ remove_all :: proc(path: string) -> Error { -getwd :: proc(allocator := context.allocator) -> (dir: string, err: Error) { +getwd :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { return _getwd(allocator) } setwd :: proc(dir: string) -> (err: Error) { diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index 8e5aa35fe..2f59d1f13 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -2,6 +2,8 @@ package os2 import "core:strings" +import "core:strconv" +import "core:runtime" import "core:sys/unix" _Path_Separator :: '/' @@ -37,31 +39,31 @@ _mkdir :: proc(path: string, perm: File_Mode) -> Error { } _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { - _mkdirat :: proc(dfd: Handle, path: []u8, perm: int, has_created: ^bool) -> Error { + _mkdirat :: proc(dfd: int, path: []u8, perm: int, has_created: ^bool) -> Error { if len(path) == 0 { - return _ok_or_error(unix.sys_close(int(dfd))) + return _ok_or_error(unix.sys_close(dfd)) } i: int for /**/; i < len(path) - 1 && path[i] != '/'; i += 1 {} path[i] = 0 - new_dfd := unix.sys_openat(int(dfd), cstring(&path[0]), _OPENDIR_FLAGS) + new_dfd := unix.sys_openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS) switch new_dfd { case -ENOENT: - if res := unix.sys_mkdirat(int(dfd), cstring(&path[0]), perm); res < 0 { + if res := unix.sys_mkdirat(dfd, cstring(&path[0]), perm); res < 0 { return _get_platform_error(res) } has_created^ = true - if new_dfd = unix.sys_openat(int(dfd), cstring(&path[0]), _OPENDIR_FLAGS); new_dfd < 0 { + if new_dfd = unix.sys_openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS); new_dfd < 0 { return _get_platform_error(new_dfd) } fallthrough case 0: - if res := unix.sys_close(int(dfd)); res < 0 { + if res := unix.sys_close(dfd); res < 0 { return _get_platform_error(res) } // skip consecutive '/' for i += 1; i < len(path) && path[i] == '/'; i += 1 {} - return _mkdirat(Handle(new_dfd), path[i:], perm, has_created) + return _mkdirat(new_dfd, path[i:], perm, has_created) case: return _get_platform_error(new_dfd) } @@ -101,7 +103,7 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { } has_created: bool - _mkdirat(Handle(dfd), path_bytes, int(perm & 0o777), &has_created) or_return + _mkdirat(dfd, path_bytes, int(perm & 0o777), &has_created) or_return if has_created { return nil } @@ -120,13 +122,13 @@ dirent64 :: struct { _remove_all :: proc(path: string) -> Error { DT_DIR :: 4 - _remove_all_dir :: proc(dfd: Handle) -> Error { + _remove_all_dir :: proc(dfd: int) -> Error { n := 64 buf := make([]u8, n) defer delete(buf) loop: for { - getdents_res := unix.sys_getdents64(int(dfd), &buf[0], n) + getdents_res := unix.sys_getdents64(dfd, &buf[0], n) switch getdents_res { case -EINVAL: delete(buf) @@ -161,15 +163,15 @@ _remove_all :: proc(path: string) -> Error { switch d.d_type { case DT_DIR: - handle_i := unix.sys_openat(int(dfd), d_name_cstr, _OPENDIR_FLAGS) - if handle_i < 0 { - return _get_platform_error(handle_i) + new_dfd := unix.sys_openat(dfd, d_name_cstr, _OPENDIR_FLAGS) + if new_dfd < 0 { + return _get_platform_error(new_dfd) } - defer unix.sys_close(handle_i) - _remove_all_dir(Handle(handle_i)) or_return - unlink_res = unix.sys_unlinkat(int(dfd), d_name_cstr, int(unix.AT_REMOVEDIR)) + defer unix.sys_close(new_dfd) + _remove_all_dir(new_dfd) or_return + unlink_res = unix.sys_unlinkat(dfd, d_name_cstr, int(unix.AT_REMOVEDIR)) case: - unlink_res = unix.sys_unlinkat(int(dfd), d_name_cstr) + unlink_res = unix.sys_unlinkat(dfd, d_name_cstr) } if unlink_res < 0 { @@ -185,21 +187,20 @@ _remove_all :: proc(path: string) -> Error { delete(path_cstr) } - handle_i := unix.sys_open(path_cstr, _OPENDIR_FLAGS) - switch handle_i { + fd := unix.sys_open(path_cstr, _OPENDIR_FLAGS) + switch fd { case -ENOTDIR: return _ok_or_error(unix.sys_unlink(path_cstr)) case -4096..<0: - return _get_platform_error(handle_i) + return _get_platform_error(fd) } - fd := Handle(handle_i) - defer close(fd) + defer unix.sys_close(fd) _remove_all_dir(fd) or_return return _ok_or_error(unix.sys_rmdir(path_cstr)) } -_getwd :: proc(allocator := context.allocator) -> (string, Error) { +_getwd :: proc(allocator: runtime.Allocator) -> (string, Error) { // NOTE(tetra): I would use PATH_MAX here, but I was not able to find // an authoritative value for it across all systems. // The largest value I could find was 4096, so might as well use the page size. @@ -227,3 +228,20 @@ _setwd :: proc(dir: string) -> Error { } return _ok_or_error(unix.sys_chdir(dir_cstr)) } + +_get_full_path :: proc(fd: int, allocator := context.allocator) -> string { + PROC_FD_PATH :: "/proc/self/fd/" + + buf: [32]u8 + copy(buf[:], PROC_FD_PATH) + + strconv.itoa(buf[len(PROC_FD_PATH):], fd) + + fullpath: string + err: Error + if fullpath, err = _read_link_cstr(cstring(&buf[0]), allocator); err != nil || fullpath[0] != '/' { + return "" + } + return fullpath +} + diff --git a/core/os/os2/path_windows.odin b/core/os/os2/path_windows.odin index 948cf8cfd..2dc667822 100644 --- a/core/os/os2/path_windows.odin +++ b/core/os/os2/path_windows.odin @@ -1,6 +1,10 @@ //+private package os2 +import win32 "core:sys/windows" +import "core:runtime" +import "core:strings" + _Path_Separator :: '\\' _Path_List_Separator :: ';' @@ -9,11 +13,58 @@ _is_path_separator :: proc(c: byte) -> bool { } _mkdir :: proc(name: string, perm: File_Mode) -> Error { + if !win32.CreateDirectoryW(_fix_long_path(name), nil) { + return _get_platform_error() + } return nil } _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { - // TODO(bill): _mkdir_all for windows + fix_root_directory :: proc(p: string) -> (s: string, allocated: bool, err: runtime.Allocator_Error) { + if len(p) == len(`\\?\c:`) { + if is_path_separator(p[0]) && is_path_separator(p[1]) && p[2] == '?' && is_path_separator(p[3]) && p[5] == ':' { + s = strings.concatenate_safe({p, `\`}, _file_allocator()) or_return + allocated = true + return + } + } + return p, false, nil + } + + dir, err := stat(path, _temp_allocator()) + if err == nil { + if dir.is_dir { + return nil + } + return .Exist + } + + i := len(path) + for i > 0 && is_path_separator(path[i-1]) { + i -= 1 + } + + j := i + for j > 0 && !is_path_separator(path[j-1]) { + j -= 1 + } + + if j > 1 { + new_path, allocated := fix_root_directory(path[:j-1]) or_return + defer if allocated { + delete(new_path, _file_allocator()) + } + mkdir_all(new_path, perm) or_return + } + + err = mkdir(path, perm) + if err != nil { + dir1, err1 := lstat(path, _temp_allocator()) + if err1 == nil && dir1.is_dir { + return nil + } + return err + } return nil } @@ -22,10 +73,90 @@ _remove_all :: proc(path: string) -> Error { return nil } -_getwd :: proc(allocator := context.allocator) -> (dir: string, err: Error) { +_getwd :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + // TODO(bill) return "", nil } _setwd :: proc(dir: string) -> (err: Error) { + // TODO(bill) return nil } + + +can_use_long_paths: bool + +@(init) +init_long_path_support :: proc() { + // TODO(bill): init_long_path_support + // ADD THIS SHIT + // registry_path := win32.L(`Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem\LongPathsEnabled`) + can_use_long_paths = false +} + + +_fix_long_path_slice :: proc(path: string) -> []u16 { + return win32.utf8_to_utf16(_fix_long_path_internal(path)) +} + +_fix_long_path :: proc(path: string) -> win32.wstring { + return win32.utf8_to_wstring(_fix_long_path_internal(path)) +} + + +_fix_long_path_internal :: proc(path: string) -> string { + if can_use_long_paths { + return path + } + + // When using win32 to create a directory, the path + // cannot be too long that you cannot append an 8.3 + // file name, because MAX_PATH is 260, 260-12 = 248 + if len(path) < 248 { + return path + } + + // UNC paths do not need to be modified + if len(path) >= 2 && path[:2] == `\\` { + return path + } + + if !_is_abs(path) { // relative path + return path + } + + PREFIX :: `\\?` + path_buf := make([]byte, len(PREFIX)+len(path)+1, _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])): + // Skip \..\ paths + return path + case: + path_buf[w] = '\\' + w += 1 + for r < n && !is_path_separator(path[r]) { + path_buf[w] = path[r] + r += 1 + w += 1 + } + } + } + + // Root directories require a trailing \ + if w == len(`\\?\c:`) { + path_buf[w] = '\\' + w += 1 + } + + return string(path_buf[:w]) + +} diff --git a/core/os/os2/pipe.odin b/core/os/os2/pipe.odin index c38f03f03..62f7ddf10 100644 --- a/core/os/os2/pipe.odin +++ b/core/os/os2/pipe.odin @@ -1,5 +1,5 @@ package os2 -pipe :: proc() -> (r, w: Handle, err: Error) { +pipe :: proc() -> (r, w: ^File, err: Error) { return _pipe() } diff --git a/core/os/os2/pipe_linux.odin b/core/os/os2/pipe_linux.odin index 0699c5720..b66ff9663 100644 --- a/core/os/os2/pipe_linux.odin +++ b/core/os/os2/pipe_linux.odin @@ -1,7 +1,7 @@ //+private package os2 -_pipe :: proc() -> (r, w: Handle, err: Error) { - return INVALID_HANDLE, INVALID_HANDLE, nil +_pipe :: proc() -> (r, w: ^File, err: Error) { + return nil, nil, nil } diff --git a/core/os/os2/pipe_windows.odin b/core/os/os2/pipe_windows.odin index 628b4c836..bab8b44f5 100644 --- a/core/os/os2/pipe_windows.odin +++ b/core/os/os2/pipe_windows.odin @@ -3,15 +3,11 @@ package os2 import win32 "core:sys/windows" -_pipe :: proc() -> (r, w: Handle, err: Error) { - sa: win32.SECURITY_ATTRIBUTES - sa.nLength = size_of(win32.SECURITY_ATTRIBUTES) - sa.bInheritHandle = true - +_pipe :: proc() -> (r, w: ^File, err: Error) { p: [2]win32.HANDLE - if !win32.CreatePipe(&p[0], &p[1], &sa, 0) { - return 0, 0, Platform_Error{i32(win32.GetLastError())} + if !win32.CreatePipe(&p[0], &p[1], nil, 0) { + return nil, nil, _get_platform_error() } - return Handle(p[0]), Handle(p[1]), nil + return new_file(uintptr(p[0]), ""), new_file(uintptr(p[1]), ""), nil } diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin index 7fc7a4ac0..db47e2f5b 100644 --- a/core/os/os2/process.odin +++ b/core/os/os2/process.odin @@ -46,7 +46,7 @@ Process :: struct { Process_Attributes :: struct { dir: string, env: []string, - files: []Handle, + files: []^File, sys: ^Process_Attributes_OS_Specific, } diff --git a/core/os/os2/stat.odin b/core/os/os2/stat.odin index 34d0de1c5..24a01fb0a 100644 --- a/core/os/os2/stat.odin +++ b/core/os/os2/stat.odin @@ -1,6 +1,7 @@ package os2 import "core:time" +import "core:runtime" File_Info :: struct { fullpath: string, @@ -13,26 +14,26 @@ File_Info :: struct { access_time: time.Time, } -file_info_slice_delete :: proc(infos: []File_Info, allocator := context.allocator) { +file_info_slice_delete :: proc(infos: []File_Info, allocator: runtime.Allocator) { for i := len(infos)-1; i >= 0; i -= 1 { file_info_delete(infos[i], allocator) } delete(infos, allocator) } -file_info_delete :: proc(fi: File_Info, allocator := context.allocator) { +file_info_delete :: proc(fi: File_Info, allocator: runtime.Allocator) { delete(fi.fullpath, allocator) } -fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Error) { - return _fstat(fd, allocator) +fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) { + return _fstat(f, allocator) } -stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { +stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { return _stat(name, allocator) } -lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { +lstat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { return _lstat(name, allocator) } diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin index a52a84027..b627cef15 100644 --- a/core/os/os2/stat_linux.odin +++ b/core/os/os2/stat_linux.odin @@ -2,6 +2,7 @@ package os2 import "core:time" +import "core:runtime" import "core:sys/unix" import "core:path/filepath" @@ -59,7 +60,7 @@ Unix_File_Time :: struct { } @private -OS_Stat :: struct { +_Stat :: struct { device_id: u64, // ID of device containing file serial: u64, // File serial number nlink: u64, // Number of hard links @@ -82,16 +83,20 @@ OS_Stat :: struct { } -_fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Error) { - s: OS_Stat - result := unix.sys_fstat(int(fd), &s) +_fstat :: proc(f: ^File, allocator := context.allocator) -> (File_Info, Error) { + return _fstat_internal(f.impl.fd, allocator) +} + +_fstat_internal :: proc(fd: int, allocator: runtime.Allocator) -> (File_Info, Error) { + s: _Stat + result := unix.sys_fstat(fd, &s) if result < 0 { return {}, _get_platform_error(result) } // TODO: As of Linux 4.11, the new statx syscall can retrieve creation_time fi := File_Info { - fullpath = _name(fd, allocator), + fullpath = _get_full_path(fd, allocator), name = "", size = s.size, mode = 0, @@ -117,7 +122,7 @@ _stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error return {}, _get_platform_error(fd) } defer unix.sys_close(fd) - return _fstat(Handle(fd), allocator) + return _fstat_internal(fd, allocator) } _lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { @@ -130,14 +135,14 @@ _lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Erro return {}, _get_platform_error(fd) } defer unix.sys_close(fd) - return _fstat(Handle(fd), allocator) + return _fstat_internal(fd, allocator) } _same_file :: proc(fi1, fi2: File_Info) -> bool { return fi1.fullpath == fi2.fullpath } -_stat_internal :: proc(name: string) -> (s: OS_Stat, res: int) { +_stat_internal :: proc(name: string) -> (s: _Stat, res: int) { name_cstr, allocated := _name_to_cstring(name) defer if allocated { delete(name_cstr) diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin index 23ab8a44a..5de5269d7 100644 --- a/core/os/os2/stat_windows.odin +++ b/core/os/os2/stat_windows.odin @@ -1,21 +1,22 @@ //+private package os2 +import "core:runtime" import "core:time" +import "core:strings" import win32 "core:sys/windows" -_fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Error) { - if fd == 0 { - return {}, .Invalid_Argument +_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) { + if f == nil || f.impl.fd == nil { + return {}, nil } - context.allocator = allocator - path, err := _cleanpath_from_handle(fd) + path, err := _cleanpath_from_handle(f, allocator) if err != nil { return {}, err } - h := win32.HANDLE(fd) + h := _handle(f) switch win32.GetFileType(h) { case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR: fi: File_Info @@ -25,58 +26,52 @@ _fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Error) return fi, nil } - return _file_info_from_get_file_information_by_handle(path, h) + return _file_info_from_get_file_information_by_handle(path, h, allocator) } -_stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { - return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS) +_stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { + return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS, allocator) } -_lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { - return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS|win32.FILE_FLAG_OPEN_REPARSE_POINT) +_lstat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { + return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS|win32.FILE_FLAG_OPEN_REPARSE_POINT, allocator) } _same_file :: proc(fi1, fi2: File_Info) -> bool { return fi1.fullpath == fi2.fullpath } -full_path_from_name :: proc(name: string, allocator := context.allocator) -> (path: string, err: Error) { - context.allocator = allocator - + + +full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path: string, err: Error) { name := name if name == "" { name = "." } - p := win32.utf8_to_utf16(name, context.temp_allocator) - buf := make([dynamic]u16, 100) - for { - n := win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil) - if n == 0 { - delete(buf) - return "", _get_platform_error() - } - if n <= u32(len(buf)) { - return win32.utf16_to_utf8(buf[:n]), nil - } - resize(&buf, len(buf)*2) - } + p := win32.utf8_to_utf16(name, _temp_allocator()) - return + n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil) + if n == 0 { + return "", _get_platform_error() + } + buf := make([]u16, n+1, _temp_allocator()) + n = win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil) + if n == 0 { + return "", _get_platform_error() + } + return win32.utf16_to_utf8(buf[:n], allocator) } -internal_stat :: proc(name: string, create_file_attributes: u32, allocator := context.allocator) -> (fi: File_Info, e: Error) { +internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) { if len(name) == 0 { return {}, .Not_Exist } - context.allocator = allocator - - - wname := win32.utf8_to_wstring(_fix_long_path(name), context.temp_allocator) + wname := _fix_long_path(name) fa: win32.WIN32_FILE_ATTRIBUTE_DATA ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa) if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { // Not a symlink - return _file_info_from_win32_file_attribute_data(&fa, name) + return _file_info_from_win32_file_attribute_data(&fa, name, allocator) } err := 0 if ok else win32.GetLastError() @@ -90,7 +85,7 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator := co } win32.FindClose(sh) - return _file_info_from_win32_find_data(&fd, name) + return _file_info_from_win32_find_data(&fd, name, allocator) } h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil) @@ -99,7 +94,7 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator := co return } defer win32.CloseHandle(h) - return _file_info_from_get_file_information_by_handle(name, h) + return _file_info_from_get_file_information_by_handle(name, h, allocator) } @@ -124,56 +119,40 @@ _cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 { } -_cleanpath_from_handle :: proc(fd: Handle) -> (string, Error) { - if fd == 0 { - return "", .Invalid_Argument +_cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (string, Error) { + if f == nil || f.impl.fd == nil { + return "", nil } - h := win32.HANDLE(fd) + h := _handle(f) - MAX_PATH := win32.DWORD(260) + 1 - buf: []u16 - for { - buf = make([]u16, MAX_PATH, context.temp_allocator) - err := win32.GetFinalPathNameByHandleW(h, raw_data(buf), MAX_PATH, 0) - switch err { - case win32.ERROR_PATH_NOT_FOUND, win32.ERROR_INVALID_PARAMETER: - return "", Platform_Error{i32(err)} - case win32.ERROR_NOT_ENOUGH_MEMORY: - MAX_PATH = MAX_PATH*2 + 1 - continue - } - break + n := win32.GetFinalPathNameByHandleW(h, nil, 0, 0) + if n == 0 { + return "", _get_platform_error() } - return _cleanpath_from_buf(buf), nil + buf := make([]u16, max(n, 260)+1, _temp_allocator()) + n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0) + return _cleanpath_from_buf(buf[:n], allocator) } -_cleanpath_from_handle_u16 :: proc(fd: Handle) -> ([]u16, Error) { - if fd == 0 { - return nil, .Invalid_Argument - } - h := win32.HANDLE(fd) - - MAX_PATH := win32.DWORD(260) + 1 - buf: []u16 - for { - buf = make([]u16, MAX_PATH, context.temp_allocator) - err := win32.GetFinalPathNameByHandleW(h, raw_data(buf), MAX_PATH, 0) - switch err { - case win32.ERROR_PATH_NOT_FOUND, win32.ERROR_INVALID_PARAMETER: - return nil, Platform_Error{i32(err)} - case win32.ERROR_NOT_ENOUGH_MEMORY: - MAX_PATH = MAX_PATH*2 + 1 - continue - } - break +_cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) { + if f == nil || f.impl.fd == nil { + return nil, nil + } + h := _handle(f) + + n := win32.GetFinalPathNameByHandleW(h, nil, 0, 0) + if n == 0 { + return nil, _get_platform_error() } - return _cleanpath_strip_prefix(buf), nil + buf := make([]u16, max(n, 260)+1, _temp_allocator()) + n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0) + return _cleanpath_strip_prefix(buf[:n]), nil } -_cleanpath_from_buf :: proc(buf: []u16) -> string { +_cleanpath_from_buf :: proc(buf: []u16, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { buf := buf buf = _cleanpath_strip_prefix(buf) - return win32.utf16_to_utf8(buf, context.allocator) + return win32.utf16_to_utf8(buf, allocator) } @@ -215,15 +194,15 @@ file_type_mode :: proc(h: win32.HANDLE) -> File_Mode { -_file_mode_from_file_attributes :: proc(FileAttributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (mode: File_Mode) { - if FileAttributes & win32.FILE_ATTRIBUTE_READONLY != 0 { +_file_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (mode: File_Mode) { + if file_attributes & win32.FILE_ATTRIBUTE_READONLY != 0 { mode |= 0o444 } else { mode |= 0o666 } is_sym := false - if FileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { + if file_attributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { is_sym = false } else { is_sym = ReparseTag == win32.IO_REPARSE_TAG_SYMLINK || ReparseTag == win32.IO_REPARSE_TAG_MOUNT_POINT @@ -232,7 +211,7 @@ _file_mode_from_file_attributes :: proc(FileAttributes: win32.DWORD, h: win32.HA if is_sym { mode |= File_Mode_Sym_Link } else { - if FileAttributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { + if file_attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { mode |= 0o111 | File_Mode_Dir } @@ -245,7 +224,7 @@ _file_mode_from_file_attributes :: proc(FileAttributes: win32.DWORD, h: win32.HA } -_file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string) -> (fi: File_Info, e: Error) { +_file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) { fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0) @@ -255,14 +234,14 @@ _file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE 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.fullpath, e = full_path_from_name(name) + fi.fullpath, e = full_path_from_name(name, allocator) fi.name = basename(fi.fullpath) return } -_file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string) -> (fi: File_Info, e: Error) { +_file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) { fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0) @@ -272,14 +251,14 @@ _file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string 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.fullpath, e = full_path_from_name(name) + fi.fullpath, e = full_path_from_name(name, allocator) fi.name = basename(fi.fullpath) return } -_file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE) -> (File_Info, Error) { +_file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE, allocator: runtime.Allocator) -> (File_Info, Error) { d: win32.BY_HANDLE_FILE_INFORMATION if !win32.GetFileInformationByHandle(h, &d) { return {}, _get_platform_error() @@ -290,7 +269,7 @@ _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HA if !win32.GetFileInformationByHandleEx(h, .FileAttributeTagInfo, &ti, size_of(ti)) { err := win32.GetLastError() if err != win32.ERROR_INVALID_PARAMETER { - return {}, Platform_Error{i32(err)} + return {}, Platform_Error(err) } // Indicate this is a symlink on FAT file systems ti.ReparseTag = 0 @@ -312,58 +291,83 @@ _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HA return fi, nil } -_is_abs :: proc(path: string) -> bool { - if len(path) > 0 && path[0] == '/' { - return true + + +reserved_names := [?]string{ + "CON", "PRN", "AUX", "NUL", + "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", + "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", +} + +_is_reserved_name :: proc(path: string) -> bool { + if len(path) == 0 { + return false } - if len(path) > 2 { - switch path[0] { - case 'A'..='Z', 'a'..='z': - return path[1] == ':' && is_path_separator(path[2]) + for reserved in reserved_names { + if strings.equal_fold(path, reserved) { + return true } } return false } -_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 +_is_UNC :: proc(path: string) -> bool { + return _volume_name_len(path) > 2 +} + +_volume_name_len :: proc(path: string) -> int { + if ODIN_OS == .Windows { + if len(path) < 2 { + return 0 + } + c := path[0] + if path[1] == ':' { + switch c { + case 'a'..='z', 'A'..='Z': + return 2 + } + } + + // URL: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx + if l := len(path); l >= 5 && _is_path_separator(path[0]) && _is_path_separator(path[1]) && + !_is_path_separator(path[2]) && path[2] != '.' { + for n := 3; n < l-1; n += 1 { + if _is_path_separator(path[n]) { + n += 1 + if !_is_path_separator(path[n]) { + if path[n] == '.' { + break + } + } + for ; n < l; n += 1 { + if _is_path_separator(path[n]) { + break + } + } + return n + } + break } } } + return 0 +} + + +_is_abs :: proc(path: string) -> bool { + if _is_reserved_name(path) { + return true + } + l := _volume_name_len(path) + if l == 0 { + return false + } - if w == len(`\\?\c:`) { - path_buf[w] = '\\' - w += 1 + path := path + path = path[l:] + if path == "" { + return false } - return string(path_buf[:w]) + return is_path_separator(path[0]) } + diff --git a/core/os/os2/temp_file.odin b/core/os/os2/temp_file.odin index 8ff0e1656..b05c186a0 100644 --- a/core/os/os2/temp_file.odin +++ b/core/os/os2/temp_file.odin @@ -1,14 +1,15 @@ package os2 +import "core:runtime" -create_temp :: proc(dir, pattern: string) -> (Handle, Error) { +create_temp :: proc(dir, pattern: string) -> (^File, Error) { return _create_temp(dir, pattern) } -mkdir_temp :: proc(dir, pattern: string, allocator := context.allocator) -> (string, Error) { - return _mkdir_temp(dir, pattern) +mkdir_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (string, Error) { + return _mkdir_temp(dir, pattern, allocator) } -temp_dir :: proc(allocator := context.allocator) -> string { +temp_dir :: proc(allocator: runtime.Allocator) -> (string, Error) { return _temp_dir(allocator) } diff --git a/core/os/os2/temp_file_linux.odin b/core/os/os2/temp_file_linux.odin index d56bc34d3..201fb0e93 100644 --- a/core/os/os2/temp_file_linux.odin +++ b/core/os/os2/temp_file_linux.odin @@ -1,18 +1,20 @@ //+private package os2 +import "core:runtime" -_create_temp :: proc(dir, pattern: string) -> (Handle, Error) { + +_create_temp :: proc(dir, pattern: string) -> (^File, Error) { //TODO - return 0, nil + return nil, nil } -_mkdir_temp :: proc(dir, pattern: string, allocator := context.allocator) -> (string, Error) { +_mkdir_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (string, Error) { //TODO return "", nil } -_temp_dir :: proc(allocator := context.allocator) -> string { +_temp_dir :: proc(allocator: runtime.Allocator) -> (string, Error) { //TODO - return "" + return "", nil } diff --git a/core/os/os2/temp_file_windows.odin b/core/os/os2/temp_file_windows.odin index 43f9f43b4..08837f7f0 100644 --- a/core/os/os2/temp_file_windows.odin +++ b/core/os/os2/temp_file_windows.odin @@ -1,29 +1,29 @@ //+private package os2 +import "core:runtime" import win32 "core:sys/windows" -_create_temp :: proc(dir, pattern: string) -> (Handle, Error) { - return 0, nil +_create_temp :: proc(dir, pattern: string) -> (^File, Error) { + return nil, nil } -_mkdir_temp :: proc(dir, pattern: string, allocator := context.allocator) -> (string, Error) { +_mkdir_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (string, Error) { return "", nil } -_temp_dir :: proc(allocator := context.allocator) -> string { - b := make([dynamic]u16, u32(win32.MAX_PATH), context.temp_allocator) - for { - n := win32.GetTempPathW(u32(len(b)), raw_data(b)) - if n > u32(len(b)) { - resize(&b, int(n)) - continue - } - if n == 3 && b[1] == ':' && b[2] == '\\' { +_temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { + n := win32.GetTempPathW(0, nil) + if n == 0 { + return "", nil + } + b := make([]u16, max(win32.MAX_PATH, n), _temp_allocator()) + n = win32.GetTempPathW(u32(len(b)), raw_data(b)) + + if n == 3 && b[1] == ':' && b[2] == '\\' { - } else if n > 0 && b[n-1] == '\\' { - n -= 1 - } - return win32.utf16_to_utf8(b[:n], allocator) + } else if n > 0 && b[n-1] == '\\' { + n -= 1 } + return win32.utf16_to_utf8(b[:n], allocator) } diff --git a/core/os/os2/user.odin b/core/os/os2/user.odin index 1926fa75d..14c0ce961 100644 --- a/core/os/os2/user.odin +++ b/core/os/os2/user.odin @@ -1,66 +1,75 @@ package os2 import "core:strings" +import "core:runtime" -user_cache_dir :: proc(allocator := context.allocator) -> (dir: string, is_defined: bool) { +user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + found: bool #partial switch ODIN_OS { case .Windows: - dir = get_env("LocalAppData") or_return - if dir != "" { - dir = strings.clone(dir, allocator) + dir, found = get_env("LocalAppData") + if found { + dir = strings.clone_safe(dir, allocator) or_return } case .Darwin: - dir = get_env("HOME") or_return - if dir != "" { - dir = strings.concatenate({dir, "/Library/Caches"}, allocator) + dir, found = get_env("HOME") + if found { + dir = strings.concatenate_safe({dir, "/Library/Caches"}, allocator) or_return } case: // All other UNIX systems - dir = get_env("XDG_CACHE_HOME") or_return - if dir == "" { - dir = get_env("HOME") or_return - if dir == "" { + dir, found = get_env("XDG_CACHE_HOME") + if found { + dir, found = get_env("HOME") + if !found { return } - dir = strings.concatenate({dir, "/.cache"}, allocator) + dir = strings.concatenate_safe({dir, "/.cache"}, allocator) or_return } } - is_defined = dir != "" + if !found || dir == "" { + err = .Invalid_Path + } return } -user_config_dir :: proc(allocator := context.allocator) -> (dir: string, is_defined: bool) { +user_config_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + found: bool #partial switch ODIN_OS { case .Windows: - dir = get_env("AppData") or_return - if dir != "" { - dir = strings.clone(dir, allocator) + dir, found = get_env("AppData") + if found { + dir = strings.clone_safe(dir, allocator) or_return } case .Darwin: - dir = get_env("HOME") or_return - if dir != "" { - dir = strings.concatenate({dir, "/Library/Application Support"}, allocator) + dir, found = get_env("HOME") + if found { + dir = strings.concatenate_safe({dir, "/Library/Application Support"}, allocator) or_return } case: // All other UNIX systems - dir = get_env("XDG_CACHE_HOME") or_return - if dir == "" { - dir = get_env("HOME") or_return - if dir == "" { + dir, found = get_env("XDG_CACHE_HOME") + if !found { + dir, found = get_env("HOME") + if !found { return } - dir = strings.concatenate({dir, "/.config"}, allocator) + dir = strings.concatenate_safe({dir, "/.config"}, allocator) or_return } } - is_defined = dir != "" + if !found || dir == "" { + err = .Invalid_Path + } return } -user_home_dir :: proc() -> (dir: string, is_defined: bool) { +user_home_dir :: proc() -> (dir: string, err: Error) { env := "HOME" #partial switch ODIN_OS { case .Windows: env = "USERPROFILE" } - v := get_env(env) or_return - return v, true + if v, found := get_env(env); found { + return v, nil + } + return "", .Invalid_Path } diff --git a/core/os/os_darwin.odin b/core/os/os_darwin.odin index ae5336849..c36823e3f 100644 --- a/core/os/os_darwin.odin +++ b/core/os/os_darwin.odin @@ -163,9 +163,6 @@ O_SYNC :: 0x0080 O_ASYNC :: 0x0040 O_CLOEXEC :: 0x1000000 -SEEK_SET :: 0 -SEEK_CUR :: 1 -SEEK_END :: 2 SEEK_DATA :: 3 SEEK_HOLE :: 4 SEEK_MAX :: SEEK_HOLE @@ -279,7 +276,7 @@ foreign libc { @(link_name="__error") __error :: proc() -> ^int --- @(link_name="open") _unix_open :: proc(path: cstring, flags: i32, mode: u16) -> Handle --- - @(link_name="close") _unix_close :: proc(handle: Handle) --- + @(link_name="close") _unix_close :: proc(handle: Handle) -> c.int --- @(link_name="read") _unix_read :: proc(handle: Handle, buffer: rawptr, count: int) -> int --- @(link_name="write") _unix_write :: proc(handle: Handle, buffer: rawptr, count: int) -> int --- @(link_name="lseek") _unix_lseek :: proc(fs: Handle, offset: int, whence: int) -> int --- @@ -298,13 +295,13 @@ foreign libc { @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- @(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) --- - - @(link_name="fcntl") _unix_fcntl :: proc(fd: Handle, cmd: c.int, buf: ^byte) -> c.int --- + + @(link_name="__fcntl") _unix__fcntl :: proc(fd: Handle, cmd: c.int, buf: ^byte) -> c.int --- @(link_name="rename") _unix_rename :: proc(old: cstring, new: cstring) -> c.int --- @(link_name="remove") _unix_remove :: proc(path: cstring) -> c.int --- - @(link_name="fchmod") _unix_fchmod :: proc(fildes: Handle, mode: u16) -> c.int --- + @(link_name="fchmod") _unix_fchmod :: proc(fd: Handle, mode: u16) -> c.int --- @(link_name="malloc") _unix_malloc :: proc(size: int) -> rawptr --- @(link_name="calloc") _unix_calloc :: proc(num, size: int) -> rawptr --- @@ -364,12 +361,12 @@ when ODIN_OS == .Darwin && ODIN_ARCH == .arm64 { return handle, 0 } -fchmod :: proc(fildes: Handle, mode: u16) -> Errno { - return cast(Errno)_unix_fchmod(fildes, mode) +fchmod :: proc(fd: Handle, mode: u16) -> Errno { + return cast(Errno)_unix_fchmod(fd, mode) } -close :: proc(fd: Handle) { - _unix_close(fd) +close :: proc(fd: Handle) -> bool { + return _unix_close(fd) == 0 } write :: proc(fd: Handle, data: []u8) -> (int, Errno) { @@ -480,12 +477,12 @@ is_dir :: proc {is_dir_path, is_dir_handle} rename :: proc(old: string, new: string) -> bool { old_cstr := strings.clone_to_cstring(old, context.temp_allocator) new_cstr := strings.clone_to_cstring(new, context.temp_allocator) - return _unix_rename(old_cstr, new_cstr) != -1 + return _unix_rename(old_cstr, new_cstr) != -1 } remove :: proc(path: string) -> bool { path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - return _unix_remove(path_cstr) != -1 + return _unix_remove(path_cstr) != -1 } @private @@ -549,7 +546,7 @@ _rewinddir :: proc(dirp: Dir) { _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) { result: ^Dirent rc := _unix_readdir_r(dirp, &entry, &result) - + if rc != 0 { err = Errno(get_last_error()) return @@ -589,7 +586,7 @@ _readlink :: proc(path: string) -> (string, Errno) { absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) { buf : [256]byte - res := _unix_fcntl(fd, F_GETPATH, &buf[0]) + res := _unix__fcntl(fd, F_GETPATH, &buf[0]) if res != 0 { return "", Errno(get_last_error()) } diff --git a/core/os/os_freebsd.odin b/core/os/os_freebsd.odin index 4a95028c1..6545423d4 100644 --- a/core/os/os_freebsd.odin +++ b/core/os/os_freebsd.odin @@ -123,9 +123,6 @@ O_ASYNC :: 0x02000 O_CLOEXEC :: 0x80000 -SEEK_SET :: 0 -SEEK_CUR :: 1 -SEEK_END :: 2 SEEK_DATA :: 3 SEEK_HOLE :: 4 SEEK_MAX :: SEEK_HOLE diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index ed73341c0..e4ce37567 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -167,9 +167,6 @@ O_ASYNC :: 0x02000 O_CLOEXEC :: 0x80000 -SEEK_SET :: 0 -SEEK_CUR :: 1 -SEEK_END :: 2 SEEK_DATA :: 3 SEEK_HOLE :: 4 SEEK_MAX :: SEEK_HOLE @@ -418,6 +415,7 @@ foreign libc { @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- + @(link_name="putenv") _unix_putenv :: proc(cstring) -> c.int --- @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr --- @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- @@ -582,6 +580,11 @@ is_dir_path :: proc(path: string, follow_links: bool = true) -> bool { is_file :: proc {is_file_path, is_file_handle} is_dir :: proc {is_dir_path, is_dir_handle} +exists :: proc(path: string) -> bool { + cpath := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_access(cpath, O_RDONLY) + return res == 0 +} // NOTE(bill): Uses startup to initialize it @@ -767,13 +770,28 @@ heap_free :: proc(ptr: rawptr) { _unix_free(ptr) } -getenv :: proc(name: string) -> (string, bool) { - path_str := strings.clone_to_cstring(name, context.temp_allocator) +lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { + path_str := strings.clone_to_cstring(key, context.temp_allocator) + // NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults. cstr := _unix_getenv(path_str) if cstr == nil { return "", false } - return string(cstr), true + return strings.clone(string(cstr), allocator), true +} + +get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { + value, _ = lookup_env(key, allocator) + return +} + +set_env :: proc(key, value: string) -> Errno { + s := strings.concatenate({key, "=", value, "\x00"}, context.temp_allocator) + res := _unix_putenv(strings.unsafe_string_to_cstring(s)) + if res < 0 { + return Errno(get_last_error()) + } + return ERROR_NONE } get_current_directory :: proc() -> string { diff --git a/core/os/os_openbsd.odin b/core/os/os_openbsd.odin index a99c8fef0..dd230f9b5 100644 --- a/core/os/os_openbsd.odin +++ b/core/os/os_openbsd.odin @@ -125,10 +125,6 @@ O_EXCL :: 0x00800 O_NOCTTY :: 0x08000 O_CLOEXEC :: 0x10000 -SEEK_SET :: 0 -SEEK_CUR :: 1 -SEEK_END :: 2 - RTLD_LAZY :: 0x001 RTLD_NOW :: 0x002 RTLD_LOCAL :: 0x000 diff --git a/core/os/stat_unix.odin b/core/os/stat_unix.odin index 395d2e73e..dae7ab2fb 100644 --- a/core/os/stat_unix.odin +++ b/core/os/stat_unix.odin @@ -119,7 +119,6 @@ lstat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, e } stat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, err: Errno) { - context.allocator = allocator s: OS_Stat diff --git a/core/os/stat_windows.odin b/core/os/stat_windows.odin index 5da925560..79bb8c42e 100644 --- a/core/os/stat_windows.odin +++ b/core/os/stat_windows.odin @@ -20,7 +20,7 @@ full_path_from_name :: proc(name: string, allocator := context.allocator) -> (pa return "", Errno(win32.GetLastError()) } if n <= u32(len(buf)) { - return win32.utf16_to_utf8(buf[:n], allocator), ERROR_NONE + return win32.utf16_to_utf8(buf[:n], allocator) or_else "", ERROR_NONE } resize(&buf, len(buf)*2) } @@ -136,7 +136,7 @@ cleanpath_from_handle :: proc(fd: Handle) -> (string, Errno) { if err != 0 { return "", err } - return win32.utf16_to_utf8(buf, context.allocator), err + return win32.utf16_to_utf8(buf, context.allocator) or_else "", err } @(private) cleanpath_from_handle_u16 :: proc(fd: Handle) -> ([]u16, Errno) { @@ -157,7 +157,7 @@ cleanpath_from_handle_u16 :: proc(fd: Handle) -> ([]u16, Errno) { cleanpath_from_buf :: proc(buf: []u16) -> string { buf := buf buf = cleanpath_strip_prefix(buf) - return win32.utf16_to_utf8(buf, context.allocator) + return win32.utf16_to_utf8(buf, context.allocator) or_else "" } @(private) diff --git a/core/path/filepath/path.odin b/core/path/filepath/path.odin index 42714d736..32e4a8a37 100644 --- a/core/path/filepath/path.odin +++ b/core/path/filepath/path.odin @@ -4,6 +4,8 @@ package filepath import "core:strings" +SEPARATOR_CHARS :: `/\` + // is_separator checks whether the byte is a valid separator character is_separator :: proc(c: byte) -> bool { switch c { @@ -69,6 +71,16 @@ volume_name_len :: proc(path: string) -> int { return 0 } +/* + Gets the file name and extension from a path. + + i.e: + 'path/to/name.tar.gz' -> 'name.tar.gz' + 'path/to/name.txt' -> 'name.txt' + 'path/to/name' -> 'name' + + Returns "." if the path is an empty string. +*/ base :: proc(path: string) -> string { if path == "" { return "." @@ -94,6 +106,118 @@ base :: proc(path: string) -> string { return path } +/* + Gets the name of a file from a path. + + The stem of a file is such that stem(path) + ext(path) = base(path). + + Only the last dot is considered when splitting the file extension. + See `short_stem`. + + i.e: + 'name.tar.gz' -> 'name.tar' + 'name.txt' -> 'name' + + Returns an empty string if there is no stem. e.g: '.gitignore'. + Returns an empty string if there's a trailing path separator. +*/ +stem :: proc(path: string) -> string { + if len(path) > 0 && is_separator(path[len(path) - 1]) { + // NOTE(tetra): Trailing separator + return "" + } + + // NOTE(tetra): Get the basename + path := path + if i := strings.last_index_any(path, SEPARATOR_CHARS); i != -1 { + path = path[i+1:] + } + + if i := strings.last_index_byte(path, '.'); i != -1 { + return path[:i] + } + + return path +} + +/* + Gets the name of a file from a path. + + The short stem is such that short_stem(path) + long_ext(path) = base(path). + + The first dot is used to split off the file extension, unlike `stem` which uses the last dot. + + i.e: + 'name.tar.gz' -> 'name' + 'name.txt' -> 'name' + + Returns an empty string if there is no stem. e.g: '.gitignore'. + Returns an empty string if there's a trailing path separator. +*/ +short_stem :: proc(path: string) -> string { + s := stem(path) + if i := strings.index_byte(s, '.'); i != -1 { + return s[:i] + } + return s +} + +/* + Gets the file extension from a path, including the dot. + + The file extension is such that stem(path) + ext(path) = base(path). + + Only the last dot is considered when splitting the file extension. + See `long_ext`. + + i.e: + 'name.tar.gz' -> '.gz' + 'name.txt' -> '.txt' + + Returns an empty string if there is no dot. + Returns an empty string if there is a trailing path separator. +*/ +ext :: proc(path: string) -> string { + for i := len(path)-1; i >= 0 && !is_separator(path[i]); i -= 1 { + if path[i] == '.' { + return path[i:] + } + } + return "" +} + +/* + Gets the file extension from a path, including the dot. + + The long file extension is such that short_stem(path) + long_ext(path) = base(path). + + The first dot is used to split off the file extension, unlike `ext` which uses the last dot. + + i.e: + 'name.tar.gz' -> '.tar.gz' + 'name.txt' -> '.txt' + + Returns an empty string if there is no dot. + Returns an empty string if there is a trailing path separator. +*/ +long_ext :: proc(path: string) -> string { + if len(path) > 0 && is_separator(path[len(path) - 1]) { + // NOTE(tetra): Trailing separator + return "" + } + + // NOTE(tetra): Get the basename + path := path + if i := strings.last_index_any(path, SEPARATOR_CHARS); i != -1 { + path = path[i+1:] + } + + if i := strings.index_byte(path, '.'); i != -1 { + return path[i:] + } + + return "" +} clean :: proc(path: string, allocator := context.allocator) -> string { context.allocator = allocator @@ -189,15 +313,6 @@ to_slash :: proc(path: string, allocator := context.allocator) -> (new_path: str return strings.replace_all(path, SEPARATOR_STRING, "/", allocator) } -ext :: proc(path: string) -> string { - for i := len(path)-1; i >= 0 && !is_separator(path[i]); i -= 1 { - if path[i] == '.' { - return path[i:] - } - } - return "" -} - Relative_Error :: enum { None, diff --git a/core/path/filepath/path_windows.odin b/core/path/filepath/path_windows.odin index 25b2ae500..28238dd6e 100644 --- a/core/path/filepath/path_windows.odin +++ b/core/path/filepath/path_windows.odin @@ -68,7 +68,7 @@ temp_full_path :: proc(name: string) -> (path: string, err: os.Errno) { return "", os.Errno(win32.GetLastError()) } if n <= u32(len(buf)) { - return win32.utf16_to_utf8(buf[:n], ta), os.ERROR_NONE + return win32.utf16_to_utf8(buf[:n], ta) or_else "", os.ERROR_NONE } resize(&buf, len(buf)*2) } diff --git a/core/runtime/core.odin b/core/runtime/core.odin index a5a190a9c..e2933d20a 100644 --- a/core/runtime/core.odin +++ b/core/runtime/core.odin @@ -401,6 +401,7 @@ Raw_Cstring :: struct { Linux, Essence, FreeBSD, + OpenBSD, WASI, JS, Freestanding, @@ -414,6 +415,7 @@ Odin_OS_Type :: type_of(ODIN_OS) Unknown, amd64, i386, + arm32, arm64, wasm32, wasm64, @@ -565,7 +567,7 @@ __init_context :: proc "contextless" (c: ^Context) { return } - // NOTE(bill): Do not initialize these procedures with a call as they are not defined with the "contexless" calling convention + // NOTE(bill): Do not initialize these procedures with a call as they are not defined with the "contextless" calling convention c.allocator.procedure = default_allocator_proc c.allocator.data = nil diff --git a/core/runtime/core_builtin.odin b/core/runtime/core_builtin.odin index 67b1bd37b..43b9ee1bf 100644 --- a/core/runtime/core_builtin.odin +++ b/core/runtime/core_builtin.odin @@ -5,6 +5,16 @@ import "core:intrinsics" @builtin Maybe :: union($T: typeid) #maybe {T} + +@builtin +container_of :: #force_inline proc "contextless" (ptr: $P/^$Field_Type, $T: typeid, $field_name: string) -> ^T + where intrinsics.type_has_field(T, field_name), + intrinsics.type_field_type(T, field_name) == Field_Type { + offset :: offset_of_by_string(T, field_name) + return (^T)(uintptr(ptr) - offset) if ptr != nil else nil +} + + @thread_local global_default_temp_allocator_data: Default_Temp_Allocator @builtin @@ -621,13 +631,15 @@ assert :: proc(condition: bool, message := "", loc := #caller_location) { // to improve performance to make the CPU not // execute speculatively, making it about an order of // magnitude faster - proc(message: string, loc: Source_Code_Location) { + @(cold) + internal :: proc(message: string, loc: Source_Code_Location) { p := context.assertion_failure_proc if p == nil { p = default_assertion_failure_proc } p("runtime assertion", message, loc) - }(message, loc) + } + internal(message, loc) } } diff --git a/core/runtime/procs.odin b/core/runtime/procs.odin index 5a1d11fe0..782efa773 100644 --- a/core/runtime/procs.odin +++ b/core/runtime/procs.odin @@ -4,7 +4,7 @@ when ODIN_NO_CRT && ODIN_OS == .Windows { foreign import lib "system:NtDll.lib" @(private="file") - @(default_calling_convention="std") + @(default_calling_convention="stdcall") foreign lib { RtlMoveMemory :: proc(dst, src: rawptr, length: int) --- RtlFillMemory :: proc(dst: rawptr, length: int, fill: i32) --- diff --git a/core/slice/slice.odin b/core/slice/slice.odin index 520e3e1e0..b8fb29ab3 100644 --- a/core/slice/slice.odin +++ b/core/slice/slice.odin @@ -10,6 +10,53 @@ _ :: builtin _ :: bits _ :: mem +/* + Turn a pointer and a length into a slice. +*/ +from_ptr :: proc "contextless" (ptr: ^$T, count: int) -> []T { + return ([^]T)(ptr)[:count] +} + +/* + Turn a pointer and a length into a byte slice. +*/ +bytes_from_ptr :: proc "contextless" (ptr: rawptr, byte_count: int) -> []byte { + return ([^]byte)(ptr)[:byte_count] +} + +/* + Turn a slice into a byte slice. + + See `slice.reinterpret` to go the other way. +*/ +to_bytes :: proc "contextless" (s: []$T) -> []byte { + return ([^]byte)(raw_data(s))[:len(s) * size_of(T)] +} + +/* + Turn a slice of one type, into a slice of another type. + + Only converts the type and length of the slice itself. + The length is rounded down to the nearest whole number of items. + + ``` + large_items := []i64{1, 2, 3, 4} + small_items := slice.reinterpret([]i32, large_items) + assert(len(small_items) == 8) + ``` + ``` + small_items := []byte{1, 0, 0, 0, 0, 0, 0, 0, + 2, 0, 0, 0} + large_items := slice.reinterpret([]i64, small_items) + assert(len(large_items) == 1) // only enough bytes to make 1 x i64; two would need at least 8 bytes. + ``` +*/ +reinterpret :: proc "contextless" ($T: typeid/[]$U, s: []$V) -> []U { + bytes := to_bytes(s) + n := len(bytes) / size_of(U) + return ([^]U)(raw_data(bytes))[:n] +} + swap :: proc(array: $T/[]$E, a, b: int) { when size_of(E) > 8 { diff --git a/core/slice/sort_private.odin b/core/slice/sort_private.odin index 7abd2f1ce..d93d74bf9 100644 --- a/core/slice/sort_private.odin +++ b/core/slice/sort_private.odin @@ -150,7 +150,7 @@ _quick_sort_general :: proc(data: $T/[]$E, a, b, max_depth: int, call: $P, $KIND a, b, max_depth := a, b, max_depth - if b-a > 12 { // only use shell sort for lengths <= 12 + for b-a > 12 { // only use shell sort for lengths <= 12 if max_depth == 0 { heap_sort(data, a, b, call) return diff --git a/core/strings/builder.odin b/core/strings/builder.odin index d6065cf70..d51e21827 100644 --- a/core/strings/builder.odin +++ b/core/strings/builder.odin @@ -124,11 +124,11 @@ reset_builder :: proc(b: ^Builder) { used in `fmt.bprint*` bytes: [8]byte // <-- gets filled - builder := strings.builder_from_slice(bytes[:]) + builder := strings.builder_from_bytes(bytes[:]) strings.write_byte(&builder, 'a') -> "a" strings.write_byte(&builder, 'b') -> "ab" */ -builder_from_slice :: proc(backing: []byte) -> Builder { +builder_from_bytes :: proc(backing: []byte) -> Builder { s := transmute(mem.Raw_Slice)backing d := mem.Raw_Dynamic_Array{ data = s.data, @@ -140,6 +140,7 @@ builder_from_slice :: proc(backing: []byte) -> Builder { buf = transmute([dynamic]byte)d, } } +builder_from_slice :: builder_from_bytes // cast the builder byte buffer to a string and return it to_string :: proc(b: Builder) -> string { diff --git a/core/strings/strings.odin b/core/strings/strings.odin index 8e774b367..2429b451d 100644 --- a/core/strings/strings.odin +++ b/core/strings/strings.odin @@ -14,8 +14,15 @@ clone :: proc(s: string, allocator := context.allocator, loc := #caller_location return string(c[:len(s)]) } +// returns a clone of the string `s` allocated using the `allocator` +clone_safe :: proc(s: string, allocator := context.allocator, loc := #caller_location) -> (str: string, err: mem.Allocator_Error) { + c := make([]byte, len(s), allocator, loc) or_return + copy(c, s) + return string(c[:len(s)]), nil +} + // returns a clone of the string `s` allocated using the `allocator` as a cstring -// a nul byte is appended to the clone, to make the cstring safe +// a nul byte is appended to the clone, to make the cstring safe clone_to_cstring :: proc(s: string, allocator := context.allocator, loc := #caller_location) -> cstring { c := make([]byte, len(s)+1, allocator, loc) copy(c, s) @@ -37,7 +44,7 @@ string_from_nul_terminated_ptr :: proc(ptr: ^byte, len: int) -> string { return s } -// returns the raw ^byte start of the string `str` +// returns the raw ^byte start of the string `str` ptr_from_string :: proc(str: string) -> ^byte { d := transmute(mem.Raw_String)str return d.data @@ -260,6 +267,25 @@ join :: proc(a: []string, sep: string, allocator := context.allocator) -> string return string(b) } +join_safe :: proc(a: []string, sep: string, allocator := context.allocator) -> (str: string, err: mem.Allocator_Error) { + if len(a) == 0 { + return "", nil + } + + n := len(sep) * (len(a) - 1) + for s in a { + n += len(s) + } + + b := make([]byte, n, allocator) or_return + i := copy(b, a[0]) + for s in a[1:] { + i += copy(b[i:], sep) + i += copy(b[i:], s) + } + return string(b), nil +} + /* returns a combined string from the slice of strings `a` without a seperator allocates the string using the `allocator` @@ -285,6 +311,23 @@ concatenate :: proc(a: []string, allocator := context.allocator) -> string { return string(b) } +concatenate_safe :: proc(a: []string, allocator := context.allocator) -> (res: string, err: mem.Allocator_Error) { + if len(a) == 0 { + return "", nil + } + + n := 0 + for s in a { + n += len(s) + } + b := make([]byte, n, allocator) or_return + i := 0 + for s in a { + i += copy(b[i:], s) + } + return string(b), nil +} + /* `rune_offset` and `rune_length` are in runes, not bytes. If `rune_length` <= 0, then it'll return the remainder of the string starting at `rune_offset`. @@ -969,7 +1012,7 @@ count :: proc(s, substr: string) -> int { repeats the string `s` multiple `count` times and returns the allocated string panics when `count` is below 0 - strings.repeat("abc", 2) -> "abcabc" + strings.repeat("abc", 2) -> "abcabc" */ repeat :: proc(s: string, count: int, allocator := context.allocator) -> string { if count < 0 { @@ -1378,7 +1421,7 @@ split_multi :: proc(s: string, substrs: []string, allocator := context.allocator // skip when no results if substrings_found < 1 { - return + return } buf = make([]string, substrings_found + 1, allocator) @@ -1809,3 +1852,65 @@ fields_iterator :: proc(s: ^string) -> (field: string, ok: bool) { s^ = s[len(s):] return } + +// `levenshtein_distance` returns the Levenshtein edit distance between 2 strings. +// This is a single-row-version of the Wagner–Fischer algorithm, based on C code by Martin Ettl. +// Note: allocator isn't used if the length of string b in runes is smaller than 64. +levenshtein_distance :: proc(a, b: string, allocator := context.allocator) -> int { + LEVENSHTEIN_DEFAULT_COSTS: []int : { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, + } + + m, n := utf8.rune_count_in_string(a), utf8.rune_count_in_string(b) + + if m == 0 { + return n + } + if n == 0 { + return m + } + + costs: []int + + if n + 1 > len(LEVENSHTEIN_DEFAULT_COSTS) { + costs = make([]int, n + 1, allocator) + for k in 0..=n { + costs[k] = k + } + } else { + costs = LEVENSHTEIN_DEFAULT_COSTS + } + + defer if n + 1 > len(LEVENSHTEIN_DEFAULT_COSTS) { + delete(costs, allocator) + } + + i: int + for c1 in a { + costs[0] = i + 1 + corner := i + j: int + for c2 in b { + upper := costs[j + 1] + if c1 == c2 { + costs[j + 1] = corner + } else { + t := upper if upper < corner else corner + costs[j + 1] = (costs[j] if costs[j] < t else t) + 1 + } + + corner = upper + j += 1 + } + + i += 1 + } + + return costs[n] +} diff --git a/core/sync/atomic.odin b/core/sync/atomic.odin index f537764c4..0900a6544 100644 --- a/core/sync/atomic.odin +++ b/core/sync/atomic.odin @@ -6,8 +6,8 @@ cpu_relax :: intrinsics.cpu_relax /* Atomic_Memory_Order :: enum { - Relaxed = 0, - Consume = 1, + Relaxed = 0, // Unordered + Consume = 1, // Monotonic Acquire = 2, Release = 3, Acq_Rel = 4, diff --git a/core/sync/extended.odin b/core/sync/extended.odin index 2cca6f961..49d296c90 100644 --- a/core/sync/extended.odin +++ b/core/sync/extended.odin @@ -16,8 +16,7 @@ wait_group_add :: proc(wg: ^Wait_Group, delta: int) { return } - mutex_lock(&wg.mutex) - defer mutex_unlock(&wg.mutex) + guard(&wg.mutex) atomic_add(&wg.counter, delta) if wg.counter < 0 { @@ -36,8 +35,7 @@ wait_group_done :: proc(wg: ^Wait_Group) { } wait_group_wait :: proc(wg: ^Wait_Group) { - mutex_lock(&wg.mutex) - defer mutex_unlock(&wg.mutex) + guard(&wg.mutex) if wg.counter != 0 { cond_wait(&wg.cond, &wg.mutex) @@ -51,8 +49,7 @@ wait_group_wait_with_timeout :: proc(wg: ^Wait_Group, duration: time.Duration) - if duration <= 0 { return false } - mutex_lock(&wg.mutex) - defer mutex_unlock(&wg.mutex) + guard(&wg.mutex) if wg.counter != 0 { if !cond_wait_with_timeout(&wg.cond, &wg.mutex, duration) { @@ -119,8 +116,7 @@ barrier_init :: proc(b: ^Barrier, thread_count: int) { // Block the current thread until all threads have rendezvoused // Barrier can be reused after all threads rendezvoused once, and can be used continuously barrier_wait :: proc(b: ^Barrier) -> (is_leader: bool) { - mutex_lock(&b.mutex) - defer mutex_unlock(&b.mutex) + guard(&b.mutex) local_gen := b.generation_id b.index += 1 if b.index < b.thread_count { @@ -289,8 +285,7 @@ Once :: struct { once_do :: proc(o: ^Once, fn: proc()) { @(cold) do_slow :: proc(o: ^Once, fn: proc()) { - mutex_lock(&o.m) - defer mutex_unlock(&o.m) + guard(&o.m) if !o.done { fn() atomic_store_explicit(&o.done, true, .Release) @@ -302,3 +297,59 @@ once_do :: proc(o: ^Once, fn: proc()) { do_slow(o, fn) } } + + + +// A Parker is an associated token which is initially not present: +// * The `park` procedure blocks the current thread unless or until the token +// is available, at which point the token is consumed. +// * The `park_with_timeout` procedures works the same as `park` but only +// blocks for the specified duration. +// * The `unpark` procedure automatically makes the token available if it +// was not already. +Parker :: struct { + state: Futex, +} + +// Blocks the current thread until the token is made available. +// +// Assumes this is only called by the thread that owns the Parker. +park :: proc(p: ^Parker) { + EMPTY :: 0 + NOTIFIED :: 1 + PARKED :: max(u32) + if atomic_sub_explicit(&p.state, 1, .Acquire) == NOTIFIED { + return + } + for { + futex_wait(&p.state, PARKED) + if _, ok := atomic_compare_exchange_strong_explicit(&p.state, NOTIFIED, EMPTY, .Acquire, .Acquire); ok { + return + } + } +} + +// Blocks the current thread until the token is made available, but only +// for a limited duration. +// +// Assumes this is only called by the thread that owns the Parker +park_with_timeout :: proc(p: ^Parker, duration: time.Duration) { + EMPTY :: 0 + NOTIFIED :: 1 + PARKED :: max(u32) + if atomic_sub_explicit(&p.state, 1, .Acquire) == NOTIFIED { + return + } + futex_wait_with_timeout(&p.state, PARKED, duration) + atomic_exchange_explicit(&p.state, EMPTY, .Acquire) +} + +// Automatically makes thee token available if it was not already. +unpark :: proc(p: ^Parker) { + EMPTY :: 0 + NOTIFIED :: 1 + PARKED :: max(Futex) + if atomic_exchange_explicit(&p.state, NOTIFIED, .Release) == PARKED { + futex_signal(&p.state) + } +}
\ No newline at end of file diff --git a/core/sync/primitives.odin b/core/sync/primitives.odin index 483f85343..bfbdc6f9b 100644 --- a/core/sync/primitives.odin +++ b/core/sync/primitives.odin @@ -195,7 +195,7 @@ sema_wait_with_timeout :: proc(s: ^Sema, duration: time.Duration) -> bool { Futex :: distinct u32 futex_wait :: proc(f: ^Futex, expected: u32) { - if u32(atomic_load(f)) != expected { + if u32(atomic_load_explicit(f, .Acquire)) != expected { return } @@ -204,7 +204,7 @@ futex_wait :: proc(f: ^Futex, expected: u32) { // returns true if the wait happened within the duration, false if it exceeded the time duration futex_wait_with_timeout :: proc(f: ^Futex, expected: u32, duration: time.Duration) -> bool { - if u32(atomic_load(f)) != expected { + if u32(atomic_load_explicit(f, .Acquire)) != expected { return true } if duration <= 0 { diff --git a/core/sync/primitives_atomic.odin b/core/sync/primitives_atomic.odin index 11fff4e60..a0f08c412 100644 --- a/core/sync/primitives_atomic.odin +++ b/core/sync/primitives_atomic.odin @@ -281,118 +281,39 @@ atomic_recursive_mutex_guard :: proc(m: ^Atomic_Recursive_Mutex) -> bool { - -@(private="file") -Queue_Item :: struct { - next: ^Queue_Item, - futex: Futex, -} - -@(private="file") -queue_item_wait :: proc(item: ^Queue_Item) { - for atomic_load_explicit(&item.futex, .Acquire) == 0 { - futex_wait(&item.futex, 0) - cpu_relax() - } -} -@(private="file") -queue_item_wait_with_timeout :: proc(item: ^Queue_Item, duration: time.Duration) -> bool { - start := time.tick_now() - for atomic_load_explicit(&item.futex, .Acquire) == 0 { - remaining := duration - time.tick_since(start) - if remaining < 0 { - return false - } - if !futex_wait_with_timeout(&item.futex, 0, remaining) { - return false - } - cpu_relax() - } - return true -} -@(private="file") -queue_item_signal :: proc(item: ^Queue_Item) { - atomic_store_explicit(&item.futex, 1, .Release) - futex_signal(&item.futex) -} - - // Atomic_Cond implements a condition variable, a rendezvous point for threads // waiting for signalling the occurence of an event // // An Atomic_Cond must not be copied after first use Atomic_Cond :: struct { - queue_mutex: Atomic_Mutex, - queue_head: ^Queue_Item, - pending: bool, + state: Futex, } atomic_cond_wait :: proc(c: ^Atomic_Cond, m: ^Atomic_Mutex) { - waiter := &Queue_Item{} - - atomic_mutex_lock(&c.queue_mutex) - waiter.next = c.queue_head - c.queue_head = waiter - - atomic_store(&c.pending, true) - atomic_mutex_unlock(&c.queue_mutex) + state := u32(atomic_load_explicit(&c.state, .Relaxed)) + unlock(m) + futex_wait(&c.state, state) + lock(m) - atomic_mutex_unlock(m) - queue_item_wait(waiter) - atomic_mutex_lock(m) } atomic_cond_wait_with_timeout :: proc(c: ^Atomic_Cond, m: ^Atomic_Mutex, duration: time.Duration) -> (ok: bool) { - waiter := &Queue_Item{} - - atomic_mutex_lock(&c.queue_mutex) - waiter.next = c.queue_head - c.queue_head = waiter - - atomic_store(&c.pending, true) - atomic_mutex_unlock(&c.queue_mutex) - - atomic_mutex_unlock(m) - ok = queue_item_wait_with_timeout(waiter, duration) - atomic_mutex_lock(m) + state := u32(atomic_load_explicit(&c.state, .Relaxed)) + unlock(m) + ok = futex_wait_with_timeout(&c.state, state, duration) + lock(m) return } atomic_cond_signal :: proc(c: ^Atomic_Cond) { - if !atomic_load(&c.pending) { - return - } - - atomic_mutex_lock(&c.queue_mutex) - waiter := c.queue_head - if c.queue_head != nil { - c.queue_head = c.queue_head.next - } - atomic_store(&c.pending, c.queue_head != nil) - atomic_mutex_unlock(&c.queue_mutex) - - if waiter != nil { - queue_item_signal(waiter) - } + atomic_add_explicit(&c.state, 1, .Release) + futex_signal(&c.state) } atomic_cond_broadcast :: proc(c: ^Atomic_Cond) { - if !atomic_load(&c.pending) { - return - } - - atomic_store(&c.pending, false) - - atomic_mutex_lock(&c.queue_mutex) - waiters := c.queue_head - c.queue_head = nil - atomic_mutex_unlock(&c.queue_mutex) - - for waiters != nil { - queue_item_signal(waiters) - waiters = waiters.next - } + atomic_add_explicit(&c.state, 1, .Release) + futex_broadcast(&c.state) } // When waited upon, blocks until the internal count is greater than zero, then subtracts one. @@ -400,30 +321,28 @@ atomic_cond_broadcast :: proc(c: ^Atomic_Cond) { // // An Atomic_Sema must not be copied after first use Atomic_Sema :: struct { - mutex: Atomic_Mutex, - cond: Atomic_Cond, - count: int, + count: Futex, } atomic_sema_post :: proc(s: ^Atomic_Sema, count := 1) { - atomic_mutex_lock(&s.mutex) - defer atomic_mutex_unlock(&s.mutex) - - s.count += count - atomic_cond_signal(&s.cond) + atomic_add_explicit(&s.count, Futex(count), .Release) + if count == 1 { + futex_signal(&s.count) + } else { + futex_broadcast(&s.count) + } } atomic_sema_wait :: proc(s: ^Atomic_Sema) { - atomic_mutex_lock(&s.mutex) - defer atomic_mutex_unlock(&s.mutex) - - for s.count == 0 { - atomic_cond_wait(&s.cond, &s.mutex) - } - - s.count -= 1 - if s.count > 0 { - atomic_cond_signal(&s.cond) + for { + original_count := atomic_load_explicit(&s.count, .Relaxed) + for original_count == 0 { + futex_wait(&s.count, u32(original_count)) + original_count = s.count + } + if original_count == atomic_compare_exchange_strong_explicit(&s.count, original_count, original_count-1, .Acquire, .Acquire) { + return + } } } @@ -431,25 +350,21 @@ atomic_sema_wait_with_timeout :: proc(s: ^Atomic_Sema, duration: time.Duration) if duration <= 0 { return false } - atomic_mutex_lock(&s.mutex) - defer atomic_mutex_unlock(&s.mutex) - - start := time.tick_now() - - for s.count == 0 { - remaining := duration - time.tick_since(start) - if remaining < 0 { - return false + for { + original_count := atomic_load_explicit(&s.count, .Relaxed) + for start := time.tick_now(); original_count == 0; /**/ { + remaining := duration - time.tick_since(start) + if remaining < 0 { + return false + } + + if !futex_wait_with_timeout(&s.count, u32(original_count), remaining) { + return false + } + original_count = s.count } - - if !atomic_cond_wait_with_timeout(&s.cond, &s.mutex, remaining) { - return false + if original_count == atomic_compare_exchange_strong_explicit(&s.count, original_count, original_count-1, .Acquire, .Acquire) { + return true } } - - s.count -= 1 - if s.count > 0 { - atomic_cond_signal(&s.cond) - } - return true } diff --git a/core/sync/primitives_darwin.odin b/core/sync/primitives_darwin.odin index 514f66f3e..726113ae7 100644 --- a/core/sync/primitives_darwin.odin +++ b/core/sync/primitives_darwin.odin @@ -3,7 +3,6 @@ package sync import "core:c" -import "core:time" import "core:intrinsics" foreign import pthread "System.framework" @@ -17,41 +16,3 @@ _current_thread_id :: proc "contextless" () -> int { pthread_threadid_np(nil, &tid) return int(tid) } - - - -_Mutex :: struct { - mutex: Atomic_Mutex, -} - -_mutex_lock :: proc(m: ^Mutex) { - atomic_mutex_lock(&m.impl.mutex) -} - -_mutex_unlock :: proc(m: ^Mutex) { - atomic_mutex_unlock(&m.impl.mutex) -} - -_mutex_try_lock :: proc(m: ^Mutex) -> bool { - return atomic_mutex_try_lock(&m.impl.mutex) -} - -_Cond :: struct { - cond: Atomic_Cond, -} - -_cond_wait :: proc(c: ^Cond, m: ^Mutex) { - atomic_cond_wait(&c.impl.cond, &m.impl.mutex) -} - -_cond_wait_with_timeout :: proc(c: ^Cond, m: ^Mutex, duration: time.Duration) -> bool { - return atomic_cond_wait_with_timeout(&c.impl.cond, &m.impl.mutex, duration) -} - -_cond_signal :: proc(c: ^Cond) { - atomic_cond_signal(&c.impl.cond) -} - -_cond_broadcast :: proc(c: ^Cond) { - atomic_cond_broadcast(&c.impl.cond) -} diff --git a/core/sync/primitives_freebsd.odin b/core/sync/primitives_freebsd.odin index b88fca181..e6219acf1 100644 --- a/core/sync/primitives_freebsd.odin +++ b/core/sync/primitives_freebsd.odin @@ -3,44 +3,7 @@ package sync import "core:os" -import "core:time" _current_thread_id :: proc "contextless" () -> int { return os.current_thread_id() } - -_Mutex :: struct { - mutex: Atomic_Mutex, -} - -_mutex_lock :: proc(m: ^Mutex) { - atomic_mutex_lock(&m.impl.mutex) -} - -_mutex_unlock :: proc(m: ^Mutex) { - atomic_mutex_unlock(&m.impl.mutex) -} - -_mutex_try_lock :: proc(m: ^Mutex) -> bool { - return atomic_mutex_try_lock(&m.impl.mutex) -} - -_Cond :: struct { - cond: Atomic_Cond, -} - -_cond_wait :: proc(c: ^Cond, m: ^Mutex) { - atomic_cond_wait(&c.impl.cond, &m.impl.mutex) -} - -_cond_wait_with_timeout :: proc(c: ^Cond, m: ^Mutex, duration: time.Duration) -> bool { - return atomic_cond_wait_with_timeout(&c.impl.cond, &m.impl.mutex, duration) -} - -_cond_signal :: proc(c: ^Cond) { - atomic_cond_signal(&c.impl.cond) -} - -_cond_broadcast :: proc(c: ^Cond) { - atomic_cond_broadcast(&c.impl.cond) -} diff --git a/core/sync/primitives_internal.odin b/core/sync/primitives_internal.odin index de9aca991..ba17c2eb5 100644 --- a/core/sync/primitives_internal.odin +++ b/core/sync/primitives_internal.odin @@ -1,99 +1,108 @@ //+private package sync -when #config(ODIN_SYNC_RECURSIVE_MUTEX_USE_FUTEX, true) { - _Recursive_Mutex :: struct { - owner: Futex, - recursion: i32, - } - - _recursive_mutex_lock :: proc(m: ^Recursive_Mutex) { - tid := Futex(current_thread_id()) - for { - prev_owner := atomic_compare_exchange_strong_explicit(&m.impl.owner, 0, tid, .Acquire, .Acquire) - switch prev_owner { - case 0, tid: - m.impl.recursion += 1 - // inside the lock - return - } - - futex_wait(&m.impl.owner, u32(prev_owner)) - } - } +import "core:time" - _recursive_mutex_unlock :: proc(m: ^Recursive_Mutex) { - m.impl.recursion -= 1 - if m.impl.recursion != 0 { - return - } - atomic_exchange_explicit(&m.impl.owner, 0, .Release) - - futex_signal(&m.impl.owner) - // outside the lock +_Sema :: struct { + atomic: Atomic_Sema, +} - } +_sema_post :: proc(s: ^Sema, count := 1) { + atomic_sema_post(&s.impl.atomic, count) +} - _recursive_mutex_try_lock :: proc(m: ^Recursive_Mutex) -> bool { - tid := Futex(current_thread_id()) +_sema_wait :: proc(s: ^Sema) { + atomic_sema_wait(&s.impl.atomic) +} + +_sema_wait_with_timeout :: proc(s: ^Sema, duration: time.Duration) -> bool { + return atomic_sema_wait_with_timeout(&s.impl.atomic, duration) +} + + +_Recursive_Mutex :: struct { + owner: Futex, + recursion: i32, +} + +_recursive_mutex_lock :: proc(m: ^Recursive_Mutex) { + tid := Futex(current_thread_id()) + for { prev_owner := atomic_compare_exchange_strong_explicit(&m.impl.owner, 0, tid, .Acquire, .Acquire) switch prev_owner { case 0, tid: m.impl.recursion += 1 // inside the lock - return true + return } - return false - } -} else { - _Recursive_Mutex :: struct { - owner: int, - recursion: int, - mutex: Mutex, + + futex_wait(&m.impl.owner, u32(prev_owner)) } +} - _recursive_mutex_lock :: proc(m: ^Recursive_Mutex) { - tid := current_thread_id() - if tid != m.impl.owner { - mutex_lock(&m.impl.mutex) - } - // inside the lock - m.impl.owner = tid - m.impl.recursion += 1 +_recursive_mutex_unlock :: proc(m: ^Recursive_Mutex) { + m.impl.recursion -= 1 + if m.impl.recursion != 0 { + return } + atomic_exchange_explicit(&m.impl.owner, 0, .Release) - _recursive_mutex_unlock :: proc(m: ^Recursive_Mutex) { - tid := current_thread_id() - assert(tid == m.impl.owner) - m.impl.recursion -= 1 - recursion := m.impl.recursion - if recursion == 0 { - m.impl.owner = 0 - } - if recursion == 0 { - mutex_unlock(&m.impl.mutex) - } - // outside the lock + futex_signal(&m.impl.owner) + // outside the lock - } +} - _recursive_mutex_try_lock :: proc(m: ^Recursive_Mutex) -> bool { - tid := current_thread_id() - if m.impl.owner == tid { - return mutex_try_lock(&m.impl.mutex) - } - if !mutex_try_lock(&m.impl.mutex) { - return false - } - // inside the lock - m.impl.owner = tid +_recursive_mutex_try_lock :: proc(m: ^Recursive_Mutex) -> bool { + tid := Futex(current_thread_id()) + prev_owner := atomic_compare_exchange_strong_explicit(&m.impl.owner, 0, tid, .Acquire, .Acquire) + switch prev_owner { + case 0, tid: m.impl.recursion += 1 + // inside the lock return true } + return false } when ODIN_OS != .Windows { + _Mutex :: struct { + mutex: Atomic_Mutex, + } + + _mutex_lock :: proc(m: ^Mutex) { + atomic_mutex_lock(&m.impl.mutex) + } + + _mutex_unlock :: proc(m: ^Mutex) { + atomic_mutex_unlock(&m.impl.mutex) + } + + _mutex_try_lock :: proc(m: ^Mutex) -> bool { + return atomic_mutex_try_lock(&m.impl.mutex) + } + + _Cond :: struct { + cond: Atomic_Cond, + } + + _cond_wait :: proc(c: ^Cond, m: ^Mutex) { + atomic_cond_wait(&c.impl.cond, &m.impl.mutex) + } + + _cond_wait_with_timeout :: proc(c: ^Cond, m: ^Mutex, duration: time.Duration) -> bool { + return atomic_cond_wait_with_timeout(&c.impl.cond, &m.impl.mutex, duration) + } + + _cond_signal :: proc(c: ^Cond) { + atomic_cond_signal(&c.impl.cond) + } + + _cond_broadcast :: proc(c: ^Cond) { + atomic_cond_broadcast(&c.impl.cond) + } + + _RW_Mutex :: struct { mutex: Atomic_RW_Mutex, } @@ -121,5 +130,4 @@ when ODIN_OS != .Windows { _rw_mutex_try_shared_lock :: proc(rw: ^RW_Mutex) -> bool { return atomic_rw_mutex_try_shared_lock(&rw.impl.mutex) } - }
\ No newline at end of file diff --git a/core/sync/primitives_linux.odin b/core/sync/primitives_linux.odin index 0a9f0cc33..1e75891df 100644 --- a/core/sync/primitives_linux.odin +++ b/core/sync/primitives_linux.odin @@ -3,45 +3,7 @@ package sync import "core:sys/unix" -import "core:time" _current_thread_id :: proc "contextless" () -> int { return unix.sys_gettid() } - - -_Mutex :: struct { - mutex: Atomic_Mutex, -} - -_mutex_lock :: proc(m: ^Mutex) { - atomic_mutex_lock(&m.impl.mutex) -} - -_mutex_unlock :: proc(m: ^Mutex) { - atomic_mutex_unlock(&m.impl.mutex) -} - -_mutex_try_lock :: proc(m: ^Mutex) -> bool { - return atomic_mutex_try_lock(&m.impl.mutex) -} - -_Cond :: struct { - cond: Atomic_Cond, -} - -_cond_wait :: proc(c: ^Cond, m: ^Mutex) { - atomic_cond_wait(&c.impl.cond, &m.impl.mutex) -} - -_cond_wait_with_timeout :: proc(c: ^Cond, m: ^Mutex, duration: time.Duration) -> bool { - return atomic_cond_wait_with_timeout(&c.impl.cond, &m.impl.mutex, duration) -} - -_cond_signal :: proc(c: ^Cond) { - atomic_cond_signal(&c.impl.cond) -} - -_cond_broadcast :: proc(c: ^Cond) { - atomic_cond_broadcast(&c.impl.cond) -} diff --git a/core/sync/primitives_openbsd.odin b/core/sync/primitives_openbsd.odin index 7794016f8..4072a14e8 100644 --- a/core/sync/primitives_openbsd.odin +++ b/core/sync/primitives_openbsd.odin @@ -3,44 +3,7 @@ package sync import "core:os" -import "core:time" _current_thread_id :: proc "contextless" () -> int { return os.current_thread_id() } - -_Mutex :: struct { - mutex: Atomic_Mutex, -} - -_mutex_lock :: proc(m: ^Mutex) { - atomic_mutex_lock(&m.impl.mutex) -} - -_mutex_unlock :: proc(m: ^Mutex) { - atomic_mutex_unlock(&m.impl.mutex) -} - -_mutex_try_lock :: proc(m: ^Mutex) -> bool { - return atomic_mutex_try_lock(&m.impl.mutex) -} - -_Cond :: struct { - cond: Atomic_Cond, -} - -_cond_wait :: proc(c: ^Cond, m: ^Mutex) { - atomic_cond_wait(&c.impl.cond, &m.impl.mutex) -} - -_cond_wait_with_timeout :: proc(c: ^Cond, m: ^Mutex, duration: time.Duration) -> bool { - return atomic_cond_wait_with_timeout(&c.impl.cond, &m.impl.mutex, duration) -} - -_cond_signal :: proc(c: ^Cond) { - atomic_cond_signal(&c.impl.cond) -} - -_cond_broadcast :: proc(c: ^Cond) { - atomic_cond_broadcast(&c.impl.cond) -} diff --git a/core/sync/sema_internal.odin b/core/sync/sema_internal.odin deleted file mode 100644 index e4a3c0bfc..000000000 --- a/core/sync/sema_internal.odin +++ /dev/null @@ -1,73 +0,0 @@ -//+private -package sync - -import "core:time" - - -when #config(ODIN_SYNC_SEMA_USE_FUTEX, true) { - _Sema :: struct { - count: Futex, - } - - _sema_post :: proc(s: ^Sema, count := 1) { - atomic_add_explicit(&s.impl.count, Futex(count), .Release) - if count == 1 { - futex_signal(&s.impl.count) - } else { - futex_broadcast(&s.impl.count) - } - } - - _sema_wait :: proc(s: ^Sema) { - for { - original_count := atomic_load_explicit(&s.impl.count, .Relaxed) - for original_count == 0 { - futex_wait(&s.impl.count, u32(original_count)) - original_count = s.impl.count - } - if original_count == atomic_compare_exchange_strong_explicit(&s.impl.count, original_count, original_count-1, .Acquire, .Acquire) { - return - } - } - } - - _sema_wait_with_timeout :: proc(s: ^Sema, duration: time.Duration) -> bool { - if duration <= 0 { - return false - } - for { - - original_count := atomic_load_explicit(&s.impl.count, .Relaxed) - for start := time.tick_now(); original_count == 0; /**/ { - remaining := duration - time.tick_since(start) - if remaining < 0 { - return false - } - - if !futex_wait_with_timeout(&s.impl.count, u32(original_count), remaining) { - return false - } - original_count = s.impl.count - } - if original_count == atomic_compare_exchange_strong_explicit(&s.impl.count, original_count, original_count-1, .Acquire, .Acquire) { - return true - } - } - } -} else { - _Sema :: struct { - wg: Wait_Group, - } - - _sema_post :: proc(s: ^Sema, count := 1) { - wait_group_add(&s.impl.wg, count) - } - - _sema_wait :: proc(s: ^Sema) { - wait_group_wait(&s.impl.wg) - } - - _sema_wait_with_timeout :: proc(s: ^Sema, duration: time.Duration) -> bool { - return wait_group_wait_with_timeout(&s.impl.wg, duration) - } -}
\ No newline at end of file diff --git a/core/sys/darwin/xnu_system_call_wrappers.odin b/core/sys/darwin/xnu_system_call_wrappers.odin index 4e4227f1f..685f75ffa 100644 --- a/core/sys/darwin/xnu_system_call_wrappers.odin +++ b/core/sys/darwin/xnu_system_call_wrappers.odin @@ -402,7 +402,7 @@ syscall_openat :: #force_inline proc(fd: int, path: cstring, oflag: u32, mode: u return cast(c.int)intrinsics.syscall(unix_offset_syscall(.openat), uintptr(fd), transmute(uintptr)path, uintptr(oflag), uintptr(mode)) } -syscall_getentropy :: #force_inline proc(buf: ^u8, buflen: u64) -> c.int { +syscall_getentropy :: #force_inline proc(buf: [^]u8, buflen: u64) -> c.int { return cast(c.int)intrinsics.syscall(unix_offset_syscall(.getentropy), uintptr(buf), uintptr(buflen)) } diff --git a/core/sys/unix/pthread_darwin.odin b/core/sys/unix/pthread_darwin.odin index 542a550cb..e138b8610 100644 --- a/core/sys/unix/pthread_darwin.odin +++ b/core/sys/unix/pthread_darwin.odin @@ -81,3 +81,16 @@ PTHREAD_MUTEX_NORMAL :: 0 PTHREAD_MUTEX_RECURSIVE :: 1 PTHREAD_MUTEX_ERRORCHECK :: 2 +PTHREAD_CANCEL_ENABLE :: 0 +PTHREAD_CANCEL_DISABLE :: 1 +PTHREAD_CANCEL_DEFERRED :: 0 +PTHREAD_CANCEL_ASYNCHRONOUS :: 1 + +foreign import pthread "System.framework" + +@(default_calling_convention="c") +foreign pthread { + pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int --- + pthread_setcanceltype :: proc (type: c.int, old_type: ^c.int) -> c.int --- + pthread_cancel :: proc (thread: pthread_t) -> c.int --- +}
\ No newline at end of file diff --git a/core/sys/unix/pthread_freebsd.odin b/core/sys/unix/pthread_freebsd.odin index dd5306417..e02345cad 100644 --- a/core/sys/unix/pthread_freebsd.odin +++ b/core/sys/unix/pthread_freebsd.odin @@ -92,6 +92,11 @@ sem_t :: struct { _padding: u32, } +PTHREAD_CANCEL_ENABLE :: 0 +PTHREAD_CANCEL_DISABLE :: 1 +PTHREAD_CANCEL_DEFERRED :: 0 +PTHREAD_CANCEL_ASYNCHRONOUS :: 1 + foreign import "system:pthread" @(default_calling_convention="c") @@ -110,5 +115,8 @@ foreign pthread { // NOTE: unclear whether pthread_yield is well-supported on Linux systems, // see https://linux.die.net/man/3/pthread_yield pthread_yield :: proc() --- -} + pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int --- + pthread_setcanceltype :: proc (type: c.int, old_type: ^c.int) -> c.int --- + pthread_cancel :: proc (thread: pthread_t) -> c.int --- +}
\ No newline at end of file diff --git a/core/sys/unix/pthread_linux.odin b/core/sys/unix/pthread_linux.odin index 099e7c7e9..9c297ef22 100644 --- a/core/sys/unix/pthread_linux.odin +++ b/core/sys/unix/pthread_linux.odin @@ -94,6 +94,11 @@ when size_of(int) == 8 { SEM_T_SIZE :: 16 } +PTHREAD_CANCEL_ENABLE :: 0 +PTHREAD_CANCEL_DISABLE :: 1 +PTHREAD_CANCEL_DEFERRED :: 0 +PTHREAD_CANCEL_ASYNCHRONOUS :: 1 + foreign import "system:pthread" @(default_calling_convention="c") @@ -112,4 +117,8 @@ foreign pthread { // NOTE: unclear whether pthread_yield is well-supported on Linux systems, // see https://linux.die.net/man/3/pthread_yield pthread_yield :: proc() -> c.int --- + + pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int --- + pthread_setcanceltype :: proc (type: c.int, old_type: ^c.int) -> c.int --- + pthread_cancel :: proc (thread: pthread_t) -> c.int --- } diff --git a/core/sys/unix/pthread_openbsd.odin b/core/sys/unix/pthread_openbsd.odin index c855f95c0..7ae82e662 100644 --- a/core/sys/unix/pthread_openbsd.odin +++ b/core/sys/unix/pthread_openbsd.odin @@ -46,6 +46,11 @@ sched_param :: struct { sem_t :: distinct rawptr +PTHREAD_CANCEL_ENABLE :: 0 +PTHREAD_CANCEL_DISABLE :: 1 +PTHREAD_CANCEL_DEFERRED :: 0 +PTHREAD_CANCEL_ASYNCHRONOUS :: 1 + foreign import libc "system:c" @(default_calling_convention="c") @@ -62,4 +67,8 @@ foreign libc { // NOTE: unclear whether pthread_yield is well-supported on Linux systems, // see https://linux.die.net/man/3/pthread_yield pthread_yield :: proc() --- -} + + pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int --- + pthread_setcanceltype :: proc (type: c.int, old_type: ^c.int) -> c.int --- + pthread_cancel :: proc (thread: pthread_t) -> c.int --- +}
\ No newline at end of file diff --git a/core/sys/unix/pthread_unix.odin b/core/sys/unix/pthread_unix.odin index 62e3701ab..8bf397647 100644 --- a/core/sys/unix/pthread_unix.odin +++ b/core/sys/unix/pthread_unix.odin @@ -4,7 +4,6 @@ package unix foreign import "system:pthread" import "core:c" -import "core:time" // // On success, these functions return 0. @@ -72,7 +71,7 @@ foreign pthread { // assumes the mutex is pre-locked pthread_cond_wait :: proc(cond: ^pthread_cond_t, mutex: ^pthread_mutex_t) -> c.int --- - pthread_cond_timedwait :: proc(cond: ^pthread_cond_t, mutex: ^pthread_mutex_t, timeout: ^time.TimeSpec) -> c.int --- + pthread_cond_timedwait :: proc(cond: ^pthread_cond_t, mutex: ^pthread_mutex_t, timeout: ^timespec) -> c.int --- pthread_condattr_init :: proc(attrs: ^pthread_condattr_t) -> c.int --- pthread_condattr_destroy :: proc(attrs: ^pthread_condattr_t) -> c.int --- @@ -95,7 +94,7 @@ foreign pthread { pthread_mutex_lock :: proc(mutex: ^pthread_mutex_t) -> c.int --- - pthread_mutex_timedlock :: proc(mutex: ^pthread_mutex_t, timeout: ^time.TimeSpec) -> c.int --- + pthread_mutex_timedlock :: proc(mutex: ^pthread_mutex_t, timeout: ^timespec) -> c.int --- pthread_mutex_unlock :: proc(mutex: ^pthread_mutex_t) -> c.int --- diff --git a/core/sys/unix/syscalls_linux.odin b/core/sys/unix/syscalls_linux.odin index 630b4ef24..d611e33f0 100644 --- a/core/sys/unix/syscalls_linux.odin +++ b/core/sys/unix/syscalls_linux.odin @@ -1114,7 +1114,7 @@ when ODIN_ARCH == .amd64 { SYS_landlock_add_rule : uintptr : 445 SYS_landlock_restrict_self : uintptr : 446 SYS_memfd_secret : uintptr : 447 -} else when ODIN_ARCH == .arm { +} else when ODIN_ARCH == .arm32 { // TODO SYS_restart_syscall : uintptr : 0 SYS_exit : uintptr : 1 SYS_fork : uintptr : 2 @@ -1567,7 +1567,7 @@ sys_gettid :: proc "contextless" () -> int { return cast(int)intrinsics.syscall(SYS_gettid) } -sys_getrandom :: proc "contextless" (buf: ^byte, buflen: int, flags: uint) -> int { +sys_getrandom :: proc "contextless" (buf: [^]byte, buflen: int, flags: uint) -> int { return cast(int)intrinsics.syscall(SYS_getrandom, buf, cast(uintptr)(buflen), cast(uintptr)(flags)) } diff --git a/core/sys/unix/time_unix.odin b/core/sys/unix/time_unix.odin new file mode 100644 index 000000000..fa3a7a29d --- /dev/null +++ b/core/sys/unix/time_unix.odin @@ -0,0 +1,75 @@ +//+build linux, darwin, freebsd, openbsd +package unix + +when ODIN_OS == .Darwin { + foreign import libc "System.framework" +} else { + foreign import libc "system:c" +} + +import "core:c" + +@(default_calling_convention="c") +foreign libc { + clock_gettime :: proc(clock_id: u64, timespec: ^timespec) -> c.int --- + sleep :: proc(seconds: c.uint) -> c.int --- + nanosleep :: proc(requested, remaining: ^timespec) -> c.int --- +} + +timespec :: struct { + tv_sec: i64, // seconds + tv_nsec: i64, // nanoseconds +} + +when ODIN_OS == .OpenBSD { + CLOCK_REALTIME :: 0 + CLOCK_PROCESS_CPUTIME_ID :: 2 + CLOCK_MONOTONIC :: 3 + CLOCK_THREAD_CPUTIME_ID :: 4 + CLOCK_UPTIME :: 5 + CLOCK_BOOTTIME :: 6 + + // CLOCK_MONOTONIC_RAW doesn't exist, use CLOCK_MONOTONIC + CLOCK_MONOTONIC_RAW :: CLOCK_MONOTONIC +} else { + CLOCK_REALTIME :: 0 // NOTE(tetra): May jump in time, when user changes the system time. + CLOCK_MONOTONIC :: 1 // NOTE(tetra): May stand still while system is asleep. + CLOCK_PROCESS_CPUTIME_ID :: 2 + CLOCK_THREAD_CPUTIME_ID :: 3 + CLOCK_MONOTONIC_RAW :: 4 // NOTE(tetra): "RAW" means: Not adjusted by NTP. + CLOCK_REALTIME_COARSE :: 5 // NOTE(tetra): "COARSE" clocks are apparently much faster, but not "fine-grained." + CLOCK_MONOTONIC_COARSE :: 6 + CLOCK_BOOTTIME :: 7 // NOTE(tetra): Same as MONOTONIC, except also including time system was asleep. + CLOCK_REALTIME_ALARM :: 8 + CLOCK_BOOTTIME_ALARM :: 9 +} + +// TODO(tetra, 2019-11-05): The original implementation of this package for Darwin used this constants. +// I do not know if Darwin programmers are used to the existance of these constants or not, so +// I'm leaving aliases to them for now. +CLOCK_SYSTEM :: CLOCK_REALTIME +CLOCK_CALENDAR :: CLOCK_MONOTONIC + +boot_time_in_nanoseconds :: proc "c" () -> i64 { + ts_now, ts_boottime: timespec + clock_gettime(CLOCK_REALTIME, &ts_now) + clock_gettime(CLOCK_BOOTTIME, &ts_boottime) + + ns := (ts_now.tv_sec - ts_boottime.tv_sec) * 1e9 + ts_now.tv_nsec - ts_boottime.tv_nsec + return i64(ns) +} + +seconds_since_boot :: proc "c" () -> f64 { + ts_boottime: timespec + clock_gettime(CLOCK_BOOTTIME, &ts_boottime) + return f64(ts_boottime.tv_sec) + f64(ts_boottime.tv_nsec) / 1e9 +} + + +inline_nanosleep :: proc "c" (nanoseconds: i64) -> (remaining: timespec, res: i32) { + s, ns := nanoseconds / 1e9, nanoseconds % 1e9 + requested := timespec{tv_sec=s, tv_nsec=ns} + res = nanosleep(&requested, &remaining) + return +} + diff --git a/core/sys/win32/crt.odin b/core/sys/win32/crt.odin deleted file mode 100644 index b39d35375..000000000 --- a/core/sys/win32/crt.odin +++ /dev/null @@ -1,15 +0,0 @@ -// +build windows -package win32 - -import "core:strings" - -foreign { - @(link_name="_wgetcwd") _get_cwd_wide :: proc(buffer: Wstring, buf_len: int) -> ^Wstring --- -} - -get_cwd :: proc(allocator := context.temp_allocator) -> string { - buffer := make([]u16, MAX_PATH_WIDE, allocator) - _get_cwd_wide(Wstring(&buffer[0]), MAX_PATH_WIDE) - file := utf16_to_utf8(buffer[:], allocator) - return strings.trim_right_null(file) -} diff --git a/core/sys/win32/gdi32.odin b/core/sys/win32/gdi32.odin deleted file mode 100644 index 13bc4796f..000000000 --- a/core/sys/win32/gdi32.odin +++ /dev/null @@ -1,26 +0,0 @@ -// +build windows -package win32 - -foreign import "system:gdi32.lib" - -WHITENESS :: 0x00FF0062 -BLACKNESS :: 0x00000042 - -@(default_calling_convention = "std") -foreign gdi32 { - @(link_name="GetStockObject") get_stock_object :: proc(fn_object: i32) -> Hgdiobj --- - - @(link_name="StretchDIBits") - stretch_dibits :: proc(hdc: Hdc, - x_dst, y_dst, width_dst, height_dst: i32, - x_src, y_src, width_src, header_src: i32, - bits: rawptr, bits_info: ^Bitmap_Info, - usage: u32, - rop: u32) -> i32 --- - - @(link_name="SetPixelFormat") set_pixel_format :: proc(hdc: Hdc, pixel_format: i32, pfd: ^Pixel_Format_Descriptor) -> Bool --- - @(link_name="ChoosePixelFormat") choose_pixel_format :: proc(hdc: Hdc, pfd: ^Pixel_Format_Descriptor) -> i32 --- - @(link_name="SwapBuffers") swap_buffers :: proc(hdc: Hdc) -> Bool --- - - @(link_name="PatBlt") pat_blt :: proc(hdc: Hdc, x, y, w, h: i32, rop: u32) -> Bool --- -} diff --git a/core/sys/win32/general.odin b/core/sys/win32/general.odin deleted file mode 100644 index d53bf8a4f..000000000 --- a/core/sys/win32/general.odin +++ /dev/null @@ -1,1176 +0,0 @@ -// +build windows -package win32 - -Uint_Ptr :: distinct uintptr -Int_Ptr :: distinct int -Long_Ptr :: distinct int - -Handle :: distinct rawptr -Hwnd :: distinct Handle -Hdc :: distinct Handle -Hinstance :: distinct Handle -Hicon :: distinct Handle -Hcursor :: distinct Handle -Hmenu :: distinct Handle -Hbitmap :: distinct Handle -Hbrush :: distinct Handle -Hgdiobj :: distinct Handle -Hmodule :: distinct Handle -Hmonitor :: distinct Handle -Hrawinput :: distinct Handle -Hresult :: distinct i32 -HKL :: distinct Handle -Wparam :: distinct Uint_Ptr -Lparam :: distinct Long_Ptr -Lresult :: distinct Long_Ptr -Wnd_Proc :: distinct #type proc "std" (Hwnd, u32, Wparam, Lparam) -> Lresult -Monitor_Enum_Proc :: distinct #type proc "std" (Hmonitor, Hdc, ^Rect, Lparam) -> bool - - - -Bool :: distinct b32 - -Wstring :: distinct ^u16 - -Point :: struct { - x, y: i32, -} - -Wnd_Class_A :: struct { - style: u32, - wnd_proc: Wnd_Proc, - cls_extra, wnd_extra: i32, - instance: Hinstance, - icon: Hicon, - cursor: Hcursor, - background: Hbrush, - menu_name, class_name: cstring, -} - -Wnd_Class_W :: struct { - style: u32, - wnd_proc: Wnd_Proc, - cls_extra, wnd_extra: i32, - instance: Hinstance, - icon: Hicon, - cursor: Hcursor, - background: Hbrush, - menu_name, class_name: Wstring, -} - -Wnd_Class_Ex_A :: struct { - size, style: u32, - wnd_proc: Wnd_Proc, - cls_extra, wnd_extra: i32, - instance: Hinstance, - icon: Hicon, - cursor: Hcursor, - background: Hbrush, - menu_name, class_name: cstring, - sm: Hicon, -} - -Wnd_Class_Ex_W :: struct { - size, style: u32, - wnd_proc: Wnd_Proc, - cls_extra, wnd_extra: i32, - instance: Hinstance, - icon: Hicon, - cursor: Hcursor, - background: Hbrush, - menu_name, class_name: Wstring, - sm: Hicon, -} - - -Msg :: struct { - hwnd: Hwnd, - message: u32, - wparam: Wparam, - lparam: Lparam, - time: u32, - pt: Point, -} - -Rect :: struct { - left: i32, - top: i32, - right: i32, - bottom: i32, -} - -Dev_Mode_A :: struct { - device_name: [32]u8, - spec_version: u16, - driver_version: u16, - size: u16, - driver_extra: u16, - fields: u32, - using _: struct #raw_union { - // Printer only fields. - using _: struct { - orientation: i16, - paper_size: i16, - paper_length: i16, - paper_width: i16, - scale: i16, - copies: i16, - default_source: i16, - print_quality: i16, - }, - // Display only fields. - using _: struct { - position: Point, - display_orientation: u32, - display_fixed_output: u32, - }, - }, - color: i16, - duplex: i16, - y_resolution: i16, - tt_option: i16, - collate: i16, - form_name: [32]u8, - log_pixels: u16, - bits_per_pel: u32, - pels_width: u32, - pels_height: u32, - using _: struct #raw_union { - display_flags: u32, - nup: u32, - }, - display_frequency: u32, - icm_method: u32, - icm_intent: u32, - media_type: u32, - dither_type: u32, - reserved_1: u32, - reserved_2: u32, - panning_width: u32, - panning_height: u32, -} - -Filetime :: struct { - lo, hi: u32, -} - -Systemtime :: struct { - year, month: u16, - day_of_week, day: u16, - hour, minute, second, millisecond: u16, -} - -By_Handle_File_Information :: struct { - file_attributes: u32, - creation_time, - last_access_time, - last_write_time: Filetime, - volume_serial_number, - file_size_high, - file_size_low, - number_of_links, - file_index_high, - file_index_low: u32, -} - -File_Attribute_Data :: struct { - file_attributes: u32, - creation_time, - last_access_time, - last_write_time: Filetime, - file_size_high, - file_size_low: u32, -} - -// NOTE(Jeroen): The widechar version might want at least the 32k MAX_PATH_WIDE -// https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-findfirstfilew#parameters -Find_Data_W :: struct{ - file_attributes: u32, - creation_time: Filetime, - last_access_time: Filetime, - last_write_time: Filetime, - file_size_high: u32, - file_size_low: u32, - reserved0: u32, - reserved1: u32, - file_name: [MAX_PATH]u16, - alternate_file_name: [14]u16, -} - -Find_Data_A :: struct{ - file_attributes: u32, - creation_time: Filetime, - last_access_time: Filetime, - last_write_time: Filetime, - file_size_high: u32, - file_size_low: u32, - reserved0: u32, - reserved1: u32, - file_name: [MAX_PATH]byte, - alternate_file_name: [14]byte, -} - -Security_Attributes :: struct { - length: u32, - security_descriptor: rawptr, - inherit_handle: Bool, -} - -Process_Information :: struct { - process: Handle, - thread: Handle, - process_id: u32, - thread_id: u32, -} - -Startup_Info :: struct { - cb: u32, - reserved: Wstring, - desktop: Wstring, - title: Wstring, - x: u32, - y: u32, - x_size: u32, - y_size: u32, - x_count_chars: u32, - y_count_chars: u32, - fill_attribute: u32, - flags: u32, - show_window: u16, - _: u16, - _: cstring, - stdin: Handle, - stdout: Handle, - stderr: Handle, -} - -Pixel_Format_Descriptor :: struct { - size, - version, - flags: u32, - - pixel_type, - color_bits, - red_bits, - red_shift, - green_bits, - green_shift, - blue_bits, - blue_shift, - alpha_bits, - alpha_shift, - accum_bits, - accum_red_bits, - accum_green_bits, - accum_blue_bits, - accum_alpha_bits, - depth_bits, - stencil_bits, - aux_buffers, - layer_type, - reserved: byte, - - layer_mask, - visible_mask, - damage_mask: u32, -} - -Critical_Section :: struct { - debug_info: ^Critical_Section_Debug, - - lock_count: i32, - recursion_count: i32, - owning_thread: Handle, - lock_semaphore: Handle, - spin_count: ^u32, -} - -Critical_Section_Debug :: struct { - typ: u16, - creator_back_trace_index: u16, - critical_section: ^Critical_Section, - process_locks_list: ^List_Entry, - entry_count: u32, - contention_count: u32, - flags: u32, - creator_back_trace_index_high: u16, - spare_word: u16, -} - -List_Entry :: struct {flink, blink: ^List_Entry} - - -Raw_Input_Device :: struct { - usage_page: u16, - usage: u16, - flags: u32, - wnd_target: Hwnd, -} - -Raw_Input_Header :: struct { - kind: u32, - size: u32, - device: Handle, - wparam: Wparam, -} - -Raw_HID :: struct { - size_hid: u32, - count: u32, - raw_data: [1]byte, -} - -Raw_Keyboard :: struct { - make_code: u16, - flags: u16, - reserved: u16, - vkey: u16, - message: u32, - extra_information: u32, -} - -Raw_Mouse :: struct { - flags: u16, - using data: struct #raw_union { - buttons: u32, - using _: struct { - button_flags: u16, - button_data: u16, - }, - }, - raw_buttons: u32, - last_x: i32, - last_y: i32, - extra_information: u32, -} - -Raw_Input :: struct { - using header: Raw_Input_Header, - data: struct #raw_union { - mouse: Raw_Mouse, - keyboard: Raw_Keyboard, - hid: Raw_HID, - }, -} - - -Overlapped :: struct { - internal: ^u64, - internal_high: ^u64, - using _: struct #raw_union { - using _: struct { - offset: u32, - offset_high: u32, - }, - pointer: rawptr, - }, - event: Handle, -} - -File_Notify_Information :: struct { - next_entry_offset: u32, - action: u32, - file_name_length: u32, - file_name: [1]u16, -} - -// https://docs.microsoft.com/en-gb/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info -System_Info :: struct { - using _: struct #raw_union { - oem_id: u32, - using _: struct #raw_union { - processor_architecture: u16, - _: u16, // reserved - }, - }, - page_size: u32, - minimum_application_address: rawptr, - maximum_application_address: rawptr, - active_processor_mask: u32, - number_of_processors: u32, - processor_type: u32, - allocation_granularity: u32, - processor_level: u16, - processor_revision: u16, -} - -// https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ns-winnt-_osversioninfoexa -OS_Version_Info_Ex_A :: struct { - os_version_info_size: u32, - major_version: u32, - minor_version: u32, - build_number: u32, - platform_id : u32, - service_pack_string: [128]u8, - service_pack_major: u16, - service_pack_minor: u16, - suite_mask: u16, - product_type: u8, - reserved: u8, -} - -MAPVK_VK_TO_VSC :: 0 -MAPVK_VSC_TO_VK :: 1 -MAPVK_VK_TO_CHAR :: 2 -MAPVK_VSC_TO_VK_EX :: 3 - -//WinUser.h -ENUM_CURRENT_SETTINGS :: u32(4294967295) // (DWORD)-1 -ENUM_REGISTRY_SETTINGS :: u32(4294967294) // (DWORD)-2 - -VK_LBUTTON :: 0x01 -VK_RBUTTON :: 0x02 -VK_CANCEL :: 0x03 -VK_MBUTTON :: 0x04 /* NOT contiguous with L & RBUTTON */ -VK_XBUTTON1 :: 0x05 /* NOT contiguous with L & RBUTTON */ -VK_XBUTTON2 :: 0x06 /* NOT contiguous with L & RBUTTON */ - -/* - * :: 0x07 : reserved - */ - -VK_BACK :: 0x08 -VK_TAB :: 0x09 - -/* - * :: 0x0A - :: 0x0B : reserved - */ - -VK_CLEAR :: 0x0C -VK_RETURN :: 0x0D - -/* - * :: 0x0E - :: 0x0F : unassigned - */ - -VK_SHIFT :: 0x10 -VK_CONTROL :: 0x11 -VK_MENU :: 0x12 -VK_PAUSE :: 0x13 -VK_CAPITAL :: 0x14 - -VK_KANA :: 0x15 -VK_HANGEUL :: 0x15 /* old name - should be here for compatibility */ -VK_HANGUL :: 0x15 - -/* - * :: 0x16 : unassigned - */ - -VK_JUNJA :: 0x17 -VK_FINAL :: 0x18 -VK_HANJA :: 0x19 -VK_KANJI :: 0x19 - -/* - * :: 0x1A : unassigned - */ - -VK_ESCAPE :: 0x1B - -VK_CONVERT :: 0x1C -VK_NONCONVERT :: 0x1D -VK_ACCEPT :: 0x1E -VK_MODECHANGE :: 0x1F - -VK_SPACE :: 0x20 -VK_PRIOR :: 0x21 -VK_NEXT :: 0x22 -VK_END :: 0x23 -VK_HOME :: 0x24 -VK_LEFT :: 0x25 -VK_UP :: 0x26 -VK_RIGHT :: 0x27 -VK_DOWN :: 0x28 -VK_SELECT :: 0x29 -VK_PRINT :: 0x2A -VK_EXECUTE :: 0x2B -VK_SNAPSHOT :: 0x2C -VK_INSERT :: 0x2D -VK_DELETE :: 0x2E -VK_HELP :: 0x2F - -/* - * VK_0 - VK_9 are the same as ASCII '0' - '9' (:: 0x30 - :: 0x39) - * :: 0x3A - :: 0x40 : unassigned - * VK_A - VK_Z are the same as ASCII 'A' - 'Z' (:: 0x41 - :: 0x5A) - */ - -VK_LWIN :: 0x5B -VK_RWIN :: 0x5C -VK_APPS :: 0x5D - -/* - * :: 0x5E : reserved - */ - -VK_SLEEP :: 0x5F - -VK_NUMPAD0 :: 0x60 -VK_NUMPAD1 :: 0x61 -VK_NUMPAD2 :: 0x62 -VK_NUMPAD3 :: 0x63 -VK_NUMPAD4 :: 0x64 -VK_NUMPAD5 :: 0x65 -VK_NUMPAD6 :: 0x66 -VK_NUMPAD7 :: 0x67 -VK_NUMPAD8 :: 0x68 -VK_NUMPAD9 :: 0x69 -VK_MULTIPLY :: 0x6A -VK_ADD :: 0x6B -VK_SEPARATOR :: 0x6C -VK_SUBTRACT :: 0x6D -VK_DECIMAL :: 0x6E -VK_DIVIDE :: 0x6F -VK_F1 :: 0x70 -VK_F2 :: 0x71 -VK_F3 :: 0x72 -VK_F4 :: 0x73 -VK_F5 :: 0x74 -VK_F6 :: 0x75 -VK_F7 :: 0x76 -VK_F8 :: 0x77 -VK_F9 :: 0x78 -VK_F10 :: 0x79 -VK_F11 :: 0x7A -VK_F12 :: 0x7B -VK_F13 :: 0x7C -VK_F14 :: 0x7D -VK_F15 :: 0x7E -VK_F16 :: 0x7F -VK_F17 :: 0x80 -VK_F18 :: 0x81 -VK_F19 :: 0x82 -VK_F20 :: 0x83 -VK_F21 :: 0x84 -VK_F22 :: 0x85 -VK_F23 :: 0x86 -VK_F24 :: 0x87 - -INVALID_HANDLE :: Handle(~uintptr(0)) - -CREATE_SUSPENDED :: 0x00000004 -STACK_SIZE_PARAM_IS_A_RESERVATION :: 0x00010000 -WAIT_ABANDONED :: 0x00000080 -WAIT_OBJECT_0 :: 0 -WAIT_TIMEOUT :: 0x00000102 -WAIT_FAILED :: 0xffffffff - -CS_VREDRAW :: 0x0001 -CS_HREDRAW :: 0x0002 -CS_OWNDC :: 0x0020 -CW_USEDEFAULT :: -0x80000000 - -WS_OVERLAPPED :: 0 -WS_MAXIMIZEBOX :: 0x00010000 -WS_MINIMIZEBOX :: 0x00020000 -WS_THICKFRAME :: 0x00040000 -WS_SYSMENU :: 0x00080000 -WS_BORDER :: 0x00800000 -WS_CAPTION :: 0x00C00000 -WS_VISIBLE :: 0x10000000 -WS_POPUP :: 0x80000000 -WS_MAXIMIZE :: 0x01000000 -WS_MINIMIZE :: 0x20000000 -WS_OVERLAPPEDWINDOW :: WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|WS_THICKFRAME|WS_MINIMIZEBOX|WS_MAXIMIZEBOX -WS_POPUPWINDOW :: WS_POPUP | WS_BORDER | WS_SYSMENU - -WS_EX_DLGMODALFRAME :: 0x00000001 -WS_EX_NOPARENTNOTIFY :: 0x00000004 -WS_EX_TOPMOST :: 0x00000008 -WS_EX_ACCEPTFILES :: 0x00000010 -WS_EX_TRANSPARENT :: 0x00000020 -WS_EX_MDICHILD :: 0x00000040 -WS_EX_TOOLWINDOW :: 0x00000080 -WS_EX_WINDOWEDGE :: 0x00000100 -WS_EX_CLIENTEDGE :: 0x00000200 -WS_EX_CONTEXTHELP :: 0x00000400 -WS_EX_RIGHT :: 0x00001000 -WS_EX_LEFT :: 0x00000000 -WS_EX_RTLREADING :: 0x00002000 -WS_EX_LTRREADING :: 0x00000000 -WS_EX_LEFTSCROLLBAR :: 0x00004000 -WS_EX_RIGHTSCROLLBAR :: 0x00000000 -WS_EX_CONTROLPARENT :: 0x00010000 -WS_EX_STATICEDGE :: 0x00020000 -WS_EX_APPWINDOW :: 0x00040000 -WS_EX_OVERLAPPEDWINDOW :: WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE -WS_EX_PALETTEWINDOW :: WS_EX_WINDOWEDGE | WS_EX_TOOLWINDOW | WS_EX_TOPMOST -WS_EX_LAYERED :: 0x00080000 -WS_EX_NOINHERITLAYOUT :: 0x00100000 // Disable inheritence of mirroring by children -WS_EX_NOREDIRECTIONBITMAP :: 0x00200000 -WS_EX_LAYOUTRTL :: 0x00400000 // Right to left mirroring -WS_EX_COMPOSITED :: 0x02000000 -WS_EX_NOACTIVATE :: 0x08000000 - -WM_ACTIVATE :: 0x0006 -WM_ACTIVATEAPP :: 0x001C -WM_CHAR :: 0x0102 -WM_CLOSE :: 0x0010 -WM_CREATE :: 0x0001 -WM_DESTROY :: 0x0002 -WM_INPUT :: 0x00FF -WM_KEYDOWN :: 0x0100 -WM_KEYUP :: 0x0101 -WM_KILLFOCUS :: 0x0008 -WM_QUIT :: 0x0012 -WM_SETCURSOR :: 0x0020 -WM_SETFOCUS :: 0x0007 -WM_SIZE :: 0x0005 -WM_SIZING :: 0x0214 -WM_SYSKEYDOWN :: 0x0104 -WM_SYSKEYUP :: 0x0105 -WM_USER :: 0x0400 -WM_WINDOWPOSCHANGED :: 0x0047 -WM_COMMAND :: 0x0111 -WM_PAINT :: 0x000F - -WM_MOUSEWHEEL :: 0x020A -WM_MOUSEMOVE :: 0x0200 -WM_LBUTTONDOWN :: 0x0201 -WM_LBUTTONUP :: 0x0202 -WM_LBUTTONDBLCLK :: 0x0203 -WM_RBUTTONDOWN :: 0x0204 -WM_RBUTTONUP :: 0x0205 -WM_RBUTTONDBLCLK :: 0x0206 -WM_MBUTTONDOWN :: 0x0207 -WM_MBUTTONUP :: 0x0208 -WM_MBUTTONDBLCLK :: 0x0209 - -PM_NOREMOVE :: 0x0000 -PM_REMOVE :: 0x0001 -PM_NOYIELD :: 0x0002 - -BLACK_BRUSH :: 4 - -SM_CXSCREEN :: 0 -SM_CYSCREEN :: 1 - -SW_SHOW :: 5 - -COLOR_BACKGROUND :: Hbrush(uintptr(1)) - -INVALID_SET_FILE_POINTER :: ~u32(0) -HEAP_ZERO_MEMORY :: 0x00000008 -INFINITE :: 0xffffffff -GWL_EXSTYLE :: -20 -GWLP_HINSTANCE :: -6 -GWLP_ID :: -12 -GWL_STYLE :: -16 -GWLP_USERDATA :: -21 -GWLP_WNDPROC :: -4 -Hwnd_TOP :: Hwnd(uintptr(0)) - -BI_RGB :: 0 -DIB_RGB_COLORS :: 0x00 -SRCCOPY: u32 : 0x00cc0020 - - -MONITOR_DEFAULTTONULL :: 0x00000000 -MONITOR_DEFAULTTOPRIMARY :: 0x00000001 -MONITOR_DEFAULTTONEAREST :: 0x00000002 - -SWP_FRAMECHANGED :: 0x0020 -SWP_NOOWNERZORDER :: 0x0200 -SWP_NOZORDER :: 0x0004 -SWP_NOSIZE :: 0x0001 -SWP_NOMOVE :: 0x0002 - - -// Raw Input - - -RID_HEADER :: 0x10000005 -RID_INPUT :: 0x10000003 - - -RIDEV_APPKEYS :: 0x00000400 -RIDEV_CAPTUREMOUSE :: 0x00000200 -RIDEV_DEVNOTIFY :: 0x00002000 -RIDEV_EXCLUDE :: 0x00000010 -RIDEV_EXINPUTSINK :: 0x00001000 -RIDEV_INPUTSINK :: 0x00000100 -RIDEV_NOHOTKEYS :: 0x00000200 -RIDEV_NOLEGACY :: 0x00000030 -RIDEV_PAGEONLY :: 0x00000020 -RIDEV_REMOVE :: 0x00000001 - - -RIM_TYPEMOUSE :: 0 -RIM_TYPEKEYBOARD :: 1 -RIM_TYPEHID :: 2 - - -MOUSE_ATTRIBUTES_CHANGED :: 0x04 -MOUSE_MOVE_RELATIVE :: 0 -MOUSE_MOVE_ABSOLUTE :: 1 -MOUSE_VIRTUAL_DESKTOP :: 0x02 - - - -RI_MOUSE_BUTTON_1_DOWN :: 0x0001 -RI_MOUSE_BUTTON_1_UP :: 0x0002 -RI_MOUSE_BUTTON_2_DOWN :: 0x0004 -RI_MOUSE_BUTTON_2_UP :: 0x0008 -RI_MOUSE_BUTTON_3_DOWN :: 0x0010 -RI_MOUSE_BUTTON_3_UP :: 0x0020 -RI_MOUSE_BUTTON_4_DOWN :: 0x0040 -RI_MOUSE_BUTTON_4_UP :: 0x0080 -RI_MOUSE_BUTTON_5_DOWN :: 0x0100 -RI_MOUSE_BUTTON_5_UP :: 0x0200 -RI_MOUSE_LEFT_BUTTON_DOWN :: 0x0001 -RI_MOUSE_LEFT_BUTTON_UP :: 0x0002 -RI_MOUSE_MIDDLE_BUTTON_DOWN :: 0x0010 -RI_MOUSE_MIDDLE_BUTTON_UP :: 0x0020 -RI_MOUSE_RIGHT_BUTTON_DOWN :: 0x0004 -RI_MOUSE_RIGHT_BUTTON_UP :: 0x0008 -RI_MOUSE_WHEEL :: 0x0400 - - -RI_KEY_MAKE :: 0x00 -RI_KEY_BREAK :: 0x01 -RI_KEY_E0 :: 0x02 -RI_KEY_E1 :: 0x04 -RI_KEY_TERMSRV_SET_LED :: 0x08 -RI_KEY_TERMSRV_SHADOW :: 0x10 - -// Windows OpenGL - -PFD_TYPE_RGBA :: 0 -PFD_TYPE_COLORINDEX :: 1 -PFD_MAIN_PLANE :: 0 -PFD_OVERLAY_PLANE :: 1 -PFD_UNDERLAY_PLANE :: -1 -PFD_DOUBLEBUFFER :: 1 -PFD_STEREO :: 2 -PFD_DRAW_TO_WINDOW :: 4 -PFD_DRAW_TO_BITMAP :: 8 -PFD_SUPPORT_GDI :: 16 -PFD_SUPPORT_OPENGL :: 32 -PFD_GENERIC_FORMAT :: 64 -PFD_NEED_PALETTE :: 128 -PFD_NEED_SYSTEM_PALETTE :: 0x00000100 -PFD_SWAP_EXCHANGE :: 0x00000200 -PFD_SWAP_COPY :: 0x00000400 -PFD_SWAP_LAYER_BUFFERS :: 0x00000800 -PFD_GENERIC_ACCELERATED :: 0x00001000 -PFD_DEPTH_DONTCARE :: 0x20000000 -PFD_DOUBLEBUFFER_DONTCARE :: 0x40000000 -PFD_STEREO_DONTCARE :: 0x80000000 - -GET_FILEEX_INFO_LEVELS :: distinct i32 -GetFileExInfoStandard: GET_FILEEX_INFO_LEVELS : 0 -GetFileExMaxInfoLevel: GET_FILEEX_INFO_LEVELS : 1 - -STARTF_USESHOWWINDOW :: 0x00000001 -STARTF_USESIZE :: 0x00000002 -STARTF_USEPOSITION :: 0x00000004 -STARTF_USECOUNTCHARS :: 0x00000008 -STARTF_USEFILLATTRIBUTE :: 0x00000010 -STARTF_RUNFULLSCREEN :: 0x00000020 // ignored for non-x86 platforms -STARTF_FORCEONFEEDBACK :: 0x00000040 -STARTF_FORCEOFFFEEDBACK :: 0x00000080 -STARTF_USESTDHANDLES :: 0x00000100 -STARTF_USEHOTKEY :: 0x00000200 -STARTF_TITLEISLINKNAME :: 0x00000800 -STARTF_TITLEISAPPID :: 0x00001000 -STARTF_PREVENTPINNING :: 0x00002000 -STARTF_UNTRUSTEDSOURCE :: 0x00008000 - - -MOVEFILE_REPLACE_EXISTING :: 0x00000001 -MOVEFILE_COPY_ALLOWED :: 0x00000002 -MOVEFILE_DELAY_UNTIL_REBOOT :: 0x00000004 -MOVEFILE_WRITE_THROUGH :: 0x00000008 -MOVEFILE_CREATE_HARDLINK :: 0x00000010 -MOVEFILE_FAIL_IF_NOT_TRACKABLE :: 0x00000020 - -FILE_NOTIFY_CHANGE_FILE_NAME :: 0x00000001 -FILE_NOTIFY_CHANGE_DIR_NAME :: 0x00000002 -FILE_NOTIFY_CHANGE_ATTRIBUTES :: 0x00000004 -FILE_NOTIFY_CHANGE_SIZE :: 0x00000008 -FILE_NOTIFY_CHANGE_LAST_WRITE :: 0x00000010 -FILE_NOTIFY_CHANGE_LAST_ACCESS :: 0x00000020 -FILE_NOTIFY_CHANGE_CREATION :: 0x00000040 -FILE_NOTIFY_CHANGE_SECURITY :: 0x00000100 - -FILE_FLAG_WRITE_THROUGH :: 0x80000000 -FILE_FLAG_OVERLAPPED :: 0x40000000 -FILE_FLAG_NO_BUFFERING :: 0x20000000 -FILE_FLAG_RANDOM_ACCESS :: 0x10000000 -FILE_FLAG_SEQUENTIAL_SCAN :: 0x08000000 -FILE_FLAG_DELETE_ON_CLOSE :: 0x04000000 -FILE_FLAG_BACKUP_SEMANTICS :: 0x02000000 -FILE_FLAG_POSIX_SEMANTICS :: 0x01000000 -FILE_FLAG_SESSION_AWARE :: 0x00800000 -FILE_FLAG_OPEN_REPARSE_POINT :: 0x00200000 -FILE_FLAG_OPEN_NO_RECALL :: 0x00100000 -FILE_FLAG_FIRST_PIPE_INSTANCE :: 0x00080000 - -FILE_ACTION_ADDED :: 0x00000001 -FILE_ACTION_REMOVED :: 0x00000002 -FILE_ACTION_MODIFIED :: 0x00000003 -FILE_ACTION_RENAMED_OLD_NAME :: 0x00000004 -FILE_ACTION_RENAMED_NEW_NAME :: 0x00000005 - -CP_ACP :: 0 // default to ANSI code page -CP_OEMCP :: 1 // default to OEM code page -CP_MACCP :: 2 // default to MAC code page -CP_THREAD_ACP :: 3 // current thread's ANSI code page -CP_SYMBOL :: 42 // SYMBOL translations -CP_UTF7 :: 65000 // UTF-7 translation -CP_UTF8 :: 65001 // UTF-8 translation - - -MB_ERR_INVALID_CHARS :: 8 -WC_ERR_INVALID_CHARS :: 128 - -utf8_to_utf16 :: proc(s: string, allocator := context.temp_allocator) -> []u16 { - if len(s) < 1 { - return nil - } - - b := transmute([]byte)s - cstr := cstring(&b[0]) - n := multi_byte_to_wide_char(CP_UTF8, MB_ERR_INVALID_CHARS, cstr, i32(len(s)), nil, 0) - if n == 0 { - return nil - } - - text := make([]u16, n+1, allocator) - - n1 := multi_byte_to_wide_char(CP_UTF8, MB_ERR_INVALID_CHARS, cstr, i32(len(s)), Wstring(&text[0]), i32(n)) - if n1 == 0 { - delete(text, allocator) - return nil - } - - text[n] = 0 - for n >= 1 && text[n-1] == 0 { - n -= 1 - } - return text[:n] -} -utf8_to_wstring :: proc(s: string, allocator := context.temp_allocator) -> Wstring { - if res := utf8_to_utf16(s, allocator); res != nil { - return Wstring(&res[0]) - } - return nil -} - -wstring_to_utf8 :: proc(s: Wstring, N: int, allocator := context.temp_allocator) -> string { - if N == 0 { - return "" - } - - n := wide_char_to_multi_byte(CP_UTF8, WC_ERR_INVALID_CHARS, s, i32(N), nil, 0, nil, nil) - if n == 0 { - return "" - } - - // If N == -1 the call to wide_char_to_multi_byte assume the wide string is null terminated - // and will scan it to find the first null terminated character. The resulting string will - // also null terminated. - // If N != -1 it assumes the wide string is not null terminated and the resulting string - // will not be null terminated, we therefore have to force it to be null terminated manually. - text := make([]byte, n+1 if N != -1 else n, allocator) - - if n1 := wide_char_to_multi_byte(CP_UTF8, WC_ERR_INVALID_CHARS, s, i32(N), cstring(&text[0]), n, nil, nil); n1 == 0 { - delete(text, allocator) - return "" - } - - for i in 0..<n { - if text[i] == 0 { - n = i - break - } - } - - return string(text[:n]) -} - -utf16_to_utf8 :: proc(s: []u16, allocator := context.temp_allocator) -> string { - if len(s) == 0 { - return "" - } - return wstring_to_utf8(cast(Wstring)&s[0], len(s), allocator) -} - -get_query_performance_frequency :: proc() -> i64 { - r: i64 - query_performance_frequency(&r) - return r -} - -HIWORD_W :: proc(wParam: Wparam) -> u16 { return u16((u32(wParam) >> 16) & 0xffff) } -HIWORD_L :: proc(lParam: Lparam) -> u16 { return u16((u32(lParam) >> 16) & 0xffff) } -LOWORD_W :: proc(wParam: Wparam) -> u16 { return u16(wParam) } -LOWORD_L :: proc(lParam: Lparam) -> u16 { return u16(lParam) } - -is_key_down :: #force_inline proc(key: Key_Code) -> bool { return get_async_key_state(i32(key)) < 0 } - - - - -MAX_PATH :: 0x00000104 -MAX_PATH_WIDE :: 0x8000 - -HANDLE_FLAG_INHERIT :: 1 -HANDLE_FLAG_PROTECT_FROM_CLOSE :: 2 - -FILE_BEGIN :: 0 -FILE_CURRENT :: 1 -FILE_END :: 2 - -FILE_SHARE_READ :: 0x00000001 -FILE_SHARE_WRITE :: 0x00000002 -FILE_SHARE_DELETE :: 0x00000004 -FILE_GENERIC_ALL :: 0x10000000 -FILE_GENERIC_EXECUTE :: 0x20000000 -FILE_GENERIC_WRITE :: 0x40000000 -FILE_GENERIC_READ :: 0x80000000 - -FILE_READ_DATA :: 0x0001 -FILE_LIST_DIRECTORY :: 0x0001 -FILE_WRITE_DATA :: 0x0002 -FILE_ADD_FILE :: 0x0002 -FILE_APPEND_DATA :: 0x0004 -FILE_ADD_SUBDIRECTORY :: 0x0004 -FILE_CREATE_PIPE_INSTANCE :: 0x0004 -FILE_READ_EA :: 0x0008 -FILE_WRITE_EA :: 0x0010 -FILE_EXECUTE :: 0x0020 -FILE_TRAVERSE :: 0x0020 -FILE_DELETE_CHILD :: 0x0040 -FILE_READ_ATTRIBUTES :: 0x0080 -FILE_WRITE_ATTRIBUTES :: 0x0100 - -STD_INPUT_HANDLE :: -10 -STD_OUTPUT_HANDLE :: -11 -STD_ERROR_HANDLE :: -12 - -CREATE_NEW :: 1 -CREATE_ALWAYS :: 2 -OPEN_EXISTING :: 3 -OPEN_ALWAYS :: 4 -TRUNCATE_EXISTING :: 5 - -INVALID_FILE_ATTRIBUTES :: -1 - -FILE_ATTRIBUTE_READONLY :: 0x00000001 -FILE_ATTRIBUTE_HIDDEN :: 0x00000002 -FILE_ATTRIBUTE_SYSTEM :: 0x00000004 -FILE_ATTRIBUTE_DIRECTORY :: 0x00000010 -FILE_ATTRIBUTE_ARCHIVE :: 0x00000020 -FILE_ATTRIBUTE_DEVICE :: 0x00000040 -FILE_ATTRIBUTE_NORMAL :: 0x00000080 -FILE_ATTRIBUTE_TEMPORARY :: 0x00000100 -FILE_ATTRIBUTE_SPARSE_FILE :: 0x00000200 -FILE_ATTRIBUTE_REPARSE_Point :: 0x00000400 -FILE_ATTRIBUTE_COMPRESSED :: 0x00000800 -FILE_ATTRIBUTE_OFFLINE :: 0x00001000 -FILE_ATTRIBUTE_NOT_CONTENT_INDEXED :: 0x00002000 -FILE_ATTRIBUTE_ENCRYPTED :: 0x00004000 - -FILE_TYPE_DISK :: 0x0001 -FILE_TYPE_CHAR :: 0x0002 -FILE_TYPE_PIPE :: 0x0003 - - -Monitor_Info :: struct { - size: u32, - monitor: Rect, - work: Rect, - flags: u32, -} - -Window_Placement :: struct { - length: u32, - flags: u32, - show_cmd: u32, - min_pos: Point, - max_pos: Point, - normal_pos: Rect, -} - -Bitmap_Info_Header :: struct { - size: u32, - width, height: i32, - planes, bit_count: i16, - compression: u32, - size_image: u32, - x_pels_per_meter: i32, - y_pels_per_meter: i32, - clr_used: u32, - clr_important: u32, -} -Bitmap_Info :: struct { - using header: Bitmap_Info_Header, - colors: [1]Rgb_Quad, -} - -Paint_Struct :: struct { - hdc: Hdc, - erase: Bool, - rc_paint: Rect, - restore: Bool, - inc_update: Bool, - rgb_reserved: [32]byte, -} - - -Rgb_Quad :: struct {blue, green, red, reserved: byte} - - -Key_Code :: enum i32 { - Unknown = 0x00, - - Lbutton = 0x01, - Rbutton = 0x02, - Cancel = 0x03, - Mbutton = 0x04, - Xbutton1 = 0x05, - Xbutton2 = 0x06, - Back = 0x08, - Tab = 0x09, - Clear = 0x0C, - Return = 0x0D, - - Shift = 0x10, - Control = 0x11, - Menu = 0x12, - Pause = 0x13, - Capital = 0x14, - Kana = 0x15, - Hangeul = 0x15, - Hangul = 0x15, - Junja = 0x17, - Final = 0x18, - Hanja = 0x19, - Kanji = 0x19, - Escape = 0x1B, - Convert = 0x1C, - NonConvert = 0x1D, - Accept = 0x1E, - ModeChange = 0x1F, - Space = 0x20, - Prior = 0x21, - Next = 0x22, - End = 0x23, - Home = 0x24, - Left = 0x25, - Up = 0x26, - Right = 0x27, - Down = 0x28, - Select = 0x29, - Print = 0x2A, - Execute = 0x2B, - Snapshot = 0x2C, - Insert = 0x2D, - Delete = 0x2E, - Help = 0x2F, - - Num0 = '0', - Num1 = '1', - Num2 = '2', - Num3 = '3', - Num4 = '4', - Num5 = '5', - Num6 = '6', - Num7 = '7', - Num8 = '8', - Num9 = '9', - A = 'A', - B = 'B', - C = 'C', - D = 'D', - E = 'E', - F = 'F', - G = 'G', - H = 'H', - I = 'I', - J = 'J', - K = 'K', - L = 'L', - M = 'M', - N = 'N', - O = 'O', - P = 'P', - Q = 'Q', - R = 'R', - S = 'S', - T = 'T', - U = 'U', - V = 'V', - W = 'W', - X = 'X', - Y = 'Y', - Z = 'Z', - - Lwin = 0x5B, - Rwin = 0x5C, - Apps = 0x5D, - - Numpad0 = 0x60, - Numpad1 = 0x61, - Numpad2 = 0x62, - Numpad3 = 0x63, - Numpad4 = 0x64, - Numpad5 = 0x65, - Numpad6 = 0x66, - Numpad7 = 0x67, - Numpad8 = 0x68, - Numpad9 = 0x69, - Multiply = 0x6A, - Add = 0x6B, - Separator = 0x6C, - Subtract = 0x6D, - Decimal = 0x6E, - Divide = 0x6F, - - F1 = 0x70, - F2 = 0x71, - F3 = 0x72, - F4 = 0x73, - F5 = 0x74, - F6 = 0x75, - F7 = 0x76, - F8 = 0x77, - F9 = 0x78, - F10 = 0x79, - F11 = 0x7A, - F12 = 0x7B, - F13 = 0x7C, - F14 = 0x7D, - F15 = 0x7E, - F16 = 0x7F, - F17 = 0x80, - F18 = 0x81, - F19 = 0x82, - F20 = 0x83, - F21 = 0x84, - F22 = 0x85, - F23 = 0x86, - F24 = 0x87, - - Numlock = 0x90, - Scroll = 0x91, - Lshift = 0xA0, - Rshift = 0xA1, - Lcontrol = 0xA2, - Rcontrol = 0xA3, - Lmenu = 0xA4, - Rmenu = 0xA5, - ProcessKey = 0xE5, - Attn = 0xF6, - Crsel = 0xF7, - Exsel = 0xF8, - Ereof = 0xF9, - Play = 0xFA, - Zoom = 0xFB, - Noname = 0xFC, - Pa1 = 0xFD, - OemClear = 0xFE, -} - diff --git a/core/sys/win32/helpers.odin b/core/sys/win32/helpers.odin deleted file mode 100644 index b04e5db95..000000000 --- a/core/sys/win32/helpers.odin +++ /dev/null @@ -1,29 +0,0 @@ -// +build windows -package win32 - -import "core:strings" - -call_external_process :: proc(program, command_line: string) -> bool { - si := Startup_Info{ cb=size_of(Startup_Info) } - pi := Process_Information{} - - return cast(bool)create_process_w( - utf8_to_wstring(program), - utf8_to_wstring(command_line), - nil, - nil, - Bool(false), - u32(0x10), - nil, - nil, - &si, - &pi, - ) -} - -open_website :: proc(url: string) -> bool { - p :: "C:\\Windows\\System32\\cmd.exe" - arg := []string{"/C", "start", url} - args := strings.join(arg, " ", context.temp_allocator) - return call_external_process(p, args) -} diff --git a/core/sys/win32/kernel32.odin b/core/sys/win32/kernel32.odin deleted file mode 100644 index 709964fb4..000000000 --- a/core/sys/win32/kernel32.odin +++ /dev/null @@ -1,237 +0,0 @@ -// +build windows -package win32 - -foreign import "system:kernel32.lib" - -@(default_calling_convention = "std") -foreign kernel32 { - @(link_name="CreateProcessA") create_process_a :: proc(application_name, command_line: cstring, - process_attributes, thread_attributes: ^Security_Attributes, - inherit_handle: Bool, creation_flags: u32, environment: rawptr, - current_directory: cstring, startup_info: ^Startup_Info, - process_information: ^Process_Information) -> Bool --- - @(link_name="CreateProcessW") create_process_w :: proc(application_name, command_line: Wstring, - process_attributes, thread_attributes: ^Security_Attributes, - inherit_handle: Bool, creation_flags: u32, environment: rawptr, - current_directory: Wstring, startup_info: ^Startup_Info, - process_information: ^Process_Information) -> Bool --- - @(link_name="GetExitCodeProcess") get_exit_code_process :: proc(process: Handle, exit: ^u32) -> Bool --- - @(link_name="ExitProcess") exit_process :: proc(exit_code: u32) --- - @(link_name="GetModuleHandleA") get_module_handle_a :: proc(module_name: cstring) -> Hmodule --- - @(link_name="GetModuleHandleW") get_module_handle_w :: proc(module_name: Wstring) -> Hmodule --- - - @(link_name="GetModuleFileNameA") get_module_file_name_a :: proc(module: Hmodule, filename: cstring, size: u32) -> u32 --- - @(link_name="GetModuleFileNameW") get_module_file_name_w :: proc(module: Hmodule, filename: Wstring, size: u32) -> u32 --- - - @(link_name="Sleep") sleep :: proc(ms: u32) --- - @(link_name="QueryPerformanceFrequency") query_performance_frequency :: proc(result: ^i64) -> i32 --- - @(link_name="QueryPerformanceCounter") query_performance_counter :: proc(result: ^i64) -> i32 --- - @(link_name="OutputDebugStringA") output_debug_string_a :: proc(c_str: cstring) --- - - @(link_name="GetCommandLineA") get_command_line_a :: proc() -> cstring --- - @(link_name="GetCommandLineW") get_command_line_w :: proc() -> Wstring --- - @(link_name="GetSystemMetrics") get_system_metrics :: proc(index: i32) -> i32 --- - @(link_name="GetSystemInfo") get_system_info :: proc(info: ^System_Info) --- - @(link_name="GetVersionExA") get_version :: proc(osvi: ^OS_Version_Info_Ex_A) --- - @(link_name="GetCurrentThreadId") get_current_thread_id :: proc() -> u32 --- - - // NOTE(tetra): Not thread safe with SetCurrentDirectory and GetFullPathName; - // The current directory is stored as a global variable in the process. - @(link_name="GetCurrentDirectoryW") get_current_directory_w :: proc(len: u32, buf: Wstring) -> u32 --- - @(link_name="SetCurrentDirectoryW") set_current_directory_w :: proc(buf: Wstring) -> u32 --- - - @(link_name="GetSystemTimeAsFileTime") get_system_time_as_file_time :: proc(system_time_as_file_time: ^Filetime) --- - @(link_name="FileTimeToLocalFileTime") file_time_to_local_file_time :: proc(file_time: ^Filetime, local_file_time: ^Filetime) -> Bool --- - @(link_name="FileTimeToSystemTime") file_time_to_system_time :: proc(file_time: ^Filetime, system_time: ^Systemtime) -> Bool --- - @(link_name="SystemTimeToFileTime") system_time_to_file_time :: proc(system_time: ^Systemtime, file_time: ^Filetime) -> Bool --- - - @(link_name="GetStdHandle") get_std_handle :: proc(h: i32) -> Handle --- - - @(link_name="CreateFileA") - create_file_a :: proc(filename: cstring, desired_access, share_module: u32, - security: rawptr, - creation, flags_and_attribs: u32, template_file: Handle) -> Handle --- - - @(link_name="CreateFileW") - create_file_w :: proc(filename: Wstring, desired_access, share_module: u32, - security: rawptr, - creation, flags_and_attribs: u32, template_file: Handle) -> Handle --- - - - @(link_name="ReadFile") read_file :: proc(h: Handle, buf: rawptr, to_read: u32, bytes_read: ^i32, overlapped: rawptr) -> Bool --- - @(link_name="WriteFile") write_file :: proc(h: Handle, buf: rawptr, len: i32, written_result: ^i32, overlapped: rawptr) -> Bool --- - - @(link_name="GetFileSizeEx") get_file_size_ex :: proc(file_handle: Handle, file_size: ^i64) -> Bool --- - @(link_name="GetFileInformationByHandle") get_file_information_by_handle :: proc(file_handle: Handle, file_info: ^By_Handle_File_Information) -> Bool --- - - @(link_name="CreateDirectoryA") create_directory_a :: proc(path: cstring, security_attributes: ^Security_Attributes) -> Bool --- - @(link_name="CreateDirectoryW") create_directory_w :: proc(path: Wstring, security_attributes: ^Security_Attributes) -> Bool --- - - @(link_name="GetFileType") get_file_type :: proc(file_handle: Handle) -> u32 --- - @(link_name="SetFilePointer") set_file_pointer :: proc(file_handle: Handle, distance_to_move: i32, distance_to_move_high: ^i32, move_method: u32) -> u32 --- - - @(link_name="SetHandleInformation") set_handle_information :: proc(obj: Handle, mask, flags: u32) -> Bool --- - - @(link_name="FindFirstFileA") find_first_file_a :: proc(file_name: cstring, data: ^Find_Data_A) -> Handle --- - @(link_name="FindNextFileA") find_next_file_a :: proc(file: Handle, data: ^Find_Data_A) -> Bool --- - - @(link_name="FindFirstFileW") find_first_file_w :: proc(file_name: Wstring, data: ^Find_Data_W) -> Handle --- - @(link_name="FindNextFileW") find_next_file_w :: proc(file: Handle, data: ^Find_Data_W) -> Bool --- - - @(link_name="FindClose") find_close :: proc(file: Handle) -> Bool --- - - @(link_name="MoveFileExA") move_file_ex_a :: proc(existing, new: cstring, flags: u32) -> Bool --- - @(link_name="DeleteFileA") delete_file_a :: proc(file_name: cstring) -> Bool --- - @(link_name="CopyFileA") copy_file_a :: proc(existing, new: cstring, fail_if_exists: Bool) -> Bool --- - - @(link_name="MoveFileExW") move_file_ex_w :: proc(existing, new: Wstring, flags: u32) -> Bool --- - @(link_name="DeleteFileW") delete_file_w :: proc(file_name: Wstring) -> Bool --- - @(link_name="CopyFileW") copy_file_w :: proc(existing, new: Wstring, fail_if_exists: Bool) -> Bool --- - - @(link_name="HeapAlloc") heap_alloc :: proc(h: Handle, flags: u32, bytes: int) -> rawptr --- - @(link_name="HeapReAlloc") heap_realloc :: proc(h: Handle, flags: u32, memory: rawptr, bytes: int) -> rawptr --- - @(link_name="HeapFree") heap_free :: proc(h: Handle, flags: u32, memory: rawptr) -> Bool --- - @(link_name="GetProcessHeap") get_process_heap :: proc() -> Handle --- - - @(link_name="LocalAlloc") local_alloc :: proc(flags: u32, bytes: int) -> rawptr --- - @(link_name="LocalReAlloc") local_realloc :: proc(mem: rawptr, bytes: int, flags: uint) -> rawptr --- - @(link_name="LocalFree") local_free :: proc(mem: rawptr) -> rawptr --- - - @(link_name="FindFirstChangeNotificationA") find_first_change_notification_a :: proc(path: cstring, watch_subtree: Bool, filter: u32) -> Handle --- - @(link_name="FindNextChangeNotification") find_next_change_notification :: proc(h: Handle) -> Bool --- - @(link_name="FindCloseChangeNotification") find_close_change_notification :: proc(h: Handle) -> Bool --- - - @(link_name="ReadDirectoryChangesW") read_directory_changes_w :: proc(dir: Handle, buf: rawptr, buf_length: u32, - watch_subtree: Bool, notify_filter: u32, - bytes_returned: ^u32, overlapped: ^Overlapped, - completion: rawptr) -> Bool --- - - @(link_name="GetOverlappedResult") get_overlapped_result :: proc(file: Handle, overlapped: ^Overlapped, number_of_bytes_transferred: ^u32, wait: Bool) -> Bool --- - - @(link_name="WideCharToMultiByte") wide_char_to_multi_byte :: proc(code_page: u32, flags: u32, - wchar_str: Wstring, wchar: i32, - multi_str: cstring, multi: i32, - default_char: cstring, used_default_char: ^Bool) -> i32 --- - - @(link_name="MultiByteToWideChar") multi_byte_to_wide_char :: proc(code_page: u32, flags: u32, - mb_str: cstring, mb: i32, - wc_str: Wstring, wc: i32) -> i32 --- - - @(link_name="CreateSemaphoreA") create_semaphore_a :: proc(attributes: ^Security_Attributes, initial_count, maximum_count: i32, name: cstring) -> Handle --- - @(link_name="CreateSemaphoreW") create_semaphore_w :: proc(attributes: ^Security_Attributes, initial_count, maximum_count: i32, name: cstring) -> Handle --- - @(link_name="ReleaseSemaphore") release_semaphore :: proc(semaphore: Handle, release_count: i32, previous_count: ^i32) -> Bool --- - @(link_name="WaitForSingleObject") wait_for_single_object :: proc(handle: Handle, milliseconds: u32) -> u32 --- -} - -// @(default_calling_convention = "c") -foreign kernel32 { - @(link_name="GetLastError") get_last_error :: proc() -> i32 --- - @(link_name="CloseHandle") close_handle :: proc(h: Handle) -> i32 --- - - @(link_name="GetFileAttributesA") get_file_attributes_a :: proc(filename: cstring) -> u32 --- - @(link_name="GetFileAttributesW") get_file_attributes_w :: proc(filename: Wstring) -> u32 --- - @(link_name="GetFileAttributesExA") get_file_attributes_ex_a :: proc(filename: cstring, info_level_id: GET_FILEEX_INFO_LEVELS, file_info: ^File_Attribute_Data) -> Bool --- - @(link_name="GetFileAttributesExW") get_file_attributes_ex_w :: proc(filename: Wstring, info_level_id: GET_FILEEX_INFO_LEVELS, file_info: ^File_Attribute_Data) -> Bool --- - @(link_name="CompareFileTime") compare_file_time :: proc(a, b: ^Filetime) -> i32 --- -} - -@(default_calling_convention = "c") -foreign kernel32 { - @(link_name="InterlockedCompareExchange") interlocked_compare_exchange :: proc(dst: ^i32, exchange, comparand: i32) -> i32 --- - @(link_name="InterlockedExchange") interlocked_exchange :: proc(dst: ^i32, desired: i32) -> i32 --- - @(link_name="InterlockedExchangeAdd") interlocked_exchange_add :: proc(dst: ^i32, desired: i32) -> i32 --- - @(link_name="InterlockedAnd") interlocked_and :: proc(dst: ^i32, desired: i32) -> i32 --- - @(link_name="InterlockedOr") interlocked_or :: proc(dst: ^i32, desired: i32) -> i32 --- - - @(link_name="InterlockedCompareExchange64") interlocked_compare_exchange64 :: proc(dst: ^i64, exchange, comparand: i64) -> i64 --- - @(link_name="InterlockedExchange64") interlocked_exchange64 :: proc(dst: ^i64, desired: i64) -> i64 --- - @(link_name="InterlockedExchangeAdd64") interlocked_exchange_add64 :: proc(dst: ^i64, desired: i64) -> i64 --- - @(link_name="InterlockedAnd64") interlocked_and64 :: proc(dst: ^i64, desired: i64) -> i64 --- - @(link_name="InterlockedOr64") interlocked_or64 :: proc(dst: ^i64, desired: i64) -> i64 --- -} - -@(default_calling_convention = "std") -foreign kernel32 { - @(link_name="_mm_pause") mm_pause :: proc() --- - @(link_name="ReadWriteBarrier") read_write_barrier :: proc() --- - @(link_name="WriteBarrier") write_barrier :: proc() --- - @(link_name="ReadBarrier") read_barrier :: proc() --- - - @(link_name="CreateThread") - create_thread :: proc(thread_attributes: ^Security_Attributes, stack_size: uint, start_routine: proc "stdcall" (rawptr) -> u32, - parameter: rawptr, creation_flags: u32, thread_id: ^u32) -> Handle --- - @(link_name="ResumeThread") resume_thread :: proc(thread: Handle) -> u32 --- - @(link_name="GetThreadPriority") get_thread_priority :: proc(thread: Handle) -> i32 --- - @(link_name="SetThreadPriority") set_thread_priority :: proc(thread: Handle, priority: i32) -> Bool --- - @(link_name="GetExitCodeThread") get_exit_code_thread :: proc(thread: Handle, exit_code: ^u32) -> Bool --- - @(link_name="TerminateThread") terminate_thread :: proc(thread: Handle, exit_code: u32) -> Bool --- - - @(link_name="InitializeCriticalSection") initialize_critical_section :: proc(critical_section: ^Critical_Section) --- - @(link_name="InitializeCriticalSectionAndSpinCount") initialize_critical_section_and_spin_count :: proc(critical_section: ^Critical_Section, spin_count: u32) -> b32 --- - @(link_name="DeleteCriticalSection") delete_critical_section :: proc(critical_section: ^Critical_Section) --- - @(link_name="SetCriticalSectionSpinCount") set_critical_section_spin_count :: proc(critical_section: ^Critical_Section, spin_count: u32) -> u32 --- - @(link_name="TryEnterCriticalSection") try_enter_critical_section :: proc(critical_section: ^Critical_Section) -> b8 --- - @(link_name="EnterCriticalSection") enter_critical_section :: proc(critical_section: ^Critical_Section) --- - @(link_name="LeaveCriticalSection") leave_critical_section :: proc(critical_section: ^Critical_Section) --- - - @(link_name="CreateEventA") create_event_a :: proc(event_attributes: ^Security_Attributes, manual_reset, initial_state: Bool, name: cstring) -> Handle --- - @(link_name="CreateEventW") create_event_w :: proc(event_attributes: ^Security_Attributes, manual_reset, initial_state: Bool, name: Wstring) -> Handle --- - @(link_name="PulseEvent") pulse_event :: proc(event: Handle) -> Bool --- - @(link_name="SetEvent") set_event :: proc(event: Handle) -> Bool --- - @(link_name="ResetEvent") reset_event :: proc(event: Handle) -> Bool --- - - @(link_name="LoadLibraryA") load_library_a :: proc(c_str: cstring) -> Hmodule --- - @(link_name="LoadLibraryW") load_library_w :: proc(c_str: Wstring) -> Hmodule --- - @(link_name="FreeLibrary") free_library :: proc(h: Hmodule) -> Bool --- - @(link_name="GetProcAddress") get_proc_address :: proc(h: Hmodule, c_str: cstring) -> rawptr --- - - @(link_name="GetFullPathNameA") get_full_path_name_a :: proc(filename: cstring, buffer_length: u32, buffer: cstring, file_part: ^Wstring) -> u32 --- - @(link_name="GetFullPathNameW") get_full_path_name_w :: proc(filename: Wstring, buffer_length: u32, buffer: Wstring, file_part: ^Wstring) -> u32 --- - @(link_name="GetLongPathNameA") get_long_path_name_a :: proc(short, long: cstring, len: u32) -> u32 --- - @(link_name="GetLongPathNameW") get_long_path_name_w :: proc(short, long: Wstring, len: u32) -> u32 --- - @(link_name="GetShortPathNameA") get_short_path_name_a :: proc(long, short: cstring, len: u32) -> u32 --- - @(link_name="GetShortPathNameW") get_short_path_name_w :: proc(long, short: Wstring, len: u32) -> u32 --- - - @(link_name="GetCurrentDirectoryA") get_current_directory_a :: proc(buffer_length: u32, buffer: cstring) -> u32 --- -} - -Memory_Basic_Information :: struct { - base_address: rawptr, - allocation_base: rawptr, - allocation_protect: u32, - region_size: uint, - state: u32, - protect: u32, - type: u32, -} - -@(default_calling_convention = "std") -foreign kernel32 { - @(link_name="VirtualAlloc") virtual_alloc :: proc(address: rawptr, size: uint, allocation_type: u32, protect: u32) -> rawptr --- - @(link_name="VirtualAllocEx") virtual_alloc_ex :: proc(process: Handle, address: rawptr, size: uint, allocation_type: u32, protect: u32) -> rawptr --- - @(link_name="VirtualFree") virtual_free :: proc(address: rawptr, size: uint, free_type: u32) -> Bool --- - @(link_name="VirtualLock") virtual_lock :: proc(address: rawptr, size: uint) -> Bool --- - @(link_name="VirtualProtect") virtual_protect :: proc(address: rawptr, size: uint, new_protect: u32, old_protect: ^u32) -> Bool --- - @(link_name="VirtualQuery") virtual_query :: proc(address: rawptr, buffer: ^Memory_Basic_Information, length: uint) -> uint --- -} - -MEM_COMMIT :: 0x00001000 -MEM_RESERVE :: 0x00002000 -MEM_DECOMMIT :: 0x00004000 -MEM_RELEASE :: 0x00008000 -MEM_RESET :: 0x00080000 -MEM_RESET_UNDO :: 0x01000000 - -MEM_LARGE_PAGES :: 0x20000000 -MEM_PHYSICAL :: 0x00400000 -MEM_TOP_DOWN :: 0x00100000 -MEM_WRITE_WATCH :: 0x00200000 - -PAGE_NOACCESS :: 0x01 -PAGE_READONLY :: 0x02 -PAGE_READWRITE :: 0x04 -PAGE_WRITECOPY :: 0x08 -PAGE_EXECUTE :: 0x10 -PAGE_EXECUTE_READ :: 0x20 -PAGE_EXECUTE_READWRITE :: 0x40 -PAGE_EXECUTE_WRITECOPY :: 0x80 diff --git a/core/sys/win32/ole32.odin b/core/sys/win32/ole32.odin deleted file mode 100644 index f4ee52399..000000000 --- a/core/sys/win32/ole32.odin +++ /dev/null @@ -1,18 +0,0 @@ -// +build windows -package win32 - -foreign import "system:ole32.lib" - -//objbase.h -Com_Init :: enum { - Multi_Threaded = 0x0, - Apartment_Threaded = 0x2, - Disable_OLE1_DDE = 0x4, - Speed_Over_Memory = 0x8, -} - -@(default_calling_convention = "std") -foreign ole32 { - @(link_name ="CoInitializeEx") com_init_ex :: proc(reserved: rawptr, co_init: Com_Init) ->Hresult --- - @(link_name = "CoUninitialize") com_shutdown :: proc() --- -} diff --git a/core/sys/win32/removal.odin b/core/sys/win32/removal.odin new file mode 100644 index 000000000..09c16aa05 --- /dev/null +++ b/core/sys/win32/removal.odin @@ -0,0 +1,3 @@ +package sys_win32 + +#panic(`"core:sys/win32" has been removed. Please use "core:sys/windows"`)
\ No newline at end of file diff --git a/core/sys/win32/shell32.odin b/core/sys/win32/shell32.odin deleted file mode 100644 index 3cedf0527..000000000 --- a/core/sys/win32/shell32.odin +++ /dev/null @@ -1,9 +0,0 @@ -// +build windows -package win32 - -foreign import "system:shell32.lib" - -@(default_calling_convention = "std") -foreign shell32 { - @(link_name="CommandLineToArgvW") command_line_to_argv_w :: proc(cmd_list: Wstring, num_args: ^i32) -> ^Wstring --- -} diff --git a/core/sys/win32/tests/general.odin b/core/sys/win32/tests/general.odin deleted file mode 100644 index 1a5fc911a..000000000 --- a/core/sys/win32/tests/general.odin +++ /dev/null @@ -1,41 +0,0 @@ -package win32_tests - -import "core:sys/win32" -import "core:testing" - -utf16_to_utf8 :: proc(t: ^testing.T, str: []u16, comparison: string, expected_result: bool, loc := #caller_location) { - result := win32.utf16_to_utf8(str[:]); - testing.expect(t, (result == comparison) == expected_result, "Incorrect utf16_to_utf8 conversion", loc); -} - -wstring_to_utf8 :: proc(t: ^testing.T, str: []u16, comparison: string, expected_result: bool, loc := #caller_location) { - result := win32.wstring_to_utf8(nil if len(str) == 0 else cast(win32.Wstring)&str[0], -1); - testing.expect(t, (result == comparison) == expected_result, "Incorrect wstring_to_utf8 conversion", loc); -} - -@test -test_utf :: proc(t: ^testing.T) { - utf16_to_utf8(t, []u16{}, "", true); - utf16_to_utf8(t, []u16{0}, "", true); - utf16_to_utf8(t, []u16{0, 't', 'e', 's', 't'}, "", true); - utf16_to_utf8(t, []u16{0, 't', 'e', 's', 't', 0}, "", true); - utf16_to_utf8(t, []u16{'t', 'e', 's', 't'}, "test", true); - utf16_to_utf8(t, []u16{'t', 'e', 's', 't', 0}, "test", true); - utf16_to_utf8(t, []u16{'t', 'e', 0, 's', 't'}, "te", true); - utf16_to_utf8(t, []u16{'t', 'e', 0, 's', 't', 0}, "te", true); - - wstring_to_utf8(t, []u16{}, "", true); - wstring_to_utf8(t, []u16{0}, "", true); - wstring_to_utf8(t, []u16{0, 't', 'e', 's', 't'}, "", true); - wstring_to_utf8(t, []u16{0, 't', 'e', 's', 't', 0}, "", true); - wstring_to_utf8(t, []u16{'t', 'e', 's', 't', 0}, "test", true); - wstring_to_utf8(t, []u16{'t', 'e', 0, 's', 't'}, "te", true); - wstring_to_utf8(t, []u16{'t', 'e', 0, 's', 't', 0}, "te", true); - - // WARNING: Passing a non-zero-terminated string to wstring_to_utf8 is dangerous, - // as it will go out of bounds looking for a zero. - // It will "fail" or "succeed" by having a zero just after the end of the input string or not. - wstring_to_utf8(t, []u16{'t', 'e', 's', 't'}, "test", false); - wstring_to_utf8(t, []u16{'t', 'e', 's', 't', 0}[:4], "test", true); - wstring_to_utf8(t, []u16{'t', 'e', 's', 't', 'q'}[:4], "test", false); -}
\ No newline at end of file diff --git a/core/sys/win32/user32.odin b/core/sys/win32/user32.odin deleted file mode 100644 index 44d1f7004..000000000 --- a/core/sys/win32/user32.odin +++ /dev/null @@ -1,289 +0,0 @@ -// +build windows -package win32 - -foreign import "system:user32.lib" - -import "core:intrinsics" - - -Menu_Bar_Info :: struct { - size: u32, - bar: Rect, - menu: Hmenu, - wnd_menu: Hwnd, - fields: u8, - // field.bar_focused: 1, - // field.focuses: 1, -} - -Menu_Item_Info_A :: struct { - size: u32, - mask: u32, - type: u32, - state: u32, - id: u32, - submenu: Hmenu, - bmp_checked: Hbitmap, - bmp_unchecked: Hbitmap, - item_data: u32, - type_data: cstring, - cch: u32, -} -Menu_Item_Info_W :: struct { - size: u32, - mask: u32, - type: u32, - state: u32, - id: u32, - submenu: Hmenu, - bmp_checked: Hbitmap, - bmp_unchecked: Hbitmap, - item_data: u32, - type_data: Wstring, - cch: u32, -} - -MF_BYCOMMAND :: 0x00000000 -MF_BYPOSITION :: 0x00000400 -MF_BITMAP :: 0x00000004 -MF_CHECKED :: 0x00000008 -MF_DISABLED :: 0x00000002 -MF_ENABLED :: 0x00000000 -MF_GRAYED :: 0x00000001 -MF_MENUBARBREAK :: 0x00000020 -MF_MENUBREAK :: 0x00000040 -MF_OWNERDRAW :: 0x00000100 -MF_POPUP :: 0x00000010 -MF_SEPARATOR :: 0x00000800 -MF_STRING :: 0x00000000 -MF_UNCHECKED :: 0x00000000 - -MB_ABORTRETRYIGNORE :: 0x00000002 -MB_CANCELTRYCONTINUE :: 0x00000006 -MB_HELP :: 0x00004000 -MB_OK :: 0x00000000 -MB_OKCANCEL :: 0x00000001 -MB_RETRYCANCEL :: 0x00000005 -MB_YESNO :: 0x00000004 -MB_YESNOCANCEL :: 0x00000003 - -MB_ICONEXCLAMATION :: 0x00000030 -MB_ICONWARNING :: 0x00000030 -MB_ICONINFORMATION :: 0x00000040 -MB_ICONASTERISK :: 0x00000040 -MB_ICONQUESTION :: 0x00000020 -MB_ICONSTOP :: 0x00000010 -MB_ICONERROR :: 0x00000010 -MB_ICONHAND :: 0x00000010 - -MB_DEFBUTTON1 :: 0x00000000 -MB_DEFBUTTON2 :: 0x00000100 -MB_DEFBUTTON3 :: 0x00000200 -MB_DEFBUTTON4 :: 0x00000300 - -MB_APPLMODAL :: 0x00000000 -MB_SYSTEMMODAL :: 0x00001000 -MB_TASKMODAL :: 0x00002000 - -MB_DEFAULT_DESKTOP_ONLY :: 0x00020000 -MB_RIGHT :: 0x00080000 -MB_RTLREADING :: 0x00100000 -MB_SETFOREGROUND :: 0x00010000 -MB_TOPMOST :: 0x00040000 -MB_SERVICE_NOTIFICATION :: 0x00200000 - - -@(default_calling_convention = "std") -foreign user32 { - @(link_name="GetDesktopWindow") get_desktop_window :: proc() -> Hwnd --- - when !intrinsics.is_package_imported("raylib") { // NOTE(bill): this is a bit of hack but it's to get around the namespace collisions - @(link_name="ShowCursor")show_cursor :: proc(show: Bool) -> i32 --- - } - @(link_name="GetCursorPos") get_cursor_pos :: proc(p: ^Point) -> Bool --- - @(link_name="SetCursorPos") set_cursor_pos :: proc(x, y: i32) -> Bool --- - @(link_name="GetCapure") get_capture :: proc(hwnd: Hwnd) -> Hwnd --- - @(link_name="SetCapture") set_capture :: proc(hwnd: Hwnd) -> Hwnd --- - @(link_name="ReleaseCapture") release_capture :: proc() -> Bool --- - @(link_name="ScreenToClient") screen_to_client :: proc(h: Hwnd, p: ^Point) -> Bool --- - @(link_name="ClientToScreen") client_to_screen :: proc(h: Hwnd, p: ^Point) -> Bool --- - @(link_name="PostQuitMessage") post_quit_message :: proc(exit_code: i32) --- - @(link_name="SetWindowTextA") set_window_text_a :: proc(hwnd: Hwnd, c_string: cstring) -> Bool --- - @(link_name="SetWindowTextW") set_window_text_w :: proc(hwnd: Hwnd, c_string: Wstring) -> Bool --- - @(link_name="RegisterClassA") register_class_a :: proc(wc: ^Wnd_Class_A) -> i16 --- - @(link_name="RegisterClassW") register_class_w :: proc(wc: ^Wnd_Class_W) -> i16 --- - @(link_name="UnregisterClassA") unregister_class_a :: proc(class_name: cstring, instance: Hinstance) -> Bool --- - @(link_name="UnregisterClassW") unregister_class_w :: proc(class_name: Wstring, instance: Hinstance) -> Bool --- - @(link_name="RegisterClassExA") register_class_ex_a :: proc(wc: ^Wnd_Class_Ex_A) -> i16 --- - @(link_name="RegisterClassExW") register_class_ex_w :: proc(wc: ^Wnd_Class_Ex_W) -> i16 --- - - @(link_name="CreateWindowExA") - create_window_ex_a :: proc(ex_style: u32, - class_name, title: cstring, - style: u32, - x, y, w, h: i32, - parent: Hwnd, menu: Hmenu, instance: Hinstance, - param: rawptr) -> Hwnd --- - - @(link_name="CreateWindowExW") - create_window_ex_w :: proc(ex_style: u32, - class_name, title: Wstring, - style: u32, - x, y, w, h: i32, - parent: Hwnd, menu: Hmenu, instance: Hinstance, - param: rawptr) -> Hwnd --- - - @(link_name="ShowWindow") show_window :: proc(hwnd: Hwnd, cmd_show: i32) -> Bool --- - @(link_name="TranslateMessage") translate_message :: proc(msg: ^Msg) -> Bool --- - @(link_name="DispatchMessageA") dispatch_message_a :: proc(msg: ^Msg) -> Lresult --- - @(link_name="DispatchMessageW") dispatch_message_w :: proc(msg: ^Msg) -> Lresult --- - @(link_name="UpdateWindow") update_window :: proc(hwnd: Hwnd) -> Bool --- - @(link_name="GetMessageA") get_message_a :: proc(msg: ^Msg, hwnd: Hwnd, msg_filter_min, msg_filter_max: u32) -> Bool --- - @(link_name="GetMessageW") get_message_w :: proc(msg: ^Msg, hwnd: Hwnd, msg_filter_min, msg_filter_max: u32) -> Bool --- - - @(link_name="PeekMessageA") peek_message_a :: proc(msg: ^Msg, hwnd: Hwnd, msg_filter_min, msg_filter_max, remove_msg: u32) -> Bool --- - @(link_name="PeekMessageW") peek_message_w :: proc(msg: ^Msg, hwnd: Hwnd, msg_filter_min, msg_filter_max, remove_msg: u32) -> Bool --- - - - @(link_name="PostMessageA") post_message_a :: proc(hwnd: Hwnd, msg: u32, wparam: Wparam, lparam: Lparam) -> Bool --- - @(link_name="PostMessageW") post_message_w :: proc(hwnd: Hwnd, msg: u32, wparam: Wparam, lparam: Lparam) -> Bool --- - @(link_name="SendMessageA") send_message_a :: proc(hwnd: Hwnd, msg: u32, wparam: Wparam, lparam: Lparam) -> Lresult --- - @(link_name="SendMessageW") send_message_w :: proc(hwnd: Hwnd, msg: u32, wparam: Wparam, lparam: Lparam) -> Lresult --- - - @(link_name="DefWindowProcA") def_window_proc_a :: proc(hwnd: Hwnd, msg: u32, wparam: Wparam, lparam: Lparam) -> Lresult --- - @(link_name="DefWindowProcW") def_window_proc_w :: proc(hwnd: Hwnd, msg: u32, wparam: Wparam, lparam: Lparam) -> Lresult --- - - @(link_name="AdjustWindowRect") adjust_window_rect :: proc(rect: ^Rect, style: u32, menu: Bool) -> Bool --- - @(link_name="GetActiveWindow") get_active_window :: proc() -> Hwnd --- - - @(link_name="DestroyWindow") destroy_window :: proc(wnd: Hwnd) -> Bool --- - @(link_name="DescribePixelFormat") describe_pixel_format :: proc(dc: Hdc, pixel_format: i32, bytes: u32, pfd: ^Pixel_Format_Descriptor) -> i32 --- - - @(link_name="GetMonitorInfoA") get_monitor_info_a :: proc(monitor: Hmonitor, mi: ^Monitor_Info) -> Bool --- - @(link_name="MonitorFromWindow") monitor_from_window :: proc(wnd: Hwnd, flags: u32) -> Hmonitor --- - - @(link_name="SetWindowPos") set_window_pos :: proc(wnd: Hwnd, wndInsertAfter: Hwnd, x, y, width, height: i32, flags: u32) -> Bool --- - - @(link_name="GetWindowPlacement") get_window_placement :: proc(wnd: Hwnd, wndpl: ^Window_Placement) -> Bool --- - @(link_name="SetWindowPlacement") set_window_placement :: proc(wnd: Hwnd, wndpl: ^Window_Placement) -> Bool --- - @(link_name="GetWindowRect") get_window_rect :: proc(wnd: Hwnd, rect: ^Rect) -> Bool --- - - @(link_name="GetWindowLongPtrA") get_window_long_ptr_a :: proc(wnd: Hwnd, index: i32) -> Long_Ptr --- - @(link_name="SetWindowLongPtrA") set_window_long_ptr_a :: proc(wnd: Hwnd, index: i32, new: Long_Ptr) -> Long_Ptr --- - @(link_name="GetWindowLongPtrW") get_window_long_ptr_w :: proc(wnd: Hwnd, index: i32) -> Long_Ptr --- - @(link_name="SetWindowLongPtrW") set_window_long_ptr_w :: proc(wnd: Hwnd, index: i32, new: Long_Ptr) -> Long_Ptr --- - - @(link_name="GetWindowText") get_window_text :: proc(wnd: Hwnd, str: cstring, maxCount: i32) -> i32 --- - - @(link_name="GetClientRect") get_client_rect :: proc(hwnd: Hwnd, rect: ^Rect) -> Bool --- - - @(link_name="GetDC") get_dc :: proc(h: Hwnd) -> Hdc --- - @(link_name="ReleaseDC") release_dc :: proc(wnd: Hwnd, hdc: Hdc) -> i32 --- - - @(link_name="MapVirtualKeyA") map_virtual_key_a :: proc(scancode: u32, map_type: u32) -> u32 --- - @(link_name="MapVirtualKeyW") map_virtual_key_w :: proc(scancode: u32, map_type: u32) -> u32 --- - - @(link_name="GetKeyState") get_key_state :: proc(v_key: i32) -> i16 --- - @(link_name="GetAsyncKeyState") get_async_key_state :: proc(v_key: i32) -> i16 --- - - @(link_name="SetForegroundWindow") set_foreground_window :: proc(h: Hwnd) -> Bool --- - @(link_name="SetFocus") set_focus :: proc(h: Hwnd) -> Hwnd --- - - - @(link_name="LoadImageA") load_image_a :: proc(instance: Hinstance, name: cstring, type_: u32, x_desired, y_desired : i32, load : u32) -> Handle --- - @(link_name="LoadIconA") load_icon_a :: proc(instance: Hinstance, icon_name: cstring) -> Hicon --- - @(link_name="DestroyIcon") destroy_icon :: proc(icon: Hicon) -> Bool --- - - @(link_name="LoadCursorA") load_cursor_a :: proc(instance: Hinstance, cursor_name: cstring) -> Hcursor --- - @(link_name="LoadCursorW") load_cursor_w :: proc(instance: Hinstance, cursor_name: Wstring) -> Hcursor --- - @(link_name="GetCursor") get_cursor :: proc() -> Hcursor --- - @(link_name="SetCursor") set_cursor :: proc(cursor: Hcursor) -> Hcursor --- - - @(link_name="RegisterRawInputDevices") register_raw_input_devices :: proc(raw_input_device: ^Raw_Input_Device, num_devices, size: u32) -> Bool --- - - @(link_name="GetRawInputData") get_raw_input_data :: proc(raw_input: Hrawinput, command: u32, data: rawptr, size: ^u32, size_header: u32) -> u32 --- - - @(link_name="MapVirtualKeyExW") map_virtual_key_ex_w :: proc(code, map_type: u32, hkl: HKL) -> u32 --- - @(link_name="MapVirtualKeyExA") map_virtual_key_ex_a :: proc(code, map_type: u32, hkl: HKL) -> u32 --- - - @(link_name="EnumDisplayMonitors") enum_display_monitors :: proc(hdc: Hdc, rect: ^Rect, enum_proc: Monitor_Enum_Proc, lparam: Lparam) -> bool --- - - @(link_name="EnumDisplaySettingsA") enum_display_settings_a :: proc(device_name: cstring, mode_number: u32, mode: ^Dev_Mode_A) -> Bool --- -} - -@(default_calling_convention = "std") -foreign user32 { - @(link_name="CreateMenu") create_menu :: proc() -> Hmenu --- - @(link_name="CreatePopupMenu") create_popup_menu :: proc() -> Hmenu --- - @(link_name="DestroyMenu") destroy_menu :: proc(menu: Hmenu) -> Bool --- - @(link_name="DeleteMenu") delete_menu :: proc(menu: Hmenu, position: u32, flags: u32) -> Bool --- - - @(link_name="EnableMenuItem") enable_menu_item :: proc(menu: Hmenu, id_enable_itme: i32, enable: u32) -> Bool --- - @(link_name="EndMenu") end_menu :: proc(menu: Hmenu, flags: u32, id_new_item: Uint_Ptr, new_item: cstring) -> Bool --- - @(link_name="GetMenu") get_menu :: proc(wnd: Hwnd) -> Hmenu --- - @(link_name="GetMenuBarInfo") get_menu_bar_info :: proc(wnd: Hwnd, id_object, id_item: u32, mbi: ^Menu_Bar_Info) -> Hmenu --- - @(link_name="GetMenuStringA") get_menu_string_a :: proc(menu: Hmenu, id_item: u32, s: cstring, cch_max: i32, flags: u32) -> i32 --- - @(link_name="GetMenuStringW") get_menu_string_w :: proc(menu: Hmenu, id_item: u32, s: Wstring, cch_max: i32, flags: u32) -> i32 --- - @(link_name="GetMenuState") get_menu_state :: proc(menu: Hmenu, id: u32, flags: u32) -> u32 --- - @(link_name="GetMenuItemRect") get_menu_item_rect :: proc(wnd: Hwnd, menu: Hmenu, id_item: u32, item: ^Rect) -> Bool --- - - @(link_name="SetMenu") set_menu :: proc(wnd: Hwnd, menu: Hmenu) -> Bool --- - - @(link_name="DrawMenuBar") draw_menu_bar :: proc(wnd: Hwnd) -> Bool --- - @(link_name="InsertMenuA") insert_menu_a :: proc(menu: Hmenu, position: u32, flags: u32, id_new_item: Uint_Ptr, new_item: cstring) -> Bool --- - @(link_name="InsertMenuW") insert_menu_w :: proc(menu: Hmenu, position: u32, flags: u32, id_new_item: Uint_Ptr, new_item: Wstring) -> Bool --- - - @(link_name="InsertMenuItemA") insert_menu_item_a :: proc(hmenu: Hmenu, item: u32, fByPosition: Bool, lpmi: ^Menu_Item_Info_A) -> Bool --- - @(link_name="InsertMenuItemW") insert_menu_item_w :: proc(hmenu: Hmenu, item: u32, fByPosition: Bool, lpmi: ^Menu_Item_Info_W) -> Bool --- - - @(link_name="AppendMenuA") append_menu_a :: proc(menu: Hmenu, flags: u32, id_new_item: Uint_Ptr, new_item: cstring) -> Bool --- - @(link_name="AppendMenuW") append_menu_w :: proc(menu: Hmenu, flags: u32, id_new_item: Uint_Ptr, new_item: Wstring) -> Bool --- - - @(link_name="CheckMenuItem") check_menu_item :: proc(menu: Hmenu, id_check_item: u32, check: u32) -> u32 --- - @(link_name="CheckMenuRadioItem") check_menu_radio_item :: proc(menu: Hmenu, first, last: u32, check: u32, flags: u32) -> Bool --- - - @(link_name="GetPropA") get_prop_a :: proc(wnd: Hwnd, s: cstring) -> Handle --- - @(link_name="GetPropW") get_prop_w :: proc(wnd: Hwnd, s: Wstring) -> Handle --- - - @(link_name="MessageBoxA") message_box_a :: proc(wnd: Hwnd, text, caption: cstring, type: u32) -> i32 --- - @(link_name="MessageBoxW") message_box_w :: proc(wnd: Hwnd, text, caption: Wstring, type: u32) -> i32 --- - - @(link_name="MessageBoxExA") message_box_ex_a :: proc(wnd: Hwnd, text, caption: cstring, type: u32, language_id: u16) -> i32 --- - @(link_name="MessageBoxExW") message_box_ex_w :: proc(wnd: Hwnd, text, caption: Wstring, type: u32, language_id: u16) -> i32 --- - - @(link_name="BeginPaint") begin_paint :: proc(wnd: Hwnd, paint: ^Paint_Struct) -> Hdc --- - @(link_name="EndPaint") end_paint :: proc(wnd: Hwnd, paint: ^Paint_Struct) -> Bool --- -} - - -_IDC_APPSTARTING := rawptr(uintptr(32650)) -_IDC_ARROW := rawptr(uintptr(32512)) -_IDC_CROSS := rawptr(uintptr(32515)) -_IDC_HAND := rawptr(uintptr(32649)) -_IDC_HELP := rawptr(uintptr(32651)) -_IDC_IBEAM := rawptr(uintptr(32513)) -_IDC_ICON := rawptr(uintptr(32641)) -_IDC_NO := rawptr(uintptr(32648)) -_IDC_SIZE := rawptr(uintptr(32640)) -_IDC_SIZEALL := rawptr(uintptr(32646)) -_IDC_SIZENESW := rawptr(uintptr(32643)) -_IDC_SIZENS := rawptr(uintptr(32645)) -_IDC_SIZENWSE := rawptr(uintptr(32642)) -_IDC_SIZEWE := rawptr(uintptr(32644)) -_IDC_UPARROW := rawptr(uintptr(32516)) -_IDC_WAIT := rawptr(uintptr(32514)) -IDC_APPSTARTING := cstring(_IDC_APPSTARTING) -IDC_ARROW := cstring(_IDC_ARROW) -IDC_CROSS := cstring(_IDC_CROSS) -IDC_HAND := cstring(_IDC_HAND) -IDC_HELP := cstring(_IDC_HELP) -IDC_IBEAM := cstring(_IDC_IBEAM) -IDC_ICON := cstring(_IDC_ICON) -IDC_NO := cstring(_IDC_NO) -IDC_SIZE := cstring(_IDC_SIZE) -IDC_SIZEALL := cstring(_IDC_SIZEALL) -IDC_SIZENESW := cstring(_IDC_SIZENESW) -IDC_SIZENS := cstring(_IDC_SIZENS) -IDC_SIZENWSE := cstring(_IDC_SIZENWSE) -IDC_SIZEWE := cstring(_IDC_SIZEWE) -IDC_UPARROW := cstring(_IDC_UPARROW) -IDC_WAIT := cstring(_IDC_WAIT) diff --git a/core/sys/win32/wgl.odin b/core/sys/win32/wgl.odin deleted file mode 100644 index e6c414b0e..000000000 --- a/core/sys/win32/wgl.odin +++ /dev/null @@ -1,114 +0,0 @@ -// +build windows -package win32 - -foreign import "system:opengl32.lib" - -CONTEXT_MAJOR_VERSION_ARB :: 0x2091 -CONTEXT_MINOR_VERSION_ARB :: 0x2092 -CONTEXT_FLAGS_ARB :: 0x2094 -CONTEXT_PROFILE_MASK_ARB :: 0x9126 -CONTEXT_FORWARD_COMPATIBLE_BIT_ARB :: 0x0002 -CONTEXT_CORE_PROFILE_BIT_ARB :: 0x00000001 -CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB :: 0x00000002 - -Hglrc :: distinct Handle -Color_Ref :: distinct u32 - -Layer_Plane_Descriptor :: struct { - size: u16, - version: u16, - flags: u32, - pixel_type: u8, - color_bits: u8, - red_bits: u8, - red_shift: u8, - green_bits: u8, - green_shift: u8, - blue_bits: u8, - blue_shift: u8, - alpha_bits: u8, - alpha_shift: u8, - accum_bits: u8, - accum_red_bits: u8, - accum_green_bits: u8, - accum_blue_bits: u8, - accum_alpha_bits: u8, - depth_bits: u8, - stencil_bits: u8, - aux_buffers: u8, - layer_type: u8, - reserved: u8, - transparent: Color_Ref, -} - -Point_Float :: struct {x, y: f32} - -Glyph_Metrics_Float :: struct { - black_box_x: f32, - black_box_y: f32, - glyph_origin: Point_Float, - cell_inc_x: f32, - cell_inc_y: f32, -} - -Create_Context_Attribs_ARB_Type :: #type proc "c" (hdc: Hdc, h_share_context: rawptr, attribList: ^i32) -> Hglrc -Choose_Pixel_Format_ARB_Type :: #type proc "c" (hdc: Hdc, attrib_i_list: ^i32, attrib_f_list: ^f32, max_formats: u32, formats: ^i32, num_formats : ^u32) -> Bool -Swap_Interval_EXT_Type :: #type proc "c" (interval: i32) -> bool -Get_Extensions_String_ARB_Type :: #type proc "c" (Hdc) -> cstring - -// Procedures - create_context_attribs_arb: Create_Context_Attribs_ARB_Type - choose_pixel_format_arb: Choose_Pixel_Format_ARB_Type - swap_interval_ext: Swap_Interval_EXT_Type - get_extensions_string_arb: Get_Extensions_String_ARB_Type - - -foreign opengl32 { - @(link_name="wglCreateContext") - create_context :: proc(hdc: Hdc) -> Hglrc --- - - @(link_name="wglMakeCurrent") - make_current :: proc(hdc: Hdc, hglrc: Hglrc) -> Bool --- - - @(link_name="wglGetProcAddress") - get_gl_proc_address :: proc(c_str: cstring) -> rawptr --- - - @(link_name="wglDeleteContext") - delete_context :: proc(hglrc: Hglrc) -> Bool --- - - @(link_name="wglCopyContext") - copy_context :: proc(src, dst: Hglrc, mask: u32) -> Bool --- - - @(link_name="wglCreateLayerContext") - create_layer_context :: proc(hdc: Hdc, layer_plane: i32) -> Hglrc --- - - @(link_name="wglDescribeLayerPlane") - describe_layer_plane :: proc(hdc: Hdc, pixel_format, layer_plane: i32, bytes: u32, pd: ^Layer_Plane_Descriptor) -> Bool --- - - @(link_name="wglGetCurrentContext") - get_current_context :: proc() -> Hglrc --- - - @(link_name="wglGetCurrentDC") - get_current_dc :: proc() -> Hdc --- - - @(link_name="wglGetLayerPaletteEntries") - get_layer_palette_entries :: proc(hdc: Hdc, layer_plane, start, entries: i32, cr: ^Color_Ref) -> i32 --- - - @(link_name="wglRealizeLayerPalette") - realize_layer_palette :: proc(hdc: Hdc, layer_plane: i32, realize: Bool) -> Bool --- - - @(link_name="wglSetLayerPaletteEntries") - set_layer_palette_entries :: proc(hdc: Hdc, layer_plane, start, entries: i32, cr: ^Color_Ref) -> i32 --- - - @(link_name="wglShareLists") - share_lists :: proc(hglrc1, hglrc2: Hglrc) -> Bool --- - - @(link_name="wglSwapLayerBuffers") - swap_layer_buffers :: proc(hdc: Hdc, planes: u32) -> Bool --- - - @(link_name="wglUseFontBitmaps") - use_font_bitmaps :: proc(hdc: Hdc, first, count, list_base: u32) -> Bool --- - - @(link_name="wglUseFontOutlines") - use_font_outlines :: proc(hdc: Hdc, first, count, list_base: u32, deviation, extrusion: f32, format: i32, gmf: ^Glyph_Metrics_Float) -> Bool --- -} diff --git a/core/sys/win32/winmm.odin b/core/sys/win32/winmm.odin deleted file mode 100644 index 0f567fbcc..000000000 --- a/core/sys/win32/winmm.odin +++ /dev/null @@ -1,12 +0,0 @@ -// +build windows -package win32 - -foreign import "system:winmm.lib" - - -@(default_calling_convention = "std") -foreign winmm { - @(link_name="timeBeginPeriod") time_begin_period :: proc(period: u32) -> u32 --- - - @(link_name="timeGetTime") time_get_time :: proc() -> u32 --- -} diff --git a/core/sys/windows/advapi32.odin b/core/sys/windows/advapi32.odin index a2a24242f..20badb5da 100644 --- a/core/sys/windows/advapi32.odin +++ b/core/sys/windows/advapi32.odin @@ -3,6 +3,8 @@ package sys_windows foreign import advapi32 "system:Advapi32.lib" +HCRYPTPROV :: distinct HANDLE + @(default_calling_convention="stdcall") foreign advapi32 { @(link_name = "SystemFunction036") @@ -10,6 +12,10 @@ foreign advapi32 { OpenProcessToken :: proc(ProcessHandle: HANDLE, DesiredAccess: DWORD, TokenHandle: ^HANDLE) -> BOOL --- + + CryptAcquireContextW :: proc(hProv: ^HCRYPTPROV, szContainer, szProvider: wstring, dwProvType, dwFlags: DWORD) -> DWORD --- + CryptGenRandom :: proc(hProv: HCRYPTPROV, dwLen: DWORD, buf: LPVOID) -> DWORD --- + CryptReleaseContext :: proc(hProv: HCRYPTPROV, dwFlags: DWORD) -> DWORD --- } // Necessary to create a token to impersonate a user with for CreateProcessAsUser diff --git a/core/sys/windows/bcrypt.odin b/core/sys/windows/bcrypt.odin index ed28d5b7f..52eb4b1b6 100644 --- a/core/sys/windows/bcrypt.odin +++ b/core/sys/windows/bcrypt.odin @@ -7,5 +7,5 @@ BCRYPT_USE_SYSTEM_PREFERRED_RNG: DWORD : 0x00000002 @(default_calling_convention="stdcall") foreign bcrypt { - BCryptGenRandom :: proc(hAlgorithm: LPVOID, pBuffer: ^u8, cbBuffer: ULONG, dwFlags: ULONG) -> LONG --- + BCryptGenRandom :: proc(hAlgorithm: LPVOID, pBuffer: [^]u8, cbBuffer: ULONG, dwFlags: ULONG) -> LONG --- } diff --git a/core/sys/windows/bluetooth.odin b/core/sys/windows/bluetooth.odin index c9f6bcc93..c2534896b 100644 --- a/core/sys/windows/bluetooth.odin +++ b/core/sys/windows/bluetooth.odin @@ -51,54 +51,27 @@ BLUETOOTH_DEVICE_INFO :: struct { name: [BLUETOOTH_MAX_NAME_SIZE]u16, // Name of the device } -@(default_calling_convention = "std") +@(default_calling_convention="stdcall") foreign bthprops { /* Version */ - @(link_name="BluetoothIsVersionAvailable") bluetooth_is_version_available :: proc( - major: u8, minor: u8, - ) -> BOOL --- + BluetoothIsVersionAvailable :: proc(major: u8, minor: u8) -> BOOL --- /* Radio enumeration */ - @(link_name="BluetoothFindFirstRadio") bluetooth_find_first_radio :: proc( - find_radio_params: ^BLUETOOTH_FIND_RADIO_PARAMS, radio: ^HANDLE, - ) -> HBLUETOOTH_RADIO_FIND --- - - @(link_name="BluetoothFindNextRadio") bluetooth_find_next_radio :: proc( - handle: HBLUETOOTH_RADIO_FIND, radio: ^HANDLE, - ) -> BOOL --- - - @(link_name="BluetoothFindRadioClose") bluetooth_find_radio_close :: proc( - handle: HBLUETOOTH_RADIO_FIND, - ) -> BOOL --- - - @(link_name="BluetoothGetRadioInfo") bluetooth_get_radio_info :: proc( - radio: HANDLE, radio_info: ^BLUETOOTH_RADIO_INFO, - ) -> DWORD --- + BluetoothFindFirstRadio :: proc(find_radio_params: ^BLUETOOTH_FIND_RADIO_PARAMS, radio: ^HANDLE) -> HBLUETOOTH_RADIO_FIND --- + BluetoothFindNextRadio :: proc(handle: HBLUETOOTH_RADIO_FIND, radio: ^HANDLE) -> BOOL --- + BluetoothFindRadioClose :: proc(handle: HBLUETOOTH_RADIO_FIND) -> BOOL --- + BluetoothGetRadioInfo :: proc(radio: HANDLE, radio_info: ^BLUETOOTH_RADIO_INFO) -> DWORD --- /* Device enumeration */ - @(link_name="BluetoothFindFirstDevice") bluetooth_find_first_device :: proc( - search_params: ^BLUETOOTH_DEVICE_SEARCH_PARAMS, device_info: ^BLUETOOTH_DEVICE_INFO, - ) -> HBLUETOOTH_DEVICE_FIND --- - - @(link_name="BluetoothFindNextDevice") bluetooth_find_next_device :: proc( - handle: HBLUETOOTH_DEVICE_FIND, device_info: ^BLUETOOTH_DEVICE_INFO, - ) -> BOOL --- - - @(link_name="BluetoothFindDeviceClose") bluetooth_find_device_close :: proc( - handle: HBLUETOOTH_DEVICE_FIND, - ) -> BOOL --- - - @(link_name="BluetoothGetDeviceInfo") bluetooth_get_device_info :: proc( - radio: HANDLE, device_info: ^BLUETOOTH_DEVICE_INFO, - ) -> DWORD --- - - @(link_name="BluetoothDisplayDeviceProperties") bluetooth_display_device_properties :: proc( - hwnd_parent: HWND, device_info: ^BLUETOOTH_DEVICE_INFO, - ) -> BOOL --- + BluetoothFindFirstDevice :: proc(search_params: ^BLUETOOTH_DEVICE_SEARCH_PARAMS, device_info: ^BLUETOOTH_DEVICE_INFO) -> HBLUETOOTH_DEVICE_FIND --- + BluetoothFindNextDevice :: proc(handle: HBLUETOOTH_DEVICE_FIND, device_info: ^BLUETOOTH_DEVICE_INFO) -> BOOL --- + BluetoothFindDeviceClose :: proc(handle: HBLUETOOTH_DEVICE_FIND) -> BOOL --- + BluetoothGetDeviceInfo :: proc(radio: HANDLE, device_info: ^BLUETOOTH_DEVICE_INFO) -> DWORD --- + BluetoothDisplayDeviceProperties :: proc(hwnd_parent: HWND, device_info: ^BLUETOOTH_DEVICE_INFO) -> BOOL --- }
\ No newline at end of file diff --git a/core/sys/win32/comdlg32.odin b/core/sys/windows/comdlg32.odin index 1e0d5c3ca..a3709cba7 100644 --- a/core/sys/win32/comdlg32.odin +++ b/core/sys/windows/comdlg32.odin @@ -1,70 +1,44 @@ // +build windows -package win32 +package sys_windows -foreign import "system:comdlg32.lib" +foreign import "system:Comdlg32.lib" import "core:strings" -OFN_Hook_Proc :: #type proc "stdcall" (hdlg: Hwnd, msg: u32, wparam: Wparam, lparam: Lparam) -> Uint_Ptr - -Open_File_Name_A :: struct { - struct_size: u32, - hwnd_owner: Hwnd, - instance: Hinstance, - filter: cstring, - custom_filter: cstring, - max_cust_filter: u32, - filter_index: u32, - file: cstring, - max_file: u32, - file_title: cstring, - max_file_title: u32, - initial_dir: cstring, - title: cstring, - flags: u32, - file_offset: u16, - file_extension: u16, - def_ext: cstring, - cust_data: Lparam, - hook: OFN_Hook_Proc, - template_name: cstring, - pv_reserved: rawptr, - dw_reserved: u32, - flags_ex: u32, +LPOFNHOOKPROC :: #type proc "stdcall" (hdlg: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> UINT_PTR + +OPENFILENAMEW :: struct { + lStructSize: DWORD, + hwndOwner: HWND, + hInstance: HINSTANCE, + lpstrFilter: wstring, + lpstrCustomFilter: wstring, + nMaxCustFilter: DWORD, + nFilterIndex: DWORD, + lpstrFile: wstring, + nMaxFile: DWORD, + lpstrFileTitle: wstring, + nMaxFileTitle: DWORD, + lpstrInitialDir: wstring, + lpstrTitle: wstring, + Flags: DWORD, + nFileOffset: WORD, + nFileExtension: WORD, + lpstrDefExt: wstring, + lCustData: LPARAM, + lpfnHook: LPOFNHOOKPROC, + lpTemplateName: wstring, + lpEditInfo: rawptr, // LPEDITMENU, + lpstrPrompt: wstring, + pvReserved: rawptr, + dwReserved: DWORD, + FlagsEx: DWORD, } -Open_File_Name_W :: struct { - struct_size: u32, - hwnd_owner: Hwnd, - instance: Hinstance, - filter: Wstring, - custom_filter: Wstring, - max_cust_filter: u32, - filter_index: u32, - file: Wstring, - max_file: u32, - file_title: Wstring, - max_file_title: u32, - initial_dir: Wstring, - title: Wstring, - flags: u32, - file_offset: u16, - file_extension: u16, - def_ext: Wstring, - cust_data: Lparam, - hook: OFN_Hook_Proc, - template_name: Wstring, - pv_reserved: rawptr, - dw_reserved: u32, - flags_ex: u32, -} - -@(default_calling_convention = "c") -foreign comdlg32 { - @(link_name="GetOpenFileNameA") get_open_file_name_a :: proc(arg1: ^Open_File_Name_A) -> Bool --- - @(link_name="GetOpenFileNameW") get_open_file_name_w :: proc(arg1: ^Open_File_Name_W) -> Bool --- - @(link_name="GetSaveFileNameA") get_save_file_name_a :: proc(arg1: ^Open_File_Name_A) -> Bool --- - @(link_name="GetSaveFileNameW") get_save_file_name_w :: proc(arg1: ^Open_File_Name_W) -> Bool --- - @(link_name="CommDlgExtendedError") comm_dlg_extended_error :: proc() -> u32 --- +@(default_calling_convention="stdcall") +foreign Comdlg32 { + GetOpenFileNameW :: proc(arg1: ^OPENFILENAMEW) -> BOOL --- + GetSaveFileNameW :: proc(arg1: ^OPENFILENAMEW) -> BOOL --- + CommDlgExtendedError :: proc() -> u32 --- } OPEN_TITLE :: "Select file to open" @@ -100,23 +74,23 @@ _open_file_dialog :: proc(title: string, dir: string, filter = strings.join(filters, "\u0000", context.temp_allocator) filter = strings.concatenate({filter, "\u0000"}, context.temp_allocator) - ofn := Open_File_Name_W{ - struct_size = size_of(Open_File_Name_W), - file = Wstring(&file_buf[0]), - max_file = MAX_PATH_WIDE, - title = utf8_to_wstring(title, context.temp_allocator), - filter = utf8_to_wstring(filter, context.temp_allocator), - initial_dir = utf8_to_wstring(dir, context.temp_allocator), - filter_index = u32(clamp(default_filter, 1, filter_len / 2)), - def_ext = utf8_to_wstring(default_ext, context.temp_allocator), - flags = u32(flags), + ofn := OPENFILENAMEW{ + lStructSize = size_of(OPENFILENAMEW), + lpstrFile = wstring(&file_buf[0]), + nMaxFile = MAX_PATH_WIDE, + lpstrTitle = utf8_to_wstring(title, context.temp_allocator), + lpstrFilter = utf8_to_wstring(filter, context.temp_allocator), + lpstrInitialDir = utf8_to_wstring(dir, context.temp_allocator), + nFilterIndex = u32(clamp(default_filter, 1, filter_len / 2)), + lpstrDefExt = utf8_to_wstring(default_ext, context.temp_allocator), + Flags = u32(flags), } switch mode { case .Open: - ok = bool(get_open_file_name_w(&ofn)) + ok = bool(GetOpenFileNameW(&ofn)) case .Save: - ok = bool(get_save_file_name_w(&ofn)) + ok = bool(GetSaveFileNameW(&ofn)) case: ok = false } @@ -124,9 +98,9 @@ _open_file_dialog :: proc(title: string, dir: string, if !ok { return } - - file_name := utf16_to_utf8(file_buf[:], allocator) + + file_name, _ := utf16_to_utf8(file_buf[:], allocator) path = strings.trim_right_null(file_name) return } @@ -140,9 +114,9 @@ select_file_to_open :: proc(title := OPEN_TITLE, dir := ".", } select_file_to_save :: proc(title := SAVE_TITLE, dir := ".", - filters := []string{"All Files", "*.*"}, default_filter := u32(1), - flags := SAVE_FLAGS, default_ext := SAVE_EXT, - allocator := context.temp_allocator) -> (path: string, ok: bool) { + filters := []string{"All Files", "*.*"}, default_filter := u32(1), + flags := SAVE_FLAGS, default_ext := SAVE_EXT, + allocator := context.temp_allocator) -> (path: string, ok: bool) { path, ok = _open_file_dialog(title, dir, filters, default_filter, flags, default_ext, Open_Save_Mode.Save, allocator) return diff --git a/core/sys/windows/gdi32.odin b/core/sys/windows/gdi32.odin index 15d5567c7..898766361 100644 --- a/core/sys/windows/gdi32.odin +++ b/core/sys/windows/gdi32.odin @@ -62,5 +62,11 @@ foreign gdi32 { ChoosePixelFormat :: proc(hdc: HDC, ppfd: ^PIXELFORMATDESCRIPTOR) -> c_int --- SwapBuffers :: proc(HDC) -> BOOL --- + SetDCBrushColor :: proc(hdc: HDC, color: COLORREF) -> COLORREF --- + GetDCBrushColor :: proc(hdc: HDC) -> COLORREF --- PatBlt :: proc(hdc: HDC, x, y, w, h: c_int, rop: DWORD) -> BOOL --- } + +RGB :: #force_inline proc "contextless" (r, g, b: u8) -> COLORREF { + return transmute(COLORREF)[4]u8{r, g, b, 0} +} diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index 8e1b8d442..b44aa0305 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -3,11 +3,10 @@ package sys_windows foreign import kernel32 "system:Kernel32.lib" - - @(default_calling_convention="stdcall") foreign kernel32 { - OutputDebugStringA :: proc(lpOutputString: LPCSTR) --- + OutputDebugStringA :: proc(lpOutputString: LPCSTR) --- // The only A thing that is allowed + OutputDebugStringW :: proc(lpOutputString: LPCSTR) --- ReadConsoleW :: proc(hConsoleInput: HANDLE, lpBuffer: LPVOID, @@ -23,12 +22,18 @@ foreign kernel32 { GetConsoleMode :: proc(hConsoleHandle: HANDLE, lpMode: LPDWORD) -> BOOL --- - + SetConsoleMode :: proc(hConsoleHandle: HANDLE, + dwMode: DWORD) -> BOOL --- GetFileInformationByHandle :: proc(hFile: HANDLE, lpFileInformation: LPBY_HANDLE_FILE_INFORMATION) -> BOOL --- SetHandleInformation :: proc(hObject: HANDLE, dwMask: DWORD, dwFlags: DWORD) -> BOOL --- + SetFileInformationByHandle :: proc(hFile: HANDLE, + FileInformationClass: FILE_INFO_BY_HANDLE_CLASS, + lpFileInformation: LPVOID, + dwBufferSize: DWORD) -> BOOL --- + AddVectoredExceptionHandler :: proc(FirstHandler: ULONG, VectoredHandler: PVECTORED_EXCEPTION_HANDLER) -> LPVOID --- AddVectoredContinueHandler :: proc(FirstHandler: ULONG, VectoredHandler: PVECTORED_EXCEPTION_HANDLER) -> LPVOID --- @@ -63,6 +68,13 @@ foreign kernel32 { GetCurrentProcessId :: proc() -> DWORD --- GetCurrentThread :: proc() -> HANDLE --- GetCurrentThreadId :: proc() -> DWORD --- + GetProcessTimes :: proc( + hProcess: HANDLE, + lpCreationTime: LPFILETIME, + lpExitTime: LPFILETIME, + lpKernelTime: LPFILETIME, + lpUserTime: LPFILETIME, + ) -> BOOL --- GetStdHandle :: proc(which: DWORD) -> HANDLE --- ExitProcess :: proc(uExitCode: c_uint) -> ! --- DeviceIoControl :: proc( @@ -93,6 +105,20 @@ foreign kernel32 { CreateSemaphoreW :: proc(attributes: LPSECURITY_ATTRIBUTES, initial_count, maximum_count: LONG, name: LPCSTR) -> HANDLE --- ReleaseSemaphore :: proc(semaphore: HANDLE, release_count: LONG, previous_count: ^LONG) -> BOOL --- + CreateWaitableTimerW :: proc( + lpTimerAttributes: LPSECURITY_ATTRIBUTES, + bManualReset: BOOL, + lpTimerName: LPCWSTR, + ) -> HANDLE --- + SetWaitableTimerEx :: proc( + hTimer: HANDLE, + lpDueTime: ^LARGE_INTEGER, + lPeriod: LONG, + pfnCompletionRoutine: PTIMERAPCROUTINE, + lpArgToCompletionRoutine: LPVOID, + WakeContext: PREASON_CONTEXT, + TolerableDelay: ULONG, + ) -> BOOL --- WaitForSingleObject :: proc(hHandle: HANDLE, dwMilliseconds: DWORD) -> DWORD --- Sleep :: proc(dwMilliseconds: DWORD) --- GetProcessId :: proc(handle: HANDLE) -> DWORD --- @@ -218,6 +244,7 @@ foreign kernel32 { bInitialState: BOOL, lpName: LPCWSTR, ) -> HANDLE --- + ResetEvent :: proc(hEvent: HANDLE) -> BOOL --- WaitForMultipleObjects :: proc( nCount: DWORD, lpHandles: ^HANDLE, @@ -246,6 +273,22 @@ foreign kernel32 { HeapReAlloc :: proc(hHeap: HANDLE, dwFlags: DWORD, lpMem: LPVOID, dwBytes: SIZE_T) -> LPVOID --- HeapFree :: proc(hHeap: HANDLE, dwFlags: DWORD, lpMem: LPVOID) -> BOOL --- + LocalAlloc :: proc(flags: UINT, bytes: SIZE_T) -> LPVOID --- + LocalReAlloc :: proc(mem: LPVOID, bytes: SIZE_T, flags: UINT) -> LPVOID --- + LocalFree :: proc(mem: LPVOID) -> LPVOID --- + + + ReadDirectoryChangesW :: proc( + hDirectory: HANDLE, + lpBuffer: LPVOID, + nBufferLength: DWORD, + bWatchSubtree: BOOL, + dwNotifyFilter: DWORD, + lpBytesReturned: LPDWORD, + lpOverlapped: LPOVERLAPPED, + lpCompletionRoutine: LPOVERLAPPED_COMPLETION_ROUTINE, + ) -> BOOL --- + InitializeSRWLock :: proc(SRWLock: ^SRWLOCK) --- AcquireSRWLockExclusive :: proc(SRWLock: ^SRWLOCK) --- TryAcquireSRWLockExclusive :: proc(SRWLock: ^SRWLOCK) -> BOOL --- @@ -738,3 +781,18 @@ foreign kernel32 { UnmapFlags: ULONG, ) -> BOOL --- } + +@(default_calling_convention="stdcall") +foreign kernel32 { + @(link_name="SetConsoleCtrlHandler") set_console_ctrl_handler :: proc(handler: Handler_Routine, add: BOOL) -> BOOL --- +} + +Handler_Routine :: proc(dwCtrlType: Control_Event) -> BOOL + +Control_Event :: enum DWORD { + control_c = 0, + _break = 1, + close = 2, + logoff = 5, + shutdown = 6, +}
\ No newline at end of file diff --git a/core/sys/windows/ntdll.odin b/core/sys/windows/ntdll.odin index 5deffd9f9..dda5b9711 100644 --- a/core/sys/windows/ntdll.odin +++ b/core/sys/windows/ntdll.odin @@ -3,7 +3,7 @@ package sys_windows foreign import ntdll_lib "system:ntdll.lib" -@(default_calling_convention="std") +@(default_calling_convention="stdcall") foreign ntdll_lib { - RtlGetVersion :: proc(lpVersionInformation: ^OSVERSIONINFOEXW) -> NTSTATUS --- + RtlGetVersion :: proc(lpVersionInformation: ^OSVERSIONINFOEXW) -> NTSTATUS --- }
\ No newline at end of file diff --git a/core/sys/windows/ole32.odin b/core/sys/windows/ole32.odin new file mode 100644 index 000000000..23fe888d2 --- /dev/null +++ b/core/sys/windows/ole32.odin @@ -0,0 +1,18 @@ +// +build windows +package sys_windows + +foreign import "system:Ole32.lib" + +//objbase.h +COINIT :: enum DWORD { + APARTMENTTHREADED = 0x2, + MULTITHREADED, + DISABLE_OLE1DDE = 0x4, + SPEED_OVER_MEMORY = 0x8, +} + +@(default_calling_convention="stdcall") +foreign Ole32 { + CoInitializeEx :: proc(reserved: rawptr, co_init: COINIT) -> HRESULT --- + CoUninitialize :: proc() --- +} diff --git a/core/sys/windows/shell32.odin b/core/sys/windows/shell32.odin index 70d8943bd..a6ecefc32 100644 --- a/core/sys/windows/shell32.odin +++ b/core/sys/windows/shell32.odin @@ -3,7 +3,7 @@ package sys_windows foreign import shell32 "system:Shell32.lib" -@(default_calling_convention = "std") +@(default_calling_convention="stdcall") foreign shell32 { CommandLineToArgvW :: proc(cmd_list: wstring, num_args: ^c_int) -> ^wstring --- } diff --git a/core/sys/windows/shlwapi.odin b/core/sys/windows/shlwapi.odin new file mode 100644 index 000000000..1852d536f --- /dev/null +++ b/core/sys/windows/shlwapi.odin @@ -0,0 +1,11 @@ +// +build windows +package sys_windows + +foreign import shlwapi "system:shlwapi.lib" + +@(default_calling_convention="stdcall") +foreign shlwapi { + PathFileExistsW :: proc(pszPath: wstring) -> BOOL --- + PathFindExtensionW :: proc(pszPath: wstring) -> wstring --- + PathFindFileNameW :: proc(pszPath: wstring) -> wstring --- +} diff --git a/core/sys/windows/synchronization.odin b/core/sys/windows/synchronization.odin index c4e1d2188..c98730aa0 100644 --- a/core/sys/windows/synchronization.odin +++ b/core/sys/windows/synchronization.odin @@ -5,7 +5,7 @@ foreign import Synchronization "system:Synchronization.lib" @(default_calling_convention="stdcall") foreign Synchronization { - WaitOnAddress :: proc(Address: PVOID, CompareAddress: PVOID, AddressSize: SIZE_T, dwMilliseconds: DWORD) -> BOOL --- + WaitOnAddress :: proc(Address: PVOID, CompareAddress: PVOID, AddressSize: SIZE_T, dwMilliseconds: DWORD) -> BOOL --- WakeByAddressSingle :: proc(Address: PVOID) --- - WakeByAddressAll :: proc(Address: PVOID) --- + WakeByAddressAll :: proc(Address: PVOID) --- } diff --git a/core/sys/windows/system_params.odin b/core/sys/windows/system_params.odin new file mode 100644 index 000000000..e94d777bf --- /dev/null +++ b/core/sys/windows/system_params.odin @@ -0,0 +1,271 @@ +// +build windows +package sys_windows + +// Parameter for SystemParametersInfo. +SPI_GETBEEP :: 0x0001 +SPI_SETBEEP :: 0x0002 +SPI_GETMOUSE :: 0x0003 +SPI_SETMOUSE :: 0x0004 +SPI_GETBORDER :: 0x0005 +SPI_SETBORDER :: 0x0006 +SPI_GETKEYBOARDSPEED :: 0x000A +SPI_SETKEYBOARDSPEED :: 0x000B +SPI_LANGDRIVER :: 0x000C +SPI_ICONHORIZONTALSPACING :: 0x000D +SPI_GETSCREENSAVETIMEOUT :: 0x000E +SPI_SETSCREENSAVETIMEOUT :: 0x000F +SPI_GETSCREENSAVEACTIVE :: 0x0010 +SPI_SETSCREENSAVEACTIVE :: 0x0011 +SPI_GETGRIDGRANULARITY :: 0x0012 +SPI_SETGRIDGRANULARITY :: 0x0013 +SPI_SETDESKWALLPAPER :: 0x0014 +SPI_SETDESKPATTERN :: 0x0015 +SPI_GETKEYBOARDDELAY :: 0x0016 +SPI_SETKEYBOARDDELAY :: 0x0017 +SPI_ICONVERTICALSPACING :: 0x0018 +SPI_GETICONTITLEWRAP :: 0x0019 +SPI_SETICONTITLEWRAP :: 0x001A +SPI_GETMENUDROPALIGNMENT :: 0x001B +SPI_SETMENUDROPALIGNMENT :: 0x001C +SPI_SETDOUBLECLKWIDTH :: 0x001D +SPI_SETDOUBLECLKHEIGHT :: 0x001E +SPI_GETICONTITLELOGFONT :: 0x001F +SPI_SETDOUBLECLICKTIME :: 0x0020 +SPI_SETMOUSEBUTTONSWAP :: 0x0021 +SPI_SETICONTITLELOGFONT :: 0x0022 +SPI_GETFASTTASKSWITCH :: 0x0023 +SPI_SETFASTTASKSWITCH :: 0x0024 + +SPI_SETDRAGFULLWINDOWS :: 0x0025 +SPI_GETDRAGFULLWINDOWS :: 0x0026 +SPI_GETNONCLIENTMETRICS :: 0x0029 +SPI_SETNONCLIENTMETRICS :: 0x002A +SPI_GETMINIMIZEDMETRICS :: 0x002B +SPI_SETMINIMIZEDMETRICS :: 0x002C +SPI_GETICONMETRICS :: 0x002D +SPI_SETICONMETRICS :: 0x002E +SPI_SETWORKAREA :: 0x002F +SPI_GETWORKAREA :: 0x0030 +SPI_SETPENWINDOWS :: 0x0031 +SPI_GETHIGHCONTRAST :: 0x0042 +SPI_SETHIGHCONTRAST :: 0x0043 +SPI_GETKEYBOARDPREF :: 0x0044 +SPI_SETKEYBOARDPREF :: 0x0045 +SPI_GETSCREENREADER :: 0x0046 +SPI_SETSCREENREADER :: 0x0047 +SPI_GETANIMATION :: 0x0048 +SPI_SETANIMATION :: 0x0049 +SPI_GETFONTSMOOTHING :: 0x004A +SPI_SETFONTSMOOTHING :: 0x004B +SPI_SETDRAGWIDTH :: 0x004C +SPI_SETDRAGHEIGHT :: 0x004D +SPI_SETHANDHELD :: 0x004E +SPI_GETLOWPOWERTIMEOUT :: 0x004F +SPI_GETPOWEROFFTIMEOUT :: 0x0050 +SPI_SETLOWPOWERTIMEOUT :: 0x0051 +SPI_SETPOWEROFFTIMEOUT :: 0x0052 +SPI_GETLOWPOWERACTIVE :: 0x0053 +SPI_GETPOWEROFFACTIVE :: 0x0054 +SPI_SETLOWPOWERACTIVE :: 0x0055 +SPI_SETPOWEROFFACTIVE :: 0x0056 +SPI_SETCURSORS :: 0x0057 +SPI_SETICONS :: 0x0058 +SPI_GETDEFAULTINPUTLANG :: 0x0059 +SPI_SETDEFAULTINPUTLANG :: 0x005A +SPI_SETLANGTOGGLE :: 0x005B +SPI_GETWINDOWSEXTENSION :: 0x005C +SPI_SETMOUSETRAILS :: 0x005D +SPI_GETMOUSETRAILS :: 0x005E +SPI_SETSCREENSAVERRUNNING :: 0x0061 + +SPI_SCREENSAVERRUNNING :: SPI_SETSCREENSAVERRUNNING +SPI_GETFILTERKEYS :: 0x0032 +SPI_SETFILTERKEYS :: 0x0033 +SPI_GETTOGGLEKEYS :: 0x0034 +SPI_SETTOGGLEKEYS :: 0x0035 +SPI_GETMOUSEKEYS :: 0x0036 +SPI_SETMOUSEKEYS :: 0x0037 +SPI_GETSHOWSOUNDS :: 0x0038 +SPI_SETSHOWSOUNDS :: 0x0039 +SPI_GETSTICKYKEYS :: 0x003A +SPI_SETSTICKYKEYS :: 0x003B +SPI_GETACCESSTIMEOUT :: 0x003C +SPI_SETACCESSTIMEOUT :: 0x003D +SPI_GETSERIALKEYS :: 0x003E +SPI_SETSERIALKEYS :: 0x003F +SPI_GETSOUNDSENTRY :: 0x0040 +SPI_SETSOUNDSENTRY :: 0x0041 +SPI_GETSNAPTODEFBUTTON :: 0x005F +SPI_SETSNAPTODEFBUTTON :: 0x0060 +SPI_GETMOUSEHOVERWIDTH :: 0x0062 +SPI_SETMOUSEHOVERWIDTH :: 0x0063 +SPI_GETMOUSEHOVERHEIGHT :: 0x0064 +SPI_SETMOUSEHOVERHEIGHT :: 0x0065 +SPI_GETMOUSEHOVERTIME :: 0x0066 +SPI_SETMOUSEHOVERTIME :: 0x0067 +SPI_GETWHEELSCROLLLINES :: 0x0068 +SPI_SETWHEELSCROLLLINES :: 0x0069 +SPI_GETMENUSHOWDELAY :: 0x006A +SPI_SETMENUSHOWDELAY :: 0x006B + +SPI_GETWHEELSCROLLCHARS :: 0x006C +SPI_SETWHEELSCROLLCHARS :: 0x006D +SPI_GETSHOWIMEUI :: 0x006E +SPI_SETSHOWIMEUI :: 0x006F +SPI_GETMOUSESPEED :: 0x0070 +SPI_SETMOUSESPEED :: 0x0071 +SPI_GETSCREENSAVERRUNNING :: 0x0072 +SPI_GETDESKWALLPAPER :: 0x0073 +SPI_GETAUDIODESCRIPTION :: 0x0074 +SPI_SETAUDIODESCRIPTION :: 0x0075 +SPI_GETSCREENSAVESECURE :: 0x0076 +SPI_SETSCREENSAVESECURE :: 0x0077 + +SPI_GETHUNGAPPTIMEOUT :: 0x0078 +SPI_SETHUNGAPPTIMEOUT :: 0x0079 +SPI_GETWAITTOKILLTIMEOUT :: 0x007A +SPI_SETWAITTOKILLTIMEOUT :: 0x007B +SPI_GETWAITTOKILLSERVICETIMEOUT :: 0x007C +SPI_SETWAITTOKILLSERVICETIMEOUT :: 0x007D +SPI_GETMOUSEDOCKTHRESHOLD :: 0x007E +SPI_SETMOUSEDOCKTHRESHOLD :: 0x007F +SPI_GETPENDOCKTHRESHOLD :: 0x0080 +SPI_SETPENDOCKTHRESHOLD :: 0x0081 +SPI_GETWINARRANGING :: 0x0082 +SPI_SETWINARRANGING :: 0x0083 +SPI_GETMOUSEDRAGOUTTHRESHOLD :: 0x0084 +SPI_SETMOUSEDRAGOUTTHRESHOLD :: 0x0085 +SPI_GETPENDRAGOUTTHRESHOLD :: 0x0086 +SPI_SETPENDRAGOUTTHRESHOLD :: 0x0087 +SPI_GETMOUSESIDEMOVETHRESHOLD :: 0x0088 +SPI_SETMOUSESIDEMOVETHRESHOLD :: 0x0089 +SPI_GETPENSIDEMOVETHRESHOLD :: 0x008A +SPI_SETPENSIDEMOVETHRESHOLD :: 0x008B +SPI_GETDRAGFROMMAXIMIZE :: 0x008C +SPI_SETDRAGFROMMAXIMIZE :: 0x008D +SPI_GETSNAPSIZING :: 0x008E +SPI_SETSNAPSIZING :: 0x008F +SPI_GETDOCKMOVING :: 0x0090 +SPI_SETDOCKMOVING :: 0x0091 + +SPI_GETACTIVEWINDOWTRACKING :: 0x1000 +SPI_SETACTIVEWINDOWTRACKING :: 0x1001 +SPI_GETMENUANIMATION :: 0x1002 +SPI_SETMENUANIMATION :: 0x1003 +SPI_GETCOMBOBOXANIMATION :: 0x1004 +SPI_SETCOMBOBOXANIMATION :: 0x1005 +SPI_GETLISTBOXSMOOTHSCROLLING :: 0x1006 +SPI_SETLISTBOXSMOOTHSCROLLING :: 0x1007 +SPI_GETGRADIENTCAPTIONS :: 0x1008 +SPI_SETGRADIENTCAPTIONS :: 0x1009 +SPI_GETKEYBOARDCUES :: 0x100A +SPI_SETKEYBOARDCUES :: 0x100B +SPI_GETMENUUNDERLINES :: SPI_GETKEYBOARDCUES +SPI_SETMENUUNDERLINES :: SPI_SETKEYBOARDCUES +SPI_GETACTIVEWNDTRKZORDER :: 0x100C +SPI_SETACTIVEWNDTRKZORDER :: 0x100D +SPI_GETHOTTRACKING :: 0x100E +SPI_SETHOTTRACKING :: 0x100F +SPI_GETMENUFADE :: 0x1012 +SPI_SETMENUFADE :: 0x1013 +SPI_GETSELECTIONFADE :: 0x1014 +SPI_SETSELECTIONFADE :: 0x1015 +SPI_GETTOOLTIPANIMATION :: 0x1016 +SPI_SETTOOLTIPANIMATION :: 0x1017 +SPI_GETTOOLTIPFADE :: 0x1018 +SPI_SETTOOLTIPFADE :: 0x1019 +SPI_GETCURSORSHADOW :: 0x101A +SPI_SETCURSORSHADOW :: 0x101B +SPI_GETMOUSESONAR :: 0x101C +SPI_SETMOUSESONAR :: 0x101D +SPI_GETMOUSECLICKLOCK :: 0x101E +SPI_SETMOUSECLICKLOCK :: 0x101F +SPI_GETMOUSEVANISH :: 0x1020 +SPI_SETMOUSEVANISH :: 0x1021 +SPI_GETFLATMENU :: 0x1022 +SPI_SETFLATMENU :: 0x1023 +SPI_GETDROPSHADOW :: 0x1024 +SPI_SETDROPSHADOW :: 0x1025 +SPI_GETBLOCKSENDINPUTRESETS :: 0x1026 +SPI_SETBLOCKSENDINPUTRESETS :: 0x1027 +SPI_GETUIEFFECTS :: 0x103E +SPI_SETUIEFFECTS :: 0x103F +SPI_GETDISABLEOVERLAPPEDCONTENT :: 0x1040 +SPI_SETDISABLEOVERLAPPEDCONTENT :: 0x1041 +SPI_GETCLIENTAREAANIMATION :: 0x1042 +SPI_SETCLIENTAREAANIMATION :: 0x1043 +SPI_GETCLEARTYPE :: 0x1048 +SPI_SETCLEARTYPE :: 0x1049 +SPI_GETSPEECHRECOGNITION :: 0x104A +SPI_SETSPEECHRECOGNITION :: 0x104B +SPI_GETCARETBROWSING :: 0x104C +SPI_SETCARETBROWSING :: 0x104D +SPI_GETTHREADLOCALINPUTSETTINGS :: 0x104E +SPI_SETTHREADLOCALINPUTSETTINGS :: 0x104F +SPI_GETSYSTEMLANGUAGEBAR :: 0x1050 +SPI_SETSYSTEMLANGUAGEBAR :: 0x1051 +SPI_GETFOREGROUNDLOCKTIMEOUT :: 0x2000 +SPI_SETFOREGROUNDLOCKTIMEOUT :: 0x2001 +SPI_GETACTIVEWNDTRKTIMEOUT :: 0x2002 +SPI_SETACTIVEWNDTRKTIMEOUT :: 0x2003 +SPI_GETFOREGROUNDFLASHCOUNT :: 0x2004 +SPI_SETFOREGROUNDFLASHCOUNT :: 0x2005 +SPI_GETCARETWIDTH :: 0x2006 +SPI_SETCARETWIDTH :: 0x2007 +SPI_GETMOUSECLICKLOCKTIME :: 0x2008 +SPI_SETMOUSECLICKLOCKTIME :: 0x2009 +SPI_GETFONTSMOOTHINGTYPE :: 0x200A +SPI_SETFONTSMOOTHINGTYPE :: 0x200B +// constants for SPI_GETFONTSMOOTHINGTYPE and SPI_SETFONTSMOOTHINGTYPE: +FE_FONTSMOOTHINGSTANDARD :: 0x0001 +FE_FONTSMOOTHINGCLEARTYPE :: 0x0002 + +SPI_GETFONTSMOOTHINGCONTRAST :: 0x200C +SPI_SETFONTSMOOTHINGCONTRAST :: 0x200D + +SPI_GETFOCUSBORDERWIDTH :: 0x200E +SPI_SETFOCUSBORDERWIDTH :: 0x200F +SPI_GETFOCUSBORDERHEIGHT :: 0x2010 +SPI_SETFOCUSBORDERHEIGHT :: 0x2011 + +SPI_GETFONTSMOOTHINGORIENTATION :: 0x2012 +SPI_SETFONTSMOOTHINGORIENTATION :: 0x2013 + +// constants for SPI_GETFONTSMOOTHINGORIENTATION and SPI_SETFONTSMOOTHINGORIENTATION: +FE_FONTSMOOTHINGORIENTATIONBGR :: 0x0000 +FE_FONTSMOOTHINGORIENTATIONRGB :: 0x0001 + +SPI_GETMINIMUMHITRADIUS :: 0x2014 +SPI_SETMINIMUMHITRADIUS :: 0x2015 +SPI_GETMESSAGEDURATION :: 0x2016 +SPI_SETMESSAGEDURATION :: 0x2017 + +SPI_GETCONTACTVISUALIZATION :: 0x2018 +SPI_SETCONTACTVISUALIZATION :: 0x2019 +// constants for SPI_GETCONTACTVISUALIZATION and SPI_SETCONTACTVISUALIZATION +CONTACTVISUALIZATION_OFF :: 0x0000 +CONTACTVISUALIZATION_ON :: 0x0001 +CONTACTVISUALIZATION_PRESENTATIONMODE :: 0x0002 + +SPI_GETGESTUREVISUALIZATION :: 0x201A +SPI_SETGESTUREVISUALIZATION :: 0x201B +// constants for SPI_GETGESTUREVISUALIZATION and SPI_SETGESTUREVISUALIZATION +GESTUREVISUALIZATION_OFF :: 0x0000 +GESTUREVISUALIZATION_ON :: 0x001F +GESTUREVISUALIZATION_TAP :: 0x0001 +GESTUREVISUALIZATION_DOUBLETAP :: 0x0002 +GESTUREVISUALIZATION_PRESSANDTAP :: 0x0004 +GESTUREVISUALIZATION_PRESSANDHOLD :: 0x0008 +GESTUREVISUALIZATION_RIGHTTAP :: 0x0010 + +SPI_GETMOUSEWHEELROUTING :: 0x201C +SPI_SETMOUSEWHEELROUTING :: 0x201D + +MOUSEWHEEL_ROUTING_FOCUS :: 0 +MOUSEWHEEL_ROUTING_HYBRID :: 1 +MOUSEWHEEL_ROUTING_MOUSE_POS :: 2 + +// Flags +SPIF_UPDATEINIFILE :: 0x0001 +SPIF_SENDWININICHANGE :: 0x0002 +SPIF_SENDCHANGE :: SPIF_SENDWININICHANGE diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index de19cd6cc..a0649c5f3 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -3,19 +3,21 @@ package sys_windows import "core:c" -c_char :: c.char -c_uchar :: c.uchar -c_int :: c.int -c_uint :: c.uint -c_long :: c.long -c_longlong :: c.longlong -c_ulong :: c.ulong -c_short :: c.short -c_ushort :: c.ushort -size_t :: c.size_t -wchar_t :: c.wchar_t +c_char :: c.char +c_uchar :: c.uchar +c_int :: c.int +c_uint :: c.uint +c_long :: c.long +c_longlong :: c.longlong +c_ulong :: c.ulong +c_ulonglong :: c.ulonglong +c_short :: c.short +c_ushort :: c.ushort +size_t :: c.size_t +wchar_t :: c.wchar_t DWORD :: c_ulong +QWORD :: c.ulonglong HANDLE :: distinct LPVOID HINSTANCE :: HANDLE HMODULE :: distinct HINSTANCE @@ -55,6 +57,8 @@ UINT_PTR :: uintptr ULONG :: c_ulong UCHAR :: BYTE NTSTATUS :: c.long +COLORREF :: DWORD +LPCOLORREF :: ^COLORREF LPARAM :: LONG_PTR WPARAM :: UINT_PTR LRESULT :: LONG_PTR @@ -123,6 +127,8 @@ PCONDITION_VARIABLE :: ^CONDITION_VARIABLE PLARGE_INTEGER :: ^LARGE_INTEGER PSRWLOCK :: ^SRWLOCK +MMRESULT :: UINT + SOCKET :: distinct uintptr // TODO socklen_t :: c_int ADDRESS_FAMILY :: USHORT @@ -160,6 +166,21 @@ FILE_GENERIC_ALL: DWORD : 0x10000000 FILE_GENERIC_EXECUTE: DWORD : 0x20000000 FILE_GENERIC_READ: DWORD : 0x80000000 +FILE_ACTION_ADDED :: 0x00000001 +FILE_ACTION_REMOVED :: 0x00000002 +FILE_ACTION_MODIFIED :: 0x00000003 +FILE_ACTION_RENAMED_OLD_NAME :: 0x00000004 +FILE_ACTION_RENAMED_NEW_NAME :: 0x00000005 + +FILE_NOTIFY_CHANGE_FILE_NAME :: 0x00000001 +FILE_NOTIFY_CHANGE_DIR_NAME :: 0x00000002 +FILE_NOTIFY_CHANGE_ATTRIBUTES :: 0x00000004 +FILE_NOTIFY_CHANGE_SIZE :: 0x00000008 +FILE_NOTIFY_CHANGE_LAST_WRITE :: 0x00000010 +FILE_NOTIFY_CHANGE_LAST_ACCESS :: 0x00000020 +FILE_NOTIFY_CHANGE_CREATION :: 0x00000040 +FILE_NOTIFY_CHANGE_SECURITY :: 0x00000100 + CREATE_NEW: DWORD : 1 CREATE_ALWAYS: DWORD : 2 OPEN_ALWAYS: DWORD : 4 @@ -172,6 +193,7 @@ FILE_WRITE_DATA: DWORD : 0x00000002 FILE_APPEND_DATA: DWORD : 0x00000004 FILE_WRITE_EA: DWORD : 0x00000010 FILE_WRITE_ATTRIBUTES: DWORD : 0x00000100 +FILE_READ_ATTRIBUTES: DWORD : 0x000000080 READ_CONTROL: DWORD : 0x00020000 SYNCHRONIZE: DWORD : 0x00100000 GENERIC_READ: DWORD : 0x80000000 @@ -195,6 +217,56 @@ GET_FILEEX_INFO_LEVELS :: distinct i32 GetFileExInfoStandard: GET_FILEEX_INFO_LEVELS : 0 GetFileExMaxInfoLevel: GET_FILEEX_INFO_LEVELS : 1 +// String resource number bases (internal use) + +MMSYSERR_BASE :: 0 +WAVERR_BASE :: 32 +MIDIERR_BASE :: 64 +TIMERR_BASE :: 96 +JOYERR_BASE :: 160 +MCIERR_BASE :: 256 +MIXERR_BASE :: 1024 + +MCI_STRING_OFFSET :: 512 +MCI_VD_OFFSET :: 1024 +MCI_CD_OFFSET :: 1088 +MCI_WAVE_OFFSET :: 1152 +MCI_SEQ_OFFSET :: 1216 + +// timer error return values +TIMERR_NOERROR :: 0 // no error +TIMERR_NOCANDO :: TIMERR_BASE + 1 // request not completed +TIMERR_STRUCT :: TIMERR_BASE + 33 // time struct size + +DIAGNOSTIC_REASON_VERSION :: 0 + +DIAGNOSTIC_REASON_SIMPLE_STRING :: 0x00000001 +DIAGNOSTIC_REASON_DETAILED_STRING :: 0x00000002 +DIAGNOSTIC_REASON_NOT_SPECIFIED :: 0x80000000 + +// Defines for power request APIs + +POWER_REQUEST_CONTEXT_VERSION :: DIAGNOSTIC_REASON_VERSION + +POWER_REQUEST_CONTEXT_SIMPLE_STRING :: DIAGNOSTIC_REASON_SIMPLE_STRING +POWER_REQUEST_CONTEXT_DETAILED_STRING :: DIAGNOSTIC_REASON_DETAILED_STRING + +REASON_CONTEXT :: struct { + Version: ULONG, + Flags: DWORD, + Reason: struct #raw_union { + Detailed: struct { + LocalizedReasonModule: HMODULE, + LocalizedReasonId: ULONG, + ReasonStringCount: ULONG, + ReasonStrings: ^LPWSTR, + }, + SimpleReasonString: LPWSTR, + }, +} +PREASON_CONTEXT :: ^REASON_CONTEXT + +PTIMERAPCROUTINE :: #type proc "stdcall" (lpArgToCompletionRoutine: LPVOID, dwTimerLowValue, dwTimerHighValue: DWORD) TIMERPROC :: #type proc "stdcall" (HWND, UINT, UINT_PTR, DWORD) @@ -734,6 +806,10 @@ HOVER_DEFAULT :: 0xFFFFFFFF USER_TIMER_MAXIMUM :: 0x7FFFFFFF USER_TIMER_MINIMUM :: 0x0000000A +// WM_ACTIVATE state values +WA_INACTIVE :: 0 +WA_ACTIVE :: 1 +WA_CLICKACTIVE :: 2 // SetWindowsHook() codes WH_MIN :: -1 @@ -1066,6 +1142,7 @@ ERROR_BROKEN_PIPE: DWORD : 109 ERROR_CALL_NOT_IMPLEMENTED: DWORD : 120 ERROR_INSUFFICIENT_BUFFER: DWORD : 122 ERROR_INVALID_NAME: DWORD : 123 +ERROR_BAD_ARGUMENTS: DWORD: 160 ERROR_LOCK_FAILED: DWORD : 167 ERROR_ALREADY_EXISTS: DWORD : 183 ERROR_NO_DATA: DWORD : 232 @@ -1082,9 +1159,21 @@ INVALID_HANDLE_VALUE :: INVALID_HANDLE FACILITY_NT_BIT: DWORD : 0x1000_0000 -FORMAT_MESSAGE_FROM_SYSTEM: DWORD : 0x00001000 -FORMAT_MESSAGE_FROM_HMODULE: DWORD : 0x00000800 -FORMAT_MESSAGE_IGNORE_INSERTS: DWORD : 0x00000200 +FORMAT_MESSAGE_ALLOCATE_BUFFER :: 0x00000100 +FORMAT_MESSAGE_IGNORE_INSERTS :: 0x00000200 +FORMAT_MESSAGE_FROM_STRING :: 0x00000400 +FORMAT_MESSAGE_FROM_HMODULE :: 0x00000800 +FORMAT_MESSAGE_FROM_SYSTEM :: 0x00001000 +FORMAT_MESSAGE_ARGUMENT_ARRAY :: 0x00002000 +FORMAT_MESSAGE_MAX_WIDTH_MASK :: 0x000000FF + +LMEM_FIXED :: 0x0000 +LMEM_MOVEABLE :: 0x0002 +LMEM_ZEROINIT :: 0x0040 +LHND :: 0x0042 +LPTR :: 0x0040 +NONZEROLHND :: LMEM_MOVEABLE +NONZEROLPTR :: LMEM_FIXED TLS_OUT_OF_INDEXES: DWORD : 0xFFFFFFFF @@ -1307,6 +1396,13 @@ FILE_END_OF_FILE_INFO :: struct { EndOfFile: LARGE_INTEGER, } +FILE_NOTIFY_INFORMATION :: struct { + next_entry_offset: DWORD, + action: DWORD, + file_name_length: DWORD, + file_name: [1]WCHAR, +} + REPARSE_DATA_BUFFER :: struct { ReparseTag: c_uint, ReparseDataLength: c_ushort, @@ -1451,6 +1547,12 @@ OVERLAPPED :: struct { hEvent: HANDLE, } +LPOVERLAPPED_COMPLETION_ROUTINE :: #type proc "stdcall" ( + dwErrorCode: DWORD, + dwNumberOfBytesTransfered: DWORD, + lpOverlapped: LPOVERLAPPED, +) + ADDRESS_MODE :: enum c_int { AddrMode1616, AddrMode1632, @@ -1476,6 +1578,33 @@ ADDRINFOA :: struct { ai_next: ^ADDRINFOA, } +PADDRINFOEXW :: ^ADDRINFOEXW +LPADDRINFOEXW :: ^ADDRINFOEXW +ADDRINFOEXW :: struct { + ai_flags: c_int, + ai_family: c_int, + ai_socktype: c_int, + ai_protocol: c_int, + ai_addrlen: size_t, + ai_canonname: wstring, + ai_addr: ^sockaddr, + ai_blob: rawptr, + ai_bloblen: size_t, + ai_provider: LPGUID, + ai_next: ^ADDRINFOEXW, +} + +LPLOOKUPSERVICE_COMPLETION_ROUTINE :: #type proc "stdcall" ( + dwErrorCode: DWORD, + dwNumberOfBytesTransfered: DWORD, + lpOverlapped: LPOVERLAPPED, +) + +sockaddr :: struct { + sa_family: USHORT, + sa_data: [14]byte, +} + sockaddr_in :: struct { sin_family: ADDRESS_FAMILY, sin_port: USHORT, @@ -2082,3 +2211,122 @@ SYSTEMTIME :: struct { second: WORD, milliseconds: WORD, } + + +@(private="file") +IMAGE_DOS_HEADER :: struct { + e_magic: WORD, + e_cblp: WORD, + e_cp: WORD, + e_crlc: WORD, + e_cparhdr: WORD, + e_minalloc: WORD, + e_maxalloc: WORD, + e_ss: WORD, + e_sp: WORD, + e_csum: WORD, + e_ip: WORD, + e_cs: WORD, + e_lfarlc: WORD, + e_ovno: WORD, + e_res_0: WORD, + e_res_1: WORD, + e_res_2: WORD, + e_res_3: WORD, + e_oemid: WORD, + e_oeminfo: WORD, + e_res2_0: WORD, + e_res2_1: WORD, + e_res2_2: WORD, + e_res2_3: WORD, + e_res2_4: WORD, + e_res2_5: WORD, + e_res2_6: WORD, + e_res2_7: WORD, + e_res2_8: WORD, + e_res2_9: WORD, + e_lfanew: DWORD, +} + +IMAGE_DATA_DIRECTORY :: struct { + VirtualAddress: DWORD, + Size: DWORD, +} + +IMAGE_FILE_HEADER :: struct { + Machine: WORD, + NumberOfSections: WORD, + TimeDateStamp: DWORD, + PointerToSymbolTable: DWORD, + NumberOfSymbols: DWORD, + SizeOfOptionalHeader: WORD, + Characteristics: WORD, +} + +IMAGE_OPTIONAL_HEADER64 :: struct { + Magic: WORD, + MajorLinkerVersion: BYTE, + MinorLinkerVersion: BYTE, + SizeOfCode: DWORD, + SizeOfInitializedData: DWORD, + SizeOfUninitializedData: DWORD, + AddressOfEntryPoint: DWORD, + BaseOfCode: DWORD, + ImageBase: QWORD, + SectionAlignment: DWORD, + FileAlignment: DWORD, + MajorOperatingSystemVersion: WORD, + MinorOperatingSystemVersion: WORD, + MajorImageVersion: WORD, + MinorImageVersion: WORD, + MajorSubsystemVersion: WORD, + MinorSubsystemVersion: WORD, + Win32VersionValue: DWORD, + SizeOfImage: DWORD, + SizeOfHeaders: DWORD, + CheckSum: DWORD, + Subsystem: WORD, + DllCharacteristics: WORD, + SizeOfStackReserve: QWORD, + SizeOfStackCommit: QWORD, + SizeOfHeapReserve: QWORD, + SizeOfHeapCommit: QWORD, + LoaderFlags: DWORD, + NumberOfRvaAndSizes: DWORD, + ExportTable: IMAGE_DATA_DIRECTORY, + ImportTable: IMAGE_DATA_DIRECTORY, + ResourceTable: IMAGE_DATA_DIRECTORY, + ExceptionTable: IMAGE_DATA_DIRECTORY, + CertificateTable: IMAGE_DATA_DIRECTORY, + BaseRelocationTable: IMAGE_DATA_DIRECTORY, + Debug: IMAGE_DATA_DIRECTORY, + Architecture: IMAGE_DATA_DIRECTORY, + GlobalPtr: IMAGE_DATA_DIRECTORY, + TLSTable: IMAGE_DATA_DIRECTORY, + LoadConfigTable: IMAGE_DATA_DIRECTORY, + BoundImport: IMAGE_DATA_DIRECTORY, + IAT: IMAGE_DATA_DIRECTORY, + DelayImportDescriptor: IMAGE_DATA_DIRECTORY, + CLRRuntimeHeader: IMAGE_DATA_DIRECTORY, + Reserved: IMAGE_DATA_DIRECTORY, +} + +IMAGE_NT_HEADERS64 :: struct { + Signature: DWORD, + FileHeader: IMAGE_FILE_HEADER, + OptionalHeader: IMAGE_OPTIONAL_HEADER64, +} + +IMAGE_EXPORT_DIRECTORY :: struct { + Characteristics: DWORD, + TimeDateStamp: DWORD, + MajorVersion: WORD, + MinorVersion: WORD, + Name: DWORD, + Base: DWORD, + NumberOfFunctions: DWORD, + NumberOfNames: DWORD, + AddressOfFunctions: DWORD, // RVA from base of image + AddressOfNames: DWORD, // RVA from base of image + AddressOfNameOrdinals: DWORD, // RVA from base of image +}
\ No newline at end of file diff --git a/core/sys/windows/user32.odin b/core/sys/windows/user32.odin index 2316d3363..1b6d23ba4 100644 --- a/core/sys/windows/user32.odin +++ b/core/sys/windows/user32.odin @@ -5,43 +5,20 @@ foreign import user32 "system:User32.lib" @(default_calling_convention="stdcall") foreign user32 { - GetClassInfoA :: proc(hInstance: HINSTANCE, lpClassNAme: LPCSTR, lpWndClass: ^WNDCLASSA) -> BOOL --- GetClassInfoW :: proc(hInstance: HINSTANCE, lpClassNAme: LPCWSTR, lpWndClass: ^WNDCLASSW) -> BOOL --- - GetClassInfoExA :: proc(hInsatnce: HINSTANCE, lpszClass: LPCSTR, lpwcx: ^WNDCLASSEXA) -> BOOL --- GetClassInfoExW :: proc(hInsatnce: HINSTANCE, lpszClass: LPCWSTR, lpwcx: ^WNDCLASSEXW) -> BOOL --- - GetClassLongA :: proc(hWnd: HWND, nIndex: c_int) -> DWORD --- GetClassLongW :: proc(hWnd: HWND, nIndex: c_int) -> DWORD --- - SetClassLongA :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG) -> DWORD --- SetClassLongW :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG) -> DWORD --- - GetWindowLongA :: proc(hWnd: HWND, nIndex: c_int) -> LONG --- GetWindowLongW :: proc(hWnd: HWND, nIndex: c_int) -> LONG --- - SetWindowLongA :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG) -> LONG --- SetWindowLongW :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG) -> LONG --- - GetClassNameA :: proc(hWnd: HWND, lpClassName: LPSTR, nMaxCount: c_int) -> c_int --- GetClassNameW :: proc(hWnd: HWND, lpClassName: LPWSTR, nMaxCount: c_int) -> c_int --- - RegisterClassA :: proc(lpWndClass: ^WNDCLASSA) -> ATOM --- RegisterClassW :: proc(lpWndClass: ^WNDCLASSW) -> ATOM --- - RegisterClassExA :: proc(^WNDCLASSEXA) -> ATOM --- RegisterClassExW :: proc(^WNDCLASSEXW) -> ATOM --- - CreateWindowExA :: proc( - dwExStyle: DWORD, - lpClassName: LPCSTR, - lpWindowName: LPCSTR, - dwStyle: DWORD, - X: c_int, - Y: c_int, - nWidth: c_int, - nHeight: c_int, - hWndParent: HWND, - hMenu: HMENU, - hInstance: HINSTANCE, - lpParam: LPVOID, - ) -> HWND --- CreateWindowExW :: proc( dwExStyle: DWORD, lpClassName: LPCWSTR, @@ -60,12 +37,16 @@ foreign user32 { DestroyWindow :: proc(hWnd: HWND) -> BOOL --- ShowWindow :: proc(hWnd: HWND, nCmdShow: c_int) -> BOOL --- + BringWindowToTop :: proc(hWnd: HWND) -> BOOL --- + GetTopWindow :: proc(hWnd: HWND) -> HWND --- + SetForegroundWindow :: proc(hWnd: HWND) -> BOOL --- + GetForegroundWindow :: proc() -> HWND --- + SetActiveWindow :: proc(hWnd: HWND) -> HWND --- + GetActiveWindow :: proc() -> HWND --- - GetMessageA :: proc(lpMsg: ^MSG, hWnd: HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT) -> BOOL --- GetMessageW :: proc(lpMsg: ^MSG, hWnd: HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT) -> BOOL --- TranslateMessage :: proc(lpMsg: ^MSG) -> BOOL --- - DispatchMessageA :: proc(lpMsg: ^MSG) -> LRESULT --- DispatchMessageW :: proc(lpMsg: ^MSG) -> LRESULT --- PeekMessageA :: proc(lpMsg: ^MSG, hWnd: HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT, wRemoveMsg: UINT) -> BOOL --- @@ -99,6 +80,7 @@ foreign user32 { GetWindowRect :: proc(hWnd: HWND, lpRect: LPRECT) -> BOOL --- GetClientRect :: proc(hWnd: HWND, lpRect: LPRECT) -> BOOL --- ClientToScreen :: proc(hWnd: HWND, lpPoint: LPPOINT) -> BOOL --- + ScreenToClient :: proc(hWnd: HWND, lpPoint: LPPOINT) -> BOOL --- SetWindowPos :: proc( hWnd: HWND, hWndInsertAfter: HWND, @@ -108,10 +90,13 @@ foreign user32 { cy: c_int, uFlags: UINT, ) -> BOOL --- + MoveWindow :: proc(hWnd: HWND, X, Y, hWidth, hHeight: c_int, bRepaint: BOOL) -> BOOL --- GetSystemMetrics :: proc(nIndex: c_int) -> c_int --- AdjustWindowRect :: proc(lpRect: LPRECT, dwStyle: DWORD, bMenu: BOOL) -> BOOL --- AdjustWindowRectEx :: proc(lpRect: LPRECT, dwStyle: DWORD, bMenu: BOOL, dwExStyle: DWORD) -> BOOL --- + SystemParametersInfoW :: proc(uiAction, uiParam: UINT, pvParam: PVOID, fWinIni: UINT) -> BOOL --- + GetWindowDC :: proc(hWnd: HWND) -> HDC --- GetDC :: proc(hWnd: HWND) -> HDC --- ReleaseDC :: proc(hWnd: HWND, hDC: HDC) -> c_int --- @@ -131,10 +116,8 @@ foreign user32 { GetKeyState :: proc(nVirtKey: c_int) -> SHORT --- GetAsyncKeyState :: proc(vKey: c_int) -> SHORT --- - MapVirtualKeyA :: proc(uCode: UINT, uMapType: UINT) -> UINT --- MapVirtualKeyW :: proc(uCode: UINT, uMapType: UINT) -> UINT --- - SetWindowsHookExA :: proc(idHook: c_int, lpfn: HOOKPROC, hmod: HINSTANCE, dwThreadId: DWORD) -> HHOOK --- SetWindowsHookExW :: proc(idHook: c_int, lpfn: HOOKPROC, hmod: HINSTANCE, dwThreadId: DWORD) -> HHOOK --- UnhookWindowsHookEx :: proc(hhk: HHOOK) -> BOOL --- CallNextHookEx :: proc(hhk: HHOOK, nCode: c_int, wParam: WPARAM, lParam: LPARAM) -> LRESULT --- @@ -142,39 +125,15 @@ foreign user32 { SetTimer :: proc(hWnd: HWND, nIDEvent: UINT_PTR, uElapse: UINT, lpTimerFunc: TIMERPROC) -> UINT_PTR --- KillTimer :: proc(hWnd: HWND, uIDEvent: UINT_PTR) -> BOOL --- - MessageBoxA :: proc(hWnd: HWND, lpText: LPCSTR, lpCaption: LPCSTR, uType: UINT) -> c_int --- + // MessageBoxA :: proc(hWnd: HWND, lpText: LPCSTR, lpCaption: LPCSTR, uType: UINT) -> c_int --- MessageBoxW :: proc(hWnd: HWND, lpText: LPCWSTR, lpCaption: LPCWSTR, uType: UINT) -> c_int --- - MessageBoxExA :: proc(hWnd: HWND, lpText: LPCSTR, lpCaption: LPCSTR, uType: UINT, wLanguageId: WORD) -> c_int --- + // MessageBoxExA :: proc(hWnd: HWND, lpText: LPCSTR, lpCaption: LPCSTR, uType: UINT, wLanguageId: WORD) -> c_int --- MessageBoxExW :: proc(hWnd: HWND, lpText: LPCWSTR, lpCaption: LPCWSTR, uType: UINT, wLanguageId: WORD) -> c_int --- -} -CreateWindowA :: #force_inline proc "stdcall" ( - lpClassName: LPCSTR, - lpWindowName: LPCSTR, - dwStyle: DWORD, - X: c_int, - Y: c_int, - nWidth: c_int, - nHeight: c_int, - hWndParent: HWND, - hMenu: HMENU, - hInstance: HINSTANCE, - lpParam: LPVOID, -) -> HWND { - return CreateWindowExA( - 0, - lpClassName, - lpWindowName, - dwStyle, - X, - Y, - nWidth, - nHeight, - hWndParent, - hMenu, - hInstance, - lpParam, - ) + ClipCursor :: proc(lpRect: LPRECT) -> BOOL --- + GetCursorPos :: proc(lpPoint: LPPOINT) -> BOOL --- + SetCursorPos :: proc(X: c_int, Y: c_int) -> BOOL --- + SetCursor :: proc(hCursor: HCURSOR) -> HCURSOR --- } CreateWindowW :: #force_inline proc "stdcall" ( @@ -209,25 +168,17 @@ CreateWindowW :: #force_inline proc "stdcall" ( when ODIN_ARCH == .amd64 { @(default_calling_convention="stdcall") foreign user32 { - GetClassLongPtrA :: proc(hWnd: HWND, nIndex: c_int) -> ULONG_PTR --- GetClassLongPtrW :: proc(hWnd: HWND, nIndex: c_int) -> ULONG_PTR --- - SetClassLongPtrA :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG_PTR) -> ULONG_PTR --- SetClassLongPtrW :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG_PTR) -> ULONG_PTR --- - GetWindowLongPtrA :: proc(hWnd: HWND, nIndex: c_int) -> LONG_PTR --- GetWindowLongPtrW :: proc(hWnd: HWND, nIndex: c_int) -> LONG_PTR --- - SetWindowLongPtrA :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG_PTR) -> LONG_PTR --- SetWindowLongPtrW :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG_PTR) -> LONG_PTR --- } } else when ODIN_ARCH == .i386 { - GetClassLongPtrA :: GetClassLongA GetClassLongPtrW :: GetClassLongW - SetClassLongPtrA :: SetClassLongA SetClassLongPtrW :: SetClassLongW - GetWindowLongPtrA :: GetWindowLongA GetWindowLongPtrW :: GetWindowLongW - SetWindowLongPtrA :: GetWindowLongA SetWindowLongPtrW :: GetWindowLongW } diff --git a/core/sys/windows/util.odin b/core/sys/windows/util.odin index d464007d3..1c8b9175b 100644 --- a/core/sys/windows/util.odin +++ b/core/sys/windows/util.odin @@ -2,7 +2,7 @@ package sys_windows import "core:strings" -import "core:sys/win32" +import "core:runtime" import "core:intrinsics" L :: intrinsics.constant_utf16_cstring @@ -15,6 +15,14 @@ HIWORD :: #force_inline proc "contextless" (x: DWORD) -> WORD { return WORD(x >> 16) } +GET_X_LPARAM :: #force_inline proc "contextless" (lp: LPARAM) -> c_int { + return cast(c_int)cast(c_short)LOWORD(cast(DWORD)lp) +} + +GET_Y_LPARAM :: #force_inline proc "contextless" (lp: LPARAM) -> c_int { + return cast(c_int)cast(c_short)HIWORD(cast(DWORD)lp) +} + utf8_to_utf16 :: proc(s: string, allocator := context.temp_allocator) -> []u16 { if len(s) < 1 { return nil @@ -48,16 +56,16 @@ utf8_to_wstring :: proc(s: string, allocator := context.temp_allocator) -> wstri return nil } -wstring_to_utf8 :: proc(s: wstring, N: int, allocator := context.temp_allocator) -> (res: string) { +wstring_to_utf8 :: proc(s: wstring, N: int, allocator := context.temp_allocator) -> (res: string, err: runtime.Allocator_Error) { context.allocator = allocator if N <= 0 { - return "" + return } n := WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, s, i32(N), nil, 0, nil, nil) if n == 0 { - return "" + return } // If N == -1 the call to WideCharToMultiByte assume the wide string is null terminated @@ -65,12 +73,12 @@ wstring_to_utf8 :: proc(s: wstring, N: int, allocator := context.temp_allocator) // also null terminated. // If N != -1 it assumes the wide string is not null terminated and the resulting string // will not be null terminated, we therefore have to force it to be null terminated manually. - text := make([]byte, n+1 if N != -1 else n) + text := make([]byte, n+1 if N != -1 else n) or_return n1 := WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, s, i32(N), raw_data(text), n, nil, nil) if n1 == 0 { delete(text, allocator) - return "" + return } for i in 0..<n { @@ -79,12 +87,12 @@ wstring_to_utf8 :: proc(s: wstring, N: int, allocator := context.temp_allocator) break } } - return string(text[:n]) + return string(text[:n]), nil } -utf16_to_utf8 :: proc(s: []u16, allocator := context.temp_allocator) -> string { +utf16_to_utf8 :: proc(s: []u16, allocator := context.temp_allocator) -> (res: string, err: runtime.Allocator_Error) { if len(s) == 0 { - return "" + return "", nil } return wstring_to_utf8(raw_data(s), len(s), allocator) } @@ -208,7 +216,7 @@ get_computer_name_and_account_sid :: proc(username: string) -> (computer_name: s if !res { return "", {}, false } - computer_name = utf16_to_utf8(cname_w, context.temp_allocator) + computer_name = utf16_to_utf8(cname_w, context.temp_allocator) or_else "" ok = true return @@ -298,7 +306,7 @@ add_user_profile :: proc(username: string) -> (ok: bool, profile_path: string) { if res == false { return false, "" } - defer win32.local_free(sb) + defer LocalFree(sb) pszProfilePath := make([]u16, 257, context.temp_allocator) res2 := CreateProfile( @@ -310,7 +318,7 @@ add_user_profile :: proc(username: string) -> (ok: bool, profile_path: string) { if res2 != 0 { return false, "" } - profile_path = wstring_to_utf8(&pszProfilePath[0], 257) + profile_path = wstring_to_utf8(&pszProfilePath[0], 257) or_else "" return true, profile_path } @@ -328,7 +336,7 @@ delete_user_profile :: proc(username: string) -> (ok: bool) { if res == false { return false } - defer win32.local_free(sb) + defer LocalFree(sb) res2 := DeleteProfileW( sb, @@ -443,20 +451,20 @@ run_as_user :: proc(username, password, application, commandline: string, pi: ^P nil, // lpProcessAttributes, nil, // lpThreadAttributes, false, // bInheritHandles, - 0, // creation flags - nil, // environment, - nil, // current directory: inherit from parent if nil - &si, - pi, - )) - if ok { - if wait { - WaitForSingleObject(pi.hProcess, INFINITE) - CloseHandle(pi.hProcess) - CloseHandle(pi.hThread) - } - return true - } else { - return false - } + 0, // creation flags + nil, // environment, + nil, // current directory: inherit from parent if nil + &si, + pi, + )) + if ok { + if wait { + WaitForSingleObject(pi.hProcess, INFINITE) + CloseHandle(pi.hProcess) + CloseHandle(pi.hThread) + } + return true + } else { + return false + } } diff --git a/core/sys/windows/wgl.odin b/core/sys/windows/wgl.odin new file mode 100644 index 000000000..689a41dea --- /dev/null +++ b/core/sys/windows/wgl.odin @@ -0,0 +1,87 @@ +// +build windows +package sys_windows + +import "core:c" + +foreign import "system:Opengl32.lib" + +CONTEXT_MAJOR_VERSION_ARB :: 0x2091 +CONTEXT_MINOR_VERSION_ARB :: 0x2092 +CONTEXT_FLAGS_ARB :: 0x2094 +CONTEXT_PROFILE_MASK_ARB :: 0x9126 +CONTEXT_FORWARD_COMPATIBLE_BIT_ARB :: 0x0002 +CONTEXT_CORE_PROFILE_BIT_ARB :: 0x00000001 +CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB :: 0x00000002 + +HGLRC :: distinct HANDLE + +LPLAYERPLANEDESCRIPTOR :: ^LAYERPLANEDESCRIPTOR +LAYERPLANEDESCRIPTOR :: struct { + nSize: WORD, + nVersion: WORD, + dwFlags: DWORD, + iPixelType: BYTE, + cColorBits: BYTE, + cRedBits: BYTE, + cRedShift: BYTE, + cGreenBits: BYTE, + cGreenShift: BYTE, + cBlueBits: BYTE, + cBlueShift: BYTE, + cAlphaBits: BYTE, + cAlphaShift: BYTE, + cAccumBits: BYTE, + cAccumRedBits: BYTE, + cAccumGreenBits: BYTE, + cAccumBlueBits: BYTE, + cAccumAlphaBits: BYTE, + cDepthBits: BYTE, + cStencilBits: BYTE, + cAuxBuffers: BYTE, + iLayerPlane: BYTE, + bReserved: BYTE, + crTransparent: COLORREF, +} + +POINTFLOAT :: struct {x, y: f32} + +LPGLYPHMETRICSFLOAT :: ^GLYPHMETRICSFLOAT +GLYPHMETRICSFLOAT :: struct { + gmfBlackBoxX: f32, + gmfBlackBoxY: f32, + gmfptGlyphOrigin: POINTFLOAT, + gmfCellIncX: f32, + gmfCellIncY: f32, +} + +CreateContextAttribsARBType :: #type proc "c" (hdc: HDC, hShareContext: rawptr, attribList: [^]c.int) -> HGLRC +ChoosePixelFormatARBType :: #type proc "c" (hdc: HDC, attribIList: [^]c.int, attribFList: [^]f32, maxFormats: DWORD, formats: [^]c.int, numFormats: [^]DWORD) -> BOOL +SwapIntervalEXTType :: #type proc "c" (interval: c.int) -> bool +GetExtensionsStringARBType :: #type proc "c" (HDC) -> cstring + +// Procedures + wglCreateContextAttribsARB: CreateContextAttribsARBType + wglChoosePixelFormatARB: ChoosePixelFormatARBType + wglSwapIntervalExt: SwapIntervalEXTType + wglGetExtensionsStringARB: GetExtensionsStringARBType + + +@(default_calling_convention="stdcall") +foreign Opengl32 { + wglCreateContext :: proc(hdc: HDC) -> HGLRC --- + wglMakeCurrent :: proc(hdc: HDC, HGLRC: HGLRC) -> BOOL --- + wglGetProcAddress :: proc(c_str: cstring) -> rawptr --- + wglDeleteContext :: proc(HGLRC: HGLRC) -> BOOL --- + wglCopyContext :: proc(src, dst: HGLRC, mask: UINT) -> BOOL --- + wglCreateLayerContext :: proc(hdc: HDC, layer_plane: c.int) -> HGLRC --- + wglDescribeLayerPlane :: proc(hdc: HDC, pixel_format, layer_plane: c.int, bytes: UINT, pd: LPLAYERPLANEDESCRIPTOR) -> BOOL --- + wglGetCurrentContext :: proc() -> HGLRC --- + wglGetCurrentDC :: proc() -> HDC --- + wglGetLayerPaletteEntries :: proc(hdc: HDC, layer_plane, start, entries: c.int, cr: ^COLORREF) -> c.int --- + wglRealizeLayerPalette :: proc(hdc: HDC, layer_plane: c.int, realize: BOOL) -> BOOL --- + wglSetLayerPaletteEntries :: proc(hdc: HDC, layer_plane, start, entries: c.int, cr: ^COLORREF) -> c.int --- + wglShareLists :: proc(HGLRC1, HGLRC2: HGLRC) -> BOOL --- + wglSwapLayerBuffers :: proc(hdc: HDC, planes: DWORD) -> BOOL --- + wglUseFontBitmaps :: proc(hdc: HDC, first, count, list_base: DWORD) -> BOOL --- + wglUseFontOutlines :: proc(hdc: HDC, first, count, list_base: DWORD, deviation, extrusion: f32, format: c.int, gmf: LPGLYPHMETRICSFLOAT) -> BOOL --- +} diff --git a/core/sys/windows/winmm.odin b/core/sys/windows/winmm.odin new file mode 100644 index 000000000..17f4d8e86 --- /dev/null +++ b/core/sys/windows/winmm.odin @@ -0,0 +1,10 @@ +// +build windows +package sys_windows + +foreign import winmm "system:Winmm.lib" + +@(default_calling_convention="stdcall") +foreign winmm { + timeBeginPeriod :: proc(uPeriod: UINT) -> MMRESULT --- + timeEndPeriod :: proc(uPeriod: UINT) -> MMRESULT --- +} diff --git a/core/sys/windows/ws2_32.odin b/core/sys/windows/ws2_32.odin index 0cff5c2da..09af86bce 100644 --- a/core/sys/windows/ws2_32.odin +++ b/core/sys/windows/ws2_32.odin @@ -87,6 +87,19 @@ foreign ws2_32 { res: ^^ADDRINFOA, ) -> c_int --- freeaddrinfo :: proc(res: ^ADDRINFOA) --- + FreeAddrInfoExW :: proc(pAddrInfoEx: PADDRINFOEXW) --- + GetAddrInfoExW :: proc( + pName: PCWSTR, + pServiceName: PCWSTR, + dwNameSpace: DWORD, + lpNspId: LPGUID, + hints: ^ADDRINFOEXW, + ppResult: ^PADDRINFOEXW, + timeout: ^timeval, + lpOverlapped: LPOVERLAPPED, + lpCompletionRoutine: LPLOOKUPSERVICE_COMPLETION_ROUTINE, + lpHandle: LPHANDLE) -> INT --- + select :: proc( nfds: c_int, readfds: ^fd_set, diff --git a/core/text/i18n/doc.odin b/core/text/i18n/doc.odin new file mode 100644 index 000000000..cff1ce11f --- /dev/null +++ b/core/text/i18n/doc.odin @@ -0,0 +1,111 @@ +//+ignore +package i18n + +/* + The i18n package is flexible and easy to use. + + It has one call to get a translation: `get`, which the user can alias into something like `T`. + + `get`, referred to as `T` here, has a few different signatures. + All of them will return the key if the entry can't be found in the active translation catalog. + + - `T(key)` returns the translation of `key`. + - `T(key, n)` returns a pluralized translation of `key` according to value `n`. + + - `T(section, key)` returns the translation of `key` in `section`. + - `T(section, key, n)` returns a pluralized translation of `key` in `section` according to value `n`. + + By default lookup take place in the global `i18n.ACTIVE` catalog for ease of use. + If you want to override which translation to use, for example in a language preview dialog, you can use the following: + + - `T(key, n, catalog)` returns the pluralized version of `key` from explictly supplied catalog. + - `T(section, key, n, catalog)` returns the pluralized version of `key` in `section` from explictly supplied catalog. + + If a catalog has translation contexts or sections, then ommitting it in the above calls looks up in section "". + + The default pluralization rule is n != 1, which is to say that passing n == 1 (or not passing n) returns the singular form. + Passing n != 1 returns plural form 1. + + Should a language not conform to this rule, you can pass a pluralizer procedure to the catalog parser. + This is a procedure that maps an integer to an integer, taking a value and returning which plural slot should be used. + + You can also assign it to a loaded catalog after parsing, of course. + + Some code examples follow. +*/ + +/* +```cpp +import "core:fmt" +import "core:text/i18n" + +T :: i18n.get + +mo :: proc() { + using fmt + + err: i18n.Error + + /* + Parse MO file and set it as the active translation so we can omit `get`'s "catalog" parameter. + */ + i18n.ACTIVE, err = i18n.parse_mo(#load("translations/nl_NL.mo")) + defer i18n.destroy() + + if err != .None { return } + + /* + These are in the .MO catalog. + */ + println("-----") + println(T("")) + println("-----") + println(T("There are 69,105 leaves here.")) + println("-----") + println(T("Hellope, World!")) + println("-----") + // We pass 1 into `T` to get the singular format string, then 1 again into printf. + printf(T("There is %d leaf.\n", 1), 1) + // We pass 42 into `T` to get the plural format string, then 42 again into printf. + printf(T("There is %d leaf.\n", 42), 42) + + /* + This isn't in the translation catalog, so the key is passed back untranslated. + */ + println("-----") + println(T("Come visit us on Discord!")) +} + +qt :: proc() { + using fmt + + err: i18n.Error + + /* + Parse QT file and set it as the active translation so we can omit `get`'s "catalog" parameter. + */ + i18n.ACTIVE, err = i18n.parse_qt(#load("translations/nl_NL-qt-ts.ts")) + defer i18n.destroy() + + if err != .None { + return + } + + /* + These are in the .TS catalog. As you can see they have sections. + */ + println("--- Page section ---") + println("Page:Text for translation =", T("Page", "Text for translation")) + println("-----") + println("Page:Also text to translate =", T("Page", "Also text to translate")) + println("-----") + println("--- installscript section ---") + println("installscript:99 bottles of beer on the wall =", T("installscript", "99 bottles of beer on the wall")) + println("-----") + println("--- apple_count section ---") + println("apple_count:%d apple(s) =") + println("\t 1 =", T("apple_count", "%d apple(s)", 1)) + println("\t 42 =", T("apple_count", "%d apple(s)", 42)) +} +``` +*/
\ No newline at end of file diff --git a/core/text/i18n/gettext.odin b/core/text/i18n/gettext.odin new file mode 100644 index 000000000..d99ec1c9b --- /dev/null +++ b/core/text/i18n/gettext.odin @@ -0,0 +1,167 @@ +package i18n +/* + A parser for GNU GetText .MO files. + + Copyright 2021-2022 Jeroen van Rijn <nom@duclavier.com>. + Made available under Odin's BSD-3 license. + + A from-scratch implementation based after the specification found here: + https://www.gnu.org/software/gettext/manual/html_node/MO-Files.html + + Options are ignored as they're not applicable to this format. + They're part of the signature for consistency with other catalog formats. + + List of contributors: + Jeroen van Rijn: Initial implementation. +*/ +import "core:os" +import "core:strings" +import "core:bytes" + +parse_mo_from_bytes :: proc(data: []byte, options := DEFAULT_PARSE_OPTIONS, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) { + context.allocator = allocator + /* + An MO file should have at least a 4-byte magic, 2 x 2 byte version info, + a 4-byte number of strings value, and 2 x 4-byte offsets. + */ + if len(data) < 20 { + return {}, .MO_File_Invalid + } + + /* + Check magic. Should be 0x950412de in native Endianness. + */ + native := true + magic := read_u32(data, native) or_return + + if magic != 0x950412de { + native = false + magic = read_u32(data, native) or_return + + if magic != 0x950412de { return {}, .MO_File_Invalid_Signature } + } + + /* + We can ignore version_minor at offset 6. + */ + version_major := read_u16(data[4:]) or_return + if version_major > 1 { return {}, .MO_File_Unsupported_Version } + + count := read_u32(data[ 8:]) or_return + original_offset := read_u32(data[12:]) or_return + translated_offset := read_u32(data[16:]) or_return + + if count == 0 { return {}, .Empty_Translation_Catalog } + + /* + Initalize Translation, interner and optional pluralizer. + */ + translation = new(Translation) + translation.pluralize = pluralizer + strings.intern_init(&translation.intern, allocator, allocator) + + // Gettext MO files only have one section. + translation.k_v[""] = {} + section := &translation.k_v[""] + + for n := u32(0); n < count; n += 1 { + /* + Grab string's original length and offset. + */ + offset := original_offset + 8 * n + if len(data) < int(offset + 8) { return translation, .MO_File_Invalid } + + o_length := read_u32(data[offset :], native) or_return + o_offset := read_u32(data[offset + 4:], native) or_return + + offset = translated_offset + 8 * n + if len(data) < int(offset + 8) { return translation, .MO_File_Invalid } + + t_length := read_u32(data[offset :], native) or_return + t_offset := read_u32(data[offset + 4:], native) or_return + + max_offset := int(max(o_offset + o_length + 1, t_offset + t_length + 1)) + if len(data) < max_offset { return translation, .Premature_EOF } + + key := data[o_offset:][:o_length] + val := data[t_offset:][:t_length] + + /* + Could be a pluralized string. + */ + zero := []byte{0} + + keys := bytes.split(key, zero) + vals := bytes.split(val, zero) + + if len(keys) != len(vals) || max(len(keys), len(vals)) > MAX_PLURALS { + return translation, .MO_File_Incorrect_Plural_Count + } + + for k in keys { + interned_key := strings.intern_get(&translation.intern, string(k)) + + interned_vals := make([]string, len(keys)) + last_val: string + + i := 0 + for v in vals { + interned_vals[i] = strings.intern_get(&translation.intern, string(v)) + last_val = interned_vals[i] + i += 1 + } + section[interned_key] = interned_vals + } + delete(vals) + delete(keys) + } + return +} + +parse_mo_file :: proc(filename: string, options := DEFAULT_PARSE_OPTIONS, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) { + context.allocator = allocator + + data, data_ok := os.read_entire_file(filename) + defer delete(data) + + if !data_ok { return {}, .File_Error } + + return parse_mo_from_bytes(data, options, pluralizer, allocator) +} + +parse_mo :: proc { parse_mo_file, parse_mo_from_bytes } + +/* + Helpers. +*/ +read_u32 :: proc(data: []u8, native_endian := true) -> (res: u32, err: Error) { + if len(data) < size_of(u32) { return 0, .Premature_EOF } + + val := (^u32)(raw_data(data))^ + + if native_endian { + return val, .None + } else { + when ODIN_ENDIAN == .Little { + return u32(transmute(u32be)val), .None + } else { + return u32(transmute(u32le)val), .None + } + } +} + +read_u16 :: proc(data: []u8, native_endian := true) -> (res: u16, err: Error) { + if len(data) < size_of(u16) { return 0, .Premature_EOF } + + val := (^u16)(raw_data(data))^ + + if native_endian { + return val, .None + } else { + when ODIN_ENDIAN == .Little { + return u16(transmute(u16be)val), .None + } else { + return u16(transmute(u16le)val), .None + } + } +}
\ No newline at end of file diff --git a/core/text/i18n/i18n.odin b/core/text/i18n/i18n.odin new file mode 100644 index 000000000..9d030db16 --- /dev/null +++ b/core/text/i18n/i18n.odin @@ -0,0 +1,182 @@ +package i18n +/* + Internationalization helpers. + + Copyright 2021-2022 Jeroen van Rijn <nom@duclavier.com>. + Made available under Odin's BSD-3 license. + + List of contributors: + Jeroen van Rijn: Initial implementation. +*/ +import "core:strings" + +/* + TODO: + - Support for more translation catalog file formats. +*/ + +/* + Currently active catalog. +*/ +ACTIVE: ^Translation + +// Allow between 1 and 255 plural forms. Default: 10. +MAX_PLURALS :: min(max(#config(ODIN_i18N_MAX_PLURAL_FORMS, 10), 1), 255) + +/* + The main data structure. This can be generated from various different file formats, as long as we have a parser for them. +*/ + +Section :: map[string][]string + +Translation :: struct { + k_v: map[string]Section, // k_v[section][key][plural_form] = ... + intern: strings.Intern, + + pluralize: proc(number: int) -> int, +} + +Error :: enum { + /* + General return values. + */ + None = 0, + Empty_Translation_Catalog, + Duplicate_Key, + + /* + Couldn't find, open or read file. + */ + File_Error, + + /* + File too short. + */ + Premature_EOF, + + /* + GNU Gettext *.MO file errors. + */ + MO_File_Invalid_Signature, + MO_File_Unsupported_Version, + MO_File_Invalid, + MO_File_Incorrect_Plural_Count, + + /* + Qt Linguist *.TS file errors. + */ + TS_File_Parse_Error, + TS_File_Expected_Context, + TS_File_Expected_Context_Name, + TS_File_Expected_Source, + TS_File_Expected_Translation, + TS_File_Expected_NumerusForm, + +} + +Parse_Options :: struct { + merge_sections: bool, +} + +DEFAULT_PARSE_OPTIONS :: Parse_Options{ + merge_sections = false, +} + +/* + Several ways to use: + - get(key), which defaults to the singular form and i18n.ACTIVE catalog, or + - get(key, number), which returns the appropriate plural from the active catalog, or + - get(key, number, catalog) to grab text from a specific one. +*/ +get_single_section :: proc(key: string, number := 0, catalog: ^Translation = ACTIVE) -> (value: string) { + /* + A lot of languages use singular for 1 item and plural for 0 or more than 1 items. This is our default pluralize rule. + */ + plural := 1 if number != 1 else 0 + + if catalog.pluralize != nil { + plural = catalog.pluralize(number) + } + return get_by_slot(key, plural, catalog) +} + +/* + Several ways to use: + - get(section, key), which defaults to the singular form and i18n.ACTIVE catalog, or + - get(section, key, number), which returns the appropriate plural from the active catalog, or + - get(section, key, number, catalog) to grab text from a specific one. +*/ +get_by_section :: proc(section, key: string, number := 0, catalog: ^Translation = ACTIVE) -> (value: string) { + /* + A lot of languages use singular for 1 item and plural for 0 or more than 1 items. This is our default pluralize rule. + */ + plural := 1 if number != 1 else 0 + + if catalog.pluralize != nil { + plural = catalog.pluralize(number) + } + return get_by_slot(section, key, plural, catalog) +} +get :: proc{get_single_section, get_by_section} + +/* + Several ways to use: + - get_by_slot(key), which defaults to the singular form and i18n.ACTIVE catalog, or + - get_by_slot(key, slot), which returns the requested plural from the active catalog, or + - get_by_slot(key, slot, catalog) to grab text from a specific one. + + If a file format parser doesn't (yet) support plural slots, each of the slots will point at the same string. +*/ +get_by_slot_single_section :: proc(key: string, slot := 0, catalog: ^Translation = ACTIVE) -> (value: string) { + return get_by_slot_by_section("", key, slot, catalog) +} + +/* + Several ways to use: + - get_by_slot(key), which defaults to the singular form and i18n.ACTIVE catalog, or + - get_by_slot(key, slot), which returns the requested plural from the active catalog, or + - get_by_slot(key, slot, catalog) to grab text from a specific one. + + If a file format parser doesn't (yet) support plural slots, each of the slots will point at the same string. +*/ +get_by_slot_by_section :: proc(section, key: string, slot := 0, catalog: ^Translation = ACTIVE) -> (value: string) { + if catalog == nil || section not_in catalog.k_v { + /* + Return the key if the catalog catalog hasn't been initialized yet, or the section is not present. + */ + return key + } + + /* + Return the translation from the requested slot if this key is known, else return the key. + */ + if translations, ok := catalog.k_v[section][key]; ok { + plural := min(max(0, slot), len(catalog.k_v[section][key]) - 1) + return translations[plural] + } + return key +} +get_by_slot :: proc{get_by_slot_single_section, get_by_slot_by_section} + +/* + Same for destroy: + - destroy(), to clean up the currently active catalog catalog i18n.ACTIVE + - destroy(catalog), to clean up a specific catalog. +*/ +destroy :: proc(catalog: ^Translation = ACTIVE, allocator := context.allocator) { + context.allocator = allocator + + if catalog == nil { + return + } + + for section in &catalog.k_v { + for key in &catalog.k_v[section] { + delete(catalog.k_v[section][key]) + } + delete(catalog.k_v[section]) + } + delete(catalog.k_v) + strings.intern_destroy(&catalog.intern) + free(catalog) +}
\ No newline at end of file diff --git a/core/text/i18n/qt_linguist.odin b/core/text/i18n/qt_linguist.odin new file mode 100644 index 000000000..036a89eeb --- /dev/null +++ b/core/text/i18n/qt_linguist.odin @@ -0,0 +1,156 @@ +package i18n +/* + A parser for Qt Linguist TS files. + + Copyright 2022 Jeroen van Rijn <nom@duclavier.com>. + Made available under Odin's BSD-3 license. + + A from-scratch implementation based after the specification found here: + https://doc.qt.io/qt-5/linguist-ts-file-format.html + + List of contributors: + Jeroen van Rijn: Initial implementation. +*/ +import "core:os" +import "core:encoding/xml" +import "core:strings" + +TS_XML_Options := xml.Options{ + flags = { + .Input_May_Be_Modified, + .Must_Have_Prolog, + .Must_Have_DocType, + .Ignore_Unsupported, + .Unbox_CDATA, + .Decode_SGML_Entities, + }, + expected_doctype = "TS", +} + +parse_qt_linguist_from_bytes :: proc(data: []byte, options := DEFAULT_PARSE_OPTIONS, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) { + context.allocator = allocator + + ts, xml_err := xml.parse(data, TS_XML_Options) + defer xml.destroy(ts) + + if xml_err != .None || ts.element_count < 1 || ts.elements[0].ident != "TS" || len(ts.elements[0].children) == 0 { + return nil, .TS_File_Parse_Error + } + + /* + Initalize Translation, interner and optional pluralizer. + */ + translation = new(Translation) + translation.pluralize = pluralizer + strings.intern_init(&translation.intern, allocator, allocator) + + section: ^Section + + for child_id in ts.elements[0].children { + // These should be <context>s. + child := ts.elements[child_id] + if child.ident != "context" { + return translation, .TS_File_Expected_Context + } + + // Find section name. + section_name_id, section_name_found := xml.find_child_by_ident(ts, child_id, "name") + if !section_name_found { + return translation, .TS_File_Expected_Context_Name, + } + + section_name := strings.intern_get(&translation.intern, "") + if !options.merge_sections { + section_name = strings.intern_get(&translation.intern, ts.elements[section_name_id].value) + } + + if section_name not_in translation.k_v { + translation.k_v[section_name] = {} + } + section = &translation.k_v[section_name] + + // Find messages in section. + nth: int + for { + message_id, message_found := xml.find_child_by_ident(ts, child_id, "message", nth) + if !message_found { + break + } + + numerus_tag, _ := xml.find_attribute_val_by_key(ts, message_id, "numerus") + has_plurals := numerus_tag == "yes" + + // We must have a <source> = key + source_id, source_found := xml.find_child_by_ident(ts, message_id, "source") + if !source_found { + return translation, .TS_File_Expected_Source + } + + // We must have a <translation> + translation_id, translation_found := xml.find_child_by_ident(ts, message_id, "translation") + if !translation_found { + return translation, .TS_File_Expected_Translation + } + + source := strings.intern_get(&translation.intern, ts.elements[source_id].value) + xlat := strings.intern_get(&translation.intern, ts.elements[translation_id].value) + + if source in section { + return translation, .Duplicate_Key + } + + if has_plurals { + if xlat != "" { + return translation, .TS_File_Expected_NumerusForm + } + + num_plurals: int + for { + numerus_id, numerus_found := xml.find_child_by_ident(ts, translation_id, "numerusform", num_plurals) + if !numerus_found { + break + } + num_plurals += 1 + } + + if num_plurals < 2 { + return translation, .TS_File_Expected_NumerusForm + } + section[source] = make([]string, num_plurals) + + num_plurals = 0 + for { + numerus_id, numerus_found := xml.find_child_by_ident(ts, translation_id, "numerusform", num_plurals) + if !numerus_found { + break + } + numerus := strings.intern_get(&translation.intern, ts.elements[numerus_id].value) + section[source][num_plurals] = numerus + + num_plurals += 1 + } + } else { + // Single translation + section[source] = make([]string, 1) + section[source][0] = xlat + } + + nth += 1 + } + } + + return +} + +parse_qt_linguist_file :: proc(filename: string, options := DEFAULT_PARSE_OPTIONS, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) { + context.allocator = allocator + + data, data_ok := os.read_entire_file(filename) + defer delete(data) + + if !data_ok { return {}, .File_Error } + + return parse_qt_linguist_from_bytes(data, options, pluralizer, allocator) +} + +parse_qt :: proc { parse_qt_linguist_file, parse_qt_linguist_from_bytes }
\ No newline at end of file diff --git a/core/thread/thread.odin b/core/thread/thread.odin index d1b95a2fd..90230ae75 100644 --- a/core/thread/thread.odin +++ b/core/thread/thread.odin @@ -53,7 +53,7 @@ join :: proc(thread: ^Thread) { } -join_mulitple :: proc(threads: ..^Thread) { +join_multiple :: proc(threads: ..^Thread) { _join_multiple(..threads) } diff --git a/core/thread/thread_pool.odin b/core/thread/thread_pool.odin index 3f782cf73..820de8ad4 100644 --- a/core/thread/thread_pool.odin +++ b/core/thread/thread_pool.odin @@ -39,15 +39,16 @@ Pool :: struct { threads: []^Thread, + tasks: [dynamic]Task, tasks_done: [dynamic]Task, } // Once initialized, the pool's memory address is not allowed to change until -// it is destroyed. If thread_count < 1, thread count 1 will be used. +// it is destroyed. // // The thread pool requires an allocator which it either owns, or which is thread safe. -pool_init :: proc(pool: ^Pool, thread_count: int, allocator: mem.Allocator) { +pool_init :: proc(pool: ^Pool, allocator: mem.Allocator, thread_count: int) { context.allocator = allocator pool.allocator = allocator pool.tasks = make([dynamic]Task) @@ -102,8 +103,17 @@ pool_join :: proc(pool: ^Pool) { yield() - for t in pool.threads { - join(t) +started_count: int + for started_count < len(pool.threads) { + started_count = 0 + for t in pool.threads { + if .Started in t.flags { + started_count += 1 + if .Joined not_in t.flags { + join(t) + } + } + } } } @@ -113,9 +123,8 @@ pool_join :: proc(pool: ^Pool) { // the thread pool. You can even add tasks from inside other tasks. // // Each task also needs an allocator which it either owns, or which is thread -// safe. By default, allocations in the task are disabled by use of the -// nil_allocator. -pool_add_task :: proc(pool: ^Pool, procedure: Task_Proc, data: rawptr, user_index: int = 0, allocator := context.allocator) { +// safe. +pool_add_task :: proc(pool: ^Pool, allocator: mem.Allocator, procedure: Task_Proc, data: rawptr, user_index: int = 0) { sync.guard(&pool.mutex) append(&pool.tasks, Task{ diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index 8452df112..8c7058f17 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -7,6 +7,8 @@ import "core:intrinsics" import "core:sync" import "core:sys/unix" +CAS :: intrinsics.atomic_compare_exchange_strong + Thread_State :: enum u8 { Started, Joined, @@ -29,6 +31,11 @@ _create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^ __linux_thread_entry_proc :: proc "c" (t: rawptr) -> rawptr { t := (^Thread)(t) + when ODIN_OS != .Darwin { + // We need to give the thread a moment to start up before we enable cancellation. + can_set_thread_cancel_state := unix.pthread_setcancelstate(unix.PTHREAD_CANCEL_DISABLE, nil) == 0 + } + context = runtime.default_context() sync.lock(&t.mutex) @@ -42,6 +49,14 @@ _create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^ init_context := t.init_context context = init_context.? or_else runtime.default_context() + when ODIN_OS != .Darwin { + // Enable thread's cancelability. + if can_set_thread_cancel_state { + unix.pthread_setcanceltype (unix.PTHREAD_CANCEL_ASYNCHRONOUS, nil) + unix.pthread_setcancelstate(unix.PTHREAD_CANCEL_DISABLE, nil) + } + } + t.procedure(t) intrinsics.atomic_store(&t.flags, t.flags + { .Done }) @@ -98,7 +113,7 @@ _create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^ } _start :: proc(t: ^Thread) { - sync.guard(&t.mutex) + // sync.guard(&t.mutex) t.flags += { .Started } sync.signal(&t.cond) } @@ -108,15 +123,22 @@ _is_done :: proc(t: ^Thread) -> bool { } _join :: proc(t: ^Thread) { - sync.guard(&t.mutex) + // sync.guard(&t.mutex) - if .Joined in t.flags || unix.pthread_equal(unix.pthread_self(), t.unix_thread) { + if unix.pthread_equal(unix.pthread_self(), t.unix_thread) { return } - unix.pthread_join(t.unix_thread, nil) + // Preserve other flags besides `.Joined`, like `.Started`. + unjoined := intrinsics.atomic_load(&t.flags) - {.Joined} + joined := unjoined + {.Joined} - t.flags += { .Joined } + // Try to set `t.flags` from unjoined to joined. If it returns joined, + // it means the previous value had that flag set and we can return. + if res, ok := CAS(&t.flags, unjoined, joined); res == joined && !ok { + return + } + unix.pthread_join(t.unix_thread, nil) } _join_multiple :: proc(threads: ..^Thread) { @@ -132,7 +154,10 @@ _destroy :: proc(t: ^Thread) { } _terminate :: proc(t: ^Thread, exit_code: int) { - // TODO(bill) + // `pthread_cancel` is unreliable on Darwin for unknown reasons. + when ODIN_OS != .Darwin { + unix.pthread_cancel(t.unix_thread) + } } _yield :: proc() { diff --git a/core/time/time.odin b/core/time/time.odin index fddb09d85..6c6e47dc0 100644 --- a/core/time/time.odin +++ b/core/time/time.odin @@ -14,6 +14,8 @@ Hour :: 60 * Minute MIN_DURATION :: Duration(-1 << 63) MAX_DURATION :: Duration(1<<63 - 1) +IS_SUPPORTED :: _IS_SUPPORTED + Time :: struct { _nsec: i64, // zero is 1970-01-01 00:00:00 } @@ -49,6 +51,14 @@ Stopwatch :: struct { _accumulation: Duration, } +now :: proc "contextless" () -> Time { + return _now() +} + +sleep :: proc "contextless" (d: Duration) { + _sleep(d) +} + stopwatch_start :: proc(using stopwatch: ^Stopwatch) { if !running { _start_time = tick_now() @@ -82,36 +92,36 @@ since :: proc(start: Time) -> Duration { return diff(start, now()) } -duration_nanoseconds :: proc(d: Duration) -> i64 { +duration_nanoseconds :: proc "contextless" (d: Duration) -> i64 { return i64(d) } -duration_microseconds :: proc(d: Duration) -> f64 { +duration_microseconds :: proc "contextless" (d: Duration) -> f64 { return duration_seconds(d) * 1e6 } -duration_milliseconds :: proc(d: Duration) -> f64 { +duration_milliseconds :: proc "contextless" (d: Duration) -> f64 { return duration_seconds(d) * 1e3 } -duration_seconds :: proc(d: Duration) -> f64 { +duration_seconds :: proc "contextless" (d: Duration) -> f64 { sec := d / Second nsec := d % Second return f64(sec) + f64(nsec)/1e9 } -duration_minutes :: proc(d: Duration) -> f64 { +duration_minutes :: proc "contextless" (d: Duration) -> f64 { min := d / Minute nsec := d % Minute return f64(min) + f64(nsec)/(60*1e9) } -duration_hours :: proc(d: Duration) -> f64 { +duration_hours :: proc "contextless" (d: Duration) -> f64 { hour := d / Hour nsec := d % Hour return f64(hour) + f64(nsec)/(60*60*1e9) } -_less_than_half :: #force_inline proc(x, y: Duration) -> bool { - return u64(x)+u64(x) < u64(y) -} - duration_round :: proc(d, m: Duration) -> Duration { + _less_than_half :: #force_inline proc(x, y: Duration) -> bool { + return u64(x)+u64(x) < u64(y) + } + if m <= 0 { return d } @@ -201,10 +211,12 @@ unix :: proc(sec: i64, nsec: i64) -> Time { return Time{(sec*1e9 + nsec) + UNIX_TO_INTERNAL} } +to_unix_seconds :: time_to_unix time_to_unix :: proc(t: Time) -> i64 { return t._nsec/1e9 } +to_unix_nanoseconds :: time_to_unix_nano time_to_unix_nano :: proc(t: Time) -> i64 { return t._nsec } @@ -213,6 +225,44 @@ time_add :: proc(t: Time, d: Duration) -> Time { return Time{t._nsec + i64(d)} } +// Accurate sleep borrowed from: https://blat-blatnik.github.io/computerBear/making-accurate-sleep-function/ +// +// Accuracy seems to be pretty good out of the box on Linux, to within around 4µs worst case. +// On Windows it depends but is comparable with regular sleep in the worst case. +// To get the same kind of accuracy as on Linux, have your program call `win32.time_begin_period(1)` to +// tell Windows to use a more accurate timer for your process. +accurate_sleep :: proc(d: Duration) { + to_sleep, estimate, mean, m2, count: Duration + + to_sleep = d + estimate = 5 * Millisecond + mean = 5 * Millisecond + count = 1 + + for to_sleep > estimate { + start := tick_now() + sleep(1 * Millisecond) + + observed := tick_since(start) + to_sleep -= observed + + count += 1 + + delta := observed - mean + mean += delta / count + m2 += delta * (observed - mean) + stddev := intrinsics.sqrt(f64(m2) / f64(count - 1)) + estimate = mean + Duration(stddev) + } + + start := tick_now() + for to_sleep > tick_since(start) { + // prevent the spinlock from taking the thread hostage, still accurate enough + _yield() + // NOTE: it might be possible that it yields for too long, in that case it should spinlock freely for a while + // TODO: needs actual testing done to check if that's the case + } +} ABSOLUTE_ZERO_YEAR :: i64(-292277022399) // Day is chosen so that 2001-01-01 is Monday in the calculations ABSOLUTE_TO_INTERNAL :: i64(-9223371966579724800) // i64((ABSOLUTE_ZERO_YEAR - 1) * 365.2425 * SECONDS_PER_DAY); @@ -227,20 +277,24 @@ INTERNAL_TO_WALL :: -WALL_TO_INTERNAL UNIX_TO_ABSOLUTE :: UNIX_TO_INTERNAL + INTERNAL_TO_ABSOLUTE ABSOLUTE_TO_UNIX :: -UNIX_TO_ABSOLUTE -_is_leap_year :: proc(year: int) -> bool { - return year%4 == 0 && (year%100 != 0 || year%400 == 0) -} +@(private) _date :: proc(t: Time, full: bool) -> (year: int, month: Month, day: int, yday: int) { year, month, day, yday = _abs_date(_time_abs(t), full) return } +@(private) _time_abs :: proc(t: Time) -> u64 { return u64(t._nsec/1e9 + UNIX_TO_ABSOLUTE) } +@(private) _abs_date :: proc(abs: u64, full: bool) -> (year: int, month: Month, day: int, yday: int) { + _is_leap_year :: proc(year: int) -> bool { + return year%4 == 0 && (year%100 != 0 || year%400 == 0) + } + d := abs / SECONDS_PER_DAY // 400 year cycles diff --git a/core/time/time_essence.odin b/core/time/time_essence.odin index 72efe9f8f..b7bc616d8 100644 --- a/core/time/time_essence.odin +++ b/core/time/time_essence.odin @@ -1,18 +1,19 @@ +//+private package time import "core:sys/es" -IS_SUPPORTED :: true; +_IS_SUPPORTED :: true -now :: proc "contextless" () -> Time { +_now :: proc "contextless" () -> Time { // TODO Replace once there's a proper time API. - return Time{_nsec = i64(es.TimeStampMs() * 1e6)}; + return Time{_nsec = i64(es.TimeStampMs() * 1e6)} } -sleep :: proc "contextless" (d: Duration) { - es.Sleep(u64(d/Millisecond)); +_sleep :: proc "contextless" (d: Duration) { + es.Sleep(u64(d/Millisecond)) } _tick_now :: proc "contextless" () -> Tick { - return Tick{_nsec = i64(es.TimeStampMs() * 1e6)}; + return Tick{_nsec = i64(es.TimeStampMs() * 1e6)} } diff --git a/core/time/time_freestanding.odin b/core/time/time_freestanding.odin index 17a21d79c..7c67cc5e8 100644 --- a/core/time/time_freestanding.odin +++ b/core/time/time_freestanding.odin @@ -1,16 +1,19 @@ +//+private //+build freestanding package time -IS_SUPPORTED :: false +_IS_SUPPORTED :: false -now :: proc() -> Time { +_now :: proc "contextless" () -> Time { return {} } -sleep :: proc(d: Duration) { +_sleep :: proc "contextless" (d: Duration) { } _tick_now :: proc "contextless" () -> Tick { return {} } +_yield :: proc "contextless" () { +} diff --git a/core/time/time_js.odin b/core/time/time_js.odin index 1b801cdad..9a7163f38 100644 --- a/core/time/time_js.odin +++ b/core/time/time_js.odin @@ -1,13 +1,14 @@ +//+private //+build js package time -IS_SUPPORTED :: false +_IS_SUPPORTED :: false -now :: proc() -> Time { +_now :: proc "contextless" () -> Time { return {} } -sleep :: proc(d: Duration) { +_sleep :: proc "contextless" (d: Duration) { } _tick_now :: proc "contextless" () -> Tick { @@ -19,3 +20,5 @@ _tick_now :: proc "contextless" () -> Tick { return {} } +_yield :: proc "contextless" () { +} diff --git a/core/time/time_unix.odin b/core/time/time_unix.odin index 37fc1fd3e..ba0d91527 100644 --- a/core/time/time_unix.odin +++ b/core/time/time_unix.odin @@ -1,104 +1,34 @@ +//+private //+build linux, darwin, freebsd, openbsd package time -IS_SUPPORTED :: true // NOTE: Times on Darwin are UTC. +import "core:sys/unix" -when ODIN_OS == .Darwin { - foreign import libc "System.framework" -} else { - foreign import libc "system:c" -} - - -@(default_calling_convention="c") -foreign libc { - @(link_name="clock_gettime") _unix_clock_gettime :: proc(clock_id: u64, timespec: ^TimeSpec) -> i32 --- - @(link_name="sleep") _unix_sleep :: proc(seconds: u32) -> i32 --- - @(link_name="nanosleep") _unix_nanosleep :: proc(requested: ^TimeSpec, remaining: ^TimeSpec) -> i32 --- -} - -TimeSpec :: struct { - tv_sec : i64, /* seconds */ - tv_nsec : i64, /* nanoseconds */ -} - -when ODIN_OS == .OpenBSD { - CLOCK_REALTIME :: 0 - CLOCK_PROCESS_CPUTIME_ID :: 2 - CLOCK_MONOTONIC :: 3 - CLOCK_THREAD_CPUTIME_ID :: 4 - CLOCK_UPTIME :: 5 - CLOCK_BOOTTIME :: 6 - - // CLOCK_MONOTONIC_RAW doesn't exist, use CLOCK_MONOTONIC - CLOCK_MONOTONIC_RAW :: CLOCK_MONOTONIC -} else { - CLOCK_REALTIME :: 0 // NOTE(tetra): May jump in time, when user changes the system time. - CLOCK_MONOTONIC :: 1 // NOTE(tetra): May stand still while system is asleep. - CLOCK_PROCESS_CPUTIME_ID :: 2 - CLOCK_THREAD_CPUTIME_ID :: 3 - CLOCK_MONOTONIC_RAW :: 4 // NOTE(tetra): "RAW" means: Not adjusted by NTP. - CLOCK_REALTIME_COARSE :: 5 // NOTE(tetra): "COARSE" clocks are apparently much faster, but not "fine-grained." - CLOCK_MONOTONIC_COARSE :: 6 - CLOCK_BOOTTIME :: 7 // NOTE(tetra): Same as MONOTONIC, except also including time system was asleep. - CLOCK_REALTIME_ALARM :: 8 - CLOCK_BOOTTIME_ALARM :: 9 -} - -// TODO(tetra, 2019-11-05): The original implementation of this package for Darwin used this constants. -// I do not know if Darwin programmers are used to the existance of these constants or not, so -// I'm leaving aliases to them for now. -CLOCK_SYSTEM :: CLOCK_REALTIME -CLOCK_CALENDAR :: CLOCK_MONOTONIC - - -clock_gettime :: proc "contextless" (clock_id: u64) -> TimeSpec { - ts : TimeSpec // NOTE(tetra): Do we need to initialize this? - _unix_clock_gettime(clock_id, &ts) - return ts -} +_IS_SUPPORTED :: true // NOTE: Times on Darwin are UTC. -now :: proc() -> Time { - time_spec_now := clock_gettime(CLOCK_REALTIME) +_now :: proc "contextless" () -> Time { + time_spec_now: unix.timespec + unix.clock_gettime(unix.CLOCK_REALTIME, &time_spec_now) ns := time_spec_now.tv_sec * 1e9 + time_spec_now.tv_nsec return Time{_nsec=ns} } -boot_time :: proc() -> Time { - ts_now := clock_gettime(CLOCK_REALTIME) - ts_boottime := clock_gettime(CLOCK_BOOTTIME) - - ns := (ts_now.tv_sec - ts_boottime.tv_sec) * 1e9 + ts_now.tv_nsec - ts_boottime.tv_nsec - return Time{_nsec=ns} -} - -seconds_since_boot :: proc() -> f64 { - ts_boottime := clock_gettime(CLOCK_BOOTTIME) - return f64(ts_boottime.tv_sec) + f64(ts_boottime.tv_nsec) / 1e9 -} - - -sleep :: proc(d: Duration) { +_sleep :: proc "contextless" (d: Duration) { ds := duration_seconds(d) seconds := u32(ds) nanoseconds := i64((ds - f64(seconds)) * 1e9) - if seconds > 0 { _unix_sleep(seconds) } - if nanoseconds > 0 { nanosleep(nanoseconds) } + if seconds > 0 { unix.sleep(seconds) } + if nanoseconds > 0 { unix.inline_nanosleep(nanoseconds) } } -nanosleep :: proc(nanoseconds: i64) -> int { - // NOTE(tetra): Should we remove this assert? We are measuring nanoseconds after all... - assert(nanoseconds <= 999999999) - - requested := TimeSpec{tv_nsec = nanoseconds} - remaining: TimeSpec // NOTE(tetra): Do we need to initialize this? - return int(_unix_nanosleep(&requested, &remaining)) +_tick_now :: proc "contextless" () -> Tick { + t: unix.timespec + unix.clock_gettime(unix.CLOCK_MONOTONIC_RAW, &t) + return Tick{_nsec = t.tv_sec*1e9 + t.tv_nsec} } - -_tick_now :: proc "contextless" () -> Tick { - t := clock_gettime(CLOCK_MONOTONIC_RAW) - _nsec := t.tv_sec*1e9 + t.tv_nsec - return Tick{_nsec = _nsec} +_yield :: proc "contextless" () { + unix.sched_yield() } + diff --git a/core/time/time_wasi.odin b/core/time/time_wasi.odin index 4a6c8afc0..9360e3591 100644 --- a/core/time/time_wasi.odin +++ b/core/time/time_wasi.odin @@ -1,15 +1,16 @@ +//+private //+build wasi package time import wasi "core:sys/wasm/wasi" -IS_SUPPORTED :: false +_IS_SUPPORTED :: false -now :: proc() -> Time { +_now :: proc "contextless" () -> Time { return {} } -sleep :: proc(d: Duration) { +_sleep :: proc "contextless" (d: Duration) { } _tick_now :: proc "contextless" () -> Tick { diff --git a/core/time/time_windows.odin b/core/time/time_windows.odin index 0fb9eaa0f..20863c323 100644 --- a/core/time/time_windows.odin +++ b/core/time/time_windows.odin @@ -1,22 +1,21 @@ +//+private package time import win32 "core:sys/windows" -IS_SUPPORTED :: true +_IS_SUPPORTED :: true -now :: proc() -> Time { +_now :: proc "contextless" () -> Time { file_time: win32.FILETIME win32.GetSystemTimeAsFileTime(&file_time) ns := win32.FILETIME_as_unix_nanoseconds(file_time) return Time{_nsec=ns} } -sleep :: proc(d: Duration) { +_sleep :: proc "contextless" (d: Duration) { win32.Sleep(win32.DWORD(d/Millisecond)) } - - _tick_now :: proc "contextless" () -> Tick { mul_div_u64 :: proc "contextless" (val, num, den: i64) -> i64 { q := val / den @@ -35,3 +34,7 @@ _tick_now :: proc "contextless" () -> Tick { _nsec := mul_div_u64(i64(now), 1e9, i64(qpc_frequency)) return Tick{_nsec = _nsec} } + +_yield :: proc "contextless" () { + win32.SwitchToThread() +} diff --git a/core/unicode/tools/generate_entity_table.odin b/core/unicode/tools/generate_entity_table.odin new file mode 100644 index 000000000..075ec1cca --- /dev/null +++ b/core/unicode/tools/generate_entity_table.odin @@ -0,0 +1,287 @@ +package xml_example + +import "core:encoding/xml" +import "core:os" +import "core:path" +import "core:mem" +import "core:strings" +import "core:strconv" +import "core:slice" +import "core:fmt" + +/* + Silent error handler for the parser. +*/ +Error_Handler :: proc(pos: xml.Pos, fmt: string, args: ..any) {} + +OPTIONS :: xml.Options{ flags = { .Ignore_Unsupported, }, expected_doctype = "unicode", } + +Entity :: struct { + name: string, + codepoint: rune, + description: string, +} + +generate_encoding_entity_table :: proc() { + using fmt + + filename := path.join(ODIN_ROOT, "tests", "core", "assets", "XML", "unicode.xml") + defer delete(filename) + + generated_filename := path.join(ODIN_ROOT, "core", "encoding", "entity", "generated.odin") + defer delete(generated_filename) + + doc, err := xml.parse(filename, OPTIONS, Error_Handler) + defer xml.destroy(doc) + + if err != .None { + printf("Load/Parse error: %v\n", err) + if err == .File_Error { + printf("\"%v\" not found. Did you run \"tests\\download_assets.py\"?", filename) + } + os.exit(1) + } + + printf("\"%v\" loaded and parsed.\n", filename) + + generated_buf: strings.Builder + defer strings.destroy_builder(&generated_buf) + w := strings.to_writer(&generated_buf) + + charlist, charlist_ok := xml.find_child_by_ident(doc.root, "charlist") + if !charlist_ok { + eprintln("Could not locate top-level `<charlist>` tag.") + os.exit(1) + } + + printf("Found `<charlist>` with %v children.\n", len(charlist.children)) + + entity_map: map[string]Entity + names: [dynamic]string + + min_name_length := max(int) + max_name_length := min(int) + shortest_name: string + longest_name: string + + count := 0 + for char in charlist.children { + if char.ident != "character" { + eprintf("Expected `<character>`, got `<%v>`\n", char.ident) + os.exit(1) + } + + if codepoint_string, ok := xml.find_attribute_val_by_key(char, "dec"); !ok { + eprintln("`<character id=\"...\">` attribute not found.") + os.exit(1) + } else { + codepoint := strconv.atoi(codepoint_string) + + desc, desc_ok := xml.find_child_by_ident(char, "description") + description := desc.value if desc_ok else "" + + /* + For us to be interested in this codepoint, it has to have at least one entity. + */ + + nth := 0 + for { + character_entity, entity_ok := xml.find_child_by_ident(char, "entity", nth) + if !entity_ok { break } + + nth += 1 + if name, name_ok := xml.find_attribute_val_by_key(character_entity, "id"); name_ok { + + if len(name) == 0 { + /* + Invalid name. Skip. + */ + continue + } + + if name == "\"\"" { + printf("%#v\n", char) + printf("%#v\n", character_entity) + } + + if len(name) > max_name_length { longest_name = name } + if len(name) < min_name_length { shortest_name = name } + + min_name_length = min(min_name_length, len(name)) + max_name_length = max(max_name_length, len(name)) + + e := Entity{ + name = name, + codepoint = rune(codepoint), + description = description, + } + + if _, seen := entity_map[name]; seen { + continue + } + + entity_map[name] = e + append(&names, name) + count += 1 + } + } + } + } + + /* + Sort by name. + */ + slice.sort(names[:]) + + printf("Found %v unique `&name;` -> rune mappings.\n", count) + printf("Shortest name: %v (%v)\n", shortest_name, min_name_length) + printf("Longest name: %v (%v)\n", longest_name, max_name_length) + + // println(rune_to_string(1234)) + + /* + Generate table. + */ + wprintln(w, "package unicode_entity") + wprintln(w, "") + wprintln(w, GENERATED) + wprintln(w, "") + wprintf (w, TABLE_FILE_PROLOG) + wprintln(w, "") + + wprintf (w, "// `&%v;`\n", shortest_name) + wprintf (w, "XML_NAME_TO_RUNE_MIN_LENGTH :: %v\n", min_name_length) + wprintf (w, "// `&%v;`\n", longest_name) + wprintf (w, "XML_NAME_TO_RUNE_MAX_LENGTH :: %v\n", max_name_length) + wprintln(w, "") + + wprintln(w, +` +/* + Input: + entity_name - a string, like "copy" that describes a user-encoded Unicode entity as used in XML. + + Output: + "decoded" - The decoded rune if found by name, or -1 otherwise. + "ok" - true if found, false if not. + + IMPORTANT: XML processors (including browsers) treat these names as case-sensitive. So do we. +*/ +named_xml_entity_to_rune :: proc(name: string) -> (decoded: rune, ok: bool) { + /* + Early out if the name is too short or too long. + min as a precaution in case the generated table has a bogus value. + */ + if len(name) < min(1, XML_NAME_TO_RUNE_MIN_LENGTH) || len(name) > XML_NAME_TO_RUNE_MAX_LENGTH { + return -1, false + } + + switch rune(name[0]) { +`) + + prefix := '?' + should_close := false + + for v in names { + if rune(v[0]) != prefix { + if should_close { + wprintln(w, "\t\t}\n") + } + + prefix = rune(v[0]) + wprintf (w, "\tcase '%v':\n", prefix) + wprintln(w, "\t\tswitch name {") + } + + e := entity_map[v] + + wprintf(w, "\t\t\tcase \"%v\": \n", e.name) + wprintf(w, "\t\t\t\t// %v\n", e.description) + wprintf(w, "\t\t\t\treturn %v, true\n", rune_to_string(e.codepoint)) + + should_close = true + } + wprintln(w, "\t\t}") + wprintln(w, "\t}") + wprintln(w, "\treturn -1, false") + wprintln(w, "}\n") + wprintln(w, GENERATED) + + println() + println(strings.to_string(generated_buf)) + println() + + written := os.write_entire_file(generated_filename, transmute([]byte)strings.to_string(generated_buf)) + + if written { + fmt.printf("Successfully written generated \"%v\".", generated_filename) + } else { + fmt.printf("Failed to write generated \"%v\".", generated_filename) + } + + delete(entity_map) + delete(names) + for name in &names { + free(&name) + } +} + +GENERATED :: `/* + ------ GENERATED ------ DO NOT EDIT ------ GENERATED ------ DO NOT EDIT ------ GENERATED ------ +*/` + +TABLE_FILE_PROLOG :: `/* + This file is generated from "https://www.w3.org/2003/entities/2007xml/unicode.xml". + + UPDATE: + - Ensure the XML file was downloaded using "tests\core\download_assets.py". + - Run "core/unicode/tools/generate_entity_table.odin" + + Odin unicode generated tables: https://github.com/odin-lang/Odin/tree/master/core/encoding/entity + + Copyright © 2021 World Wide Web Consortium, (Massachusetts Institute of Technology, + European Research Consortium for Informatics and Mathematics, Keio University, Beihang). + + All Rights Reserved. + + This work is distributed under the W3C® Software License [1] in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + [1] http://www.w3.org/Consortium/Legal/copyright-software + + See also: LICENSE_table.md +*/ +` + +rune_to_string :: proc(r: rune) -> (res: string) { + res = fmt.tprintf("%08x", int(r)) + for len(res) > 2 && res[:2] == "00" { + res = res[2:] + } + return fmt.tprintf("rune(0x%v)", res) +} + +is_dotted_name :: proc(name: string) -> (dotted: bool) { + for r in name { + if r == '.' { return true} + } + return false +} + +main :: proc() { + using fmt + + track: mem.Tracking_Allocator + mem.tracking_allocator_init(&track, context.allocator) + context.allocator = mem.tracking_allocator(&track) + + generate_encoding_entity_table() + + if len(track.allocation_map) > 0 { + println() + for _, v in track.allocation_map { + printf("%v Leaked %v bytes.\n", v.location, v.size) + } + } + println("Done and cleaned up!") +}
\ No newline at end of file |