merge from upstream and convert to ^File types

This commit is contained in:
jason
2022-05-16 13:49:57 -04:00
276 changed files with 30247 additions and 7931 deletions
+8 -3
View File
@@ -39,7 +39,9 @@ jobs:
make make
timeout-minutes: 10 timeout-minutes: 10
- name: Odin issues tests - name: Odin issues tests
run: tests/issues/run.sh run: |
cd tests/issues
./run.sh
timeout-minutes: 10 timeout-minutes: 10
- name: Odin check examples/all for Linux i386 - name: Odin check examples/all for Linux i386
run: ./odin check examples/all -vet -strict-style -target:linux_i386 run: ./odin check examples/all -vet -strict-style -target:linux_i386
@@ -91,7 +93,9 @@ jobs:
make make
timeout-minutes: 10 timeout-minutes: 10
- name: Odin issues tests - name: Odin issues tests
run: tests/issues/run.sh run: |
cd tests/issues
./run.sh
timeout-minutes: 10 timeout-minutes: 10
- name: Odin check examples/all for Darwin arm64 - name: Odin check examples/all for Darwin arm64
run: ./odin check examples/all -vet -strict-style -target:darwin_arm64 run: ./odin check examples/all -vet -strict-style -target:darwin_arm64
@@ -163,7 +167,8 @@ jobs:
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat
call tests\issues\run.bat cd tests\issues
call run.bat
timeout-minutes: 10 timeout-minutes: 10
- name: Odin check examples/all for Windows 32bits - name: Odin check examples/all for Windows 32bits
shell: cmd shell: cmd
+2
View File
@@ -269,6 +269,8 @@ bin/
# - Linux/MacOS # - Linux/MacOS
odin odin
odin.dSYM odin.dSYM
*.bin
demo.bin
# shared collection # shared collection
shared/ shared/
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2016-2021 Ginger Bill. All rights reserved. Copyright (c) 2016-2022 Ginger Bill. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
+2 -2
View File
@@ -1,7 +1,7 @@
all: debug demo all: debug
demo: demo:
./odin run examples/demo/demo.odin ./odin run examples/demo/demo.odin -file
report: report:
./odin report ./odin report
+66 -66
View File
@@ -1,90 +1,90 @@
// This is purely for documentation // This is purely for documentation
package builtin package builtin
nil :: nil; nil :: nil
false :: 0!=0; false :: 0!=0
true :: 0==0; true :: 0==0
ODIN_OS :: ODIN_OS; ODIN_OS :: ODIN_OS
ODIN_ARCH :: ODIN_ARCH; ODIN_ARCH :: ODIN_ARCH
ODIN_ENDIAN :: ODIN_ENDIAN; ODIN_ENDIAN :: ODIN_ENDIAN
ODIN_VENDOR :: ODIN_VENDOR; ODIN_VENDOR :: ODIN_VENDOR
ODIN_VERSION :: ODIN_VERSION; ODIN_VERSION :: ODIN_VERSION
ODIN_ROOT :: ODIN_ROOT; ODIN_ROOT :: ODIN_ROOT
ODIN_DEBUG :: ODIN_DEBUG; ODIN_DEBUG :: ODIN_DEBUG
byte :: u8; // alias byte :: u8 // alias
bool :: bool; bool :: bool
b8 :: b8; b8 :: b8
b16 :: b16; b16 :: b16
b32 :: b32; b32 :: b32
b64 :: b64; b64 :: b64
i8 :: i8; i8 :: i8
u8 :: u8; u8 :: u8
i16 :: i16; i16 :: i16
u16 :: u16; u16 :: u16
i32 :: i32; i32 :: i32
u32 :: u32; u32 :: u32
i64 :: i64; i64 :: i64
u64 :: u64; u64 :: u64
i128 :: i128; i128 :: i128
u128 :: u128; u128 :: u128
rune :: rune; rune :: rune
f16 :: f16; f16 :: f16
f32 :: f32; f32 :: f32
f64 :: f64; f64 :: f64
complex32 :: complex32; complex32 :: complex32
complex64 :: complex64; complex64 :: complex64
complex128 :: complex128; complex128 :: complex128
quaternion64 :: quaternion64; quaternion64 :: quaternion64
quaternion128 :: quaternion128; quaternion128 :: quaternion128
quaternion256 :: quaternion256; quaternion256 :: quaternion256
int :: int; int :: int
uint :: uint; uint :: uint
uintptr :: uintptr; uintptr :: uintptr
rawptr :: rawptr; rawptr :: rawptr
string :: string; string :: string
cstring :: cstring; cstring :: cstring
any :: any; any :: any
typeid :: typeid; typeid :: typeid
// Endian Specific Types // Endian Specific Types
i16le :: i16le; i16le :: i16le
u16le :: u16le; u16le :: u16le
i32le :: i32le; i32le :: i32le
u32le :: u32le; u32le :: u32le
i64le :: i64le; i64le :: i64le
u64le :: u64le; u64le :: u64le
i128le :: i128le; i128le :: i128le
u128le :: u128le; u128le :: u128le
i16be :: i16be; i16be :: i16be
u16be :: u16be; u16be :: u16be
i32be :: i32be; i32be :: i32be
u32be :: u32be; u32be :: u32be
i64be :: i64be; i64be :: i64be
u64be :: u64be; u64be :: u64be
i128be :: i128be; i128be :: i128be
u128be :: u128be; u128be :: u128be
f16le :: f16le; f16le :: f16le
f32le :: f32le; f32le :: f32le
f64le :: f64le; f64le :: f64le
f16be :: f16be; f16be :: f16be
f32be :: f32be; f32be :: f32be
f64be :: f64be; f64be :: f64be
+45 -1
View File
@@ -10,7 +10,14 @@ clone :: proc(s: []byte, allocator := context.allocator, loc := #caller_location
return c[:len(s)] 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 d := transmute(mem.Raw_String)str
return d.data return d.data
} }
@@ -134,6 +141,25 @@ join :: proc(a: [][]byte, sep: []byte, allocator := context.allocator) -> []byte
return b 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 { concatenate :: proc(a: [][]byte, allocator := context.allocator) -> []byte {
if len(a) == 0 { if len(a) == 0 {
return nil return nil
@@ -151,6 +177,24 @@ concatenate :: proc(a: [][]byte, allocator := context.allocator) -> []byte {
return b 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 @private
_split :: proc(s, sep: []byte, sep_save, n: int, allocator := context.allocator) -> [][]byte { _split :: proc(s, sep: []byte, sep_save, n: int, allocator := context.allocator) -> [][]byte {
s, n := s, n s, n := s, n
-4
View File
@@ -128,7 +128,6 @@ Deflate_Error :: enum {
BType_3, BType_3,
} }
// General I/O context for ZLIB, LZW, etc. // General I/O context for ZLIB, LZW, etc.
Context_Memory_Input :: struct #packed { Context_Memory_Input :: struct #packed {
input_data: []u8, input_data: []u8,
@@ -151,7 +150,6 @@ when size_of(rawptr) == 8 {
#assert(size_of(Context_Memory_Input) == 52) #assert(size_of(Context_Memory_Input) == 52)
} }
Context_Stream_Input :: struct #packed { Context_Stream_Input :: struct #packed {
input_data: []u8, input_data: []u8,
input: io.Stream, 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. 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) { input_size_from_memory :: proc(z: ^Context_Memory_Input) -> (res: i64, err: Error) {
return i64(len(z.input_data)), nil return i64(len(z.input_data)), nil
} }
+1 -1
View File
@@ -45,7 +45,7 @@ main :: proc() {
if len(args) < 2 { if len(args) < 2 {
stderr("No input file specified.\n") 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 { if err == nil {
stdout("Displaying test vector: ") stdout("Displaying test vector: ")
stdout(bytes.buffer_to_string(&buf)) stdout(bytes.buffer_to_string(&buf))
+4 -4
View File
@@ -102,7 +102,7 @@ E_Deflate :: compress.Deflate_Error
GZIP_MAX_PAYLOAD_SIZE :: i64(max(u32le)) 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) { load_from_file :: proc(filename: string, buf: ^bytes.Buffer, expected_output_size := -1, allocator := context.allocator) -> (err: Error) {
context.allocator = allocator 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 err = E_General.File_Not_Found
if ok { 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 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 buf := buf
z := &compress.Context_Memory_Input{ z := &compress.Context_Memory_Input{
input_data = slice, input_data = data,
output = buf, output = buf,
} }
return load_from_context(z, buf, known_gzip_size, expected_output_size, allocator) return load_from_context(z, buf, known_gzip_size, expected_output_size, allocator)
+148
View File
@@ -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 },
},
}
+318
View File
@@ -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}
@@ -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
}
+23 -14
View File
@@ -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 { set :: proc(c: ^$C/Cache($Key, $Value), key: Key, value: Value) -> runtime.Allocator_Error {
if e, ok := c.entries[key]; ok { if e, ok := c.entries[key]; ok {
e.value = value e.value = value
_pop_node(c, e)
_push_front_node(c, e)
return nil return nil
} }
e := new(Node(Key, Value), c.node_allocator) or_return e : ^Node(Key, Value) = nil
e.key = key assert(c.count <= c.capacity)
e.value = value if c.count == c.capacity {
e = c.tail
_push_front_node(c, e) _remove_node(c, e)
if c.count > c.capacity { }
_remove_node(c, c.tail) 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)
c.entries[key] = e c.entries[key] = e
return nil return nil
} }
@@ -122,6 +130,8 @@ remove :: proc(c: ^$C/Cache($Key, $Value), key: Key) -> bool {
return false return false
} }
_remove_node(c, e) _remove_node(c, e)
free(node, c.node_allocator)
c.count -= 1
return true return true
} }
@@ -143,14 +153,9 @@ _remove_node :: proc(c: ^$C/Cache($Key, $Value), node: ^Node(Key, Value)) {
node.prev = nil node.prev = nil
node.next = nil node.next = nil
c.count -= 1
delete_key(&c.entries, node.key) delete_key(&c.entries, node.key)
_call_on_remove(c, node) _call_on_remove(c, node)
free(node, c.node_allocator)
} }
@(private) @(private)
@@ -171,8 +176,6 @@ _push_front_node :: proc(c: ^$C/Cache($Key, $Value), e: ^Node(Key, Value)) {
c.tail = e c.tail = e
} }
e.prev = nil e.prev = nil
c.count += 1
} }
@(private) @(private)
@@ -180,6 +183,12 @@ _pop_node :: proc(c: ^$C/Cache($Key, $Value), e: ^Node(Key, Value)) {
if e == nil { if e == nil {
return return
} }
if c.head == e {
c.head = e.next
}
if c.tail == e {
c.tail = e.prev
}
if e.prev != nil { if e.prev != nil {
e.prev.next = e.next e.prev.next = e.next
} }
+2 -2
View File
@@ -86,7 +86,7 @@ pop_back_safe :: proc(a: ^$A/Small_Array($N, $T)) -> (item: T, ok: bool) {
return 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 { if N > 0 && a.len > 0 {
item = a.data[0] item = a.data[0]
s := slice(a) s := slice(a)
@@ -114,4 +114,4 @@ push_back_elems :: proc(a: ^$A/Small_Array($N, $T), items: ..T) {
append_elem :: push_back append_elem :: push_back
append_elems :: push_back_elems append_elems :: push_back_elems
push :: proc{push_back, push_back_elems} push :: proc{push_back, push_back_elems}
append :: proc{push_back, push_back_elems} append :: proc{push_back, push_back_elems}
+4 -2
View File
@@ -419,8 +419,10 @@ update :: proc(ctx: ^$T, data: []byte) {
sha2_transf(ctx, shifted_message, block_nb) sha2_transf(ctx, shifted_message, block_nb)
rem_len = new_len % CURR_BLOCK_SIZE rem_len = new_len % CURR_BLOCK_SIZE
when T == Sha256_Context {copy(ctx.block[:], shifted_message[block_nb << 6:rem_len])} if rem_len > 0 {
else when T == Sha512_Context {copy(ctx.block[:], shifted_message[block_nb << 7:rem_len])} 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 ctx.length = rem_len
when T == Sha256_Context {ctx.tot_len += (block_nb + 1) << 6} when T == Sha256_Context {ctx.tot_len += (block_nb + 1) << 6}
+65 -21
View File
@@ -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 // 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, 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 // reuse_record controls whether calls to 'read' may return a slice using the backing buffer
// for performance // for performance
// By default, each call to 'read' returns a newly allocated slice // By default, each call to 'read' returns a newly allocated slice
@@ -194,32 +198,72 @@ is_valid_delim :: proc(r: rune) -> bool {
@private @private
_read_record :: proc(r: ^Reader, dst: ^[dynamic]string, allocator := context.allocator) -> ([]string, Error) { _read_record :: proc(r: ^Reader, dst: ^[dynamic]string, allocator := context.allocator) -> ([]string, Error) {
read_line :: proc(r: ^Reader) -> ([]byte, io.Error) { read_line :: proc(r: ^Reader) -> ([]byte, io.Error) {
line, err := bufio.reader_read_slice(&r.r, '\n') if !r.multiline_fields {
if err == .Buffer_Full { line, err := bufio.reader_read_slice(&r.r, '\n')
clear(&r.raw_buffer) if err == .Buffer_Full {
append(&r.raw_buffer, ..line) clear(&r.raw_buffer)
for err == .Buffer_Full {
line, err = bufio.reader_read_slice(&r.r, '\n')
append(&r.raw_buffer, ..line) 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 len(line) > 0 && err == .EOF { if line[len(line)-1] == '\r' {
err = nil line = line[:len(line)-1]
if line[len(line)-1] == '\r' { }
line = line[:len(line)-1]
} }
} r.line_count += 1
r.line_count += 1
// normalize \r\n to \n // normalize \r\n to \n
n := len(line) n := len(line)
for n >= 2 && string(line[n-2:]) == "\r\n" { for n >= 2 && string(line[n-2:]) == "\r\n" {
line[n-2] = '\n' line[n-2] = '\n'
line = line[:n-1] line = line[:n-1]
} }
return line, err
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.
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 { length_newline :: proc(b: []byte) -> int {
+23
View File
@@ -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
+153
View File
@@ -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)
}
+21
View File
@@ -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
+374
View File
@@ -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
}
@@ -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)
}
}
}
+28
View File
@@ -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]!&#32;&copy;&#x20;<!-- <![CDATA[&#32;&reg;&#x20;]]> -->42&;1234&
</div>
<!-- EXPECTED: Foozle]! © 42&;1234& -->
<div id="test_cdata_unwrap_and_passthrough">
Foozle]!&#32;&copy;&#x20;<![CDATA[BOX&#32;&reg;&#x20;/BOX]]>42&;1234&
</div>
<!-- EXPECTED: Foozle]! © BOX ® /BOX42&;1234& -->
<div>
&verbar; &vert; &VerticalLine; &fjlig; &grave; &bsol; &reg; &rhov; &CounterClockwiseContourIntegral; &bsemi;
</div>
</body>
</html>
File diff suppressed because it is too large Load Diff
+6
View File
@@ -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 b := bytes_make(len(s) + 2*utf8.UTF_MAX, 1, allocator) or_return
w := copy(b, s[0:i]) 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) { loop: for i < len(s) {
c := s[i] c := s[i]
switch { switch {
+14 -13
View File
@@ -4,24 +4,25 @@
Author of this Odin package: Jeroen van Rijn Author of this Odin package: Jeroen van Rijn
Example: Example:
```odin ```odin
import "core:encoding/varint" import "core:encoding/varint"
import "core:fmt" import "core:fmt"
main :: proc() { main :: proc() {
buf: [varint.LEB128_MAX_BYTES]u8 buf: [varint.LEB128_MAX_BYTES]u8
value := u128(42) value := u128(42)
encode_size, encode_err := varint.encode_uleb128(buf[:], value) encode_size, encode_err := varint.encode_uleb128(buf[:], value)
assert(encode_size == 1 && encode_err == .None) 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 package varint
+76 -52
View File
@@ -10,12 +10,10 @@
// the LEB128 format as used by DWARF debug info, Android .dex and other file formats. // the LEB128 format as used by DWARF debug info, Android .dex and other file formats.
package varint package varint
import "core:fmt"
// In theory we should use the bigint package. In practice, varints bigger than this indicate a corrupted file. // 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 // 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`. // 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 { Error :: enum {
None = 0, None = 0,
@@ -25,61 +23,90 @@ Error :: enum {
// Decode a slice of bytes encoding an unsigned LEB128 integer into value and number of bytes used. // 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. // 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) { decode_uleb128_buffer :: proc(buf: []u8) -> (val: u128, size: int, err: Error) {
more := true
for v, i in buf {
size = i + 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 && v > 0b0000_0011 {
return 0, 0, .Value_Too_Large
}
val |= u128(v & 0x7f) << uint(i * 7)
if v < 128 {
more = false
break
}
}
// If the buffer runs out before the number ends, return an error.
if more {
return 0, 0, .Buffer_Too_Small
}
return
}
// 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
if len(buf) == 0 { if len(buf) == 0 {
return 0, 0, .Buffer_Too_Small return 0, 0, .Buffer_Too_Small
} }
for v in buf { for v in buf {
size += 1 val, size, err = decode_uleb128_byte(v, size, val)
if err != .Buffer_Too_Small {
// 18 * 7 bits = 126, which including sign means we can have a 19th byte. return
if size > LEB128_MAX_BYTES || size == LEB128_MAX_BYTES && v > 0x7f {
return 0, 0, .Value_Too_Large
} }
val |= i128(v & 0x7f) << shift
shift += 7
if v < 128 { break }
} }
if buf[size - 1] & 0x40 == 0x40 { if err == .Buffer_Too_Small {
val |= max(i128) << shift val, size = 0, 0
} }
return return
} }
// 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
}
val = accumulator | u128(input & 0x7f) << uint(offset * 7)
if input < 128 {
// We're done
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_buffer :: proc(buf: []u8) -> (val: i128, size: int, err: Error) {
if len(buf) == 0 {
return 0, 0, .Buffer_Too_Small
}
for v in buf {
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
}
// 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)
// 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
}
val = accumulator | i128(input & 0x7f) << shift
if input < 128 {
if input & 0x40 == 0x40 {
val |= max(i128) << (shift + 7)
}
return val, size, .None
}
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. // Encode `val` into `buf` as an unsigned LEB128 encoded series of bytes.
// `buf` must be appropriately sized. // `buf` must be appropriately sized.
encode_uleb128 :: proc(buf: []u8, val: u128) -> (size: int, err: Error) { encode_uleb128 :: proc(buf: []u8, val: u128) -> (size: int, err: Error) {
@@ -89,7 +116,6 @@ encode_uleb128 :: proc(buf: []u8, val: u128) -> (size: int, err: Error) {
size += 1 size += 1
if size > len(buf) { if size > len(buf) {
fmt.println(val, buf[:size - 1])
return 0, .Buffer_Too_Small return 0, .Buffer_Too_Small
} }
@@ -106,14 +132,12 @@ encode_uleb128 :: proc(buf: []u8, val: u128) -> (size: int, err: Error) {
return return
} }
@(private)
SIGN_MASK :: (i128(1) << 121) // sign extend mask
// Encode `val` into `buf` as a signed LEB128 encoded series of bytes. // Encode `val` into `buf` as a signed LEB128 encoded series of bytes.
// `buf` must be appropriately sized. // `buf` must be appropriately sized.
encode_ileb128 :: proc(buf: []u8, val: i128) -> (size: int, err: Error) { encode_ileb128 :: proc(buf: []u8, val: i128) -> (size: int, err: Error) {
val := val SIGN_MASK :: i128(1) << 121 // sign extend mask
more := true
val, more := val, true
for more { for more {
size += 1 size += 1
+86
View File
@@ -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
}
+112
View File
@@ -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!")
}
+45
View File
@@ -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
}
+436
View File
@@ -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}
}
+713
View File
@@ -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 `&gt;`, `&#32;` and `&#x20;` 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
}
+3 -3
View File
@@ -119,17 +119,17 @@ tprintf :: proc(fmt: string, args: ..any) -> string {
// bprint procedures return a string using a buffer from an array // bprint procedures return a string using a buffer from an array
bprint :: proc(buf: []byte, args: ..any, sep := " ") -> string { 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) return sbprint(buf=&sb, args=args, sep=sep)
} }
// bprintln procedures return a string using a buffer from an array // bprintln procedures return a string using a buffer from an array
bprintln :: proc(buf: []byte, args: ..any, sep := " ") -> string { 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) return sbprintln(buf=&sb, args=args, sep=sep)
} }
// bprintf procedures return a string using a buffer from an array // bprintf procedures return a string using a buffer from an array
bprintf :: proc(buf: []byte, fmt: string, args: ..any) -> string { 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) return sbprintf(&sb, fmt, ..args)
} }
+7 -5
View File
@@ -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_64_reset_with_seed :: XXH3_128_reset_with_seed
XXH3_128_update :: proc(state: ^XXH3_state, input: []u8) -> (err: Error) { 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) return XXH3_update(state, input, XXH3_accumulate_512, XXH3_scramble_accumulator)
} }
XXH3_64_update :: XXH3_128_update 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 err = nil if mem_error == nil else .Error
XXH3_init_state(state) XXH3_init_state(state)
XXH3_128_reset(state)
return state, nil return state, nil
} }
@@ -213,7 +211,9 @@ XXH3_update :: #force_inline proc(
length := len(input) length := len(input)
secret := state.custom_secret[:] if len(state.external_secret) == 0 else state.external_secret[:] 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) state.total_length += u64(length)
assert(state.buffered_size <= XXH3_INTERNAL_BUFFER_SIZE) assert(state.buffered_size <= XXH3_INTERNAL_BUFFER_SIZE)
@@ -234,7 +234,9 @@ XXH3_update :: #force_inline proc(
*/ */
if state.buffered_size > 0 { if state.buffered_size > 0 {
load_size := int(XXH3_INTERNAL_BUFFER_SIZE - state.buffered_size) 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:] input = input[load_size:]
XXH3_consume_stripes( XXH3_consume_stripes(
+2 -1
View File
@@ -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) { XXH32_create_state :: proc(allocator := context.allocator) -> (res: ^XXH32_state, err: Error) {
state := new(XXH32_state, allocator) state := new(XXH32_state, allocator)
XXH32_reset_state(state)
return state, .None if state != nil else .Error 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 v3 := state.v3
v4 := state.v4 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 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 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:] #no_bounds_check v3 = XXH32_round(v3, XXH32_read32(buf, .Unaligned)); buf = buf[4:]
+1
View File
@@ -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) { XXH64_create_state :: proc(allocator := context.allocator) -> (res: ^XXH64_state, err: Error) {
state := new(XXH64_state, allocator) state := new(XXH64_state, allocator)
XXH64_reset_state(state)
return state, .None if state != nil else .Error return state, .None if state != nil else .Error
} }
+885 -23
View File
File diff suppressed because it is too large Load Diff
+61
View File
@@ -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)
}
}
+33
View File
@@ -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
+27
View File
@@ -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 = ""
}
}
+763
View File
@@ -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)
}
+6 -7
View File
@@ -242,17 +242,16 @@ srgb :: proc(c: image.PNG_Chunk) -> (res: sRGB, ok: bool) {
} }
plte :: proc(c: image.PNG_Chunk) -> (res: PLTE, 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 return {}, false
} }
i := 0; j := 0; ok = true plte := mem.slice_data_cast([]image.RGB_Pixel, c.data[:])
for j < int(c.header.length) { for color, i in plte {
res.entries[i] = {c.data[j], c.data[j+1], c.data[j+2]} res.entries[i] = color
i += 1; j += 3
} }
res.used = u16(i) res.used = u16(len(plte))
return return res, true
} }
splt :: proc(c: image.PNG_Chunk) -> (res: sPLT, ok: bool) { splt :: proc(c: image.PNG_Chunk) -> (res: sPLT, ok: bool) {
+59 -46
View File
@@ -18,23 +18,16 @@ import "core:compress/zlib"
import "core:image" import "core:image"
import "core:os" import "core:os"
import "core:strings"
import "core:hash" import "core:hash"
import "core:bytes" import "core:bytes"
import "core:io" import "core:io"
import "core:mem" import "core:mem"
import "core:intrinsics" import "core:intrinsics"
/* // Limit chunk sizes.
67_108_864 pixels max by default. // By default: IDAT = 8k x 8k x 16-bits + 8k filter bytes.
Maximum allowed dimensions are capped at 65535 * 65535. // The total number of pixels defaults to 64 Megapixel and can be tuned in image/common.odin.
*/
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.
*/
_MAX_IDAT_DEFAULT :: ( 8192 /* Width */ * 8192 /* Height */ * 2 /* 16-bit */) + 8192 /* 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 */ _MAX_IDAT :: (65535 /* Width */ * 65535 /* Height */ * 2 /* 16-bit */) + 65535 /* Filter bytes */
@@ -64,7 +57,7 @@ Row_Filter :: enum u8 {
Paeth = 4, Paeth = 4,
} }
PLTE_Entry :: [3]u8 PLTE_Entry :: image.RGB_Pixel
PLTE :: struct #packed { PLTE :: struct #packed {
entries: [256]PLTE_Entry, entries: [256]PLTE_Entry,
@@ -244,7 +237,7 @@ append_chunk :: proc(list: ^[dynamic]image.PNG_Chunk, src: image.PNG_Chunk, allo
append(list, c) append(list, c)
if len(list) != length + 1 { if len(list) != length + 1 {
// Resize during append failed. // Resize during append failed.
return mem.Allocator_Error.Out_Of_Memory return .Unable_To_Allocate_Or_Resize
} }
return return
@@ -259,7 +252,7 @@ read_header :: proc(ctx: ^$C) -> (image.PNG_IHDR, Error) {
header := (^image.PNG_IHDR)(raw_data(c.data))^ header := (^image.PNG_IHDR)(raw_data(c.data))^
// Validate IHDR // Validate IHDR
using header 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 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 { chunk_type_to_name :: proc(type: ^image.PNG_Chunk_Type) -> string {
t := transmute(^u8)type return string(([^]u8)(type)[:4])
return strings.string_from_ptr(t, 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{ 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) defer delete(data)
if ok { if ok {
return load_from_slice(data, options) return load_from_bytes(data, options)
} else { } else {
img = new(Image) return nil, .Unable_To_Read_File
return img, compress.General_Error.File_Not_Found
} }
} }
@@ -366,6 +357,10 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
options -= {.info} 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 { if .alpha_drop_if_present in options && .alpha_add_if_missing in options {
return {}, compress.General_Error.Incompatible_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 { if img == nil {
img = new(Image) img = new(Image)
} }
img.which = .PNG
info := new(image.PNG_Info) info := new(image.PNG_Info)
img.metadata = info img.metadata = info
signature, io_error := compress.read_data(ctx, Signature) signature, io_error := compress.read_data(ctx, Signature)
if io_error != .None || signature != .PNG { if io_error != .None || signature != .PNG {
return img, .Invalid_PNG_Signature return img, .Invalid_Signature
} }
idat: []u8 idat: []u8
@@ -392,7 +388,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
idat_length := u64(0) idat_length := u64(0)
c: image.PNG_Chunk c: image.PNG_Chunk
ch: image.PNG_Chunk_Header ch: image.PNG_Chunk_Header
e: io.Error e: io.Error
@@ -473,6 +469,10 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
} }
info.header = h 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: case .PLTE:
seen_plte = true seen_plte = true
// PLTE must appear before IDAT and can't appear for color types 0, 4. // 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 seen_iend = true
case .bKGD: case .bKGD:
// TODO: Make sure that 16-bit bKGD + tRNS chunks return u16 instead of u16be
c = read_chunk(ctx) or_return c = read_chunk(ctx) or_return
seen_bkgd = true seen_bkgd = true
if .return_metadata in options { if .return_metadata in options {
@@ -594,23 +591,36 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
*/ */
final_image_channels += 1 final_image_channels += 1
seen_trns = true 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 { if info.header.bit_depth < 8 && .Paletted not_in info.header.color_type {
// Rescale tRNS data so key matches intensity // Rescale tRNS data so key matches intensity
dsc := depth_scale_table dsc := depth_scale_table
scale := dsc[info.header.bit_depth] scale := dsc[info.header.bit_depth]
if scale != 1 { if scale != 1 {
key := mem.slice_data_cast([]u16be, c.data)[0] * u16be(scale) key := mem.slice_data_cast([]u16be, c.data)[0] * u16be(scale)
c.data = []u8{0, u8(key & 255)} c.data = []u8{0, u8(key & 255)}
} }
} }
trns = c trns = c
case .iDOT, .CbGI: case .iDOT, .CgBI:
/* /*
iPhone PNG bastardization that doesn't adhere to spec with broken IDAT chunk. 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. across one of these files, use a utility to defry it.
*/ */
return img, .Image_Does_Not_Adhere_to_Spec 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 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. 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. 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 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 { if .Paletted in header.color_type && .do_not_expand_indexed in options {
return img, nil return img, nil
} }
@@ -699,7 +704,10 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
return img, nil 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 raw_image_channels := img.channels
out_image_channels := 3 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) dest_raw_size := compute_buffer_size(int(header.width), int(header.height), out_image_channels, 8)
t := bytes.Buffer{} t := bytes.Buffer{}
if !resize(&t.buf, dest_raw_size) { if !resize(&t.buf, dest_raw_size) {
return {}, mem.Allocator_Error.Out_Of_Memory return {}, .Unable_To_Allocate_Or_Resize
} }
i := 0; j := 0 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) dest_raw_size := compute_buffer_size(int(header.width), int(header.height), out_image_channels, 16)
t := bytes.Buffer{} t := bytes.Buffer{}
if !resize(&t.buf, dest_raw_size) { 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[:]) 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) dest_raw_size := compute_buffer_size(int(header.width), int(header.height), out_image_channels, 8)
t := bytes.Buffer{} t := bytes.Buffer{}
if !resize(&t.buf, dest_raw_size) { 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[:]) 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 return img, nil
} }
filter_paeth :: #force_inline proc(left, up, up_left: u8) -> u8 { filter_paeth :: #force_inline proc(left, up, up_left: u8) -> u8 {
aa, bb, cc := i16(left), i16(up), i16(up_left) aa, bb, cc := i16(left), i16(up), i16(up_left)
p := aa + bb - cc 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) num_bytes := compute_buffer_size(width, height, channels, depth == 16 ? 16 : 8)
if !resize(&img.pixels.buf, num_bytes) { if !resize(&img.pixels.buf, num_bytes) {
return mem.Allocator_Error.Out_Of_Memory return .Unable_To_Allocate_Or_Resize
} }
filter_ok: bool filter_ok: bool
@@ -1568,7 +1575,7 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IH
temp: bytes.Buffer temp: bytes.Buffer
temp_len := compute_buffer_size(x, y, channels, depth == 16 ? 16 : 8) temp_len := compute_buffer_size(x, y, channels, depth == 16 ? 16 : 8)
if !resize(&temp.buf, temp_len) { if !resize(&temp.buf, temp_len) {
return mem.Allocator_Error.Out_Of_Memory return .Unable_To_Allocate_Or_Resize
} }
params := Filter_Params{ params := Filter_Params{
@@ -1630,4 +1637,10 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IH
return nil 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)
}
+411
View File
@@ -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)
}
+101
View File
@@ -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}
+179
View File
@@ -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
}
+57 -27
View File
@@ -41,6 +41,10 @@ mem_copy_non_overlapping :: proc(dst, src: rawptr, len: int) ---
mem_zero :: proc(ptr: rawptr, len: int) --- mem_zero :: proc(ptr: rawptr, len: int) ---
mem_zero_volatile :: 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_load :: proc(src: ^$T) -> T ---
unaligned_store :: proc(dst: ^$T, val: 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 :: proc(dst: ^$T) -> T ---
atomic_load_explicit :: proc(dst: ^$T, order: Atomic_Memory_Order) -> T --- atomic_load_explicit :: proc(dst: ^$T, order: Atomic_Memory_Order) -> T ---
atomic_add :: proc(dst; ^$T, val: T) -> T --- // fetch then operator
atomic_add_explicit :: proc(dst; ^$T, val: T, order: Atomic_Memory_Order) -> T --- atomic_add :: proc(dst: ^$T, val: T) -> T ---
atomic_sub :: proc(dst; ^$T, val: T) -> T --- atomic_add_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) -> T ---
atomic_sub_explicit :: proc(dst; ^$T, val: T, order: Atomic_Memory_Order) -> T --- atomic_sub :: proc(dst: ^$T, val: T) -> T ---
atomic_and :: proc(dst; ^$T, val: T) -> T --- atomic_sub_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) -> T ---
atomic_and_explicit :: proc(dst; ^$T, val: T, order: Atomic_Memory_Order) -> T --- atomic_and :: proc(dst: ^$T, val: T) -> T ---
atomic_nand :: proc(dst; ^$T, val: T) -> T --- atomic_and_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) -> T ---
atomic_nand_explicit :: proc(dst; ^$T, val: T, order: Atomic_Memory_Order) -> T --- atomic_nand :: proc(dst: ^$T, val: T) -> T ---
atomic_or :: proc(dst; ^$T, val: T) -> T --- atomic_nand_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) -> T ---
atomic_or_explicit :: proc(dst; ^$T, val: T, order: Atomic_Memory_Order) -> T --- atomic_or :: proc(dst: ^$T, val: T) -> T ---
atomic_xor :: proc(dst; ^$T, val: T) -> T --- atomic_or_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) -> T ---
atomic_xor_explicit :: proc(dst; ^$T, val: T, order: Atomic_Memory_Order) -> T --- atomic_xor :: proc(dst: ^$T, val: T) -> T ---
atomic_exchange :: proc(dst; ^$T, val: T) -> T --- atomic_xor_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) -> T ---
atomic_exchange_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 :: 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 --- 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_typeid :: proc($T: typeid) -> bool ---
type_is_any :: proc($T: typeid) -> bool --- type_is_any :: proc($T: typeid) -> bool ---
type_is_endian_platform :: proc($T: typeid) -> bool --- type_is_endian_platform :: proc($T: typeid) -> bool ---
type_is_endian_little :: proc($T: typeid) -> bool --- type_is_endian_little :: proc($T: typeid) -> bool ---
type_is_endian_big :: proc($T: typeid) -> bool --- type_is_endian_big :: proc($T: typeid) -> bool ---
type_is_unsigned :: proc($T: typeid) -> bool --- type_is_unsigned :: proc($T: typeid) -> bool ---
type_is_numeric :: proc($T: typeid) -> bool --- type_is_numeric :: proc($T: typeid) -> bool ---
type_is_ordered :: proc($T: typeid) -> bool --- type_is_ordered :: proc($T: typeid) -> bool ---
type_is_ordered_numeric :: proc($T: typeid) -> bool --- type_is_ordered_numeric :: proc($T: typeid) -> bool ---
type_is_indexable :: proc($T: typeid) -> bool --- type_is_indexable :: proc($T: typeid) -> bool ---
type_is_sliceable :: proc($T: typeid) -> bool --- type_is_sliceable :: proc($T: typeid) -> bool ---
type_is_comparable :: 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_simple_compare :: proc($T: typeid) -> bool --- // easily compared using memcmp (== and !=)
type_is_dereferenceable :: proc($T: typeid) -> bool --- type_is_dereferenceable :: proc($T: typeid) -> bool ---
type_is_valid_map_key :: 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_named :: proc($T: typeid) -> bool ---
type_is_pointer :: 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_array :: proc($T: typeid) -> bool ---
type_is_enumerated_array :: proc($T: typeid) -> bool --- type_is_enumerated_array :: proc($T: typeid) -> bool ---
type_is_slice :: 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_proc :: proc($T: typeid) -> bool ---
type_is_bit_set :: proc($T: typeid) -> bool --- type_is_bit_set :: proc($T: typeid) -> bool ---
type_is_simd_vector :: 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 --- 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_is_variant_of :: proc($U, $V: typeid) -> bool where type_is_union(U) ---
type_has_field :: proc($T: typeid, $name: string) -> bool --- 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_parameter_count :: proc($T: typeid) -> int where type_is_proc(T) ---
type_proc_return_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_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_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_count :: proc($T: typeid) -> typeid ---
type_polymorphic_record_parameter_value :: proc($T: typeid, index: int) -> $V --- 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_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_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) --- 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 targets only
wasm_memory_grow :: proc(index, delta: uintptr) -> int --- wasm_memory_grow :: proc(index, delta: uintptr) -> int ---
wasm_memory_size :: proc(index: 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 // Internal compiler use only
__entry_point :: proc() --- __entry_point :: proc() ---
+2 -7
View File
@@ -4,7 +4,6 @@
package io package io
import "core:intrinsics" import "core:intrinsics"
import "core:runtime"
import "core:unicode/utf8" import "core:unicode/utf8"
// Seek whence values // 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 return 0, .Empty
} }
curr_offset: i64 curr_offset := r->impl_seek(offset, .Current) or_return
curr_offset, err = r->impl_seek(offset, .Current)
if err != nil {
return 0, err
}
n, err = r->impl_read(p) n, err = r->impl_read(p)
_, err1 := r->impl_seek(curr_offset, .Start) _, 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 // 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 { for {
nr, er := read(src, buf) nr, er := read(src, buf)
+1 -1
View File
@@ -67,7 +67,7 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string
h = data.file_handle h = data.file_handle
} }
backing: [1024]byte //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths. 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) do_level_header(options, level, &buf)
+3 -3
View File
@@ -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.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 { angle_from_quaternion_f32 :: proc(q: Quaternionf32) -> f32 {
if abs(q.w) > math.SQRT_THREE*0.5 { 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.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 { angle_from_quaternion_f64 :: proc(q: Quaternionf64) -> f64 {
if abs(q.w) > math.SQRT_THREE*0.5 { 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.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 :: proc{
angle_from_quaternion_f16, angle_from_quaternion_f16,
+312
View File
@@ -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))
}
+19 -1
View File
@@ -5,6 +5,7 @@ import "core:intrinsics"
Rand :: struct { Rand :: struct {
state: u64, state: u64,
inc: u64, inc: u64,
is_system: bool,
} }
@@ -29,6 +30,16 @@ init :: proc(r: ^Rand, seed: u64) {
_random(r) _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 { _random :: proc(r: ^Rand) -> u32 {
r := r r := r
if r == nil { if r == nil {
@@ -36,6 +47,12 @@ _random :: proc(r: ^Rand) -> u32 {
// enforce the global random state if necessary with `nil` // enforce the global random state if necessary with `nil`
r = &global_rand r = &global_rand
} }
when #defined(_system_random) {
if r.is_system {
return _system_random()
}
}
old_state := r.state old_state := r.state
r.state = old_state * 6364136223846793005 + (r.inc|1) r.state = old_state * 6364136223846793005 + (r.inc|1)
xor_shifted := u32(((old_state>>18) ~ old_state) >> 27) 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) } 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)) } 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 } 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 } 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) { read :: proc(p: []byte, r: ^Rand = nil) -> (n: int) {
pos := i8(0) pos := i8(0)
val := i64(0) val := i64(0)
+21
View File
@@ -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
}
}
+27
View File
@@ -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
}
}
+12
View File
@@ -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
}
+5 -19
View File
@@ -6,24 +6,7 @@ import "core:runtime"
nil_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, nil_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
size, alignment: int, size, alignment: int,
old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) {
switch mode { return nil, nil
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
} }
nil_allocator :: proc() -> Allocator { nil_allocator :: proc() -> Allocator {
@@ -679,6 +662,7 @@ dynamic_pool_destroy :: proc(using pool: ^Dynamic_Pool) {
dynamic_pool_free_all(pool) dynamic_pool_free_all(pool)
delete(unused_blocks) delete(unused_blocks)
delete(used_blocks) delete(used_blocks)
delete(out_band_allocations)
zero(pool, size_of(pool^)) zero(pool, size_of(pool^))
} }
@@ -763,6 +747,8 @@ dynamic_pool_reset :: proc(using pool: ^Dynamic_Pool) {
free(a, block_allocator) free(a, block_allocator)
} }
clear(&out_band_allocations) clear(&out_band_allocations)
bytes_left = 0 // Make new allocations call `cycle_new_block` again.
} }
dynamic_pool_free_all :: proc(using pool: ^Dynamic_Pool) { 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 result: []byte
err: Allocator_Error 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{ append(&data.bad_free_array, Tracking_Allocator_Bad_Free_Entry{
memory = old_memory, memory = old_memory,
location = loc, location = loc,
+7 -6
View File
@@ -3,6 +3,12 @@ package mem
import "core:runtime" import "core:runtime"
import "core:intrinsics" 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 { set :: proc "contextless" (data: rawptr, value: byte, len: int) -> rawptr {
return runtime.memset(data, i32(value), len) 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) { slice_to_components :: proc "contextless" (slice: $E/[]$T) -> (data: ^T, len: int) {
s := transmute(Raw_Slice)slice 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 { 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 { is_power_of_two :: proc "contextless" (x: uintptr) -> bool {
if x <= 0 { if x <= 0 {
return false return false
+1 -1
View File
@@ -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) { do_commit_if_necessary :: proc(block: ^Memory_Block, size: uint) -> (err: Allocator_Error) {
if block.committed - block.used < size { if block.committed - block.used < size {
pmblock := (^Platform_Memory_Block)(block) 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 platform_total_commit := base_offset + block.used + size
assert(pmblock.committed <= pmblock.reserved) assert(pmblock.committed <= pmblock.reserved)
+1 -1
View File
@@ -151,7 +151,7 @@ print :: proc(p: ^Printer, file: ^ast.File) -> string {
fix_lines(p) 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 last_line := 0
+1 -1
View File
@@ -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 { if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 {
return 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.fullpath = path
fi.name = basename(path) fi.name = basename(path)
fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
+2 -2
View File
@@ -22,7 +22,7 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
} }
if n <= u32(len(b)) { if n <= u32(len(b)) {
value = win32.utf16_to_utf8(b[:n], allocator) value, _ = win32.utf16_to_utf8(b[:n], allocator)
found = true found = true
return return
} }
@@ -76,7 +76,7 @@ environ :: proc(allocator := context.allocator) -> []string {
if i <= from { if i <= from {
break 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 from = i + 1
} }
} }
+1 -1
View File
@@ -365,7 +365,7 @@ get_current_directory :: proc(allocator := context.allocator) -> string {
win32.ReleaseSRWLockExclusive(&cwd_lock) 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) { set_current_directory :: proc(path: string) -> (err: Errno) {
+4
View File
@@ -9,6 +9,10 @@ OS :: ODIN_OS
ARCH :: ODIN_ARCH ARCH :: ODIN_ARCH
ENDIAN :: ODIN_ENDIAN ENDIAN :: ODIN_ENDIAN
SEEK_SET :: 0
SEEK_CUR :: 1
SEEK_END :: 2
write_string :: proc(fd: Handle, str: string) -> (int, Errno) { write_string :: proc(fd: Handle, str: string) -> (int, Errno) {
return write(fd, transmute([]byte)str) return write(fd, transmute([]byte)str)
} }
+30 -42
View File
@@ -1,33 +1,35 @@
//+private //+private
package os2 package os2
//import "core:runtime"
//import "core:mem"
import win32 "core:sys/windows" 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 == "" { if key == "" {
return return
} }
wkey := win32.utf8_to_wstring(key) wkey := win32.utf8_to_wstring(key)
// https://docs.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-getenvironmentvariablew n := win32.GetEnvironmentVariableW(wkey, nil, 0)
buf_len := win32.GetEnvironmentVariableW(wkey, nil, 0)
if buf_len == 0 {
return
}
buf := make([dynamic]u16, buf_len, context.temp_allocator)
n := win32.GetEnvironmentVariableW(wkey, raw_data(buf), buf_len)
if n == 0 { if n == 0 {
if win32.GetLastError() == win32.ERROR_ENVVAR_NOT_FOUND { err := win32.GetLastError()
if err == win32.ERROR_ENVVAR_NOT_FOUND {
return "", false return "", false
} }
value = "" return "", true
found = true }
return b := make([]u16, n+1, _temp_allocator())
n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b)))
if n == 0 {
err := win32.GetLastError()
if err == win32.ERROR_ENVVAR_NOT_FOUND {
return "", false
}
return "", false
} }
value = win32.utf16_to_utf8(buf[:n], allocator) value = win32.utf16_to_utf8(b[:n], allocator) or_else ""
found = true found = true
return return
} }
@@ -36,21 +38,18 @@ _set_env :: proc(key, value: string) -> bool {
k := win32.utf8_to_wstring(key) k := win32.utf8_to_wstring(key)
v := win32.utf8_to_wstring(value) 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)) return bool(win32.SetEnvironmentVariableW(k, v))
} }
_unset_env :: proc(key: string) -> bool { _unset_env :: proc(key: string) -> bool {
k := win32.utf8_to_wstring(key) 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)) return bool(win32.SetEnvironmentVariableW(k, nil))
} }
_clear_env :: proc() { _clear_env :: proc() {
envs := environ(context.temp_allocator) envs := environ(_temp_allocator())
for env in envs { for env in envs {
#no_bounds_check for j in 1..<len(env) { for j in 1..<len(env) {
if env[j] == '=' { if env[j] == '=' {
unset_env(env[0:j]) unset_env(env[0:j])
break break
@@ -59,34 +58,23 @@ _clear_env :: proc() {
} }
} }
_environ :: proc(allocator := context.allocator) -> []string { _environ :: proc(allocator: runtime.Allocator) -> []string {
envs := ([^]u16)(win32.GetEnvironmentStringsW()) envs := win32.GetEnvironmentStringsW()
if envs == nil { if envs == nil {
return nil return nil
} }
defer win32.FreeEnvironmentStringsW(envs) defer win32.FreeEnvironmentStringsW(envs)
length := 0 r := make([dynamic]string, 0, 50, allocator)
n := 0 for from, i, p := 0, 0, envs; true; i += 1 {
count_loop: for { c := ([^]u16)(p)[i]
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]
if c == 0 { if c == 0 {
wstr := envs[offset:i] if i <= from {
append(&r, win32.utf16_to_utf8(wstr, allocator)) break
i += 1 }
offset = i w := ([^]u16)(p)[from:i]
append(&r, win32.utf16_to_utf8(w, allocator) or_else "")
from = i + 1
} }
} }
+54 -54
View File
@@ -1,9 +1,10 @@
package os2 package os2
import "core:io" import "core:io"
import "core:runtime"
General_Error :: enum u32 { General_Error :: enum u32 {
Invalid_Argument, None,
Permission_Denied, Permission_Denied,
Exist, Exist,
@@ -11,79 +12,78 @@ General_Error :: enum u32 {
Closed, Closed,
Timeout, Timeout,
Invalid_File,
Invalid_Dir,
Invalid_Path,
Unsupported,
} }
Platform_Error :: struct { Platform_Error :: enum i32 {None=0}
err: i32,
}
Error :: union { Error :: union #shared_nil {
General_Error, General_Error,
io.Error, io.Error,
runtime.Allocator_Error,
Platform_Error, Platform_Error,
} }
#assert(size_of(Error) == size_of(u64)) #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) { is_platform_error :: proc(ferr: Error) -> (err: i32, ok: bool) {
v := ferr.(Platform_Error) or_else {} v := ferr.(Platform_Error) or_else {}
return v.err, v.err != 0 return i32(v), i32(v) != 0
} }
error_string :: proc(ferr: Error) -> string { 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 { if ferr == nil {
return "" return ""
} }
switch e in ferr {
switch err in ferr { case General_Error:
case General_Error: return general_error_strings[err] switch e {
case io.Error: return io_error_strings[err] case .None: return ""
case Platform_Error: return _error_string(err.err) 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" return "unknown error"
+1 -1
View File
@@ -130,7 +130,7 @@ EHWPOISON :: 133 /* Memory page has hardware error */
_get_platform_error :: proc(res: int) -> Error { _get_platform_error :: proc(res: int) -> Error {
errno := unix.get_errno(res) errno := unix.get_errno(res)
return Platform_Error{i32(errno)} return Platform_Error(i32(errno))
} }
_ok_or_error :: proc(res: int) -> Error { _ok_or_error :: proc(res: int) -> Error {
+46
View File
@@ -12,3 +12,49 @@ _error_string :: proc(errno: i32) -> string {
// FormatMessageW // FormatMessageW
return "" 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)
}
+133 -101
View File
@@ -2,10 +2,11 @@ package os2
import "core:io" import "core:io"
import "core:time" import "core:time"
import "core:runtime"
Handle :: distinct uintptr File :: struct {
impl: _File,
INVALID_HANDLE :: ~Handle(0) }
Seek_From :: enum { Seek_From :: enum {
Start = 0, // seek relative to the origin of the file 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_Char_Device :: File_Mode(1<<19)
File_Mode_Sym_Link :: File_Mode(1<<20) File_Mode_Sym_Link :: File_Mode(1<<20)
File_Mode_Perm :: File_Mode(0o777) // Unix permision bits
File_Flag :: enum u32 { File_Flags :: distinct bit_set[File_Flag; uint]
Read = 0, File_Flag :: enum {
Write = 1, Read,
Append = 2, Write,
Create = 3, Append,
Excl = 4, Create,
Sync = 5, Excl,
Trunc = 6, Sync,
Close_On_Exec = 7, Trunc,
Sparse,
Close_On_Exec,
Unbuffered_IO,
} }
File_Flags :: distinct bit_set[File_Flag; u32]
O_RDONLY :: File_Flags{.Read} O_RDONLY :: File_Flags{.Read}
O_WRONLY :: File_Flags{.Write} O_WRONLY :: File_Flags{.Write}
O_RDWR :: File_Flags{.Read, .Write} O_RDWR :: File_Flags{.Read, .Write}
O_APPEND :: File_Flags{.Append} O_APPEND :: File_Flags{.Append}
O_CREATE :: File_Flags{.Create} O_CREATE :: File_Flags{.Create}
O_EXCL :: File_Flags{.Excl} O_EXCL :: File_Flags{.Excl}
O_SYNC :: File_Flags{.Sync} O_SYNC :: File_Flags{.Sync}
O_TRUNC :: File_Flags{.Trunc} O_TRUNC :: File_Flags{.Trunc}
O_SPARSE :: File_Flags{.Sparse}
O_CLOEXEC :: File_Flags{.Close_On_Exec} O_CLOEXEC :: File_Flags{.Close_On_Exec}
Std_Handle_Kind :: enum u8 {
stdin = 0,
stdout = 1, stdin: ^File = nil // OS-Specific
stderr = 2, stdout: ^File = nil // OS-Specific
stderr: ^File = nil // OS-Specific
create :: proc(name: string) -> (^File, Error) {
return open(name, {.Read, .Write, .Create}, File_Mode(0o777))
} }
stdin: Handle = std_handle(.stdin) open :: proc(name: string, flags := File_Flags{.Read}, perm := File_Mode(0o777)) -> (^File, Error) {
stdout: Handle = std_handle(.stdout)
stderr: Handle = std_handle(.stderr)
std_handle :: proc(kind: Std_Handle_Kind) -> Handle {
return _std_handle(kind)
}
create :: proc(name: string, perm: File_Mode = 0) -> (Handle, Error) {
return open(name, {.Read, .Write, .Create}, perm)
}
open :: proc(name: string, flags := File_Flags{.Read}, perm: File_Mode = 0) -> (Handle, Error) {
flags := flags
if .Write not_in flags {
flags += {.Read}
}
return _open(name, flags, perm) return _open(name, flags, perm)
} }
close :: proc(fd: Handle) -> Error { new_file :: proc(handle: uintptr, name: string) -> ^File {
return _close(fd) return _new_file(handle, name)
} }
name :: proc(fd: Handle, allocator := context.allocator) -> string { fd :: proc(f: ^File) -> uintptr {
return _name(fd) return _fd(f)
}
seek :: proc(fd: Handle, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) {
return _seek(fd, offset, whence)
}
read :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) {
return _read(fd, p)
}
read_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) {
return _read_at(fd, p, offset)
}
read_from :: proc(fd: Handle, r: io.Reader) -> (n: i64, err: Error) {
return _read_from(fd, r)
}
write :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) {
return _write(fd, p)
}
write_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) {
return _write_at(fd, p, offset)
}
write_to :: proc(fd: Handle, w: io.Writer) -> (n: i64, err: Error) {
return _write_to(fd, w)
}
file_size :: proc(fd: Handle) -> (n: i64, err: Error) {
return _file_size(fd)
} }
sync :: proc(fd: Handle) -> Error { close :: proc(f: ^File) -> Error {
return _sync(fd) return _close(f)
} }
flush :: proc(fd: Handle) -> Error { name :: proc(f: ^File) -> string {
return _flush(fd) return _name(f)
} }
truncate :: proc(fd: Handle, size: i64) -> Error { seek :: proc(f: ^File, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) {
return _truncate(fd, size) return _seek(f, offset, whence)
}
read :: proc(f: ^File, p: []byte) -> (n: int, err: Error) {
return _read(f, p)
}
read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) {
return _read_at(f, p, offset)
}
read_from :: proc(f: ^File, r: io.Reader) -> (n: i64, err: Error) {
return _read_from(f, r)
}
write :: proc(f: ^File, p: []byte) -> (n: int, err: Error) {
return _write(f, p)
}
write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) {
return _write_at(f, p, offset)
}
write_to :: proc(f: ^File, w: io.Writer) -> (n: i64, err: Error) {
return _write_to(f, w)
}
file_size :: proc(f: ^File) -> (n: i64, err: Error) {
return _file_size(f)
}
sync :: proc(f: ^File) -> Error {
return _sync(f)
}
flush :: proc(f: ^File) -> Error {
return _flush(f)
}
truncate :: proc(f: ^File, size: i64) -> Error {
return _truncate(f, size)
} }
remove :: proc(name: string) -> Error { remove :: proc(name: string) -> Error {
@@ -139,28 +143,36 @@ symlink :: proc(old_name, new_name: string) -> Error {
return _symlink(old_name, new_name) return _symlink(old_name, new_name)
} }
read_link :: proc(name: string) -> (string, Error) { read_link :: proc(name: string, allocator: runtime.Allocator) -> (string, Error) {
return _read_link(name) return _read_link(name,allocator)
}
unlink :: proc(path: string) -> Error {
return _unlink(path)
} }
chdir :: proc(name: string) -> Error {
chdir :: proc(fd: Handle) -> Error { return _chdir(name)
return _chdir(fd)
} }
chmod :: proc(fd: Handle, mode: File_Mode) -> Error { chmod :: proc(name: string, mode: File_Mode) -> Error {
return _chmod(fd, mode) return _chmod(name, mode)
} }
chown :: proc(fd: Handle, uid, gid: int) -> Error { chown :: proc(name: string, uid, gid: int) -> Error {
return _chown(fd, uid, gid) return _chown(name, uid, gid)
} }
fchdir :: proc(f: ^File) -> Error {
return _fchdir(f)
}
fchmod :: proc(f: ^File, mode: File_Mode) -> Error {
return _fchmod(f, mode)
}
fchown :: proc(f: ^File, uid, gid: int) -> Error {
return _fchown(f, uid, gid)
}
lchown :: proc(name: string, uid, gid: int) -> Error { lchown :: proc(name: string, uid, gid: int) -> Error {
return _lchown(name, uid, gid) 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 { chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
return _chtimes(name, atime, mtime) return _chtimes(name, atime, mtime)
} }
fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
return _fchtimes(f, atime, mtime)
}
exists :: proc(path: string) -> bool { exists :: proc(path: string) -> bool {
return _exists(path) return _exists(path)
} }
is_file :: proc(fd: Handle) -> bool { is_file :: proc(path: string) -> bool {
return _is_file(fd) return _is_file(path)
} }
is_dir :: proc(fd: Handle) -> bool { is_dir :: proc(path: string) -> bool {
return _is_dir(fd) 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
}
+141 -67
View File
@@ -4,13 +4,10 @@ package os2
import "core:io" import "core:io"
import "core:time" import "core:time"
import "core:strings" import "core:strings"
import "core:strconv" import "core:runtime"
import "core:sys/unix" import "core:sys/unix"
INVALID_HANDLE :: -1
_std_handle :: proc(kind: Std_Handle_Kind) -> Handle {
return Handle(kind)
}
_O_RDONLY :: 0o0 _O_RDONLY :: 0o0
_O_WRONLY :: 0o1 _O_WRONLY :: 0o1
@@ -31,7 +28,17 @@ _AT_FDCWD :: -100
_CSTRING_NAME_HEAP_THRESHOLD :: 512 _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) name_cstr, allocated := _name_to_cstring(name)
defer if allocated { defer if allocated {
delete(name_cstr) 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_TRUNC * int(.Trunc in flags))
flags_i |= (_O_CLOEXEC * int(.Close_On_Exec in flags)) flags_i |= (_O_CLOEXEC * int(.Close_On_Exec in flags))
handle_i := unix.sys_open(name_cstr, flags_i, int(perm)) fd := unix.sys_open(name_cstr, flags_i, int(perm))
if handle_i < 0 { if fd < 0 {
return INVALID_HANDLE, _get_platform_error(handle_i) return nil, _get_platform_error(fd)
} }
return Handle(handle_i), nil return _new_file(uintptr(fd), name), nil
} }
_close :: proc(fd: Handle) -> Error { _new_file :: proc(fd: uintptr, _: string) -> ^File {
res := unix.sys_close(int(fd)) 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
}
_destroy :: proc(f: ^File) -> Error {
if f == nil {
return nil
}
delete(f.impl.name, f.impl.allocator)
free(f, f.impl.allocator)
return nil
}
_close :: proc(f: ^File) -> Error {
res := unix.sys_close(f.impl.fd)
return _ok_or_error(res) return _ok_or_error(res)
} }
_name :: proc(fd: Handle, allocator := context.allocator) -> string { _fd :: proc(f: ^File) -> uintptr {
// NOTE: Not sure how portable this really is if f == nil {
PROC_FD_PATH :: "/proc/self/fd/" return ~uintptr(0)
buf: [32]u8
copy(buf[:], PROC_FD_PATH)
strconv.itoa(buf[len(PROC_FD_PATH):], int(fd))
realpath: string
err: Error
if realpath, err = _read_link_cstr(cstring(&buf[0]), allocator); err != nil || realpath[0] != '/' {
return ""
} }
return realpath return uintptr(f.impl.fd)
} }
_seek :: proc(fd: Handle, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) { _name :: proc(f: ^File) -> string {
res := unix.sys_lseek(int(fd), offset, int(whence)) return f.impl.name if f != nil else ""
}
_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 { if res < 0 {
return -1, _get_platform_error(int(res)) return -1, _get_platform_error(int(res))
} }
return res, nil 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 { if len(p) == 0 {
return 0, nil 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 { if n < 0 {
return -1, _get_platform_error(int(unix.get_errno(n))) return -1, _get_platform_error(n)
} }
return n, nil 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 { if offset < 0 {
return 0, .Invalid_Offset return 0, .Invalid_Offset
} }
b, offset := p, offset b, offset := p, offset
for len(b) > 0 { 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 { if m < 0 {
return -1, _get_platform_error(m) return -1, _get_platform_error(m)
} }
@@ -118,30 +137,30 @@ _read_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) {
return 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 //TODO
return 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 { if len(p) == 0 {
return 0, nil 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 { if n < 0 {
return -1, _get_platform_error(n) return -1, _get_platform_error(n)
} }
return int(n), nil 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 { if offset < 0 {
return 0, .Invalid_Offset return 0, .Invalid_Offset
} }
b, offset := p, offset b, offset := p, offset
for len(b) > 0 { 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 { if m < 0 {
return -1, _get_platform_error(m) return -1, _get_platform_error(m)
} }
@@ -152,30 +171,30 @@ _write_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) {
return 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 //TODO
return return
} }
_file_size :: proc(fd: Handle) -> (n: i64, err: Error) { _file_size :: proc(f: ^File) -> (n: i64, err: Error) {
s: OS_Stat = --- s: _Stat = ---
res := unix.sys_fstat(int(fd), &s) res := unix.sys_fstat(f.impl.fd, &s)
if res < 0 { if res < 0 {
return -1, _get_platform_error(res) return -1, _get_platform_error(res)
} }
return s.size, nil return s.size, nil
} }
_sync :: proc(fd: Handle) -> Error { _sync :: proc(f: ^File) -> Error {
return _ok_or_error(unix.sys_fsync(int(fd))) return _ok_or_error(unix.sys_fsync(f.impl.fd))
} }
_flush :: proc(fd: Handle) -> Error { _flush :: proc(f: ^File) -> Error {
return _ok_or_error(unix.sys_fsync(int(fd))) return _ok_or_error(unix.sys_fsync(f.impl.fd))
} }
_truncate :: proc(fd: Handle, size: i64) -> Error { _truncate :: proc(f: ^File, size: i64) -> Error {
return _ok_or_error(unix.sys_ftruncate(int(fd), size)) return _ok_or_error(unix.sys_ftruncate(f.impl.fd, size))
} }
_remove :: proc(name: string) -> Error { _remove :: proc(name: string) -> Error {
@@ -184,13 +203,13 @@ _remove :: proc(name: string) -> Error {
delete(name_cstr) delete(name_cstr)
} }
handle_i := unix.sys_open(name_cstr, int(File_Flags.Read)) fd := unix.sys_open(name_cstr, int(File_Flags.Read))
if handle_i < 0 { if fd < 0 {
return _get_platform_error(handle_i) 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_rmdir(name_cstr))
} }
return _ok_or_error(unix.sys_unlink(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) rc := unix.sys_readlink(name_cstr, &(buf[0]), bufsz)
if rc < 0 { if rc < 0 {
delete(buf) delete(buf)
return "", _get_platform_error(int(unix.get_errno(rc))) return "", _get_platform_error(rc)
} else if rc == int(bufsz) { } else if rc == int(bufsz) {
bufsz *= 2 bufsz *= 2
delete(buf) delete(buf)
@@ -269,18 +288,40 @@ _unlink :: proc(name: string) -> Error {
return _ok_or_error(unix.sys_unlink(name_cstr)) return _ok_or_error(unix.sys_unlink(name_cstr))
} }
_chdir :: proc(fd: Handle) -> Error { _chdir :: proc(name: string) -> Error {
return _ok_or_error(unix.sys_fchdir(int(fd))) name_cstr, allocated := _name_to_cstring(name)
defer if allocated {
delete(name_cstr)
}
return _ok_or_error(unix.sys_chdir(name_cstr))
} }
_chmod :: proc(fd: Handle, mode: File_Mode) -> Error { _fchdir :: proc(f: ^File) -> Error {
return _ok_or_error(unix.sys_fchmod(int(fd), int(mode))) return _ok_or_error(unix.sys_fchdir(f.impl.fd))
} }
_chown :: proc(fd: Handle, uid, gid: int) -> Error { _chmod :: proc(name: string, mode: File_Mode) -> Error {
return _ok_or_error(unix.sys_fchown(int(fd), uid, gid)) name_cstr, allocated := _name_to_cstring(name)
defer if allocated {
delete(name_cstr)
}
return _ok_or_error(unix.sys_chmod(name_cstr, int(mode)))
} }
_fchmod :: proc(f: ^File, mode: File_Mode) -> Error {
return _ok_or_error(unix.sys_fchmod(f.impl.fd, int(mode)))
}
// 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 { _lchown :: proc(name: string, uid, gid: int) -> Error {
name_cstr, allocated := _name_to_cstring(name) name_cstr, allocated := _name_to_cstring(name)
defer if allocated { 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)) 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 { _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
name_cstr, allocated := _name_to_cstring(name) name_cstr, allocated := _name_to_cstring(name)
defer if allocated { 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, &times, 0)) return _ok_or_error(unix.sys_utimensat(_AT_FDCWD, name_cstr, &times, 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, &times, 0))
}
_exists :: proc(name: string) -> bool { _exists :: proc(name: string) -> bool {
name_cstr, allocated := _name_to_cstring(name) name_cstr, allocated := _name_to_cstring(name)
defer if allocated { defer if allocated {
@@ -309,18 +363,38 @@ _exists :: proc(name: string) -> bool {
return unix.sys_access(name_cstr, F_OK) == 0 return unix.sys_access(name_cstr, F_OK) == 0
} }
_is_file :: proc(fd: Handle) -> bool { _is_file :: proc(name: string) -> bool {
s: OS_Stat name_cstr, allocated := _name_to_cstring(name)
res := unix.sys_fstat(int(fd), &s) 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 if res < 0 { // error
return false return false
} }
return S_ISREG(s.mode) return S_ISREG(s.mode)
} }
_is_dir :: proc(fd: Handle) -> bool { _is_dir :: proc(name: string) -> bool {
s: OS_Stat name_cstr, allocated := _name_to_cstring(name)
res := unix.sys_fstat(int(fd), &s) 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 if res < 0 { // error
return false 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 // 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. // 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 // 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) { _name_to_cstring :: proc(name: string) -> (cname: cstring, allocated: bool) {
if len(name) > _CSTRING_NAME_HEAP_THRESHOLD { if len(name) > _CSTRING_NAME_HEAP_THRESHOLD {
cname = strings.clone_to_cstring(name) cname = strings.clone_to_cstring(name)
+30 -22
View File
@@ -2,12 +2,20 @@ package os2
import "core:io" import "core:io"
file_to_stream :: proc(fd: Handle) -> (s: io.Stream) { to_stream :: proc(f: ^File) -> (s: io.Stream) {
s.stream_data = rawptr(uintptr(fd)) s.stream_data = f
s.stream_vtable = _file_stream_vtable s.stream_vtable = _file_stream_vtable
return 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) @(private)
error_to_io_error :: proc(ferr: Error) -> io.Error { error_to_io_error :: proc(ferr: Error) -> io.Error {
if ferr == nil { if ferr == nil {
@@ -20,66 +28,66 @@ error_to_io_error :: proc(ferr: Error) -> io.Error {
@(private) @(private)
_file_stream_vtable := &io.Stream_VTable{ _file_stream_vtable := &io.Stream_VTable{
impl_read = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) { 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 ferr: Error
n, ferr = read(fd, p) n, ferr = read(f, p)
err = error_to_io_error(ferr) err = error_to_io_error(ferr)
return return
}, },
impl_read_at = proc(s: io.Stream, p: []byte, offset: i64) -> (n: int, err: io.Error) { 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 ferr: Error
n, ferr = read_at(fd, p, offset) n, ferr = read_at(f, p, offset)
err = error_to_io_error(ferr) err = error_to_io_error(ferr)
return return
}, },
impl_write_to = proc(s: io.Stream, w: io.Writer) -> (n: i64, err: io.Error) { 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 ferr: Error
n, ferr = write_to(fd, w) n, ferr = write_to(f, w)
err = error_to_io_error(ferr) err = error_to_io_error(ferr)
return return
}, },
impl_write = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) { 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 ferr: Error
n, ferr = write(fd, p) n, ferr = write(f, p)
err = error_to_io_error(ferr) err = error_to_io_error(ferr)
return return
}, },
impl_write_at = proc(s: io.Stream, p: []byte, offset: i64) -> (n: int, err: io.Error) { 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 ferr: Error
n, ferr = write_at(fd, p, offset) n, ferr = write_at(f, p, offset)
err = error_to_io_error(ferr) err = error_to_io_error(ferr)
return return
}, },
impl_read_from = proc(s: io.Stream, r: io.Reader) -> (n: i64, err: io.Error) { 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 ferr: Error
n, ferr = read_from(fd, r) n, ferr = read_from(f, r)
err = error_to_io_error(ferr) err = error_to_io_error(ferr)
return return
}, },
impl_seek = proc(s: io.Stream, offset: i64, whence: io.Seek_From) -> (i64, io.Error) { impl_seek = proc(s: io.Stream, offset: i64, whence: io.Seek_From) -> (i64, io.Error) {
fd := Handle(uintptr(s.stream_data)) f := (^File)(s.stream_data)
n, ferr := seek(fd, offset, Seek_From(whence)) n, ferr := seek(f, offset, Seek_From(whence))
err := error_to_io_error(ferr) err := error_to_io_error(ferr)
return n, err return n, err
}, },
impl_size = proc(s: io.Stream) -> i64 { impl_size = proc(s: io.Stream) -> i64 {
fd := Handle(uintptr(s.stream_data)) f := (^File)(s.stream_data)
sz, _ := file_size(fd) sz, _ := file_size(f)
return sz return sz
}, },
impl_flush = proc(s: io.Stream) -> io.Error { impl_flush = proc(s: io.Stream) -> io.Error {
fd := Handle(uintptr(s.stream_data)) f := (^File)(s.stream_data)
ferr := flush(fd) ferr := flush(f)
return error_to_io_error(ferr) return error_to_io_error(ferr)
}, },
impl_close = proc(s: io.Stream) -> io.Error { impl_close = proc(s: io.Stream) -> io.Error {
fd := Handle(uintptr(s.stream_data)) f := (^File)(s.stream_data)
ferr := close(fd) ferr := close(f)
return error_to_io_error(ferr) return error_to_io_error(ferr)
}, },
} }
+61 -83
View File
@@ -4,25 +4,25 @@ import "core:mem"
import "core:strconv" import "core:strconv"
import "core:unicode/utf8" import "core:unicode/utf8"
write_string :: proc(fd: Handle, s: string) -> (n: int, err: Error) { write_string :: proc(f: ^File, s: string) -> (n: int, err: Error) {
return write(fd, transmute([]byte)s) return write(f, transmute([]byte)s)
} }
write_byte :: proc(fd: Handle, b: byte) -> (n: int, err: Error) { write_byte :: proc(f: ^File, b: byte) -> (n: int, err: Error) {
return write(fd, []byte{b}) 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 { if r < utf8.RUNE_SELF {
return write_byte(fd, byte(r)) return write_byte(f, byte(r))
} }
b: [4]byte b: [4]byte
b, n = utf8.encode_rune(r) 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 { wrap :: proc(m: int, merr: Error, n: ^int, err: ^Error) -> bool {
n^ += m n^ += m
if merr != nil { if merr != nil {
@@ -32,102 +32,78 @@ write_encoded_rune :: proc(fd: Handle, r: rune) -> (n: int, err: Error) {
return false return false
} }
if wrap(write_byte(fd, '\''), &n, &err) { return } if wrap(write_byte(f, '\''), &n, &err) { return }
switch r { switch r {
case '\a': if wrap(write_string(fd, "\\a"), &n, &err) { return } case '\a': if wrap(write_string(f, "\\a"), &n, &err) { return }
case '\b': if wrap(write_string(fd, "\\b"), &n, &err) { return } case '\b': if wrap(write_string(f, "\\b"), &n, &err) { return }
case '\e': if wrap(write_string(fd, "\\e"), &n, &err) { return } case '\e': if wrap(write_string(f, "\\e"), &n, &err) { return }
case '\f': if wrap(write_string(fd, "\\f"), &n, &err) { return } case '\f': if wrap(write_string(f, "\\f"), &n, &err) { return }
case '\n': if wrap(write_string(fd, "\\n"), &n, &err) { return } case '\n': if wrap(write_string(f, "\\n"), &n, &err) { return }
case '\r': if wrap(write_string(fd, "\\r"), &n, &err) { return } case '\r': if wrap(write_string(f, "\\r"), &n, &err) { return }
case '\t': if wrap(write_string(fd, "\\t"), &n, &err) { return } case '\t': if wrap(write_string(f, "\\t"), &n, &err) { return }
case '\v': if wrap(write_string(fd, "\\v"), &n, &err) { return } case '\v': if wrap(write_string(f, "\\v"), &n, &err) { return }
case: case:
if r < 32 { if r < 32 {
if wrap(write_string(fd, "\\x"), &n, &err) { return } if wrap(write_string(f, "\\x"), &n, &err) { return }
b: [2]byte b: [2]byte
s := strconv.append_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil) s := strconv.append_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil)
switch len(s) { switch len(s) {
case 0: if wrap(write_string(fd, "00"), &n, &err) { return } case 0: if wrap(write_string(f, "00"), &n, &err) { return }
case 1: if wrap(write_rune(fd, '0'), &n, &err) { return } case 1: if wrap(write_rune(f, '0'), &n, &err) { return }
case 2: if wrap(write_string(fd, s), &n, &err) { return } case 2: if wrap(write_string(f, s), &n, &err) { return }
} }
} else { } 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 return
} }
write_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} s := transmute([]byte)mem.Raw_Slice{data, len}
return write(fd, s) return write(f, s)
} }
read_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (n: int, err: Error) { read_ptr :: proc(f: ^File, data: rawptr, len: int) -> (n: int, err: Error) {
s := transmute([]byte)mem.Raw_Slice{data, len} s := transmute([]byte)mem.Raw_Slice{data, len}
return read(fd, s) return read(f, s)
} }
read_at_least :: proc(fd: Handle, buf: []byte, min: int) -> (n: int, err: Error) {
if len(buf) < min { read_entire_file :: proc(name: string, allocator := context.allocator) -> (data: []byte, err: Error) {
return 0, .Short_Buffer f, ferr := open(name)
if ferr != nil {
return nil, ferr
} }
for n < min && err == nil { defer close(f)
nn: int
nn, err = read(fd, buf[n:]) size: int
n += nn if size64, err := file_size(f); err == nil {
if i64(int(size64)) != size64 {
size = int(size64)
}
} }
if n >= min { size += 1 // for EOF
err = nil
// 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
}
} }
return
}
read_full :: proc(fd: Handle, buf: []byte) -> (n: int, err: Error) {
return read_at_least(fd, buf, len(buf))
}
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_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
}
data = make([]byte, int(length), allocator)
if data == nil {
return nil, .Short_Buffer
}
defer if err != nil {
delete(data, allocator)
}
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 { 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 { if truncate {
flags |= O_TRUNC flags |= O_TRUNC
} }
f := open(name, flags, perm) or_return f, err := open(name, flags, perm)
if err != nil {
_, err := write(f, data) return err
}
_, err = write(f, data)
if cerr := close(f); cerr != nil && err == nil { if cerr := close(f); cerr != nil && err == nil {
err = cerr err = cerr
} }
+550 -242
View File
@@ -2,323 +2,455 @@
package os2 package os2
import "core:io" import "core:io"
import "core:mem"
import "core:sync"
import "core:runtime"
import "core:strings"
import "core:time" import "core:time"
import "core:unicode/utf16" import "core:unicode/utf16"
import win32 "core:sys/windows" import win32 "core:sys/windows"
_get_platform_error :: proc() -> Error { INVALID_HANDLE :: ~uintptr(0)
// TODO(bill): map some of these errors correctly
err := win32.GetLastError() S_IWRITE :: 0o200
if err == 0 { _ERROR_BAD_NETPATH :: 53
return nil MAX_RW :: 1<<30
}
return Platform_Error{i32(err)} _file_allocator :: proc() -> runtime.Allocator {
return heap_allocator()
} }
_ok_or_error :: proc(ok: win32.BOOL) -> Error { _temp_allocator :: proc() -> runtime.Allocator {
return nil if ok else _get_platform_error() // 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 { _File_Kind :: enum u8 {
case .stdin: return get_handle(win32.STD_INPUT_HANDLE) File,
case .stdout: return get_handle(win32.STD_OUTPUT_HANDLE) Console,
case .stderr: return get_handle(win32.STD_ERROR_HANDLE) Pipe,
}
unreachable()
} }
_open :: proc(path: string, flags: File_Flags, perm: File_Mode) -> (handle: Handle, err: Error) { _File :: struct {
handle = INVALID_HANDLE fd: rawptr,
if len(path) == 0 { 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
}
_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 err = .Not_Exist
return return
} }
path := _fix_long_path(name)
access: u32 access: u32
switch flags & O_RDONLY|O_WRONLY|O_RDWR { switch flags & {.Read, .Write} {
case O_RDONLY: access = win32.FILE_GENERIC_READ case {.Read}: access = win32.FILE_GENERIC_READ
case O_WRONLY: access = win32.FILE_GENERIC_WRITE case {.Write}: access = win32.FILE_GENERIC_WRITE
case O_RDWR: access = win32.FILE_GENERIC_READ | 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 { if .Create in flags {
access |= win32.FILE_GENERIC_WRITE access |= win32.FILE_GENERIC_WRITE
} }
if .Append in flags {
share_mode := win32.FILE_SHARE_READ|win32.FILE_SHARE_WRITE access &~= win32.FILE_GENERIC_WRITE
sa: ^win32.SECURITY_ATTRIBUTES = nil access |= win32.FILE_APPEND_DATA
sa_inherit := win32.SECURITY_ATTRIBUTES{nLength = size_of(win32.SECURITY_ATTRIBUTES), bInheritHandle = true} }
if .Close_On_Exec in flags { share_mode := u32(win32.FILE_SHARE_READ | win32.FILE_SHARE_WRITE)
sa = &sa_inherit 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 { switch {
case flags&(O_CREATE|O_EXCL) == (O_CREATE | O_EXCL): case flags & {.Create, .Excl} == {.Create, .Excl}:
create_mode = win32.CREATE_NEW 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 create_mode = win32.CREATE_ALWAYS
case flags&O_CREATE == O_CREATE: case flags & {.Create} == {.Create}:
create_mode = win32.OPEN_ALWAYS create_mode = win32.OPEN_ALWAYS
case flags&O_TRUNC == O_TRUNC: case flags & {.Trunc} == {.Trunc}:
create_mode = win32.TRUNCATE_EXISTING 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)) attrs: u32 = win32.FILE_ATTRIBUTE_NORMAL
if handle == INVALID_HANDLE { if perm & S_IWRITE == 0 {
err = _get_platform_error() 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 { _open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (f: ^File, err: Error) {
return .Invalid_Argument 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 f.impl.allocator = _file_allocator()
_ok_or_error(win32.GetFileInformationByHandle(hnd, &file_info)) or_return 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)
if file_info.dwFileAttributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { 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
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 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 _close :: proc(f: ^File) -> Error {
handle := win32.HANDLE(fd) if f == nil {
buf_len := win32.GetFinalPathNameByHandleW(handle, nil, 0, FILE_NAME_NORMALIZED) return nil
if buf_len == 0 {
return ""
} }
buf := make([]u16, buf_len, context.temp_allocator) if !win32.CloseHandle(win32.HANDLE(f.impl.fd)) {
n := win32.GetFinalPathNameByHandleW(handle, raw_data(buf), buf_len, FILE_NAME_NORMALIZED) return .Closed
return win32.utf16_to_utf8(buf[:n], allocator) }
return _destroy(f)
} }
_seek :: proc(fd: Handle, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) { _name :: proc(f: ^File) -> string {
new_offset: win32.LARGE_INTEGER return f.impl.name if f != nil else ""
move_method: win32.DWORD }
_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 { switch whence {
case .Start: move_method = win32.FILE_BEGIN case .Start: w = win32.FILE_BEGIN
case .Current: move_method = win32.FILE_CURRENT case .Current: w = win32.FILE_CURRENT
case .End: move_method = win32.FILE_END case .End: w = win32.FILE_END
} }
ok := win32.SetFilePointerEx(win32.HANDLE(fd), win32.LARGE_INTEGER(offset), &new_offset, move_method) hi := i32(offset>>32)
ret = i64(new_offset) lo := i32(offset)
if !ok {
err = .Invalid_Whence 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") // TODO(bill): should this be moved to `_File` instead?
_read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) { BUF_SIZE :: 386
if len(b) == 0 { buf16: [BUF_SIZE]u16
return 0, nil buf8: [4*BUF_SIZE]u8
}
BUF_SIZE :: 386 for n < len(b) && err == nil {
buf16: [BUF_SIZE]u16 min_read := max(len(b)/4, 1 if len(b) > 0 else 0)
buf8: [4*BUF_SIZE]u8 max_read := u32(min(BUF_SIZE, min_read))
if max_read == 0 {
break
}
for n < len(b) && err == nil { single_read_length: u32
max_read := u32(min(BUF_SIZE, len(b)/4)) ok := win32.ReadConsoleW(handle, &buf16[0], max_read, &single_read_length, nil)
if !ok {
err = _get_platform_error()
}
single_read_length: u32 buf8_len := utf16.decode_to_utf8(buf8[:], buf16[:single_read_length])
err = _ok_or_error(win32.ReadConsoleW(handle, &buf16[0], max_read, &single_read_length, nil)) src := buf8[:buf8_len]
buf8_len := utf16.decode_to_utf8(buf8[:], buf16[:single_read_length]) ctrl_z := false
src := buf8[:buf8_len] 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 // NOTE(bill): if the last two values were a newline, then it is expected that
for i := 0; i < len(src) && n+i < len(b); i += 1 { // this is the end of the input
x := src[i] if n >= 2 && single_read_length == max_read && string(b[n-2:n]) == "\r\n" {
if x == 0x1a { // ctrl-z
ctrl_z = true
break break
} }
b[n] = x
n += 1
}
if ctrl_z || single_read_length < len(buf16) {
break
} }
return
} }
return handle := _handle(f)
}
_read :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) {
if len(p) == 0 {
return 0, nil
}
handle := win32.HANDLE(fd)
m: u32
is_console := win32.GetConsoleMode(handle, &m)
single_read_length: win32.DWORD single_read_length: win32.DWORD
total_read: int total_read: int
length := len(p) length := len(p)
to_read := min(win32.DWORD(length), MAX_RW) sync.shared_guard(&f.impl.rw_mutex) // multiple readers
e: win32.BOOL if sync.guard(&f.impl.p_mutex) {
if is_console { to_read := min(win32.DWORD(length), MAX_RW)
n, err := _read_console(handle, p[total_read:][:to_read]) ok: win32.BOOL
total_read += n if f.impl.kind == .Console {
if err != nil { n, err := read_console(handle, p[total_read:][:to_read])
return int(total_read), err 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)
}
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 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 { curr_offset := seek(f, offset, .Current) or_return
return 0, .Invalid_Offset 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 sync.guard(&f.impl.p_mutex)
for len(b) > 0 {
m := _pread(fd, b, offset) or_return p, offset := p, offset
for len(p) > 0 {
m := pread(f, p, offset) or_return
n += m n += m
b = b[m:] p = p[m:]
offset += i64(m) offset += i64(m)
} }
return 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 return
} }
_write :: proc(f: ^File, p: []byte) -> (n: int, err: Error) {
if len(p) == 0 {
_pread :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { return
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),
} }
// TODO(bill): Determine the correct behaviour for consoles single_write_length: win32.DWORD
total_write: i64
length := i64(len(p))
h := win32.HANDLE(fd) handle := _handle(f)
done: win32.DWORD
_ok_or_error(win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o)) or_return sync.guard(&f.impl.rw_mutex)
return int(done), nil 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)
}
return int(total_write), nil
} }
_pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { _write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) {
buf := data pwrite :: proc(f: ^File, data: []byte, offset: i64) -> (n: int, err: Error) {
if len(buf) > MAX_RW { buf := data
buf = buf[:MAX_RW] if len(buf) > MAX_RW {
buf = buf[:MAX_RW]
} }
curr_offset := seek(fd, offset, .Current) or_return curr_offset := seek(f, offset, .Current) or_return
defer seek(fd, curr_offset, .Start) defer seek(f, curr_offset, .Start)
o := win32.OVERLAPPED{ o := win32.OVERLAPPED{
OffsetHigh = u32(offset>>32), OffsetHigh = u32(offset>>32),
Offset = u32(offset), Offset = u32(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
} }
h := win32.HANDLE(fd) sync.guard(&f.impl.p_mutex)
done: win32.DWORD p, offset := p, offset
_ok_or_error(win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o)) or_return for len(p) > 0 {
return int(done), nil m := pwrite(f, p, offset) or_return
}
_write :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) {
return
}
_write_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) {
if offset < 0 {
return 0, .Invalid_Offset
}
b, offset := p, offset
for len(b) > 0 {
m := _pwrite(fd, b, offset) or_return
n += m n += m
b = b[m:] p = p[m:]
offset += i64(m) offset += i64(m)
} }
return 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 return
} }
_file_size :: proc(fd: Handle) -> (n: i64, err: Error) { _file_size :: proc(f: ^File) -> (n: i64, err: Error) {
length: win32.LARGE_INTEGER length: win32.LARGE_INTEGER
err = _ok_or_error(win32.GetFileSizeEx(win32.HANDLE(fd), &length)) handle := _handle(f)
return i64(length), err if !win32.GetFileSizeEx(handle, &length) {
err = _get_platform_error()
}
n = i64(length)
return
} }
_sync :: proc(fd: Handle) -> Error { _sync :: proc(f: ^File) -> Error {
return _flush(f)
}
_flush :: proc(f: ^File) -> Error {
handle := _handle(f)
if !win32.FlushFileBuffers(handle) {
return _get_platform_error()
}
return nil return nil
} }
_flush :: proc(fd: Handle) -> Error { _truncate :: proc(f: ^File, size: i64) -> Error {
return _ok_or_error(win32.FlushFileBuffers(win32.HANDLE(fd))) if f == nil {
} return nil
}
_truncate :: proc(fd: Handle, size: i64) -> Error { curr_off := seek(f, 0, .Current) or_return
offset := seek(fd, size, .Start) or_return defer seek(f, curr_off, .Start)
defer seek(fd, offset, .Start) seek(f, size, .Start) or_return
handle := _handle(f)
return _ok_or_error(win32.SetEndOfFile(win32.HANDLE(fd))) if !win32.SetEndOfFile(handle) {
return _get_platform_error()
}
return nil
} }
_remove :: proc(name: string) -> Error { _remove :: proc(name: string) -> Error {
p := win32.utf8_to_wstring(_fix_long_path(name)) p := _fix_long_path(name)
err, err1: Error
err := _ok_or_error(win32.DeleteFileW(p)) if !win32.DeleteFileW(p) {
err = _get_platform_error()
}
if err == nil { if err == nil {
return nil return nil
} }
err1 := _ok_or_error(win32.RemoveDirectoryW(p)) if !win32.RemoveDirectoryW(p) {
err1 = _get_platform_error()
}
if err1 == nil { if err1 == nil {
return nil return nil
} }
@@ -332,7 +464,10 @@ _remove :: proc(name: string) -> Error {
err = err1 err = err1
} else if a & win32.FILE_ATTRIBUTE_READONLY != 0 { } else if a & win32.FILE_ATTRIBUTE_READONLY != 0 {
if win32.SetFileAttributesW(p, a &~ win32.FILE_ATTRIBUTE_READONLY) { 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 { _rename :: proc(old_path, new_path: string) -> Error {
from := win32.utf8_to_wstring(old_path, context.temp_allocator) from := _fix_long_path(old_path)
to := win32.utf8_to_wstring(new_path, context.temp_allocator) to := _fix_long_path(new_path)
return _ok_or_error(win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING)) if win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) {
return nil
}
return _get_platform_error()
} }
_link :: proc(old_name, new_name: string) -> Error { _link :: proc(old_name, new_name: string) -> Error {
n := win32.utf8_to_wstring(_fix_long_path(new_name)) o := _fix_long_path(old_name)
o := win32.utf8_to_wstring(_fix_long_path(old_name)) n := _fix_long_path(new_name)
return _ok_or_error(win32.CreateHardLinkW(n, o, nil)) if win32.CreateHardLinkW(n, o, nil) {
return nil
}
return _get_platform_error()
} }
_symlink :: proc(old_name, new_name: string) -> Error { _symlink :: proc(old_name, new_name: string) -> Error {
return nil return .Unsupported
} }
_read_link :: proc(name: string) -> (string, Error) { _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
}
_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
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 return "", nil
} }
_unlink :: proc(path: string) -> Error {
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
return _ok_or_error(win32.DeleteFileW(wpath))
}
_fchdir :: proc(f: ^File) -> Error {
_chdir :: proc(fd: Handle) -> Error { if f == nil {
return nil
}
if !win32.SetCurrentDirectoryW(f.impl.wname) {
return _get_platform_error()
}
return nil 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 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 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 { _lchown :: proc(name: string, uid, gid: int) -> Error {
return nil return .Unsupported
} }
_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { _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 return nil
} }
_exists :: proc(path: string) -> bool { _exists :: proc(path: string) -> bool {
wpath := win32.utf8_to_wstring(path, context.temp_allocator) wpath := _fix_long_path(path)
return bool(win32.PathFileExistsW(wpath)) attribs := win32.GetFileAttributesW(wpath)
return i32(attribs) != win32.INVALID_FILE_ATTRIBUTES
} }
_is_file :: proc(fd: Handle) -> bool { _is_file :: proc(path: string) -> bool {
hnd := win32.HANDLE(fd) wpath := _fix_long_path(path)
attribs := win32.GetFileAttributesW(wpath)
file_info: win32.BY_HANDLE_FILE_INFORMATION if i32(attribs) != win32.INVALID_FILE_ATTRIBUTES {
if ok := win32.GetFileInformationByHandle(hnd, &file_info); !ok { return attribs & win32.FILE_ATTRIBUTE_DIRECTORY == 0
return false
} }
no_flags :: win32.FILE_ATTRIBUTE_DIRECTORY | win32.FILE_ATTRIBUTE_DEVICE return false
yes_flags :: win32.FILE_ATTRIBUTE_NORMAL
return (file_info.dwFileAttributes & no_flags == 0) && (file_info.dwFileAttributes & yes_flags != 0)
} }
_is_dir :: proc(fd: Handle) -> bool { _is_dir :: proc(path: string) -> bool {
hnd := win32.HANDLE(fd) wpath := _fix_long_path(path)
attribs := win32.GetFileAttributesW(wpath)
file_info: win32.BY_HANDLE_FILE_INFORMATION if i32(attribs) != win32.INVALID_FILE_ATTRIBUTES {
if ok := win32.GetFileInformationByHandle(hnd, &file_info); !ok { return attribs & win32.FILE_ATTRIBUTE_DIRECTORY != 0
return false
} }
return file_info.dwFileAttributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 return false
} }
-1
View File
@@ -102,7 +102,6 @@ MMAP_PROT :: unix.PROT_READ | unix.PROT_WRITE
@thread_local _local_region: ^Region @thread_local _local_region: ^Region
//_local_region: ^Region
global_regions: ^Region global_regions: ^Region
+3 -1
View File
@@ -1,5 +1,7 @@
package os2 package os2
import "core:runtime"
Path_Separator :: _Path_Separator // OS-Specific Path_Separator :: _Path_Separator // OS-Specific
Path_List_Separator :: _Path_List_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) return _getwd(allocator)
} }
setwd :: proc(dir: string) -> (err: Error) { setwd :: proc(dir: string) -> (err: Error) {
+41 -23
View File
@@ -2,6 +2,8 @@
package os2 package os2
import "core:strings" import "core:strings"
import "core:strconv"
import "core:runtime"
import "core:sys/unix" import "core:sys/unix"
_Path_Separator :: '/' _Path_Separator :: '/'
@@ -37,31 +39,31 @@ _mkdir :: proc(path: string, perm: File_Mode) -> Error {
} }
_mkdir_all :: 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 { if len(path) == 0 {
return _ok_or_error(unix.sys_close(int(dfd))) return _ok_or_error(unix.sys_close(dfd))
} }
i: int i: int
for /**/; i < len(path) - 1 && path[i] != '/'; i += 1 {} for /**/; i < len(path) - 1 && path[i] != '/'; i += 1 {}
path[i] = 0 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 { switch new_dfd {
case -ENOENT: 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) return _get_platform_error(res)
} }
has_created^ = true 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) return _get_platform_error(new_dfd)
} }
fallthrough fallthrough
case 0: case 0:
if res := unix.sys_close(int(dfd)); res < 0 { if res := unix.sys_close(dfd); res < 0 {
return _get_platform_error(res) return _get_platform_error(res)
} }
// skip consecutive '/' // skip consecutive '/'
for i += 1; i < len(path) && path[i] == '/'; i += 1 {} 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: case:
return _get_platform_error(new_dfd) return _get_platform_error(new_dfd)
} }
@@ -101,7 +103,7 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error {
} }
has_created: bool 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 { if has_created {
return nil return nil
} }
@@ -120,13 +122,13 @@ dirent64 :: struct {
_remove_all :: proc(path: string) -> Error { _remove_all :: proc(path: string) -> Error {
DT_DIR :: 4 DT_DIR :: 4
_remove_all_dir :: proc(dfd: Handle) -> Error { _remove_all_dir :: proc(dfd: int) -> Error {
n := 64 n := 64
buf := make([]u8, n) buf := make([]u8, n)
defer delete(buf) defer delete(buf)
loop: for { loop: for {
getdents_res := unix.sys_getdents64(int(dfd), &buf[0], n) getdents_res := unix.sys_getdents64(dfd, &buf[0], n)
switch getdents_res { switch getdents_res {
case -EINVAL: case -EINVAL:
delete(buf) delete(buf)
@@ -161,15 +163,15 @@ _remove_all :: proc(path: string) -> Error {
switch d.d_type { switch d.d_type {
case DT_DIR: case DT_DIR:
handle_i := unix.sys_openat(int(dfd), d_name_cstr, _OPENDIR_FLAGS) new_dfd := unix.sys_openat(dfd, d_name_cstr, _OPENDIR_FLAGS)
if handle_i < 0 { if new_dfd < 0 {
return _get_platform_error(handle_i) return _get_platform_error(new_dfd)
} }
defer unix.sys_close(handle_i) defer unix.sys_close(new_dfd)
_remove_all_dir(Handle(handle_i)) or_return _remove_all_dir(new_dfd) or_return
unlink_res = unix.sys_unlinkat(int(dfd), d_name_cstr, int(unix.AT_REMOVEDIR)) unlink_res = unix.sys_unlinkat(dfd, d_name_cstr, int(unix.AT_REMOVEDIR))
case: case:
unlink_res = unix.sys_unlinkat(int(dfd), d_name_cstr) unlink_res = unix.sys_unlinkat(dfd, d_name_cstr)
} }
if unlink_res < 0 { if unlink_res < 0 {
@@ -185,21 +187,20 @@ _remove_all :: proc(path: string) -> Error {
delete(path_cstr) delete(path_cstr)
} }
handle_i := unix.sys_open(path_cstr, _OPENDIR_FLAGS) fd := unix.sys_open(path_cstr, _OPENDIR_FLAGS)
switch handle_i { switch fd {
case -ENOTDIR: case -ENOTDIR:
return _ok_or_error(unix.sys_unlink(path_cstr)) return _ok_or_error(unix.sys_unlink(path_cstr))
case -4096..<0: case -4096..<0:
return _get_platform_error(handle_i) return _get_platform_error(fd)
} }
fd := Handle(handle_i) defer unix.sys_close(fd)
defer close(fd)
_remove_all_dir(fd) or_return _remove_all_dir(fd) or_return
return _ok_or_error(unix.sys_rmdir(path_cstr)) 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 // NOTE(tetra): I would use PATH_MAX here, but I was not able to find
// an authoritative value for it across all systems. // an authoritative value for it across all systems.
// The largest value I could find was 4096, so might as well use the page size. // 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)) 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
}
+133 -2
View File
@@ -1,6 +1,10 @@
//+private //+private
package os2 package os2
import win32 "core:sys/windows"
import "core:runtime"
import "core:strings"
_Path_Separator :: '\\' _Path_Separator :: '\\'
_Path_List_Separator :: ';' _Path_List_Separator :: ';'
@@ -9,11 +13,58 @@ _is_path_separator :: proc(c: byte) -> bool {
} }
_mkdir :: proc(name: string, perm: File_Mode) -> Error { _mkdir :: proc(name: string, perm: File_Mode) -> Error {
if !win32.CreateDirectoryW(_fix_long_path(name), nil) {
return _get_platform_error()
}
return nil return nil
} }
_mkdir_all :: proc(path: string, perm: File_Mode) -> Error { _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 return nil
} }
@@ -22,10 +73,90 @@ _remove_all :: proc(path: string) -> Error {
return nil return nil
} }
_getwd :: proc(allocator := context.allocator) -> (dir: string, err: Error) { _getwd :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
// TODO(bill)
return "", nil return "", nil
} }
_setwd :: proc(dir: string) -> (err: Error) { _setwd :: proc(dir: string) -> (err: Error) {
// TODO(bill)
return nil 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])
}
+1 -1
View File
@@ -1,5 +1,5 @@
package os2 package os2
pipe :: proc() -> (r, w: Handle, err: Error) { pipe :: proc() -> (r, w: ^File, err: Error) {
return _pipe() return _pipe()
} }
+2 -2
View File
@@ -1,7 +1,7 @@
//+private //+private
package os2 package os2
_pipe :: proc() -> (r, w: Handle, err: Error) { _pipe :: proc() -> (r, w: ^File, err: Error) {
return INVALID_HANDLE, INVALID_HANDLE, nil return nil, nil, nil
} }
+4 -8
View File
@@ -3,15 +3,11 @@ package os2
import win32 "core:sys/windows" import win32 "core:sys/windows"
_pipe :: proc() -> (r, w: Handle, err: Error) { _pipe :: proc() -> (r, w: ^File, err: Error) {
sa: win32.SECURITY_ATTRIBUTES
sa.nLength = size_of(win32.SECURITY_ATTRIBUTES)
sa.bInheritHandle = true
p: [2]win32.HANDLE p: [2]win32.HANDLE
if !win32.CreatePipe(&p[0], &p[1], &sa, 0) { if !win32.CreatePipe(&p[0], &p[1], nil, 0) {
return 0, 0, Platform_Error{i32(win32.GetLastError())} 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
} }
+1 -1
View File
@@ -46,7 +46,7 @@ Process :: struct {
Process_Attributes :: struct { Process_Attributes :: struct {
dir: string, dir: string,
env: []string, env: []string,
files: []Handle, files: []^File,
sys: ^Process_Attributes_OS_Specific, sys: ^Process_Attributes_OS_Specific,
} }
+7 -6
View File
@@ -1,6 +1,7 @@
package os2 package os2
import "core:time" import "core:time"
import "core:runtime"
File_Info :: struct { File_Info :: struct {
fullpath: string, fullpath: string,
@@ -13,26 +14,26 @@ File_Info :: struct {
access_time: time.Time, 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 { for i := len(infos)-1; i >= 0; i -= 1 {
file_info_delete(infos[i], allocator) file_info_delete(infos[i], allocator)
} }
delete(infos, 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) delete(fi.fullpath, allocator)
} }
fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Error) { fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) {
return _fstat(fd, allocator) 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) 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) return _lstat(name, allocator)
} }
+13 -8
View File
@@ -2,6 +2,7 @@
package os2 package os2
import "core:time" import "core:time"
import "core:runtime"
import "core:sys/unix" import "core:sys/unix"
import "core:path/filepath" import "core:path/filepath"
@@ -59,7 +60,7 @@ Unix_File_Time :: struct {
} }
@private @private
OS_Stat :: struct { _Stat :: struct {
device_id: u64, // ID of device containing file device_id: u64, // ID of device containing file
serial: u64, // File serial number serial: u64, // File serial number
nlink: u64, // Number of hard links nlink: u64, // Number of hard links
@@ -82,16 +83,20 @@ OS_Stat :: struct {
} }
_fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Error) { _fstat :: proc(f: ^File, allocator := context.allocator) -> (File_Info, Error) {
s: OS_Stat return _fstat_internal(f.impl.fd, allocator)
result := unix.sys_fstat(int(fd), &s) }
_fstat_internal :: proc(fd: int, allocator: runtime.Allocator) -> (File_Info, Error) {
s: _Stat
result := unix.sys_fstat(fd, &s)
if result < 0 { if result < 0 {
return {}, _get_platform_error(result) return {}, _get_platform_error(result)
} }
// TODO: As of Linux 4.11, the new statx syscall can retrieve creation_time // TODO: As of Linux 4.11, the new statx syscall can retrieve creation_time
fi := File_Info { fi := File_Info {
fullpath = _name(fd, allocator), fullpath = _get_full_path(fd, allocator),
name = "", name = "",
size = s.size, size = s.size,
mode = 0, mode = 0,
@@ -117,7 +122,7 @@ _stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error
return {}, _get_platform_error(fd) return {}, _get_platform_error(fd)
} }
defer unix.sys_close(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) { _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) return {}, _get_platform_error(fd)
} }
defer unix.sys_close(fd) defer unix.sys_close(fd)
return _fstat(Handle(fd), allocator) return _fstat_internal(fd, allocator)
} }
_same_file :: proc(fi1, fi2: File_Info) -> bool { _same_file :: proc(fi1, fi2: File_Info) -> bool {
return fi1.fullpath == fi2.fullpath 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) name_cstr, allocated := _name_to_cstring(name)
defer if allocated { defer if allocated {
delete(name_cstr) delete(name_cstr)
+131 -127
View File
@@ -1,21 +1,22 @@
//+private //+private
package os2 package os2
import "core:runtime"
import "core:time" import "core:time"
import "core:strings"
import win32 "core:sys/windows" import win32 "core:sys/windows"
_fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Error) { _fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) {
if fd == 0 { if f == nil || f.impl.fd == nil {
return {}, .Invalid_Argument return {}, nil
} }
context.allocator = allocator
path, err := _cleanpath_from_handle(fd) path, err := _cleanpath_from_handle(f, allocator)
if err != nil { if err != nil {
return {}, err return {}, err
} }
h := win32.HANDLE(fd) h := _handle(f)
switch win32.GetFileType(h) { switch win32.GetFileType(h) {
case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR: case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR:
fi: File_Info fi: File_Info
@@ -25,58 +26,52 @@ _fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Error)
return fi, nil 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) { _stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS) return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS, allocator)
} }
_lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { _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) return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS|win32.FILE_FLAG_OPEN_REPARSE_POINT, allocator)
} }
_same_file :: proc(fi1, fi2: File_Info) -> bool { _same_file :: proc(fi1, fi2: File_Info) -> bool {
return fi1.fullpath == fi2.fullpath 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 name := name
if name == "" { if name == "" {
name = "." name = "."
} }
p := win32.utf8_to_utf16(name, context.temp_allocator) p := win32.utf8_to_utf16(name, _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)
}
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 { if len(name) == 0 {
return {}, .Not_Exist return {}, .Not_Exist
} }
context.allocator = allocator wname := _fix_long_path(name)
wname := win32.utf8_to_wstring(_fix_long_path(name), context.temp_allocator)
fa: win32.WIN32_FILE_ATTRIBUTE_DATA fa: win32.WIN32_FILE_ATTRIBUTE_DATA
ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa) ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa)
if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
// Not a symlink // Not a symlink
return _file_info_from_win32_file_attribute_data(&fa, name) return _file_info_from_win32_file_attribute_data(&fa, name, allocator)
} }
err := 0 if ok else win32.GetLastError() 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) 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) 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 return
} }
defer win32.CloseHandle(h) 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) { _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (string, Error) {
if fd == 0 { if f == nil || f.impl.fd == nil {
return "", .Invalid_Argument return "", nil
} }
h := win32.HANDLE(fd) h := _handle(f)
MAX_PATH := win32.DWORD(260) + 1 n := win32.GetFinalPathNameByHandleW(h, nil, 0, 0)
buf: []u16 if n == 0 {
for { return "", _get_platform_error()
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
} }
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) { _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) {
if fd == 0 { if f == nil || f.impl.fd == nil {
return nil, .Invalid_Argument return nil, nil
} }
h := win32.HANDLE(fd) h := _handle(f)
MAX_PATH := win32.DWORD(260) + 1 n := win32.GetFinalPathNameByHandleW(h, nil, 0, 0)
buf: []u16 if n == 0 {
for { return nil, _get_platform_error()
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
} }
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 := buf
buf = _cleanpath_strip_prefix(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) { _file_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (mode: File_Mode) {
if FileAttributes & win32.FILE_ATTRIBUTE_READONLY != 0 { if file_attributes & win32.FILE_ATTRIBUTE_READONLY != 0 {
mode |= 0o444 mode |= 0o444
} else { } else {
mode |= 0o666 mode |= 0o666
} }
is_sym := false is_sym := false
if FileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { if file_attributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
is_sym = false is_sym = false
} else { } else {
is_sym = ReparseTag == win32.IO_REPARSE_TAG_SYMLINK || ReparseTag == win32.IO_REPARSE_TAG_MOUNT_POINT is_sym = ReparseTag == win32.IO_REPARSE_TAG_SYMLINK || ReparseTag == win32.IO_REPARSE_TAG_MOUNT_POINT
@@ -232,7 +211,7 @@ _file_mode_from_file_attributes :: proc(FileAttributes: win32.DWORD, h: win32.HA
if is_sym { if is_sym {
mode |= File_Mode_Sym_Link mode |= File_Mode_Sym_Link
} else { } else {
if FileAttributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { if file_attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 {
mode |= 0o111 | File_Mode_Dir 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.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0) 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.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime))
fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime))
fi.fullpath, e = full_path_from_name(name) fi.fullpath, e = full_path_from_name(name, allocator)
fi.name = basename(fi.fullpath) fi.name = basename(fi.fullpath)
return 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.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0) 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.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime))
fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime))
fi.fullpath, e = full_path_from_name(name) fi.fullpath, e = full_path_from_name(name, allocator)
fi.name = basename(fi.fullpath) fi.name = basename(fi.fullpath)
return 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 d: win32.BY_HANDLE_FILE_INFORMATION
if !win32.GetFileInformationByHandle(h, &d) { if !win32.GetFileInformationByHandle(h, &d) {
return {}, _get_platform_error() 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)) { if !win32.GetFileInformationByHandleEx(h, .FileAttributeTagInfo, &ti, size_of(ti)) {
err := win32.GetLastError() err := win32.GetLastError()
if err != win32.ERROR_INVALID_PARAMETER { if err != win32.ERROR_INVALID_PARAMETER {
return {}, Platform_Error{i32(err)} return {}, Platform_Error(err)
} }
// Indicate this is a symlink on FAT file systems // Indicate this is a symlink on FAT file systems
ti.ReparseTag = 0 ti.ReparseTag = 0
@@ -312,58 +291,83 @@ _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HA
return fi, nil 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 { for reserved in reserved_names {
switch path[0] { if strings.equal_fold(path, reserved) {
case 'A'..='Z', 'a'..='z': return true
return path[1] == ':' && is_path_separator(path[2])
} }
} }
return false return false
} }
_fix_long_path :: proc(path: string) -> string { _is_UNC :: proc(path: string) -> bool {
if len(path) < 248 { return _volume_name_len(path) > 2
return path }
}
if len(path) >= 2 && path[:2] == `\\` { _volume_name_len :: proc(path: string) -> int {
return path if ODIN_OS == .Windows {
} if len(path) < 2 {
if !_is_abs(path) { return 0
return path }
} c := path[0]
if path[1] == ':' {
switch c {
case 'a'..='z', 'A'..='Z':
return 2
}
}
prefix :: `\\?` // 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]) &&
path_buf := make([]byte, len(prefix)+len(path)+len(`\`), context.temp_allocator) !_is_path_separator(path[2]) && path[2] != '.' {
copy(path_buf, prefix) for n := 3; n < l-1; n += 1 {
n := len(path) if _is_path_separator(path[n]) {
r, w := 0, len(prefix) n += 1
for r < n { if !_is_path_separator(path[n]) {
switch { if path[n] == '.' {
case is_path_separator(path[r]): break
r += 1 }
case path[r] == '.' && (r+1 == n || is_path_separator(path[r+1])): }
r += 1 for ; n < l; n += 1 {
case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || is_path_separator(path[r+2])): if _is_path_separator(path[n]) {
return path break
case: }
path_buf[w] = '\\' }
w += 1 return n
for ; r < n && !is_path_separator(path[r]); r += 1 { }
path_buf[w] = path[r] break
w += 1
} }
} }
} }
return 0
if w == len(`\\?\c:`) {
path_buf[w] = '\\'
w += 1
}
return string(path_buf[:w])
} }
_is_abs :: proc(path: string) -> bool {
if _is_reserved_name(path) {
return true
}
l := _volume_name_len(path)
if l == 0 {
return false
}
path := path
path = path[l:]
if path == "" {
return false
}
return is_path_separator(path[0])
}
+5 -4
View File
@@ -1,14 +1,15 @@
package os2 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) return _create_temp(dir, pattern)
} }
mkdir_temp :: proc(dir, pattern: string, allocator := context.allocator) -> (string, Error) { mkdir_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (string, Error) {
return _mkdir_temp(dir, pattern) 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) return _temp_dir(allocator)
} }
+7 -5
View File
@@ -1,18 +1,20 @@
//+private //+private
package os2 package os2
import "core:runtime"
_create_temp :: proc(dir, pattern: string) -> (Handle, Error) {
_create_temp :: proc(dir, pattern: string) -> (^File, Error) {
//TODO //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 //TODO
return "", nil return "", nil
} }
_temp_dir :: proc(allocator := context.allocator) -> string { _temp_dir :: proc(allocator: runtime.Allocator) -> (string, Error) {
//TODO //TODO
return "" return "", nil
} }
+17 -17
View File
@@ -1,29 +1,29 @@
//+private //+private
package os2 package os2
import "core:runtime"
import win32 "core:sys/windows" import win32 "core:sys/windows"
_create_temp :: proc(dir, pattern: string) -> (Handle, Error) { _create_temp :: proc(dir, pattern: string) -> (^File, Error) {
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) {
return "", nil return "", nil
} }
_temp_dir :: proc(allocator := context.allocator) -> string { _temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) {
b := make([dynamic]u16, u32(win32.MAX_PATH), context.temp_allocator) n := win32.GetTempPathW(0, nil)
for { if n == 0 {
n := win32.GetTempPathW(u32(len(b)), raw_data(b)) return "", nil
if n > u32(len(b)) {
resize(&b, int(n))
continue
}
if n == 3 && b[1] == ':' && b[2] == '\\' {
} else if n > 0 && b[n-1] == '\\' {
n -= 1
}
return win32.utf16_to_utf8(b[:n], allocator)
} }
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)
} }
+38 -29
View File
@@ -1,66 +1,75 @@
package os2 package os2
import "core:strings" 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 { #partial switch ODIN_OS {
case .Windows: case .Windows:
dir = get_env("LocalAppData") or_return dir, found = get_env("LocalAppData")
if dir != "" { if found {
dir = strings.clone(dir, allocator) dir = strings.clone_safe(dir, allocator) or_return
} }
case .Darwin: case .Darwin:
dir = get_env("HOME") or_return dir, found = get_env("HOME")
if dir != "" { if found {
dir = strings.concatenate({dir, "/Library/Caches"}, allocator) dir = strings.concatenate_safe({dir, "/Library/Caches"}, allocator) or_return
} }
case: // All other UNIX systems case: // All other UNIX systems
dir = get_env("XDG_CACHE_HOME") or_return dir, found = get_env("XDG_CACHE_HOME")
if dir == "" { if found {
dir = get_env("HOME") or_return dir, found = get_env("HOME")
if dir == "" { if !found {
return 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 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 { #partial switch ODIN_OS {
case .Windows: case .Windows:
dir = get_env("AppData") or_return dir, found = get_env("AppData")
if dir != "" { if found {
dir = strings.clone(dir, allocator) dir = strings.clone_safe(dir, allocator) or_return
} }
case .Darwin: case .Darwin:
dir = get_env("HOME") or_return dir, found = get_env("HOME")
if dir != "" { if found {
dir = strings.concatenate({dir, "/Library/Application Support"}, allocator) dir = strings.concatenate_safe({dir, "/Library/Application Support"}, allocator) or_return
} }
case: // All other UNIX systems case: // All other UNIX systems
dir = get_env("XDG_CACHE_HOME") or_return dir, found = get_env("XDG_CACHE_HOME")
if dir == "" { if !found {
dir = get_env("HOME") or_return dir, found = get_env("HOME")
if dir == "" { if !found {
return 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 return
} }
user_home_dir :: proc() -> (dir: string, is_defined: bool) { user_home_dir :: proc() -> (dir: string, err: Error) {
env := "HOME" env := "HOME"
#partial switch ODIN_OS { #partial switch ODIN_OS {
case .Windows: case .Windows:
env = "USERPROFILE" env = "USERPROFILE"
} }
v := get_env(env) or_return if v, found := get_env(env); found {
return v, true return v, nil
}
return "", .Invalid_Path
} }
+12 -15
View File
@@ -163,9 +163,6 @@ O_SYNC :: 0x0080
O_ASYNC :: 0x0040 O_ASYNC :: 0x0040
O_CLOEXEC :: 0x1000000 O_CLOEXEC :: 0x1000000
SEEK_SET :: 0
SEEK_CUR :: 1
SEEK_END :: 2
SEEK_DATA :: 3 SEEK_DATA :: 3
SEEK_HOLE :: 4 SEEK_HOLE :: 4
SEEK_MAX :: SEEK_HOLE SEEK_MAX :: SEEK_HOLE
@@ -279,7 +276,7 @@ foreign libc {
@(link_name="__error") __error :: proc() -> ^int --- @(link_name="__error") __error :: proc() -> ^int ---
@(link_name="open") _unix_open :: proc(path: cstring, flags: i32, mode: u16) -> Handle --- @(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="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="write") _unix_write :: proc(handle: Handle, buffer: rawptr, count: int) -> int ---
@(link_name="lseek") _unix_lseek :: proc(fs: Handle, offset: int, whence: 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="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int ---
@(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) --- @(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="rename") _unix_rename :: proc(old: cstring, new: cstring) -> c.int ---
@(link_name="remove") _unix_remove :: proc(path: 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="malloc") _unix_malloc :: proc(size: int) -> rawptr ---
@(link_name="calloc") _unix_calloc :: proc(num, 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 return handle, 0
} }
fchmod :: proc(fildes: Handle, mode: u16) -> Errno { fchmod :: proc(fd: Handle, mode: u16) -> Errno {
return cast(Errno)_unix_fchmod(fildes, mode) return cast(Errno)_unix_fchmod(fd, mode)
} }
close :: proc(fd: Handle) { close :: proc(fd: Handle) -> bool {
_unix_close(fd) return _unix_close(fd) == 0
} }
write :: proc(fd: Handle, data: []u8) -> (int, Errno) { 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 { rename :: proc(old: string, new: string) -> bool {
old_cstr := strings.clone_to_cstring(old, context.temp_allocator) old_cstr := strings.clone_to_cstring(old, context.temp_allocator)
new_cstr := strings.clone_to_cstring(new, 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 { remove :: proc(path: string) -> bool {
path_cstr := strings.clone_to_cstring(path, context.temp_allocator) path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
return _unix_remove(path_cstr) != -1 return _unix_remove(path_cstr) != -1
} }
@private @private
@@ -549,7 +546,7 @@ _rewinddir :: proc(dirp: Dir) {
_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) { _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) {
result: ^Dirent result: ^Dirent
rc := _unix_readdir_r(dirp, &entry, &result) rc := _unix_readdir_r(dirp, &entry, &result)
if rc != 0 { if rc != 0 {
err = Errno(get_last_error()) err = Errno(get_last_error())
return return
@@ -589,7 +586,7 @@ _readlink :: proc(path: string) -> (string, Errno) {
absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) { absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) {
buf : [256]byte buf : [256]byte
res := _unix_fcntl(fd, F_GETPATH, &buf[0]) res := _unix__fcntl(fd, F_GETPATH, &buf[0])
if res != 0 { if res != 0 {
return "", Errno(get_last_error()) return "", Errno(get_last_error())
} }
-3
View File
@@ -123,9 +123,6 @@ O_ASYNC :: 0x02000
O_CLOEXEC :: 0x80000 O_CLOEXEC :: 0x80000
SEEK_SET :: 0
SEEK_CUR :: 1
SEEK_END :: 2
SEEK_DATA :: 3 SEEK_DATA :: 3
SEEK_HOLE :: 4 SEEK_HOLE :: 4
SEEK_MAX :: SEEK_HOLE SEEK_MAX :: SEEK_HOLE
+24 -6
View File
@@ -167,9 +167,6 @@ O_ASYNC :: 0x02000
O_CLOEXEC :: 0x80000 O_CLOEXEC :: 0x80000
SEEK_SET :: 0
SEEK_CUR :: 1
SEEK_END :: 2
SEEK_DATA :: 3 SEEK_DATA :: 3
SEEK_HOLE :: 4 SEEK_HOLE :: 4
SEEK_MAX :: SEEK_HOLE 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="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr ---
@(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring ---
@(link_name="putenv") _unix_putenv :: proc(cstring) -> c.int ---
@(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr --- @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr ---
@(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- @(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_file :: proc {is_file_path, is_file_handle}
is_dir :: proc {is_dir_path, is_dir_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 // NOTE(bill): Uses startup to initialize it
@@ -767,13 +770,28 @@ heap_free :: proc(ptr: rawptr) {
_unix_free(ptr) _unix_free(ptr)
} }
getenv :: proc(name: string) -> (string, bool) { lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
path_str := strings.clone_to_cstring(name, context.temp_allocator) 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) cstr := _unix_getenv(path_str)
if cstr == nil { if cstr == nil {
return "", false 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 { get_current_directory :: proc() -> string {
-4
View File
@@ -125,10 +125,6 @@ O_EXCL :: 0x00800
O_NOCTTY :: 0x08000 O_NOCTTY :: 0x08000
O_CLOEXEC :: 0x10000 O_CLOEXEC :: 0x10000
SEEK_SET :: 0
SEEK_CUR :: 1
SEEK_END :: 2
RTLD_LAZY :: 0x001 RTLD_LAZY :: 0x001
RTLD_NOW :: 0x002 RTLD_NOW :: 0x002
RTLD_LOCAL :: 0x000 RTLD_LOCAL :: 0x000
-1
View File
@@ -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) { stat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, err: Errno) {
context.allocator = allocator context.allocator = allocator
s: OS_Stat s: OS_Stat
+3 -3
View File
@@ -20,7 +20,7 @@ full_path_from_name :: proc(name: string, allocator := context.allocator) -> (pa
return "", Errno(win32.GetLastError()) return "", Errno(win32.GetLastError())
} }
if n <= u32(len(buf)) { 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) resize(&buf, len(buf)*2)
} }
@@ -136,7 +136,7 @@ cleanpath_from_handle :: proc(fd: Handle) -> (string, Errno) {
if err != 0 { if err != 0 {
return "", err return "", err
} }
return win32.utf16_to_utf8(buf, context.allocator), err return win32.utf16_to_utf8(buf, context.allocator) or_else "", err
} }
@(private) @(private)
cleanpath_from_handle_u16 :: proc(fd: Handle) -> ([]u16, Errno) { 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 { cleanpath_from_buf :: proc(buf: []u16) -> string {
buf := buf buf := buf
buf = cleanpath_strip_prefix(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) @(private)
+124 -9
View File
@@ -4,6 +4,8 @@ package filepath
import "core:strings" import "core:strings"
SEPARATOR_CHARS :: `/\`
// is_separator checks whether the byte is a valid separator character // is_separator checks whether the byte is a valid separator character
is_separator :: proc(c: byte) -> bool { is_separator :: proc(c: byte) -> bool {
switch c { switch c {
@@ -69,6 +71,16 @@ volume_name_len :: proc(path: string) -> int {
return 0 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 { base :: proc(path: string) -> string {
if path == "" { if path == "" {
return "." return "."
@@ -94,6 +106,118 @@ base :: proc(path: string) -> string {
return path 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 { clean :: proc(path: string, allocator := context.allocator) -> string {
context.allocator = allocator 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) 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 { Relative_Error :: enum {
None, None,
+1 -1
View File
@@ -68,7 +68,7 @@ temp_full_path :: proc(name: string) -> (path: string, err: os.Errno) {
return "", os.Errno(win32.GetLastError()) return "", os.Errno(win32.GetLastError())
} }
if n <= u32(len(buf)) { 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) resize(&buf, len(buf)*2)
} }
+3 -1
View File
@@ -401,6 +401,7 @@ Raw_Cstring :: struct {
Linux, Linux,
Essence, Essence,
FreeBSD, FreeBSD,
OpenBSD,
WASI, WASI,
JS, JS,
Freestanding, Freestanding,
@@ -414,6 +415,7 @@ Odin_OS_Type :: type_of(ODIN_OS)
Unknown, Unknown,
amd64, amd64,
i386, i386,
arm32,
arm64, arm64,
wasm32, wasm32,
wasm64, wasm64,
@@ -565,7 +567,7 @@ __init_context :: proc "contextless" (c: ^Context) {
return 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.procedure = default_allocator_proc
c.allocator.data = nil c.allocator.data = nil
+14 -2
View File
@@ -5,6 +5,16 @@ import "core:intrinsics"
@builtin @builtin
Maybe :: union($T: typeid) #maybe {T} 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 @thread_local global_default_temp_allocator_data: Default_Temp_Allocator
@builtin @builtin
@@ -621,13 +631,15 @@ assert :: proc(condition: bool, message := "", loc := #caller_location) {
// to improve performance to make the CPU not // to improve performance to make the CPU not
// execute speculatively, making it about an order of // execute speculatively, making it about an order of
// magnitude faster // magnitude faster
proc(message: string, loc: Source_Code_Location) { @(cold)
internal :: proc(message: string, loc: Source_Code_Location) {
p := context.assertion_failure_proc p := context.assertion_failure_proc
if p == nil { if p == nil {
p = default_assertion_failure_proc p = default_assertion_failure_proc
} }
p("runtime assertion", message, loc) p("runtime assertion", message, loc)
}(message, loc) }
internal(message, loc)
} }
} }
+1 -1
View File
@@ -4,7 +4,7 @@ when ODIN_NO_CRT && ODIN_OS == .Windows {
foreign import lib "system:NtDll.lib" foreign import lib "system:NtDll.lib"
@(private="file") @(private="file")
@(default_calling_convention="std") @(default_calling_convention="stdcall")
foreign lib { foreign lib {
RtlMoveMemory :: proc(dst, src: rawptr, length: int) --- RtlMoveMemory :: proc(dst, src: rawptr, length: int) ---
RtlFillMemory :: proc(dst: rawptr, length: int, fill: i32) --- RtlFillMemory :: proc(dst: rawptr, length: int, fill: i32) ---
+47
View File
@@ -10,6 +10,53 @@ _ :: builtin
_ :: bits _ :: bits
_ :: mem _ :: 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) { swap :: proc(array: $T/[]$E, a, b: int) {
when size_of(E) > 8 { when size_of(E) > 8 {
+1 -1
View File
@@ -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 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 { if max_depth == 0 {
heap_sort(data, a, b, call) heap_sort(data, a, b, call)
return return
+3 -2
View File
@@ -124,11 +124,11 @@ reset_builder :: proc(b: ^Builder) {
used in `fmt.bprint*` used in `fmt.bprint*`
bytes: [8]byte // <-- gets filled 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, 'a') -> "a"
strings.write_byte(&builder, 'b') -> "ab" 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 s := transmute(mem.Raw_Slice)backing
d := mem.Raw_Dynamic_Array{ d := mem.Raw_Dynamic_Array{
data = s.data, data = s.data,
@@ -140,6 +140,7 @@ builder_from_slice :: proc(backing: []byte) -> Builder {
buf = transmute([dynamic]byte)d, buf = transmute([dynamic]byte)d,
} }
} }
builder_from_slice :: builder_from_bytes
// cast the builder byte buffer to a string and return it // cast the builder byte buffer to a string and return it
to_string :: proc(b: Builder) -> string { to_string :: proc(b: Builder) -> string {

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