From 7f6c6945aef7b26632093fa36b56b2951cb27165 Mon Sep 17 00:00:00 2001 From: Despacito696969 <56641258+Despacito696969@users.noreply.github.com> Date: Tue, 5 Apr 2022 20:17:47 +0200 Subject: [PATCH 01/43] Fix for `slice_to_components` Using `slice_to_components` wouldn't compile because `s.data` is type of `rawptr` and return type is `^T` --- core/mem/mem.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/mem/mem.odin b/core/mem/mem.odin index 817c0ea5f..d3dcf3a3d 100644 --- a/core/mem/mem.odin +++ b/core/mem/mem.odin @@ -166,7 +166,7 @@ slice_data_cast :: proc "contextless" ($T: typeid/[]$A, slice: $S/[]$B) -> T { slice_to_components :: proc "contextless" (slice: $E/[]$T) -> (data: ^T, len: int) { s := transmute(Raw_Slice)slice - return s.data, s.len + return (^T)(s.data), s.len } buffer_from_slice :: proc "contextless" (backing: $T/[]$E) -> [dynamic]E { From 939973acd7a8ea17cc9dfda2a55e79dd0109a49c Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 17 Apr 2022 12:35:34 +0200 Subject: [PATCH 02/43] [QOI] Add to examples/all. --- examples/all/all_main.odin | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index 6a039e4dd..4f5bfbdc1 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -61,6 +61,7 @@ import hash "core:hash" import image "core:image" import png "core:image/png" +import qoi "core:image/qoi" import io "core:io" import log "core:log" @@ -159,6 +160,7 @@ _ :: fmt _ :: hash _ :: image _ :: png +_ :: qoi _ :: io _ :: log _ :: math From b78f3a806930fe026c027aedf511696bc6691b96 Mon Sep 17 00:00:00 2001 From: hikari Date: Sun, 17 Apr 2022 19:17:38 +0300 Subject: [PATCH 03/43] sys/windows: add timeEndPeriod --- core/sys/windows/winmm.odin | 1 + 1 file changed, 1 insertion(+) diff --git a/core/sys/windows/winmm.odin b/core/sys/windows/winmm.odin index 6d3fc409e..9edd56acc 100644 --- a/core/sys/windows/winmm.odin +++ b/core/sys/windows/winmm.odin @@ -6,4 +6,5 @@ foreign import winmm "system:Winmm.lib" @(default_calling_convention="stdcall") foreign winmm { timeBeginPeriod :: proc(uPeriod: UINT) -> MMRESULT --- + timeEndPeriod :: proc(uPeriod: UINT) -> MMRESULT --- } From 4247ba67ed83a16c321363cdf8260f71826325ca Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Mon, 18 Apr 2022 15:24:54 +0800 Subject: [PATCH 04/43] Fix bugs in core:container/lru --- core/container/lru/lru_cache.odin | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/core/container/lru/lru_cache.odin b/core/container/lru/lru_cache.odin index f8e6f7b46..81f0142b0 100644 --- a/core/container/lru/lru_cache.odin +++ b/core/container/lru/lru_cache.odin @@ -60,6 +60,8 @@ clear :: proc(c: ^$C/Cache($Key, $Value), call_on_remove: bool) { set :: proc(c: ^$C/Cache($Key, $Value), key: Key, value: Value) -> runtime.Allocator_Error { if e, ok := c.entries[key]; ok { e.value = value + _pop_node(c, e) + _push_front_node(c, e) return nil } @@ -67,10 +69,14 @@ set :: proc(c: ^$C/Cache($Key, $Value), key: Key, value: Value) -> runtime.Alloc e.key = key e.value = value - _push_front_node(c, e) - if c.count > c.capacity { + assert(c.count <= c.capacity) + if c.count == c.capacity { _remove_node(c, c.tail) } + else { + c.count += 1 + } + _push_front_node(c, e) c.entries[key] = e return nil @@ -122,6 +128,7 @@ remove :: proc(c: ^$C/Cache($Key, $Value), key: Key) -> bool { return false } _remove_node(c, e) + c.count -= 1 return true } @@ -143,8 +150,6 @@ _remove_node :: proc(c: ^$C/Cache($Key, $Value), node: ^Node(Key, Value)) { node.prev = nil node.next = nil - c.count -= 1 - delete_key(&c.entries, node.key) _call_on_remove(c, node) @@ -171,8 +176,6 @@ _push_front_node :: proc(c: ^$C/Cache($Key, $Value), e: ^Node(Key, Value)) { c.tail = e } e.prev = nil - - c.count += 1 } @(private) @@ -180,6 +183,12 @@ _pop_node :: proc(c: ^$C/Cache($Key, $Value), e: ^Node(Key, Value)) { if e == nil { return } + if c.head == e { + c.head = e.next + } + if c.tail == e { + c.tail = e.prev + } if e.prev != nil { e.prev.next = e.next } From 7428e5226426d1d19601d5b557c3df51db233857 Mon Sep 17 00:00:00 2001 From: Tetralux Date: Thu, 14 Apr 2022 16:53:48 +0000 Subject: [PATCH 05/43] Duplicate some basic slice procedures from core:mem into core:slice --- core/slice/slice.odin | 47 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/core/slice/slice.odin b/core/slice/slice.odin index 520e3e1e0..b8fb29ab3 100644 --- a/core/slice/slice.odin +++ b/core/slice/slice.odin @@ -10,6 +10,53 @@ _ :: builtin _ :: bits _ :: mem +/* + Turn a pointer and a length into a slice. +*/ +from_ptr :: proc "contextless" (ptr: ^$T, count: int) -> []T { + return ([^]T)(ptr)[:count] +} + +/* + Turn a pointer and a length into a byte slice. +*/ +bytes_from_ptr :: proc "contextless" (ptr: rawptr, byte_count: int) -> []byte { + return ([^]byte)(ptr)[:byte_count] +} + +/* + Turn a slice into a byte slice. + + See `slice.reinterpret` to go the other way. +*/ +to_bytes :: proc "contextless" (s: []$T) -> []byte { + return ([^]byte)(raw_data(s))[:len(s) * size_of(T)] +} + +/* + Turn a slice of one type, into a slice of another type. + + Only converts the type and length of the slice itself. + The length is rounded down to the nearest whole number of items. + + ``` + large_items := []i64{1, 2, 3, 4} + small_items := slice.reinterpret([]i32, large_items) + assert(len(small_items) == 8) + ``` + ``` + small_items := []byte{1, 0, 0, 0, 0, 0, 0, 0, + 2, 0, 0, 0} + large_items := slice.reinterpret([]i64, small_items) + assert(len(large_items) == 1) // only enough bytes to make 1 x i64; two would need at least 8 bytes. + ``` +*/ +reinterpret :: proc "contextless" ($T: typeid/[]$U, s: []$V) -> []U { + bytes := to_bytes(s) + n := len(bytes) / size_of(U) + return ([^]U)(raw_data(bytes))[:n] +} + swap :: proc(array: $T/[]$E, a, b: int) { when size_of(E) > 8 { From df4a0c62add28af74ac8c926b3a6e0612296ccd1 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 18 Apr 2022 19:10:53 +0200 Subject: [PATCH 06/43] Delete accidentally added test artefact. --- tests/core/encoding/varint/varint | Bin 308984 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100755 tests/core/encoding/varint/varint diff --git a/tests/core/encoding/varint/varint b/tests/core/encoding/varint/varint deleted file mode 100755 index 1e982544582db7a8f719d5229759a019afa45103..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 308984 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�(1)x9! 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 From fdd24f787fd6df5c1f3339d6317c76a1bafe60f5 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 18 Apr 2022 23:28:34 +0200 Subject: [PATCH 07/43] [image/tga] Writer for RGB(A) 8-bit images. --- core/image/common.odin | 14 ++++++ core/image/tga/tga.odin | 103 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 core/image/tga/tga.odin diff --git a/core/image/common.odin b/core/image/common.odin index 2e7bca17e..8c77ec48a 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -320,6 +320,20 @@ QOI_Info :: struct { header: QOI_Header, } +TGA_Header :: struct #packed { + id_length: u8, + color_map_type: u8, + data_type_code: u8, + color_map_origin: u16le, + color_map_length: u16le, + color_map_depth: u8, + origin: [2]u16le, + dimensions: [2]u16le, + bits_per_pixel: u8, + image_descriptor: u8, +} +#assert(size_of(TGA_Header) == 18) + // Function to help with image buffer calculations compute_buffer_size :: proc(width, height, channels, depth: int, extra_row_bytes := int(0)) -> (size: int) { size = ((((channels * width * depth) + 7) >> 3) + extra_row_bytes) * height diff --git a/core/image/tga/tga.odin b/core/image/tga/tga.odin new file mode 100644 index 000000000..3c860cb62 --- /dev/null +++ b/core/image/tga/tga.odin @@ -0,0 +1,103 @@ +/* + Copyright 2022 Jeroen van Rijn . + 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:compress" +import "core:bytes" +import "core:os" + +Error :: image.Error +General :: compress.General_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 General.Resize_Failed + } + + 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 General.Cannot_Open_File +} + +save :: proc{save_to_memory, save_to_file} \ No newline at end of file From aa4eb35671c519c520bd25ff90837c1d70558c6b Mon Sep 17 00:00:00 2001 From: hikari Date: Tue, 19 Apr 2022 05:58:22 +0300 Subject: [PATCH 08/43] sys/windows: add some procedures --- core/sys/windows/kernel32.odin | 7 +++++++ core/sys/windows/user32.odin | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index 735e065e2..cb90f71da 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -62,6 +62,13 @@ foreign kernel32 { GetCurrentProcessId :: proc() -> DWORD --- GetCurrentThread :: proc() -> HANDLE --- GetCurrentThreadId :: proc() -> DWORD --- + GetProcessTimes :: proc( + hProcess: HANDLE, + lpCreationTime: LPFILETIME, + lpExitTime: LPFILETIME, + lpKernelTime: LPFILETIME, + lpUserTime: LPFILETIME, + ) -> BOOL --- GetStdHandle :: proc(which: DWORD) -> HANDLE --- ExitProcess :: proc(uExitCode: c_uint) -> ! --- DeviceIoControl :: proc( diff --git a/core/sys/windows/user32.odin b/core/sys/windows/user32.odin index 2316d3363..dd45df42a 100644 --- a/core/sys/windows/user32.odin +++ b/core/sys/windows/user32.odin @@ -60,6 +60,12 @@ foreign user32 { DestroyWindow :: proc(hWnd: HWND) -> BOOL --- ShowWindow :: proc(hWnd: HWND, nCmdShow: c_int) -> BOOL --- + BringWindowToTop :: proc(hWnd: HWND) -> BOOL --- + GetTopWindow :: proc(hWnd: HWND) -> HWND --- + SetForegroundWindow :: proc(hWnd: HWND) -> BOOL --- + GetForegroundWindow :: proc() -> HWND --- + SetActiveWindow :: proc(hWnd: HWND) -> HWND --- + GetActiveWindow :: proc() -> HWND --- GetMessageA :: proc(lpMsg: ^MSG, hWnd: HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT) -> BOOL --- GetMessageW :: proc(lpMsg: ^MSG, hWnd: HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT) -> BOOL --- From ded8342f3fdb176d6c264057f3bdcb890bfcfad7 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Tue, 19 Apr 2022 20:46:33 +0800 Subject: [PATCH 09/43] Reduce allocations --- core/container/lru/lru_cache.odin | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/core/container/lru/lru_cache.odin b/core/container/lru/lru_cache.odin index 81f0142b0..b59f29f0c 100644 --- a/core/container/lru/lru_cache.odin +++ b/core/container/lru/lru_cache.odin @@ -65,20 +65,22 @@ set :: proc(c: ^$C/Cache($Key, $Value), key: Key, value: Value) -> runtime.Alloc return nil } - e := new(Node(Key, Value), c.node_allocator) or_return - e.key = key - e.value = value - + e : ^Node(Key, Value) = nil assert(c.count <= c.capacity) if c.count == c.capacity { - _remove_node(c, c.tail) + e = c.tail + _remove_node(c, e) } else { c.count += 1 + e = new(Node(Key, Value), c.node_allocator) or_return } - _push_front_node(c, e) + e.key = key + e.value = value + _push_front_node(c, e) c.entries[key] = e + return nil } @@ -128,6 +130,7 @@ remove :: proc(c: ^$C/Cache($Key, $Value), key: Key) -> bool { return false } _remove_node(c, e) + free(node, c.node_allocator) c.count -= 1 return true } @@ -153,9 +156,6 @@ _remove_node :: proc(c: ^$C/Cache($Key, $Value), node: ^Node(Key, Value)) { delete_key(&c.entries, node.key) _call_on_remove(c, node) - - free(node, c.node_allocator) - } @(private) From 7654afc2db9e88b1803862310698ca047bf10a01 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 19 Apr 2022 15:01:54 +0200 Subject: [PATCH 10/43] Revert "Update `mem.nil_allocator` to match the same in `runtime`" The change broke JSON unmarshaling. This reverts commit 4484a3433d6c58f1d1c594a4c36317f323cb5102. --- core/mem/allocators.odin | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/core/mem/allocators.odin b/core/mem/allocators.odin index 4954122ed..b8bd9a065 100644 --- a/core/mem/allocators.odin +++ b/core/mem/allocators.odin @@ -6,24 +6,7 @@ import "core:runtime" nil_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, size, alignment: int, old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { - switch mode { - case .Alloc: - return nil, .Out_Of_Memory - case .Free: - return nil, .None - case .Free_All: - return nil, .Mode_Not_Implemented - case .Resize: - if size == 0 { - return nil, .None - } - return nil, .Out_Of_Memory - case .Query_Features: - return nil, .Mode_Not_Implemented - case .Query_Info: - return nil, .Mode_Not_Implemented - } - return nil, .None + return nil, nil } nil_allocator :: proc() -> Allocator { From 323e7a2d02819ef0b7137abbdfee8a0d48f5cecb Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 19 Apr 2022 15:03:09 +0200 Subject: [PATCH 11/43] Add JSON unmarshal test. --- tests/core/encoding/json/test_core_json.odin | 253 +++++++++++++++++++ 1 file changed, 253 insertions(+) diff --git a/tests/core/encoding/json/test_core_json.odin b/tests/core/encoding/json/test_core_json.odin index 285cc04a1..c83710352 100644 --- a/tests/core/encoding/json/test_core_json.odin +++ b/tests/core/encoding/json/test_core_json.odin @@ -31,6 +31,7 @@ main :: proc() { parse_json(&t) marshal_json(&t) + unmarshal_json(&t) fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) if TEST_fail > 0 { @@ -90,3 +91,255 @@ marshal_json :: proc(t: ^testing.T) { expect(t, err == nil, "expected json error to be none") } + +PRODUCTS := ` +{ + "cash": "0", + "products": [ + { + "name": "Cog Cola", + "cost": "3", + "owned": "1", + + "profit": "4", + "seconds": 3, + "multiplier": 1, + "auto_click": false + }, + { + "name": "gingerBeer", + "cost": "9", + "owned": "0", + + "profit": "16", + "seconds": 5, + "multiplier": 1, + "auto_click": false + }, + { + "name": "Coffee", + "cost": "27", + "owned": "0", + + "profit": "64", + "seconds": 7, + "multiplier": 1, + "auto_click": false + }, + { + "name": "Haggis", + "cost": "81", + "owned": "0", + + "profit": "256", + "seconds": 11, + "multiplier": 1, + "auto_click": false + }, + { + "name": "Lasagna", + "cost": "243", + "owned": "0", + + "profit": "1024", + "seconds": 13, + "multiplier": 1, + "auto_click": false + }, + { + "name": "Asparagus", + "cost": "729", + "owned": "0", + + "profit": "4096", + "seconds": 17, + "multiplier": 1, + "auto_click": false + }, + { + "name": "Yorkshire Pudding", + "cost": "2187", + "owned": "0", + + "profit": "16384", + "seconds": 19, + "multiplier": 1, + "auto_click": false + }, + { + "name": "Salmon Wrap", + "cost": "6561", + "owned": "0", + + "profit": "65536", + "seconds": 23, + "multiplier": 1, + "auto_click": false + }, + { + "name": "Poke Bowl", + "cost": "19683", + "owned": "0", + + "profit": "262144", + "seconds": 29, + "multiplier": 1, + "auto_click": false + }, + { + "name": "Chili Con Carne", + "cost": "59049", + "owned": "0", + + "profit": "1048576", + "seconds": 59, + "multiplier": 1, + "auto_click": false + }, + ], +} +` + +original_data := Game_Marshal{ + cash = "0", + products = { + { + name = "Cog Cola", + cost = "3", + owned = "1", + profit = "4", + seconds = 3, + multiplier = 1, + auto_click = false, + }, + { + name = "gingerBeer", + cost = "9", + owned = "0", + profit = "16", + seconds = 5, + multiplier = 1, + auto_click = false, + }, + { + name = "Coffee", + cost = "27", + owned = "0", + profit = "64", + seconds = 7, + multiplier = 1, + auto_click = false, + }, + { + name = "Haggis", + cost = "81", + owned = "0", + profit = "256", + seconds = 11, + multiplier = 1, + auto_click = false, + }, + { + name = "Lasagna", + cost = "243", + owned = "0", + profit = "1024", + seconds = 13, + multiplier = 1, + auto_click = false, + }, + { + name = "Asparagus", + cost = "729", + owned = "0", + profit = "4096", + seconds = 17, + multiplier = 1, + auto_click = false, + }, + { + name = "Yorkshire Pudding", + cost = "2187", + owned = "0", + profit = "16384", + seconds = 19, + multiplier = 1, + auto_click = false, + }, + { + name = "Salmon Wrap", + cost = "6561", + owned = "0", + profit = "65536", + seconds = 23, + multiplier = 1, + auto_click = false, + }, + { + name = "Poke Bowl", + cost = "19683", + owned = "0", + profit = "262144", + seconds = 29, + multiplier = 1, + auto_click = false, + }, + { + name = "Chili Con Carne", + cost = "59049", + owned = "0", + profit = "1048576", + seconds = 59, + multiplier = 1, + auto_click = false, + }, + }, +} + +Product_Marshal :: struct { + name: cstring, + owned: string, + + cost: string, + + profit: string, + seconds: int, + multiplier: int, + + auto_click: bool, +} + +Game_Marshal :: struct { + cash: string, + products: []Product_Marshal, +} + +cleanup :: proc(g: Game_Marshal) { + for p in g.products { + delete(p.name) + delete(p.owned) + delete(p.cost) + delete(p.profit) + } + delete(g.products) + delete(g.cash) +} + +@test +unmarshal_json :: proc(t: ^testing.T) { + g: Game_Marshal + err := json.unmarshal(transmute([]u8)PRODUCTS, &g, json.DEFAULT_SPECIFICATION) + defer cleanup(g) + + expect(t, err == nil, "Expected json error to be nil") + + msg := fmt.tprintf("Expected %v products to have been unmarshaled, got %v", len(original_data.products), len(g.products)) + expect(t, len(g.products) == len(original_data.products), msg) + + msg = fmt.tprintf("Expected cash to have been unmarshaled as %v, got %v", original_data.cash, g.cash) + expect(t, original_data.cash == g.cash, "Cash unmarshaled improperly") + + for p, i in g.products { + expect(t, p == original_data.products[i], "Producted unmarshaled improperly") + } +} \ No newline at end of file From 581255bf23af90b77bb2b6e2671b40e2b565754e Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 19 Apr 2022 20:04:38 +0200 Subject: [PATCH 12/43] Fix unmarshal for escaped strings. --- core/encoding/json/parser.odin | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/encoding/json/parser.odin b/core/encoding/json/parser.odin index c682ec9bd..0b9a1cf33 100644 --- a/core/encoding/json/parser.odin +++ b/core/encoding/json/parser.odin @@ -281,6 +281,11 @@ parse_object :: proc(p: ^Parser) -> (value: Value, err: Error) { // IMPORTANT NOTE(bill): unquote_string assumes a mostly valid string unquote_string :: proc(token: Token, spec: Specification, allocator := context.allocator) -> (value: string, err: Error) { + if allocator.data == nil { + // We were called from `unmarshal_count_array`, return early. + return "", nil + } + get_u2_rune :: proc(s: string) -> rune { if len(s) < 4 || s[0] != '\\' || s[1] != 'x' { return -1 From 29b2c0476698d0f4b240e87945cfa278da82b57a Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 19 Apr 2022 20:11:02 +0200 Subject: [PATCH 13/43] Revert "Fix unmarshal for escaped strings." This reverts commit 581255bf23af90b77bb2b6e2671b40e2b565754e. --- core/encoding/json/parser.odin | 5 ----- 1 file changed, 5 deletions(-) diff --git a/core/encoding/json/parser.odin b/core/encoding/json/parser.odin index 0b9a1cf33..c682ec9bd 100644 --- a/core/encoding/json/parser.odin +++ b/core/encoding/json/parser.odin @@ -281,11 +281,6 @@ parse_object :: proc(p: ^Parser) -> (value: Value, err: Error) { // IMPORTANT NOTE(bill): unquote_string assumes a mostly valid string unquote_string :: proc(token: Token, spec: Specification, allocator := context.allocator) -> (value: string, err: Error) { - if allocator.data == nil { - // We were called from `unmarshal_count_array`, return early. - return "", nil - } - get_u2_rune :: proc(s: string) -> rune { if len(s) < 4 || s[0] != '\\' || s[1] != 'x' { return -1 From a30b9b17b3a91bc856a037c1e1025e389a8524b3 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 19 Apr 2022 20:32:22 +0200 Subject: [PATCH 14/43] [json/unmarshal] Fix quoted strings. --- core/encoding/json/parser.odin | 6 ++++++ tests/core/encoding/json/test_core_json.odin | 20 +++++++++++--------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/core/encoding/json/parser.odin b/core/encoding/json/parser.odin index c682ec9bd..7bf88c565 100644 --- a/core/encoding/json/parser.odin +++ b/core/encoding/json/parser.odin @@ -354,6 +354,12 @@ unquote_string :: proc(token: Token, spec: Specification, allocator := context.a b := bytes_make(len(s) + 2*utf8.UTF_MAX, 1, allocator) or_return w := copy(b, s[0:i]) + + if len(b) == 0 && allocator.data == nil { + // `unmarshal_count_array` calls us with a nil allocator + return string(b[:w]), nil + } + loop: for i < len(s) { c := s[i] switch { diff --git a/tests/core/encoding/json/test_core_json.odin b/tests/core/encoding/json/test_core_json.odin index c83710352..0e6a6412f 100644 --- a/tests/core/encoding/json/test_core_json.odin +++ b/tests/core/encoding/json/test_core_json.odin @@ -71,7 +71,8 @@ parse_json :: proc(t: ^testing.T) { _, err := json.parse(transmute([]u8)json_data) - expect(t, err == .None, "expected json error to be none") + msg := fmt.tprintf("Expected `json.parse` to return nil, got %v", err) + expect(t, err == nil, msg) } @test @@ -88,8 +89,8 @@ marshal_json :: proc(t: ^testing.T) { } _, err := json.marshal(my_struct) - - expect(t, err == nil, "expected json error to be none") + msg := fmt.tprintf("Expected `json.marshal` to return nil, got %v", err) + expect(t, err == nil, msg) } PRODUCTS := ` @@ -97,7 +98,7 @@ PRODUCTS := ` "cash": "0", "products": [ { - "name": "Cog Cola", + "name": "Cog\nCola", "cost": "3", "owned": "1", @@ -204,7 +205,7 @@ original_data := Game_Marshal{ cash = "0", products = { { - name = "Cog Cola", + name = "Cog\nCola", cost = "3", owned = "1", profit = "4", @@ -331,13 +332,14 @@ unmarshal_json :: proc(t: ^testing.T) { err := json.unmarshal(transmute([]u8)PRODUCTS, &g, json.DEFAULT_SPECIFICATION) defer cleanup(g) - expect(t, err == nil, "Expected json error to be nil") + msg := fmt.tprintf("Expected `json.unmarshal` to return nil, got %v", err) + expect(t, err == nil, msg) - msg := fmt.tprintf("Expected %v products to have been unmarshaled, got %v", len(original_data.products), len(g.products)) + msg = fmt.tprintf("Expected %v products to have been unmarshaled, got %v", len(original_data.products), len(g.products)) expect(t, len(g.products) == len(original_data.products), msg) - msg = fmt.tprintf("Expected cash to have been unmarshaled as %v, got %v", original_data.cash, g.cash) - expect(t, original_data.cash == g.cash, "Cash unmarshaled improperly") + msg = fmt.tprintf("Expected cash to have been unmarshaled as %v, got %v", original_data.cash, g.cash) + expect(t, original_data.cash == g.cash, msg) for p, i in g.products { expect(t, p == original_data.products[i], "Producted unmarshaled improperly") From eee97f7f62bbd65dd03ea3ec8668fef3fcfc685c Mon Sep 17 00:00:00 2001 From: hikari Date: Thu, 21 Apr 2022 20:49:32 +0300 Subject: [PATCH 15/43] strings: add levenshtein_distance procedure --- core/strings/strings.odin | 59 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/core/strings/strings.odin b/core/strings/strings.odin index 8e774b367..87bbb42cf 100644 --- a/core/strings/strings.odin +++ b/core/strings/strings.odin @@ -1809,3 +1809,62 @@ fields_iterator :: proc(s: ^string) -> (field: string, ok: bool) { s^ = s[len(s):] return } + +// `levenshtein_distance` returns the Levenshtein edit distance between 2 strings. +// This is a single-row-version of the Wagner–Fischer algorithm, based on C code by Martin Ettl. +// Note: allocator isn't used if the length of string b in runes is smaller than 70. +levenshtein_distance :: proc(a, b: string, allocator := context.allocator) -> int { + LEVENSHTEIN_DEFAULT_COSTS: []int : { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, + } + + m, n := utf8.rune_count_in_string(a), utf8.rune_count_in_string(b) + + if m == 0 do return n + if n == 0 do return m + + costs: []int + + if n + 1 > len(LEVENSHTEIN_DEFAULT_COSTS) { + costs = make([]int, n + 1, allocator) + } else { + costs = LEVENSHTEIN_DEFAULT_COSTS + } + + defer if n + 1 > len(LEVENSHTEIN_DEFAULT_COSTS) { + delete(costs, allocator) + } + + for k in 0..=n { + costs[k] = k + } + + i: int + for c1 in a { + costs[0] = i + 1 + corner := i + j: int + for c2 in b { + upper := costs[j + 1] + if c1 == c2 { + costs[j + 1] = corner + } else { + t := upper if upper < corner else corner + costs[j + 1] = (costs[j] if costs[j] < t else t) + 1 + } + + corner = upper + j += 1 + } + + i += 1 + } + + return costs[n] +} From 591732f347c094e706471994996fcffa44687e89 Mon Sep 17 00:00:00 2001 From: hikari Date: Thu, 21 Apr 2022 20:58:50 +0300 Subject: [PATCH 16/43] strings: levenshtein_distance: remove costs calculation for default array --- core/strings/strings.odin | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/core/strings/strings.odin b/core/strings/strings.odin index 87bbb42cf..3b33a5dc8 100644 --- a/core/strings/strings.odin +++ b/core/strings/strings.odin @@ -1833,6 +1833,9 @@ levenshtein_distance :: proc(a, b: string, allocator := context.allocator) -> in if n + 1 > len(LEVENSHTEIN_DEFAULT_COSTS) { costs = make([]int, n + 1, allocator) + for k in 0..=n { + costs[k] = k + } } else { costs = LEVENSHTEIN_DEFAULT_COSTS } @@ -1841,10 +1844,6 @@ levenshtein_distance :: proc(a, b: string, allocator := context.allocator) -> in delete(costs, allocator) } - for k in 0..=n { - costs[k] = k - } - i: int for c1 in a { costs[0] = i + 1 From d8f0da164b92f52eda045d77e5f6abb5c5146f2a Mon Sep 17 00:00:00 2001 From: hikari Date: Thu, 21 Apr 2022 21:15:11 +0300 Subject: [PATCH 17/43] strings: levenshtein_distance: improve potential caching --- core/strings/strings.odin | 43 ++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/core/strings/strings.odin b/core/strings/strings.odin index 3b33a5dc8..f876aab3d 100644 --- a/core/strings/strings.odin +++ b/core/strings/strings.odin @@ -15,7 +15,7 @@ clone :: proc(s: string, allocator := context.allocator, loc := #caller_location } // returns a clone of the string `s` allocated using the `allocator` as a cstring -// a nul byte is appended to the clone, to make the cstring safe +// a nul byte is appended to the clone, to make the cstring safe clone_to_cstring :: proc(s: string, allocator := context.allocator, loc := #caller_location) -> cstring { c := make([]byte, len(s)+1, allocator, loc) copy(c, s) @@ -37,7 +37,7 @@ string_from_nul_terminated_ptr :: proc(ptr: ^byte, len: int) -> string { return s } -// returns the raw ^byte start of the string `str` +// returns the raw ^byte start of the string `str` ptr_from_string :: proc(str: string) -> ^byte { d := transmute(mem.Raw_String)str return d.data @@ -969,7 +969,7 @@ count :: proc(s, substr: string) -> int { repeats the string `s` multiple `count` times and returns the allocated string panics when `count` is below 0 - strings.repeat("abc", 2) -> "abcabc" + strings.repeat("abc", 2) -> "abcabc" */ repeat :: proc(s: string, count: int, allocator := context.allocator) -> string { if count < 0 { @@ -1378,7 +1378,7 @@ split_multi :: proc(s: string, substrs: []string, allocator := context.allocator // skip when no results if substrings_found < 1 { - return + return } buf = make([]string, substrings_found + 1, allocator) @@ -1812,16 +1812,35 @@ fields_iterator :: proc(s: ^string) -> (field: string, ok: bool) { // `levenshtein_distance` returns the Levenshtein edit distance between 2 strings. // This is a single-row-version of the Wagner–Fischer algorithm, based on C code by Martin Ettl. -// Note: allocator isn't used if the length of string b in runes is smaller than 70. +// Note: allocator isn't used if the length of string b in runes is smaller than 256. levenshtein_distance :: proc(a, b: string, allocator := context.allocator) -> int { LEVENSHTEIN_DEFAULT_COSTS: []int : { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, - 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, - 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, - 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, - 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, - 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, + 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, + 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, + 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, + 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, + 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, + 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, + 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, + 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, + 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, + 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, + 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, + 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, + 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, + 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, + 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, + 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, + 250, 251, 252, 253, 254, 255, } m, n := utf8.rune_count_in_string(a), utf8.rune_count_in_string(b) From 71b1cce517e421fd7eeff4d5ac15e5165dd318c0 Mon Sep 17 00:00:00 2001 From: hikari Date: Thu, 21 Apr 2022 21:19:11 +0300 Subject: [PATCH 18/43] strings: levenshtein_distance: 64 is actually faster than 256 --- core/strings/strings.odin | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/core/strings/strings.odin b/core/strings/strings.odin index f876aab3d..6e01f5c8a 100644 --- a/core/strings/strings.odin +++ b/core/strings/strings.odin @@ -1812,7 +1812,7 @@ fields_iterator :: proc(s: ^string) -> (field: string, ok: bool) { // `levenshtein_distance` returns the Levenshtein edit distance between 2 strings. // This is a single-row-version of the Wagner–Fischer algorithm, based on C code by Martin Ettl. -// Note: allocator isn't used if the length of string b in runes is smaller than 256. +// Note: allocator isn't used if the length of string b in runes is smaller than 64. levenshtein_distance :: proc(a, b: string, allocator := context.allocator) -> int { LEVENSHTEIN_DEFAULT_COSTS: []int : { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, @@ -1821,26 +1821,7 @@ levenshtein_distance :: proc(a, b: string, allocator := context.allocator) -> in 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, - 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, - 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, - 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, - 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, - 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, - 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, - 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, - 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, - 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, - 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, - 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, - 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, - 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, - 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, - 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, - 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, - 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, - 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, - 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, - 250, 251, 252, 253, 254, 255, + 60, 61, 62, 63, } m, n := utf8.rune_count_in_string(a), utf8.rune_count_in_string(b) From f0267536929c5909a8098baf95cb5eb1a4fa6522 Mon Sep 17 00:00:00 2001 From: hikari Date: Thu, 21 Apr 2022 21:19:43 +0300 Subject: [PATCH 19/43] strings: levenshtein_distance: remove `do` --- core/strings/strings.odin | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/core/strings/strings.odin b/core/strings/strings.odin index 6e01f5c8a..a3d9fa93e 100644 --- a/core/strings/strings.odin +++ b/core/strings/strings.odin @@ -1826,8 +1826,12 @@ levenshtein_distance :: proc(a, b: string, allocator := context.allocator) -> in m, n := utf8.rune_count_in_string(a), utf8.rune_count_in_string(b) - if m == 0 do return n - if n == 0 do return m + if m == 0 { + return n + } + if n == 0 { + return m + } costs: []int From e799476f90537ca173205a64da1fbf87b894b42e Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 22 Apr 2022 16:55:47 +0200 Subject: [PATCH 20/43] [compress/shoco] Add short string compressor. --- core/compress/shoco/model.odin | 148 +++++++++ core/compress/shoco/shoco.odin | 318 ++++++++++++++++++++ examples/all/all_main.odin | 2 + tests/core/assets/Shoco/LICENSE | 26 ++ tests/core/assets/Shoco/LICENSE.shoco | Bin 0 -> 1269 bytes tests/core/assets/Shoco/README.md | 95 ++++++ tests/core/assets/Shoco/README.md.shoco | Bin 0 -> 2227 bytes tests/core/compress/test_core_compress.odin | 57 +++- 8 files changed, 645 insertions(+), 1 deletion(-) create mode 100644 core/compress/shoco/model.odin create mode 100644 core/compress/shoco/shoco.odin create mode 100644 tests/core/assets/Shoco/LICENSE create mode 100644 tests/core/assets/Shoco/LICENSE.shoco create mode 100644 tests/core/assets/Shoco/README.md create mode 100644 tests/core/assets/Shoco/README.md.shoco diff --git a/core/compress/shoco/model.odin b/core/compress/shoco/model.odin new file mode 100644 index 000000000..49e3dd97e --- /dev/null +++ b/core/compress/shoco/model.odin @@ -0,0 +1,148 @@ +/* + This file was generated, so don't edit this by hand. + Transliterated from https://github.com/Ed-von-Schleck/shoco/blob/master/shoco_model.h, + which is an English word model. +*/ + +// package shoco is an implementation of the shoco short string compressor +package shoco + +DEFAULT_MODEL :: Shoco_Model { + min_char = 39, + max_char = 122, + characters_by_id = { + 'e', 'a', 'i', 'o', 't', 'h', 'n', 'r', 's', 'l', 'u', 'c', 'w', 'm', 'd', 'b', 'p', 'f', 'g', 'v', 'y', 'k', '-', 'H', 'M', 'T', '\'', 'B', 'x', 'I', 'W', 'L', + }, + ids_by_character = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26, -1, -1, -1, -1, -1, 22, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 27, -1, -1, -1, -1, -1, 23, 29, -1, -1, 31, 24, -1, -1, -1, -1, -1, -1, 25, -1, -1, 30, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 15, 11, 14, 0, 17, 18, 5, 2, -1, 21, 9, 13, 6, 3, 16, -1, 7, 8, 4, 10, 19, 12, 28, 20, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }, + successors_by_bigram = { + {7, 4, 12, -1, 6, -1, 1, 0, 3, 5, -1, 9, -1, 8, 2, -1, 15, 14, -1, 10, 11, -1, -1, -1, -1, -1, -1, -1, 13, -1, -1, -1}, + {-1, -1, 6, -1, 1, -1, 0, 3, 2, 4, 15, 11, -1, 9, 5, 10, 13, -1, 12, 8, 7, 14, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {9, 11, -1, 4, 2, -1, 0, 8, 1, 5, -1, 6, -1, 3, 7, 15, -1, 12, 10, 13, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {-1, -1, 14, 7, 5, -1, 1, 2, 8, 9, 0, 15, 6, 4, 11, -1, 12, 3, -1, 10, -1, 13, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 4, 3, 1, 5, 0, -1, 6, 10, 9, 7, 12, 11, -1, -1, -1, -1, 13, -1, -1, 8, -1, 15, -1, -1, -1, 14, -1, -1, -1, -1, -1}, + {0, 1, 2, 3, 4, -1, -1, 5, 9, 10, 6, -1, -1, 8, 15, 11, -1, 14, -1, -1, 7, -1, 13, -1, -1, -1, 12, -1, -1, -1, -1, -1}, + {2, 8, 7, 4, 3, -1, 9, -1, 6, 11, -1, 5, -1, -1, 0, -1, -1, 14, 1, 15, 10, 12, -1, -1, -1, -1, 13, -1, -1, -1, -1, -1}, + {0, 3, 1, 2, 6, -1, 9, 8, 4, 12, 13, 10, -1, 11, 7, -1, -1, 15, 14, -1, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 6, 3, 4, 1, 2, -1, -1, 5, 10, 7, 9, 11, 12, -1, -1, 8, 14, -1, -1, 15, 13, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 6, 2, 5, 9, -1, -1, -1, 10, 1, 8, -1, 12, 14, 4, -1, 15, 7, -1, 13, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {8, 10, 9, 15, 1, -1, 4, 0, 3, 2, -1, 6, -1, 12, 11, 13, 7, 14, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 3, 6, 0, 4, 2, -1, 7, 13, 8, 9, 11, -1, -1, 15, -1, -1, -1, -1, -1, 10, 5, 14, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 0, 1, 4, -1, 2, 5, 6, 7, 8, -1, 14, -1, -1, 9, 15, -1, 12, -1, -1, -1, 10, 11, -1, -1, -1, 13, -1, -1, -1, -1, -1}, + {0, 1, 3, 2, 15, -1, 12, -1, 7, 14, 4, -1, -1, 9, -1, 8, 5, 10, -1, -1, 6, -1, 13, -1, -1, -1, 11, -1, -1, -1, -1, -1}, + {0, 3, 1, 2, -1, -1, 12, 6, 4, 9, 7, -1, -1, 14, 8, -1, -1, 15, 11, 13, 5, -1, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 5, 7, 2, 10, 13, -1, 6, 8, 1, 3, -1, -1, 14, 15, 11, -1, -1, -1, 12, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 2, 6, 3, 7, 10, -1, 1, 9, 4, 8, -1, -1, 15, -1, 12, 5, -1, -1, -1, 11, -1, 13, -1, -1, -1, 14, -1, -1, -1, -1, -1}, + {1, 3, 4, 0, 7, -1, 12, 2, 11, 8, 6, 13, -1, -1, -1, -1, -1, 5, -1, -1, 10, 15, 9, -1, -1, -1, 14, -1, -1, -1, -1, -1}, + {1, 3, 5, 2, 13, 0, 9, 4, 7, 6, 8, -1, -1, 15, -1, 11, -1, -1, 10, -1, 14, -1, 12, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 2, 1, 3, -1, -1, -1, 6, -1, -1, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 11, 4, 0, 3, -1, 13, 12, 2, 7, -1, -1, 15, 10, 5, 8, 14, -1, -1, -1, -1, -1, 9, -1, -1, -1, 6, -1, -1, -1, -1, -1}, + {0, 9, 2, 14, 15, 4, 1, 13, 3, 5, -1, -1, 10, -1, -1, -1, -1, 6, 12, -1, 7, -1, 8, -1, -1, -1, 11, -1, -1, -1, -1, -1}, + {-1, 2, 14, -1, 1, 5, 8, 7, 4, 12, -1, 6, 9, 11, 13, 3, 10, 15, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 1, 3, 2, -1, -1, -1, -1, -1, -1, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {4, 3, 1, 5, -1, -1, -1, 0, -1, -1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 8, 4, 1, -1, 0, -1, 6, -1, -1, 5, -1, 7, -1, -1, -1, -1, -1, -1, -1, 10, -1, -1, 9, -1, -1, -1, -1, -1, -1, -1, -1}, + {12, 5, -1, -1, 1, -1, -1, 7, 0, 3, -1, 2, -1, 4, 6, -1, -1, -1, -1, 8, -1, -1, 15, -1, 13, 9, -1, -1, -1, -1, -1, 11}, + {1, 3, 2, 4, -1, -1, -1, 5, -1, 7, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, 6, -1, -1, -1, -1, -1, -1, -1, -1, 8, -1, -1}, + {5, 3, 4, 12, 1, 6, -1, -1, -1, -1, 8, 2, -1, -1, -1, -1, 0, 9, -1, -1, 11, -1, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {-1, -1, -1, -1, 0, -1, 1, 12, 3, -1, -1, -1, -1, 5, -1, -1, -1, 2, -1, -1, -1, -1, -1, -1, -1, -1, 4, -1, -1, 6, -1, 10}, + {2, 3, 1, 4, -1, 0, -1, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 7, -1, -1, -1, -1, -1, -1, -1, -1, 6, -1, -1}, + {5, 1, 3, 0, -1, -1, -1, -1, -1, -1, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, -1, -1, -1, -1, -1, 9, -1, -1, 6, -1, 7}, + }, + successors_reversed = { + {'s', 't', 'c', 'l', 'm', 'a', 'd', 'r', 'v', 'T', 'A', 'L', 'e', 'M', 'Y', '-'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'-', 't', 'a', 'b', 's', 'h', 'c', 'r', 'n', 'w', 'p', 'm', 'l', 'd', 'i', 'f'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'u', 'e', 'i', 'a', 'o', 'r', 'y', 'l', 'I', 'E', 'R', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'e', 'a', 'o', 'i', 'u', 'A', 'y', 'E', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'t', 'n', 'f', 's', '\'', 'm', 'I', 'N', 'A', 'E', 'L', 'Z', 'r', 'V', 'R', 'C'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'o', 'a', 'y', 'i', 'u', 'e', 'I', 'L', 'D', '\'', 'E', 'Y', '\x00', '\x00', '\x00', '\x00'}, + {'r', 'i', 'y', 'a', 'e', 'o', 'u', 'Y', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'h', 'o', 'e', 'E', 'i', 'u', 'r', 'w', 'a', 'H', 'y', 'R', 'Z', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'h', 'i', 'e', 'a', 'o', 'r', 'I', 'y', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'n', 't', 's', 'r', 'l', 'd', 'i', 'y', 'v', 'm', 'b', 'c', 'g', 'p', 'k', 'u'}, + {'e', 'l', 'o', 'u', 'y', 'a', 'r', 'i', 's', 'j', 't', 'b', 'v', 'h', 'm', 'd'}, + {'o', 'e', 'h', 'a', 't', 'k', 'i', 'r', 'l', 'u', 'y', 'c', 'q', 's', '-', 'd'}, + {'e', 'i', 'o', 'a', 's', 'y', 'r', 'u', 'd', 'l', '-', 'g', 'n', 'v', 'm', 'f'}, + {'r', 'n', 'd', 's', 'a', 'l', 't', 'e', 'm', 'c', 'v', 'y', 'i', 'x', 'f', 'p'}, + {'o', 'e', 'r', 'a', 'i', 'f', 'u', 't', 'l', '-', 'y', 's', 'n', 'c', '\'', 'k'}, + {'h', 'e', 'o', 'a', 'r', 'i', 'l', 's', 'u', 'n', 'g', 'b', '-', 't', 'y', 'm'}, + {'e', 'a', 'i', 'o', 't', 'r', 'u', 'y', 'm', 's', 'l', 'b', '\'', '-', 'f', 'd'}, + {'n', 's', 't', 'm', 'o', 'l', 'c', 'd', 'r', 'e', 'g', 'a', 'f', 'v', 'z', 'b'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'e', 'n', 'i', 's', 'h', 'l', 'f', 'y', '-', 'a', 'w', '\'', 'g', 'r', 'o', 't'}, + {'e', 'l', 'i', 'y', 'd', 'o', 'a', 'f', 'u', 't', 's', 'k', 'w', 'v', 'm', 'p'}, + {'e', 'a', 'o', 'i', 'u', 'p', 'y', 's', 'b', 'm', 'f', '\'', 'n', '-', 'l', 't'}, + {'d', 'g', 'e', 't', 'o', 'c', 's', 'i', 'a', 'n', 'y', 'l', 'k', '\'', 'f', 'v'}, + {'u', 'n', 'r', 'f', 'm', 't', 'w', 'o', 's', 'l', 'v', 'd', 'p', 'k', 'i', 'c'}, + {'e', 'r', 'a', 'o', 'l', 'p', 'i', 't', 'u', 's', 'h', 'y', 'b', '-', '\'', 'm'}, + {'\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'e', 'i', 'o', 'a', 's', 'y', 't', 'd', 'r', 'n', 'c', 'm', 'l', 'u', 'g', 'f'}, + {'e', 't', 'h', 'i', 'o', 's', 'a', 'u', 'p', 'c', 'l', 'w', 'm', 'k', 'f', 'y'}, + {'h', 'o', 'e', 'i', 'a', 't', 'r', 'u', 'y', 'l', 's', 'w', 'c', 'f', '\'', '-'}, + {'r', 't', 'l', 's', 'n', 'g', 'c', 'p', 'e', 'i', 'a', 'd', 'm', 'b', 'f', 'o'}, + {'e', 'i', 'a', 'o', 'y', 'u', 'r', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}, + {'a', 'i', 'h', 'e', 'o', 'n', 'r', 's', 'l', 'd', 'k', '-', 'f', '\'', 'c', 'b'}, + {'p', 't', 'c', 'a', 'i', 'e', 'h', 'q', 'u', 'f', '-', 'y', 'o', '\x00', '\x00', '\x00'}, + {'o', 'e', 's', 't', 'i', 'd', '\'', 'l', 'b', '-', 'm', 'a', 'r', 'n', 'p', 'w'}, + }, + + character_count = 32, + successor_count = 16, + + max_successor_n = 7, + packs = { + { 0x80000000, 1, 2, { 26, 24, 24, 24, 24, 24, 24, 24 }, { 15, 3, 0, 0, 0, 0, 0, 0 }, 0xc0, 0x80 }, + { 0xc0000000, 2, 4, { 25, 22, 19, 16, 16, 16, 16, 16 }, { 15, 7, 7, 7, 0, 0, 0, 0 }, 0xe0, 0xc0 }, + { 0xe0000000, 4, 8, { 23, 19, 15, 11, 8, 5, 2, 0 }, { 31, 15, 15, 15, 7, 7, 7, 3 }, 0xf0, 0xe0 }, + }, +} \ No newline at end of file diff --git a/core/compress/shoco/shoco.odin b/core/compress/shoco/shoco.odin new file mode 100644 index 000000000..9c5008f5d --- /dev/null +++ b/core/compress/shoco/shoco.odin @@ -0,0 +1,318 @@ +/* + Copyright 2022 Jeroen van Rijn . + 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.successors_reversed) != int(model.max_char - model.min_char) { + return 0, .Unknown_Compression_Method + } + + 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) || len(model.successors_by_bigram[0]) != int(model.character_count) { + return 0, .Unknown_Compression_Method + } + + if len(model.successors_reversed[0]) != int(model.successor_count) { + 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] + + last_chr = model.successors_reversed[last_chr - model.min_char][(code >> offset) & u32(mask)] + 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][current_index] + if successor_index < 0 { + break + } + + indices[n_consecutive] = i16(successor_index) + last_chr_index = current_index + } + + if n_consecutive < 2 { + last_resort = true + continue encode + } + + pack_n := find_best_encoding(indices, n_consecutive) + if pack_n >= 0 { + if out + int(model.packs[pack_n].bytes_packed) > out_end { + return out, .Output_Too_Short + } + + pack := model.packs[pack_n] + code := pack.word + + for i := 0; i < int(pack.bytes_unpacked); i += 1 { + code |= u32(indices[i]) << pack.offsets[i] + } + + // In the little-endian world, we need to swap what's in the register to match the memory representation. + when ODIN_ENDIAN == .Little { + code = intrinsics.byte_swap(code) + } + out_ptr := raw_data(output[out:]) + + switch pack.bytes_packed { + case 4: + intrinsics.unaligned_store(transmute(^u32)out_ptr, code) + case 2: + intrinsics.unaligned_store(transmute(^u16)out_ptr, u16(code)) + case 1: + intrinsics.unaligned_store(transmute(^u8)out_ptr, u8(code)) + case: + return out, .Unknown_Compression_Method + } + + out += int(pack.bytes_packed) + inp += int(pack.bytes_unpacked) + } else { + last_resort = true + continue encode + } + } + } + return out, nil +} + +compress_string :: proc(input: string, model := DEFAULT_MODEL, allocator := context.allocator) -> (output: []u8, err: compress.Error) { + context.allocator = allocator + + if len(input) == 0 { + return {}, .Stream_Too_Short + } + + max_output_size := compress_bound(len(input)) + + buf: [dynamic]u8 + if !resize(&buf, max_output_size) { + return {}, .Out_Of_Memory + } + + length, result := compress_string_to_buffer(input, buf[:]) + resize(&buf, length) + return buf[:length], result +} +compress :: proc{compress_string_to_buffer, compress_string} \ No newline at end of file diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index 4f5bfbdc1..27f199062 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -10,6 +10,7 @@ import c "core:c" import libc "core:c/libc" import compress "core:compress" +import shoco "core:compress/shoco" import gzip "core:compress/gzip" import zlib "core:compress/zlib" @@ -115,6 +116,7 @@ _ :: bytes _ :: c _ :: libc _ :: compress +_ :: shoco _ :: gzip _ :: zlib _ :: bit_array diff --git a/tests/core/assets/Shoco/LICENSE b/tests/core/assets/Shoco/LICENSE new file mode 100644 index 000000000..9ca94bcdf --- /dev/null +++ b/tests/core/assets/Shoco/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2016-2021 Ginger Bill. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/tests/core/assets/Shoco/LICENSE.shoco b/tests/core/assets/Shoco/LICENSE.shoco new file mode 100644 index 0000000000000000000000000000000000000000..5d5e4d6236019bf208cc2ebb7a81ed06469b78a8 GIT binary patch literal 1269 zcmZ?HFQ}ZGo>8Kpk*uj;WMF8fYh++#sNjBtvq-@ybE%$!<5C5Xe6hkgiK4P5Jua@G z1Dz$0`)3v^w4^AkPF3hoxX7>;#7^pHuT)6u%PrPXxDlYxr*I>nr-WM6i1C|2kPo4sa- z!fi=~b0!@iO^N%;QWe%f-F#Mit&YM8K`sRag-e|Z{V=myQa~yp#_xRMW0s)D#btzS zZifQQu^^M@6wJH(5u^jdY>>ex1o4}#0Cv`{r{@?8GkZZAT2l1;6plL-Dctg!o0wA! z@$roSkOg-?@7#bvCg;Fo%&p$H_Hm$*ftAH;z9YWigcg}gqHNM7O=P#|;_gUr3g zJ@?+pqGE-ul?r(YOC^-B3g#>vzg@*VC1uG~y1}k_5 zD{+A&9Q`5{Tq6R4T!VuZ{DTxceFJUI-tSznemE zs8eu=XGmy>tAe|~zY8d!xq@AT!aSW_gRK>O{6RtQrVtwJs-xiI7~%-FI3UR1%`+s} z8srG4&|pt+pn3X*xCRA<284L}`)Mk8_=mfOxdw4LJB9|kx`0F1-%kM)p&=fw{y~wT zC;){4IPi58!aZC=JY0i70SXQ&M^In{hXi>#gW?k!M><^oK?))MK_N(fRPb|k_wjUh z^>cOwxyB!4O1NjRtEPfukY})`pF7A%khS5CkqZ8yAs~A}DKRwImCN5v0VT!ifMZg@ i(@nwACCn4#Y_JMfg#iEHU{7c|0SB40hXN$H^|%1|ER=2l literal 0 HcmV?d00001 diff --git a/tests/core/assets/Shoco/README.md b/tests/core/assets/Shoco/README.md new file mode 100644 index 000000000..9e46f80d0 --- /dev/null +++ b/tests/core/assets/Shoco/README.md @@ -0,0 +1,95 @@ + + +# The Odin Programming Language + + +Odin is a general-purpose programming language with distinct typing, built for high performance, modern systems, and built-in data-oriented data types. The Odin Programming Language, the C alternative for the joy of programming. + +Website: [https://odin-lang.org/](https://odin-lang.org/) + +```odin +package main + +import "core:fmt" + +main :: proc() { + program := "+ + * 😃 - /" + accumulator := 0 + + for token in program { + switch token { + case '+': accumulator += 1 + case '-': accumulator -= 1 + case '*': accumulator *= 2 + case '/': accumulator /= 2 + case '😃': accumulator *= accumulator + case: // Ignore everything else + } + } + + fmt.printf("The program \"%s\" calculates the value %d\n", + program, accumulator) +} + +``` + +## Documentation + +#### [Getting Started](https://odin-lang.org/docs/install) + +Instructions for downloading and installing the Odin compiler and libraries. + +#### [Nightly Builds](https://odin-lang.org/docs/nightly/) + +Get the latest nightly builds of Odin. + +### Learning Odin + +#### [Overview of Odin](https://odin-lang.org/docs/overview) + +An overview of the Odin programming language. + +#### [Frequently Asked Questions (FAQ)](https://odin-lang.org/docs/faq) + +Answers to common questions about Odin. + +#### [Packages](https://pkg.odin-lang.org/) + +Documentation for all the official packages part of the [core](https://pkg.odin-lang.org/core/) and [vendor](https://pkg.odin-lang.org/vendor/) library collections. + +#### [The Odin Wiki](https://github.com/odin-lang/Odin/wiki) + +A wiki maintained by the Odin community. + +#### [Odin Discord](https://discord.gg/sVBPHEv) + +Get live support and talk with other odiners on the Odin Discord. + +### Articles + +#### [The Odin Blog](https://odin-lang.org/news/) + +The official blog of the Odin programming language, featuring announcements, news, and in-depth articles by the Odin team and guests. + +## Warnings + +* The Odin compiler is still in development. diff --git a/tests/core/assets/Shoco/README.md.shoco b/tests/core/assets/Shoco/README.md.shoco new file mode 100644 index 0000000000000000000000000000000000000000..013f4f46928fb3d5c6ea62d76ddda91f29c6e420 GIT binary patch literal 2227 zcmcC1P)J;so@c9c&AUO#j!Qv7LBXawU7@%r*;Z+Lak73+etN!c@v>aKf|*JRi8&>< zO8z_Z6hNX%3JWWj*eY#InP6pRstVJaRHP3!C1i?%OKYO8|J(ynDGEL<>7|M3sS0U* z3c)R@3c>v)HzX8XQp-|v@(XTp>p^UV=~S>muo4wA=A_vwWt5Z@6kF-*ryprc(p!_O zpT9Ft_i`hM(tq64;;etUYa!Hr6!*ilb*Jks$~>5&mzfVXLI0daTPjF1)C|4ivUI3+ z8~sGE>lJLMZgWy%N_wh(K~8I0Uv9B(_z|Y^VpSu98dW0$pN`TBD06GFf3R-)IZLcg zN-Dzcww=Y-#P!nCK|Z?N2=xe|Z~*DiH#M^`G`6rbHMB4`Gc-3cF*39VMXW7I5^4@9 z0g`xKCa)N(53BuHqOZI!JMD70zO$!3IDA0nQBc%qan-%xUQv3Iz=>8Y4w@6t?Xs%2Oz=T$q|$tfSD90#>8j zp>XGn?!yTuvlQ-}0cmd5!{H1ag$bz&&I*Y+lZx_MGt0o~enP53R(_>IKg=%fB5T%4TOE-0vLt6M2Rb!pow7(zvL5u&;X zQ7we1maT#jRINTjR39M_4{ov}E3c)Akny|&ruH<6$;pjshf~G%FOemq2_FeN6zDIoAaAH9;{FU3S2!2$v#|oW7>Q zviq|u71liI@F^^YI}=(Sg=c1GB6Opbcm6x`^fzW_f;_IUF*{S?SvT{HCWWL*kdr}y z`?SetUM1W_kcdm?HF1P-pkf(R&KHL{1$elYK@-8UvQ&kOfBQ;6t}jW<$yT@#pm5xw zNFo1@Q&F)(FU)X|Q4q5g9M8{NQVdHJP=`C^5#w# zxeqlt=qSAC;%7-$c+j$7>MibK9feutkkYe5cSpelg+rf{mlPv;cT!?5$j#}c%_Wd< fR0wZBd$5>`OAA)4g0o4d!iCrzg${)s@YVtV8=oH9 literal 0 HcmV?d00001 diff --git a/tests/core/compress/test_core_compress.odin b/tests/core/compress/test_core_compress.odin index 51952a568..ee7233e52 100644 --- a/tests/core/compress/test_core_compress.odin +++ b/tests/core/compress/test_core_compress.odin @@ -7,13 +7,14 @@ package test_core_compress List of contributors: Jeroen van Rijn: Initial implementation. - A test suite for ZLIB, GZIP. + A test suite for ZLIB, GZIP and Shoco. */ import "core:testing" import "core:compress/zlib" import "core:compress/gzip" +import "core:compress/shoco" import "core:bytes" import "core:fmt" @@ -48,6 +49,7 @@ main :: proc() { t := testing.T{w=w} zlib_test(&t) gzip_test(&t) + shoco_test(&t) fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) if TEST_fail > 0 { @@ -134,3 +136,56 @@ gzip_test :: proc(t: ^testing.T) { expect(t, false, error) } } + +@test +shoco_test :: proc(t: ^testing.T) { + + Shoco_Tests :: []struct{ + compressed: []u8, + raw: []u8, + short_pack: int, + short_sentinel: int, + }{ + { #load("../assets/Shoco/README.md.shoco"), #load("../assets/Shoco/README.md"), 10, 1006 }, + { #load("../assets/Shoco/LICENSE.shoco"), #load("../assets/Shoco/LICENSE"), 25, 68 }, + } + + for v in Shoco_Tests { + expected_raw := len(v.raw) + expected_compressed := len(v.compressed) + + biggest_unpacked := shoco.decompress_bound(expected_compressed) + biggest_packed := shoco.compress_bound(expected_raw) + + buffer := make([]u8, max(biggest_packed, biggest_unpacked)) + defer delete(buffer) + + size, err := shoco.decompress(v.compressed, buffer[:]) + msg := fmt.tprintf("Expected `decompress` to return `nil`, got %v", err) + expect(t, err == nil, msg) + + msg = fmt.tprintf("Decompressed %v bytes into %v. Expected to decompress into %v bytes.", len(v.compressed), size, expected_raw) + expect(t, size == expected_raw, msg) + expect(t, string(buffer[:size]) == string(v.raw), "Decompressed contents don't match.") + + size, err = shoco.compress(string(v.raw), buffer[:]) + expect(t, err == nil, "Expected `compress` to return `nil`.") + + msg = fmt.tprintf("Compressed %v bytes into %v. Expected to compress into %v bytes.", expected_raw, size, expected_compressed) + expect(t, size == expected_compressed, msg) + + size, err = shoco.decompress(v.compressed, buffer[:expected_raw - 10]) + msg = fmt.tprintf("Decompressing into too small a buffer returned %v, expected `.Output_Too_Short`", err) + expect(t, err == .Output_Too_Short, msg) + + size, err = shoco.compress(string(v.raw), buffer[:expected_compressed - 10]) + msg = fmt.tprintf("Compressing into too small a buffer returned %v, expected `.Output_Too_Short`", err) + expect(t, err == .Output_Too_Short, msg) + + size, err = shoco.decompress(v.compressed[:v.short_pack], buffer[:]) + expect(t, err == .Stream_Too_Short, "Expected `decompress` to return `Stream_Too_Short` because there was no more data after selecting a pack.") + + size, err = shoco.decompress(v.compressed[:v.short_sentinel], buffer[:]) + expect(t, err == .Stream_Too_Short, "Expected `decompress` to return `Stream_Too_Short` because there was no more data after non-ASCII sentinel.") + } +} \ No newline at end of file From ac9a358c65820894968cc11b55b08de0646d98d4 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 22 Apr 2022 17:52:38 +0200 Subject: [PATCH 21/43] [shoco] Replace 2D slices in model with 1D slices. --- core/compress/shoco/model.odin | 230 ++++++++++++++++----------------- core/compress/shoco/shoco.odin | 22 ++-- 2 files changed, 127 insertions(+), 125 deletions(-) diff --git a/core/compress/shoco/model.odin b/core/compress/shoco/model.odin index 49e3dd97e..bbc38903d 100644 --- a/core/compress/shoco/model.odin +++ b/core/compress/shoco/model.odin @@ -17,123 +17,123 @@ DEFAULT_MODEL :: Shoco_Model { -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}, + 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'}, + '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, diff --git a/core/compress/shoco/shoco.odin b/core/compress/shoco/shoco.odin index 9c5008f5d..3f5b696ea 100644 --- a/core/compress/shoco/shoco.odin +++ b/core/compress/shoco/shoco.odin @@ -14,6 +14,8 @@ package shoco import "core:intrinsics" import "core:compress" +import "core:fmt" + Shoco_Pack :: struct { word: u32, bytes_packed: i8, @@ -29,8 +31,8 @@ Shoco_Model :: struct { max_char: u8, characters_by_id: []u8, ids_by_character: [256]i16, - successors_by_bigram: [][]i8, - successors_reversed: [][]u8, + successors_by_bigram: []i8, + successors_reversed: []u8, character_count: u8, successor_count: u8, @@ -74,19 +76,15 @@ find_best_encoding :: proc(indices: []i16, n_consecutive: i8, model := DEFAULT_M } validate_model :: proc(model: Shoco_Model) -> (int, compress.Error) { - if len(model.successors_reversed) != int(model.max_char - model.min_char) { - return 0, .Unknown_Compression_Method - } - 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) || len(model.successors_by_bigram[0]) != int(model.character_count) { + if len(model.successors_by_bigram) != int(model.character_count) * int(model.character_count) { return 0, .Unknown_Compression_Method } - if len(model.successors_reversed[0]) != int(model.successor_count) { + if len(model.successors_reversed) != int(model.successor_count) * int(model.max_char - model.min_char) { return 0, .Unknown_Compression_Method } @@ -155,7 +153,11 @@ decompress_slice_to_output_buffer :: proc(input: []u8, output: []u8, model := DE offset = pack.offsets[i] mask = pack.masks[i] - last_chr = model.successors_reversed[last_chr - model.min_char][(code >> offset) & u32(mask)] + 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 } @@ -242,7 +244,7 @@ compress_string_to_buffer :: proc(input: string, output: []u8, model := DEFAULT_ break } - successor_index := model.successors_by_bigram[last_chr_index][current_index] + successor_index := model.successors_by_bigram[last_chr_index * i16(model.character_count) + current_index] if successor_index < 0 { break } From b022167df12b3d62ddacd51e64f7ab3b4e170852 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 22 Apr 2022 17:56:34 +0200 Subject: [PATCH 22/43] Remove unused fmt. --- core/compress/shoco/shoco.odin | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/compress/shoco/shoco.odin b/core/compress/shoco/shoco.odin index 3f5b696ea..f94ce70b7 100644 --- a/core/compress/shoco/shoco.odin +++ b/core/compress/shoco/shoco.odin @@ -14,8 +14,6 @@ package shoco import "core:intrinsics" import "core:compress" -import "core:fmt" - Shoco_Pack :: struct { word: u32, bytes_packed: i8, From b44b6e7e5099cab83f4a6d0feb5af9f245dea738 Mon Sep 17 00:00:00 2001 From: Tetralux Date: Sat, 23 Apr 2022 03:33:35 +0000 Subject: [PATCH 23/43] [path/filepath] Add file stem and long-extension procedures Adds stem(), short_stem(), and long_ext(); also adds doc-comments to base() and ext(). The 'stem' is usually 'the name' of the file; the basename without the file extension. To this end, this adds stem(), which is such that: stem(path) + ext(path) = base(path) However, 'file extension' has two different meanings to what constitutes it! > What is the extension of: 'name.tar.gz' ? Colloquially, you would likely think of it as 'a tarball' - which you might think is '.tar.gz'. But, if you're writing code to process a file of this type, you would first treat it as a Gzip file, and then treat the result as a TAR file - i.e: '.gz' ... _followed by_ '.tar'. ext() returns '.gz' here, since that is the most-immediate format that you would need to use to decode it; it would be a Gzip stream. Sometimes though, you do actually want to consider these longer file extensions. Perhaps you're extracting a tarball, and what to know what to call the intermediate tar file; perhaps you want to check to see if this file is a tarball, or just a Gzip file; or maybe you just want 'the name' of the file, and not this "strange 'name-and-part-of-the-extension' thing". So, this also adds short_stem() and long_ext(), such that: short_stem(path) + long_ext(path) = base(path) Thus, we can use either, but the most immediately-useful one is the easiest to reach for: stem('name.tar.gz') -> 'name.tar' ext('name.tar.gz') -> '.gz' short_stem('name.tar.gz') -> 'name' long_ext('name.tar.gz') -> '.tar.gz' These procedures are identical to their counterparts when the path only has a simple extension: stem('name.txt') -> 'name' ext('name.txt') -> '.txt' short_stem('name.txt') -> 'name' long_ext('name.txt') -> '.txt' --- core/path/filepath/path.odin | 133 ++++++++++++++++++++++++++++++++--- 1 file changed, 124 insertions(+), 9 deletions(-) diff --git a/core/path/filepath/path.odin b/core/path/filepath/path.odin index 42714d736..32e4a8a37 100644 --- a/core/path/filepath/path.odin +++ b/core/path/filepath/path.odin @@ -4,6 +4,8 @@ package filepath import "core:strings" +SEPARATOR_CHARS :: `/\` + // is_separator checks whether the byte is a valid separator character is_separator :: proc(c: byte) -> bool { switch c { @@ -69,6 +71,16 @@ volume_name_len :: proc(path: string) -> int { return 0 } +/* + Gets the file name and extension from a path. + + i.e: + 'path/to/name.tar.gz' -> 'name.tar.gz' + 'path/to/name.txt' -> 'name.txt' + 'path/to/name' -> 'name' + + Returns "." if the path is an empty string. +*/ base :: proc(path: string) -> string { if path == "" { return "." @@ -94,6 +106,118 @@ base :: proc(path: string) -> string { return path } +/* + Gets the name of a file from a path. + + The stem of a file is such that stem(path) + ext(path) = base(path). + + Only the last dot is considered when splitting the file extension. + See `short_stem`. + + i.e: + 'name.tar.gz' -> 'name.tar' + 'name.txt' -> 'name' + + Returns an empty string if there is no stem. e.g: '.gitignore'. + Returns an empty string if there's a trailing path separator. +*/ +stem :: proc(path: string) -> string { + if len(path) > 0 && is_separator(path[len(path) - 1]) { + // NOTE(tetra): Trailing separator + return "" + } + + // NOTE(tetra): Get the basename + path := path + if i := strings.last_index_any(path, SEPARATOR_CHARS); i != -1 { + path = path[i+1:] + } + + if i := strings.last_index_byte(path, '.'); i != -1 { + return path[:i] + } + + return path +} + +/* + Gets the name of a file from a path. + + The short stem is such that short_stem(path) + long_ext(path) = base(path). + + The first dot is used to split off the file extension, unlike `stem` which uses the last dot. + + i.e: + 'name.tar.gz' -> 'name' + 'name.txt' -> 'name' + + Returns an empty string if there is no stem. e.g: '.gitignore'. + Returns an empty string if there's a trailing path separator. +*/ +short_stem :: proc(path: string) -> string { + s := stem(path) + if i := strings.index_byte(s, '.'); i != -1 { + return s[:i] + } + return s +} + +/* + Gets the file extension from a path, including the dot. + + The file extension is such that stem(path) + ext(path) = base(path). + + Only the last dot is considered when splitting the file extension. + See `long_ext`. + + i.e: + 'name.tar.gz' -> '.gz' + 'name.txt' -> '.txt' + + Returns an empty string if there is no dot. + Returns an empty string if there is a trailing path separator. +*/ +ext :: proc(path: string) -> string { + for i := len(path)-1; i >= 0 && !is_separator(path[i]); i -= 1 { + if path[i] == '.' { + return path[i:] + } + } + return "" +} + +/* + Gets the file extension from a path, including the dot. + + The long file extension is such that short_stem(path) + long_ext(path) = base(path). + + The first dot is used to split off the file extension, unlike `ext` which uses the last dot. + + i.e: + 'name.tar.gz' -> '.tar.gz' + 'name.txt' -> '.txt' + + Returns an empty string if there is no dot. + Returns an empty string if there is a trailing path separator. +*/ +long_ext :: proc(path: string) -> string { + if len(path) > 0 && is_separator(path[len(path) - 1]) { + // NOTE(tetra): Trailing separator + return "" + } + + // NOTE(tetra): Get the basename + path := path + if i := strings.last_index_any(path, SEPARATOR_CHARS); i != -1 { + path = path[i+1:] + } + + if i := strings.index_byte(path, '.'); i != -1 { + return path[i:] + } + + return "" +} clean :: proc(path: string, allocator := context.allocator) -> string { context.allocator = allocator @@ -189,15 +313,6 @@ to_slash :: proc(path: string, allocator := context.allocator) -> (new_path: str return strings.replace_all(path, SEPARATOR_STRING, "/", allocator) } -ext :: proc(path: string) -> string { - for i := len(path)-1; i >= 0 && !is_separator(path[i]); i -= 1 { - if path[i] == '.' { - return path[i:] - } - } - return "" -} - Relative_Error :: enum { None, From 3cab2592c3e5a06882ffd711871a08c893b043f1 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 6 Apr 2022 18:26:23 +0200 Subject: [PATCH 24/43] Compiler: Add early error for output path being a directory. - Introduce new `Path` type and an array of build paths on the build context. - Resolve input and output paths/files early (before parsing). - Error early if inputs are missing or outputs are directories. - Plumb new file path generation into linker stage instead of its adhoc method. TODO: - Remove more adhoc file path generation in parser and linker stage. - Make intermediate object file generation use new path system. - Round out and robustify Path helper functions. --- .gitignore | 1 + Makefile | 4 +- build_odin.sh | 4 +- src/build_settings.cpp | 220 +++++++++++++++++++--- src/common.cpp | 257 +------------------------- src/gb/gb.h | 50 +++-- src/llvm_backend.cpp | 14 +- src/llvm_backend_general.cpp | 1 - src/main.cpp | 152 ++++++++-------- src/parser.cpp | 2 +- src/path.cpp | 333 ++++++++++++++++++++++++++++++++++ src/string.cpp | 10 +- tests/core/build.bat | 28 +-- tests/core/math/big/build.bat | 2 +- tests/issues/run.sh | 4 +- 15 files changed, 676 insertions(+), 406 deletions(-) create mode 100644 src/path.cpp diff --git a/.gitignore b/.gitignore index e8b3d3050..d03a86fd7 100644 --- a/.gitignore +++ b/.gitignore @@ -269,6 +269,7 @@ bin/ # - Linux/MacOS odin odin.dSYM +*.bin # shared collection shared/ diff --git a/Makefile b/Makefile index 82150c6a2..1a1c93180 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ -all: debug demo +all: debug demo: - ./odin run examples/demo/demo.odin + ./odin run examples/demo report: ./odin report diff --git a/build_odin.sh b/build_odin.sh index aef3f2836..4810cafd2 100755 --- a/build_odin.sh +++ b/build_odin.sh @@ -102,7 +102,7 @@ build_odin() { } run_demo() { - ./odin run examples/demo/demo.odin -file + ./odin run examples/demo } case $OS in @@ -147,4 +147,4 @@ if [[ $# -eq 1 ]]; then exit 0 else panic "Too many arguments!" -fi +fi \ No newline at end of file diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 2f3eb03a5..0b582eac8 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -3,7 +3,6 @@ #include #endif - // #if defined(GB_SYSTEM_WINDOWS) // #define DEFAULT_TO_THREADED_CHECKER // #endif @@ -198,6 +197,22 @@ enum RelocMode : u8 { RelocMode_DynamicNoPIC, }; +enum BuildPath : u8 { + BuildPath_Main_Package, // Input Path to the package directory (or file) we're building. + BuildPath_RC, // Input Path for .rc file, can be set with `-resource:`. + BuildPath_RES, // Output Path for .res file, generated from previous. + BuildPath_Win_SDK_Root, // windows_sdk_root + BuildPath_Win_SDK_UM_Lib, // windows_sdk_um_library_path + BuildPath_Win_SDK_UCRT_Lib, // windows_sdk_ucrt_library_path + BuildPath_VS_EXE, // vs_exe_path + BuildPath_VS_LIB, // vs_library_path + + BuildPath_Output, // Output Path for .exe, .dll, .so, etc. Can be overridden with `-out:`. + BuildPath_PDB, // Output Path for .pdb file, can be overridden with `-pdb-name:`. + + BuildPathCOUNT, +}; + // This stores the information for the specify architecture of this build struct BuildContext { // Constants @@ -226,9 +241,13 @@ struct BuildContext { bool show_help; + Array build_paths; // Contains `Path` objects to output filename, pdb, resource and intermediate files. + // BuildPath enum contains the indices of paths we know *before* the work starts. + String out_filepath; String resource_filepath; String pdb_filepath; + bool has_resource; String link_flags; String extra_linker_flags; @@ -300,8 +319,6 @@ struct BuildContext { }; - - gb_global BuildContext build_context = {0}; bool global_warnings_as_errors(void) { @@ -605,28 +622,6 @@ bool allow_check_foreign_filepath(void) { // is_abs_path // has_subdir -enum TargetFileValidity : u8 { - TargetFileValidity_Invalid, - - TargetFileValidity_Writable_File, - TargetFileValidity_No_Write_Permission, - TargetFileValidity_Directory, - - TargetTargetFileValidity_COUNT, -}; - -TargetFileValidity set_output_filename(void) { - // Assembles the output filename from build_context information. - // Returns `true` if it doesn't exist or is a file. - // Returns `false` if a directory or write-protected file. - - - - - return TargetFileValidity_Writable_File; -} - - String const WIN32_SEPARATOR_STRING = {cast(u8 *)"\\", 1}; String const NIX_SEPARATOR_STRING = {cast(u8 *)"/", 1}; @@ -973,7 +968,6 @@ char *token_pos_to_string(TokenPos const &pos) { return s; } - void init_build_context(TargetMetrics *cross_target) { BuildContext *bc = &build_context; @@ -1152,8 +1146,178 @@ void init_build_context(TargetMetrics *cross_target) { bc->optimization_level = gb_clamp(bc->optimization_level, 0, 3); - - #undef LINK_FLAG_X64 #undef LINK_FLAG_386 } + +#if defined(GB_SYSTEM_WINDOWS) +// NOTE(IC): In order to find Visual C++ paths without relying on environment variables. +// NOTE(Jeroen): No longer needed in `main.cpp -> linker_stage`. We now resolve those paths in `init_build_paths`. +#include "microsoft_craziness.h" +#endif + +// NOTE(Jeroen): Set/create the output and other paths and report an error as appropriate. +// We've previously called `parse_build_flags`, so `out_filepath` should be set. +bool init_build_paths(String init_filename) { + gbAllocator ha = heap_allocator(); + BuildContext *bc = &build_context; + + // NOTE(Jeroen): We're pre-allocating BuildPathCOUNT slots so that certain paths are always at the same enumerated index. + array_init(&bc->build_paths, permanent_allocator(), BuildPathCOUNT); + + // [BuildPathMainPackage] Turn given init path into a `Path`, which includes normalizing it into a full path. + bc->build_paths[BuildPath_Main_Package] = path_from_string(ha, init_filename); + + bool produces_output_file = false; + if (bc->command_kind == Command_doc && bc->cmd_doc_flags & CmdDocFlag_DocFormat) { + produces_output_file = true; + } else if (bc->command_kind & Command__does_build) { + produces_output_file = true; + } + + if (!produces_output_file) { + // Command doesn't produce output files. We're done. + return true; + } + + #if defined(GB_SYSTEM_WINDOWS) + if (bc->resource_filepath.len > 0) { + bc->build_paths[BuildPath_RC] = path_from_string(ha, bc->resource_filepath); + bc->build_paths[BuildPath_RES] = path_from_string(ha, bc->resource_filepath); + bc->build_paths[BuildPath_RC].ext = copy_string(ha, STR_LIT("rc")); + bc->build_paths[BuildPath_RES].ext = copy_string(ha, STR_LIT("res")); + } + + if (bc->pdb_filepath.len > 0) { + bc->build_paths[BuildPath_PDB] = path_from_string(ha, bc->pdb_filepath); + } + + if ((bc->command_kind & Command__does_build) && (!bc->ignore_microsoft_magic)) { + // NOTE(ic): It would be nice to extend this so that we could specify the Visual Studio version that we want instead of defaulting to the latest. + Find_Result_Utf8 find_result = find_visual_studio_and_windows_sdk_utf8(); + + if (find_result.windows_sdk_version == 0) { + gb_printf_err("Windows SDK not found.\n"); + return false; + } + + GB_ASSERT(find_result.windows_sdk_um_library_path.len > 0); + GB_ASSERT(find_result.windows_sdk_ucrt_library_path.len > 0); + + if (find_result.windows_sdk_root.len > 0) { + bc->build_paths[BuildPath_Win_SDK_Root] = path_from_string(ha, find_result.windows_sdk_root); + } + + if (find_result.windows_sdk_um_library_path.len > 0) { + bc->build_paths[BuildPath_Win_SDK_UM_Lib] = path_from_string(ha, find_result.windows_sdk_um_library_path); + } + + if (find_result.windows_sdk_ucrt_library_path.len > 0) { + bc->build_paths[BuildPath_Win_SDK_UCRT_Lib] = path_from_string(ha, find_result.windows_sdk_ucrt_library_path); + } + + if (find_result.vs_exe_path.len > 0) { + bc->build_paths[BuildPath_VS_EXE] = path_from_string(ha, find_result.vs_exe_path); + } + + if (find_result.vs_library_path.len > 0) { + bc->build_paths[BuildPath_VS_LIB] = path_from_string(ha, find_result.vs_library_path); + } + + gb_free(ha, find_result.windows_sdk_root.text); + gb_free(ha, find_result.windows_sdk_um_library_path.text); + gb_free(ha, find_result.windows_sdk_ucrt_library_path.text); + gb_free(ha, find_result.vs_exe_path.text); + gb_free(ha, find_result.vs_library_path.text); + + } + #endif + + // All the build targets and OSes. + String output_extension; + + if (bc->command_kind == Command_doc && bc->cmd_doc_flags & CmdDocFlag_DocFormat) { + output_extension = STR_LIT("odin-doc"); + } else if (is_arch_wasm()) { + output_extension = STR_LIT("wasm"); + } else if (build_context.build_mode == BuildMode_Executable) { + // By default use a .bin executable extension. + output_extension = STR_LIT("bin"); + + if (build_context.metrics.os == TargetOs_windows) { + output_extension = STR_LIT("exe"); + } else if (build_context.cross_compiling && selected_target_metrics->metrics == &target_essence_amd64) { + output_extension = make_string(nullptr, 0); + } + } else if (build_context.build_mode == BuildMode_DynamicLibrary) { + // By default use a .so shared library extension. + output_extension = STR_LIT("so"); + + if (build_context.metrics.os == TargetOs_windows) { + output_extension = STR_LIT("dll"); + } else if (build_context.metrics.os == TargetOs_darwin) { + output_extension = STR_LIT("dylib"); + } + } else if (build_context.build_mode == BuildMode_Object) { + // By default use a .o object extension. + output_extension = STR_LIT("o"); + + if (build_context.metrics.os == TargetOs_windows) { + output_extension = STR_LIT("obj"); + } + } else if (build_context.build_mode == BuildMode_Assembly) { + // By default use a .S asm extension. + output_extension = STR_LIT("S"); + } else if (build_context.build_mode == BuildMode_LLVM_IR) { + output_extension = STR_LIT("ll"); + } else { + GB_PANIC("Unhandled build mode/target combination.\n"); + } + + if (bc->out_filepath.len > 0) { + bc->build_paths[BuildPath_Output] = path_from_string(ha, bc->out_filepath); + } else { + String output_name = remove_directory_from_path(init_filename); + output_name = remove_extension_from_path(output_name); + output_name = copy_string(ha, string_trim_whitespace(output_name)); + + /* + NOTE(Jeroen): This fallback substitution can't be made at this stage. + if (gen->output_name.len == 0) { + gen->output_name = c->info.init_scope->pkg->name; + } + */ + Path output_path = path_from_string(ha, output_name); + + #ifndef GB_SYSTEM_WINDOWS + char cwd[4096]; + getcwd(&cwd[0], 4096); + + const u8 * cwd_str = (const u8 *)&cwd[0]; + output_path.basename = copy_string(ha, make_string(cwd_str, strlen(cwd))); + #endif + + // Replace extension. + if (output_path.ext.len > 0) { + gb_free(ha, output_path.ext.text); + } + output_path.ext = copy_string(ha, output_extension); + + bc->build_paths[BuildPath_Output] = output_path; + } + + // Do we have an extension? We might not if the output filename was supplied. + if (bc->build_paths[BuildPath_Output].ext.len == 0) { + bc->build_paths[BuildPath_Output].ext = copy_string(ha, output_extension); + } + + // Check if output path is a directory. + if (path_is_directory(bc->build_paths[BuildPath_Output])) { + String output_file = path_to_string(ha, bc->build_paths[BuildPath_Output]); + defer (gb_free(ha, output_file.text)); + gb_printf_err("Output path %.*s is a directory.\n", LIT(output_file)); + return false; + } + + return true; +} \ No newline at end of file diff --git a/src/common.cpp b/src/common.cpp index aaacda04b..94248fb62 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -675,262 +675,7 @@ wchar_t **command_line_to_wargv(wchar_t *cmd_line, int *_argc) { #endif - -#if defined(GB_SYSTEM_WINDOWS) - bool path_is_directory(String path) { - gbAllocator a = heap_allocator(); - String16 wstr = string_to_string16(a, path); - defer (gb_free(a, wstr.text)); - - i32 attribs = GetFileAttributesW(wstr.text); - if (attribs < 0) return false; - - return (attribs & FILE_ATTRIBUTE_DIRECTORY) != 0; - } - -#else - bool path_is_directory(String path) { - gbAllocator a = heap_allocator(); - char *copy = cast(char *)copy_string(a, path).text; - defer (gb_free(a, copy)); - - struct stat s; - if (stat(copy, &s) == 0) { - return (s.st_mode & S_IFDIR) != 0; - } - return false; - } -#endif - - -String path_to_full_path(gbAllocator a, String path) { - gbAllocator ha = heap_allocator(); - char *path_c = gb_alloc_str_len(ha, cast(char *)path.text, path.len); - defer (gb_free(ha, path_c)); - - char *fullpath = gb_path_get_full_name(a, path_c); - String res = string_trim_whitespace(make_string_c(fullpath)); -#if defined(GB_SYSTEM_WINDOWS) - for (isize i = 0; i < res.len; i++) { - if (res.text[i] == '\\') { - res.text[i] = '/'; - } - } -#endif - return res; -} - - - -struct FileInfo { - String name; - String fullpath; - i64 size; - bool is_dir; -}; - -enum ReadDirectoryError { - ReadDirectory_None, - - ReadDirectory_InvalidPath, - ReadDirectory_NotExists, - ReadDirectory_Permission, - ReadDirectory_NotDir, - ReadDirectory_Empty, - ReadDirectory_Unknown, - - ReadDirectory_COUNT, -}; - -i64 get_file_size(String path) { - char *c_str = alloc_cstring(heap_allocator(), path); - defer (gb_free(heap_allocator(), c_str)); - - gbFile f = {}; - gbFileError err = gb_file_open(&f, c_str); - defer (gb_file_close(&f)); - if (err != gbFileError_None) { - return -1; - } - return gb_file_size(&f); -} - - -#if defined(GB_SYSTEM_WINDOWS) -ReadDirectoryError read_directory(String path, Array *fi) { - GB_ASSERT(fi != nullptr); - - gbAllocator a = heap_allocator(); - - while (path.len > 0) { - Rune end = path[path.len-1]; - if (end == '/') { - path.len -= 1; - } else if (end == '\\') { - path.len -= 1; - } else { - break; - } - } - - if (path.len == 0) { - return ReadDirectory_InvalidPath; - } - { - char *c_str = alloc_cstring(a, path); - defer (gb_free(a, c_str)); - - gbFile f = {}; - gbFileError file_err = gb_file_open(&f, c_str); - defer (gb_file_close(&f)); - - switch (file_err) { - case gbFileError_Invalid: return ReadDirectory_InvalidPath; - case gbFileError_NotExists: return ReadDirectory_NotExists; - // case gbFileError_Permission: return ReadDirectory_Permission; - } - } - - if (!path_is_directory(path)) { - return ReadDirectory_NotDir; - } - - - char *new_path = gb_alloc_array(a, char, path.len+3); - defer (gb_free(a, new_path)); - - gb_memmove(new_path, path.text, path.len); - gb_memmove(new_path+path.len, "/*", 2); - new_path[path.len+2] = 0; - - String np = make_string(cast(u8 *)new_path, path.len+2); - String16 wstr = string_to_string16(a, np); - defer (gb_free(a, wstr.text)); - - WIN32_FIND_DATAW file_data = {}; - HANDLE find_file = FindFirstFileW(wstr.text, &file_data); - if (find_file == INVALID_HANDLE_VALUE) { - return ReadDirectory_Unknown; - } - defer (FindClose(find_file)); - - array_init(fi, a, 0, 100); - - do { - wchar_t *filename_w = file_data.cFileName; - i64 size = cast(i64)file_data.nFileSizeLow; - size |= (cast(i64)file_data.nFileSizeHigh) << 32; - String name = string16_to_string(a, make_string16_c(filename_w)); - if (name == "." || name == "..") { - gb_free(a, name.text); - continue; - } - - String filepath = {}; - filepath.len = path.len+1+name.len; - filepath.text = gb_alloc_array(a, u8, filepath.len+1); - defer (gb_free(a, filepath.text)); - gb_memmove(filepath.text, path.text, path.len); - gb_memmove(filepath.text+path.len, "/", 1); - gb_memmove(filepath.text+path.len+1, name.text, name.len); - - FileInfo info = {}; - info.name = name; - info.fullpath = path_to_full_path(a, filepath); - info.size = size; - info.is_dir = (file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; - array_add(fi, info); - } while (FindNextFileW(find_file, &file_data)); - - if (fi->count == 0) { - return ReadDirectory_Empty; - } - - return ReadDirectory_None; -} -#elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD) - -#include - -ReadDirectoryError read_directory(String path, Array *fi) { - GB_ASSERT(fi != nullptr); - - gbAllocator a = heap_allocator(); - - char *c_path = alloc_cstring(a, path); - defer (gb_free(a, c_path)); - - DIR *dir = opendir(c_path); - if (!dir) { - switch (errno) { - case ENOENT: - return ReadDirectory_NotExists; - case EACCES: - return ReadDirectory_Permission; - case ENOTDIR: - return ReadDirectory_NotDir; - default: - // ENOMEM: out of memory - // EMFILE: per-process limit on open fds reached - // ENFILE: system-wide limit on total open files reached - return ReadDirectory_Unknown; - } - GB_PANIC("unreachable"); - } - - array_init(fi, a, 0, 100); - - for (;;) { - struct dirent *entry = readdir(dir); - if (entry == nullptr) { - break; - } - - String name = make_string_c(entry->d_name); - if (name == "." || name == "..") { - continue; - } - - String filepath = {}; - filepath.len = path.len+1+name.len; - filepath.text = gb_alloc_array(a, u8, filepath.len+1); - defer (gb_free(a, filepath.text)); - gb_memmove(filepath.text, path.text, path.len); - gb_memmove(filepath.text+path.len, "/", 1); - gb_memmove(filepath.text+path.len+1, name.text, name.len); - filepath.text[filepath.len] = 0; - - - struct stat dir_stat = {}; - - if (stat((char *)filepath.text, &dir_stat)) { - continue; - } - - if (S_ISDIR(dir_stat.st_mode)) { - continue; - } - - i64 size = dir_stat.st_size; - - FileInfo info = {}; - info.name = name; - info.fullpath = path_to_full_path(a, filepath); - info.size = size; - array_add(fi, info); - } - - if (fi->count == 0) { - return ReadDirectory_Empty; - } - - return ReadDirectory_None; -} -#else -#error Implement read_directory -#endif - - +#include "path.cpp" struct LoadedFile { void *handle; diff --git a/src/gb/gb.h b/src/gb/gb.h index b72a893f7..3b2d6434c 100644 --- a/src/gb/gb.h +++ b/src/gb/gb.h @@ -6273,20 +6273,44 @@ char *gb_path_get_full_name(gbAllocator a, char const *path) { #else char *p, *result, *fullpath = NULL; isize len; - p = realpath(path, NULL); - fullpath = p; - if (p == NULL) { - // NOTE(bill): File does not exist - fullpath = cast(char *)path; + fullpath = realpath(path, NULL); + + if (fullpath == NULL) { + // NOTE(Jeroen): Path doesn't exist. + if (gb_strlen(path) > 0 && path[0] == '/') { + // But it is an absolute path, so return as-is. + + fullpath = (char *)path; + len = gb_strlen(fullpath) + 1; + result = gb_alloc_array(a, char, len + 1); + + gb_memmove(result, fullpath, len); + result[len] = 0; + + } else { + // Appears to be a relative path, so construct an absolute one relative to . + char cwd[4096]; + getcwd(&cwd[0], 4096); + + isize path_len = gb_strlen(path); + isize cwd_len = gb_strlen(cwd); + len = cwd_len + 1 + path_len + 1; + result = gb_alloc_array(a, char, len); + + gb_memmove(result, (void *)cwd, cwd_len); + result[cwd_len] = '/'; + + gb_memmove(result + cwd_len + 1, (void *)path, gb_strlen(path)); + result[len] = 0; + + } + } else { + len = gb_strlen(fullpath) + 1; + result = gb_alloc_array(a, char, len + 1); + gb_memmove(result, fullpath, len); + result[len] = 0; + free(fullpath); } - - len = gb_strlen(fullpath); - - result = gb_alloc_array(a, char, len + 1); - gb_memmove(result, fullpath, len); - result[len] = 0; - free(p); - return result; #endif } diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index f5cb84785..7781997f7 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -967,7 +967,12 @@ lbProcedure *lb_create_main_procedure(lbModule *m, lbProcedure *startup_runtime) } String lb_filepath_ll_for_module(lbModule *m) { - String path = m->gen->output_base; + String path = concatenate3_strings(permanent_allocator(), + build_context.build_paths[BuildPath_Output].basename, + STR_LIT("/"), + build_context.build_paths[BuildPath_Output].name + ); + if (m->pkg) { path = concatenate3_strings(permanent_allocator(), path, STR_LIT("-"), m->pkg->name); } else if (USE_SEPARATE_MODULES) { @@ -978,7 +983,12 @@ String lb_filepath_ll_for_module(lbModule *m) { return path; } String lb_filepath_obj_for_module(lbModule *m) { - String path = m->gen->output_base; + String path = concatenate3_strings(permanent_allocator(), + build_context.build_paths[BuildPath_Output].basename, + STR_LIT("/"), + build_context.build_paths[BuildPath_Output].name + ); + if (m->pkg) { path = concatenate3_strings(permanent_allocator(), path, STR_LIT("-"), m->pkg->name); } diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 330059622..1a431a4ac 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -87,7 +87,6 @@ bool lb_init_generator(lbGenerator *gen, Checker *c) { return false; } - String init_fullpath = c->parser->init_fullpath; if (build_context.out_filepath.len == 0) { diff --git a/src/main.cpp b/src/main.cpp index fc8792ceb..7b0364149 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -46,7 +46,6 @@ gb_global Timings global_timings = {0}; #include "checker.cpp" #include "docs.cpp" - #include "llvm_backend.cpp" #if defined(GB_SYSTEM_OSX) @@ -57,16 +56,8 @@ gb_global Timings global_timings = {0}; #endif #include "query_data.cpp" - - -#if defined(GB_SYSTEM_WINDOWS) -// NOTE(IC): In order to find Visual C++ paths without relying on environment variables. -#include "microsoft_craziness.h" -#endif - #include "bug_report.cpp" - // NOTE(bill): 'name' is used in debugging and profiling modes i32 system_exec_command_line_app(char const *name, char const *fmt, ...) { isize const cmd_cap = 64<<20; // 64 MiB should be more than enough @@ -130,34 +121,35 @@ i32 system_exec_command_line_app(char const *name, char const *fmt, ...) { } - - i32 linker_stage(lbGenerator *gen) { i32 result = 0; Timings *timings = &global_timings; - String output_base = gen->output_base; + String output_filename = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]); + debugf("Linking %.*s\n", LIT(output_filename)); + + // TOOD(Jeroen): Make a `build_paths[BuildPath_Object] to avoid `%.*s.o`. if (is_arch_wasm()) { timings_start_section(timings, str_lit("wasm-ld")); #if defined(GB_SYSTEM_WINDOWS) result = system_exec_command_line_app("wasm-ld", - "\"%.*s\\bin\\wasm-ld\" \"%.*s.wasm.o\" -o \"%.*s.wasm\" %.*s %.*s", + "\"%.*s\\bin\\wasm-ld\" \"%.*s.o\" -o \"%.*s\" %.*s %.*s", LIT(build_context.ODIN_ROOT), - LIT(output_base), LIT(output_base), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags)); + LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags)); #else result = system_exec_command_line_app("wasm-ld", - "wasm-ld \"%.*s.wasm.o\" -o \"%.*s.wasm\" %.*s %.*s", - LIT(output_base), LIT(output_base), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags)); + "wasm-ld \"%.*s.o\" -o \"%.*s\" %.*s %.*s", + LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags)); #endif return result; } if (build_context.cross_compiling && selected_target_metrics->metrics == &target_essence_amd64) { -#ifdef GB_SYSTEM_UNIX +#if defined(GB_SYSTEM_UNIX) result = system_exec_command_line_app("linker", "x86_64-essence-gcc \"%.*s.o\" -o \"%.*s\" %.*s %.*s", - LIT(output_base), LIT(output_base), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags)); + LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags)); #else gb_printf_err("Linking for cross compilation for this platform is not yet supported (%.*s %.*s)\n", LIT(target_os_names[build_context.metrics.os]), @@ -181,28 +173,11 @@ i32 linker_stage(lbGenerator *gen) { gbString lib_str = gb_string_make(heap_allocator(), ""); defer (gb_string_free(lib_str)); - char const *output_ext = "exe"; gbString link_settings = gb_string_make_reserve(heap_allocator(), 256); defer (gb_string_free(link_settings)); - - // NOTE(ic): It would be nice to extend this so that we could specify the Visual Studio version that we want instead of defaulting to the latest. - Find_Result_Utf8 find_result = find_visual_studio_and_windows_sdk_utf8(); - - if (find_result.windows_sdk_version == 0) { - gb_printf_err("Windows SDK not found.\n"); - exit(1); - } - - if (build_context.ignore_microsoft_magic) { - find_result = {}; - } - // Add library search paths. - if (find_result.vs_library_path.len > 0) { - GB_ASSERT(find_result.windows_sdk_um_library_path.len > 0); - GB_ASSERT(find_result.windows_sdk_ucrt_library_path.len > 0); - + if (build_context.build_paths[BuildPath_VS_LIB].basename.len > 0) { String path = {}; auto add_path = [&](String path) { if (path[path.len-1] == '\\') { @@ -210,9 +185,9 @@ i32 linker_stage(lbGenerator *gen) { } link_settings = gb_string_append_fmt(link_settings, " /LIBPATH:\"%.*s\"", LIT(path)); }; - add_path(find_result.windows_sdk_um_library_path); - add_path(find_result.windows_sdk_ucrt_library_path); - add_path(find_result.vs_library_path); + add_path(build_context.build_paths[BuildPath_Win_SDK_UM_Lib].basename); + add_path(build_context.build_paths[BuildPath_Win_SDK_UCRT_Lib].basename); + add_path(build_context.build_paths[BuildPath_VS_LIB].basename); } @@ -252,14 +227,14 @@ i32 linker_stage(lbGenerator *gen) { if (build_context.build_mode == BuildMode_DynamicLibrary) { - output_ext = "dll"; link_settings = gb_string_append_fmt(link_settings, " /DLL"); } else { link_settings = gb_string_append_fmt(link_settings, " /ENTRY:mainCRTStartup"); } if (build_context.pdb_filepath != "") { - link_settings = gb_string_append_fmt(link_settings, " /PDB:%.*s", LIT(build_context.pdb_filepath)); + String pdb_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_PDB]); + link_settings = gb_string_append_fmt(link_settings, " /PDB:%.*s", LIT(pdb_path)); } if (build_context.no_crt) { @@ -300,13 +275,21 @@ i32 linker_stage(lbGenerator *gen) { object_files = gb_string_append_fmt(object_files, "\"%.*s\" ", LIT(object_path)); } + String vs_exe_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_VS_EXE]); + defer (gb_free(heap_allocator(), vs_exe_path.text)); + char const *subsystem_str = build_context.use_subsystem_windows ? "WINDOWS" : "CONSOLE"; if (!build_context.use_lld) { // msvc if (build_context.has_resource) { + String rc_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_RC]); + String res_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_RES]); + defer (gb_free(heap_allocator(), rc_path.text)); + defer (gb_free(heap_allocator(), res_path.text)); + result = system_exec_command_line_app("msvc-link", - "\"rc.exe\" /nologo /fo \"%.*s.res\" \"%.*s.rc\"", - LIT(output_base), - LIT(build_context.resource_filepath) + "\"rc.exe\" /nologo /fo \"%.*s\" \"%.*s\"", + LIT(res_path), + LIT(rc_path) ); if (result) { @@ -314,13 +297,13 @@ i32 linker_stage(lbGenerator *gen) { } result = system_exec_command_line_app("msvc-link", - "\"%.*slink.exe\" %s \"%.*s.res\" -OUT:\"%.*s.%s\" %s " + "\"%.*slink.exe\" %s \"%.*s\" -OUT:\"%.*s\" %s " "/nologo /incremental:no /opt:ref /subsystem:%s " " %.*s " " %.*s " " %s " "", - LIT(find_result.vs_exe_path), object_files, LIT(output_base), LIT(output_base), output_ext, + LIT(vs_exe_path), object_files, LIT(res_path), LIT(output_filename), link_settings, subsystem_str, LIT(build_context.link_flags), @@ -329,13 +312,13 @@ i32 linker_stage(lbGenerator *gen) { ); } else { result = system_exec_command_line_app("msvc-link", - "\"%.*slink.exe\" %s -OUT:\"%.*s.%s\" %s " + "\"%.*slink.exe\" %s -OUT:\"%.*s\" %s " "/nologo /incremental:no /opt:ref /subsystem:%s " " %.*s " " %.*s " " %s " "", - LIT(find_result.vs_exe_path), object_files, LIT(output_base), output_ext, + LIT(vs_exe_path), object_files, LIT(output_filename), link_settings, subsystem_str, LIT(build_context.link_flags), @@ -350,13 +333,13 @@ i32 linker_stage(lbGenerator *gen) { } else { // lld result = system_exec_command_line_app("msvc-lld-link", - "\"%.*s\\bin\\lld-link\" %s -OUT:\"%.*s.%s\" %s " + "\"%.*s\\bin\\lld-link\" %s -OUT:\"%.*s\" %s " "/nologo /incremental:no /opt:ref /subsystem:%s " " %.*s " " %.*s " " %s " "", - LIT(build_context.ODIN_ROOT), object_files, LIT(output_base),output_ext, + LIT(build_context.ODIN_ROOT), object_files, LIT(output_filename), link_settings, subsystem_str, LIT(build_context.link_flags), @@ -415,7 +398,7 @@ i32 linker_stage(lbGenerator *gen) { } else if (string_ends_with(lib, str_lit(".so"))) { // dynamic lib, relative path to executable // NOTE(vassvik): it is the user's responsibility to make sure the shared library files are visible - // at runtimeto the executable + // at runtime to the executable lib_str = gb_string_append_fmt(lib_str, " -l:\"%s/%.*s\" ", cwd, LIT(lib)); } else { // dynamic or static system lib, just link regularly searching system library paths @@ -431,9 +414,6 @@ i32 linker_stage(lbGenerator *gen) { object_files = gb_string_append_fmt(object_files, "\"%.*s\" ", LIT(object_path)); } - // Unlike the Win32 linker code, the output_ext includes the dot, because - // typically executable files on *NIX systems don't have extensions. - String output_ext = {}; gbString link_settings = gb_string_make_reserve(heap_allocator(), 32); if (build_context.no_crt) { @@ -461,26 +441,12 @@ i32 linker_stage(lbGenerator *gen) { // correctly this way since all the other dependencies provided implicitly // by the compiler frontend are still needed and most of the command // line arguments prepared previously are incompatible with ld. - // - // Shared libraries are .dylib on MacOS and .so on Linux. - if (build_context.metrics.os == TargetOs_darwin) { - output_ext = STR_LIT(".dylib"); - } else { - output_ext = STR_LIT(".so"); - } link_settings = gb_string_appendc(link_settings, "-Wl,-init,'_odin_entry_point' "); link_settings = gb_string_appendc(link_settings, "-Wl,-fini,'_odin_exit_point' "); } else if (build_context.metrics.os != TargetOs_openbsd) { // OpenBSD defaults to PIE executable. do not pass -no-pie for it. link_settings = gb_string_appendc(link_settings, "-no-pie "); } - if (build_context.out_filepath.len > 0) { - //NOTE(thebirk): We have a custom -out arguments, so we should use the extension from that - isize pos = string_extension_position(build_context.out_filepath); - if (pos > 0) { - output_ext = substring(build_context.out_filepath, pos, build_context.out_filepath.len); - } - } gbString platform_lib_str = gb_string_make(heap_allocator(), ""); defer (gb_string_free(platform_lib_str)); @@ -507,7 +473,7 @@ i32 linker_stage(lbGenerator *gen) { defer (gb_string_free(link_command_line)); link_command_line = gb_string_appendc(link_command_line, object_files); - link_command_line = gb_string_append_fmt(link_command_line, " -o \"%.*s%.*s\" ", LIT(output_base), LIT(output_ext)); + link_command_line = gb_string_append_fmt(link_command_line, " -o \"%.*s\" ", LIT(output_filename)); link_command_line = gb_string_append_fmt(link_command_line, " %s ", platform_lib_str); link_command_line = gb_string_append_fmt(link_command_line, " %s ", lib_str); link_command_line = gb_string_append_fmt(link_command_line, " %.*s ", LIT(build_context.link_flags)); @@ -524,9 +490,7 @@ i32 linker_stage(lbGenerator *gen) { if (build_context.ODIN_DEBUG) { // NOTE: macOS links DWARF symbols dynamically. Dsymutil will map the stubs in the exe // to the symbols in the object file - result = system_exec_command_line_app("dsymutil", - "dsymutil %.*s%.*s", LIT(output_base), LIT(output_ext) - ); + result = system_exec_command_line_app("dsymutil", "dsymutil %.*s", LIT(output_filename)); if (result) { return result; @@ -1526,6 +1490,10 @@ bool parse_build_flags(Array args) { gb_printf_err("Invalid -resource path %.*s, missing .rc\n", LIT(path)); bad_flags = true; break; + } else if (!gb_file_exists((const char *)path.text)) { + gb_printf_err("Invalid -resource path %.*s, file does not exist.\n", LIT(path)); + bad_flags = true; + break; } build_context.resource_filepath = substring(path, 0, string_extension_position(path)); build_context.has_resource = true; @@ -1540,6 +1508,11 @@ bool parse_build_flags(Array args) { String path = value.value_string; path = string_trim_whitespace(path); if (is_build_flag_path_valid(path)) { + if (path_is_directory(path)) { + gb_printf_err("Invalid -pdb-name path. %.*s, is a directory.\n", LIT(path)); + bad_flags = true; + break; + } // #if defined(GB_SYSTEM_WINDOWS) // String ext = path_extension(path); // if (ext != ".pdb") { @@ -2666,6 +2639,8 @@ int main(int arg_count, char const **arg_ptr) { return 1; } + init_filename = copy_string(permanent_allocator(), init_filename); + if (init_filename == "-help" || init_filename == "--help") { build_context.show_help = true; @@ -2688,6 +2663,12 @@ int main(int arg_count, char const **arg_ptr) { gb_printf_err("Did you mean `%.*s %.*s %.*s -file`?\n", LIT(args[0]), LIT(command), LIT(init_filename)); gb_printf_err("The `-file` flag tells it to treat a file as a self-contained package.\n"); return 1; + } else { + String const ext = str_lit(".odin"); + if (!string_ends_with(init_filename, ext)) { + gb_printf_err("Expected either a directory or a .odin file, got '%.*s'\n", LIT(init_filename)); + return 1; + } } } } @@ -2709,13 +2690,24 @@ int main(int arg_count, char const **arg_ptr) { get_fullpath_relative(heap_allocator(), odin_root_dir(), str_lit("shared"))); } - init_build_context(selected_target_metrics ? selected_target_metrics->metrics : nullptr); // if (build_context.word_size == 4 && build_context.metrics.os != TargetOs_js) { // print_usage_line(0, "%.*s 32-bit is not yet supported for this platform", LIT(args[0])); // return 1; // } + // Set and check build paths... + if (!init_build_paths(init_filename)) { + return 1; + } + + if (build_context.show_debug_messages) { + for_array(i, build_context.build_paths) { + String build_path = path_to_string(heap_allocator(), build_context.build_paths[i]); + debugf("build_paths[%ld]: %.*s\n", i, LIT(build_path)); + } + } + init_global_thread_pool(); defer (thread_pool_destroy(&global_thread_pool)); @@ -2732,6 +2724,8 @@ int main(int arg_count, char const **arg_ptr) { } defer (destroy_parser(parser)); + // TODO(jeroen): Remove the `init_filename` param. + // Let's put that on `build_context.build_paths[0]` instead. if (parse_packages(parser, init_filename) != ParseFile_None) { return 1; } @@ -2810,16 +2804,14 @@ int main(int arg_count, char const **arg_ptr) { } if (run_output) { + String exe_name = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]); + defer (gb_free(heap_allocator(), exe_name.text)); + #if defined(GB_SYSTEM_WINDOWS) - return system_exec_command_line_app("odin run", "%.*s.exe %.*s", LIT(gen->output_base), LIT(run_args_string)); + return system_exec_command_line_app("odin run", "%.*s %.*s", LIT(exe_name), LIT(run_args_string)); #else - //NOTE(thebirk): This whole thing is a little leaky - String output_ext = {}; - String complete_path = concatenate_strings(permanent_allocator(), gen->output_base, output_ext); - complete_path = path_to_full_path(permanent_allocator(), complete_path); - return system_exec_command_line_app("odin run", "\"%.*s\" %.*s", LIT(complete_path), LIT(run_args_string)); + return system_exec_command_line_app("odin run", "\"%.*s\" %.*s", LIT(exe_name), LIT(run_args_string)); #endif } - return 0; } diff --git a/src/parser.cpp b/src/parser.cpp index 767119aa8..df7f908a6 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -5751,7 +5751,7 @@ ParseFileError parse_packages(Parser *p, String init_filename) { } } } - + { // Add these packages serially and then process them parallel mutex_lock(&p->wait_mutex); diff --git a/src/path.cpp b/src/path.cpp new file mode 100644 index 000000000..8d8e532b8 --- /dev/null +++ b/src/path.cpp @@ -0,0 +1,333 @@ +/* + Path handling utilities. +*/ + +#if defined(GB_SYSTEM_WINDOWS) + bool path_is_directory(String path) { + gbAllocator a = heap_allocator(); + String16 wstr = string_to_string16(a, path); + defer (gb_free(a, wstr.text)); + + i32 attribs = GetFileAttributesW(wstr.text); + if (attribs < 0) return false; + + return (attribs & FILE_ATTRIBUTE_DIRECTORY) != 0; + } + +#else + bool path_is_directory(String path) { + gbAllocator a = heap_allocator(); + char *copy = cast(char *)copy_string(a, path).text; + defer (gb_free(a, copy)); + + struct stat s; + if (stat(copy, &s) == 0) { + return (s.st_mode & S_IFDIR) != 0; + } + return false; + } +#endif + + +String path_to_full_path(gbAllocator a, String path) { + gbAllocator ha = heap_allocator(); + char *path_c = gb_alloc_str_len(ha, cast(char *)path.text, path.len); + defer (gb_free(ha, path_c)); + + char *fullpath = gb_path_get_full_name(a, path_c); + String res = string_trim_whitespace(make_string_c(fullpath)); +#if defined(GB_SYSTEM_WINDOWS) + for (isize i = 0; i < res.len; i++) { + if (res.text[i] == '\\') { + res.text[i] = '/'; + } + } +#endif + return copy_string(a, res); +} + +struct Path { + String basename; + String name; + String ext; +}; + +// NOTE(Jeroen): Naively turns a Path into a string. +String path_to_string(gbAllocator a, Path path) { + if (path.basename.len + path.name.len + path.ext.len == 0) { + return make_string(nullptr, 0); + } + + isize len = path.basename.len + 1 + path.name.len + 1; + if (path.ext.len > 0) { + len += path.ext.len + 1; + } + + u8 *str = gb_alloc_array(a, u8, len); + + isize i = 0; + gb_memmove(str+i, path.basename.text, path.basename.len); i += path.basename.len; + gb_memmove(str+i, "/", 1); i += 1; + gb_memmove(str+i, path.name.text, path.name.len); i += path.name.len; + if (path.ext.len > 0) { + gb_memmove(str+i, ".", 1); i += 1; + gb_memmove(str+i, path.ext.text, path.ext.len); i += path.ext.len; + } + str[i] = 0; + + String res = make_string(str, i); + res = string_trim_whitespace(res); + return res; +} + +// NOTE(Jeroen): Naively turns a Path into a string, then normalizes it using `path_to_full_path`. +String path_to_full_path(gbAllocator a, Path path) { + String temp = path_to_string(heap_allocator(), path); + defer (gb_free(heap_allocator(), temp.text)); + + return path_to_full_path(a, temp); +} + +// NOTE(Jeroen): Takes a path like "odin" or "W:\Odin", turns it into a full path, +// and then breaks it into its components to make a Path. +Path path_from_string(gbAllocator a, String const &path) { + Path res = {}; + + if (path.len == 0) return res; + + String fullpath = path_to_full_path(a, path); + defer (gb_free(heap_allocator(), fullpath.text)); + + res.basename = directory_from_path(fullpath); + res.basename = copy_string(a, res.basename); + + if (string_ends_with(fullpath, '/')) { + // It's a directory. We don't need to tinker with the name and extension. + return res; + } + + isize name_start = (res.basename.len > 0) ? res.basename.len + 1 : res.basename.len; + res.name = substring(fullpath, name_start, fullpath.len); + res.name = remove_extension_from_path(res.name); + res.name = copy_string(a, res.name); + + res.ext = path_extension(fullpath, false); // false says not to include the dot. + res.ext = copy_string(a, res.ext); + return res; +} + +bool path_is_directory(Path path) { + String path_string = path_to_full_path(heap_allocator(), path); + defer (gb_free(heap_allocator(), path_string.text)); + + return path_is_directory(path_string); +} + +struct FileInfo { + String name; + String fullpath; + i64 size; + bool is_dir; +}; + +enum ReadDirectoryError { + ReadDirectory_None, + + ReadDirectory_InvalidPath, + ReadDirectory_NotExists, + ReadDirectory_Permission, + ReadDirectory_NotDir, + ReadDirectory_Empty, + ReadDirectory_Unknown, + + ReadDirectory_COUNT, +}; + +i64 get_file_size(String path) { + char *c_str = alloc_cstring(heap_allocator(), path); + defer (gb_free(heap_allocator(), c_str)); + + gbFile f = {}; + gbFileError err = gb_file_open(&f, c_str); + defer (gb_file_close(&f)); + if (err != gbFileError_None) { + return -1; + } + return gb_file_size(&f); +} + + +#if defined(GB_SYSTEM_WINDOWS) +ReadDirectoryError read_directory(String path, Array *fi) { + GB_ASSERT(fi != nullptr); + + gbAllocator a = heap_allocator(); + + while (path.len > 0) { + Rune end = path[path.len-1]; + if (end == '/') { + path.len -= 1; + } else if (end == '\\') { + path.len -= 1; + } else { + break; + } + } + + if (path.len == 0) { + return ReadDirectory_InvalidPath; + } + { + char *c_str = alloc_cstring(a, path); + defer (gb_free(a, c_str)); + + gbFile f = {}; + gbFileError file_err = gb_file_open(&f, c_str); + defer (gb_file_close(&f)); + + switch (file_err) { + case gbFileError_Invalid: return ReadDirectory_InvalidPath; + case gbFileError_NotExists: return ReadDirectory_NotExists; + // case gbFileError_Permission: return ReadDirectory_Permission; + } + } + + if (!path_is_directory(path)) { + return ReadDirectory_NotDir; + } + + + char *new_path = gb_alloc_array(a, char, path.len+3); + defer (gb_free(a, new_path)); + + gb_memmove(new_path, path.text, path.len); + gb_memmove(new_path+path.len, "/*", 2); + new_path[path.len+2] = 0; + + String np = make_string(cast(u8 *)new_path, path.len+2); + String16 wstr = string_to_string16(a, np); + defer (gb_free(a, wstr.text)); + + WIN32_FIND_DATAW file_data = {}; + HANDLE find_file = FindFirstFileW(wstr.text, &file_data); + if (find_file == INVALID_HANDLE_VALUE) { + return ReadDirectory_Unknown; + } + defer (FindClose(find_file)); + + array_init(fi, a, 0, 100); + + do { + wchar_t *filename_w = file_data.cFileName; + i64 size = cast(i64)file_data.nFileSizeLow; + size |= (cast(i64)file_data.nFileSizeHigh) << 32; + String name = string16_to_string(a, make_string16_c(filename_w)); + if (name == "." || name == "..") { + gb_free(a, name.text); + continue; + } + + String filepath = {}; + filepath.len = path.len+1+name.len; + filepath.text = gb_alloc_array(a, u8, filepath.len+1); + defer (gb_free(a, filepath.text)); + gb_memmove(filepath.text, path.text, path.len); + gb_memmove(filepath.text+path.len, "/", 1); + gb_memmove(filepath.text+path.len+1, name.text, name.len); + + FileInfo info = {}; + info.name = name; + info.fullpath = path_to_full_path(a, filepath); + info.size = size; + info.is_dir = (file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + array_add(fi, info); + } while (FindNextFileW(find_file, &file_data)); + + if (fi->count == 0) { + return ReadDirectory_Empty; + } + + return ReadDirectory_None; +} +#elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD) + +#include + +ReadDirectoryError read_directory(String path, Array *fi) { + GB_ASSERT(fi != nullptr); + + gbAllocator a = heap_allocator(); + + char *c_path = alloc_cstring(a, path); + defer (gb_free(a, c_path)); + + DIR *dir = opendir(c_path); + if (!dir) { + switch (errno) { + case ENOENT: + return ReadDirectory_NotExists; + case EACCES: + return ReadDirectory_Permission; + case ENOTDIR: + return ReadDirectory_NotDir; + default: + // ENOMEM: out of memory + // EMFILE: per-process limit on open fds reached + // ENFILE: system-wide limit on total open files reached + return ReadDirectory_Unknown; + } + GB_PANIC("unreachable"); + } + + array_init(fi, a, 0, 100); + + for (;;) { + struct dirent *entry = readdir(dir); + if (entry == nullptr) { + break; + } + + String name = make_string_c(entry->d_name); + if (name == "." || name == "..") { + continue; + } + + String filepath = {}; + filepath.len = path.len+1+name.len; + filepath.text = gb_alloc_array(a, u8, filepath.len+1); + defer (gb_free(a, filepath.text)); + gb_memmove(filepath.text, path.text, path.len); + gb_memmove(filepath.text+path.len, "/", 1); + gb_memmove(filepath.text+path.len+1, name.text, name.len); + filepath.text[filepath.len] = 0; + + + struct stat dir_stat = {}; + + if (stat((char *)filepath.text, &dir_stat)) { + continue; + } + + if (S_ISDIR(dir_stat.st_mode)) { + continue; + } + + i64 size = dir_stat.st_size; + + FileInfo info = {}; + info.name = name; + info.fullpath = path_to_full_path(a, filepath); + info.size = size; + array_add(fi, info); + } + + if (fi->count == 0) { + return ReadDirectory_Empty; + } + + return ReadDirectory_None; +} +#else +#error Implement read_directory +#endif + diff --git a/src/string.cpp b/src/string.cpp index d3dbc6904..3515df48e 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -245,15 +245,14 @@ gb_inline isize string_extension_position(String const &str) { return dot_pos; } -String path_extension(String const &str) { +String path_extension(String const &str, bool include_dot = true) { isize pos = string_extension_position(str); if (pos < 0) { return make_string(nullptr, 0); } - return substring(str, pos, str.len); + return substring(str, include_dot ? pos : pos + 1, str.len); } - String string_trim_whitespace(String str) { while (str.len > 0 && rune_is_whitespace(str[str.len-1])) { str.len--; @@ -328,7 +327,10 @@ String directory_from_path(String const &s) { break; } } - return substring(s, 0, i); + if (i >= 0) { + return substring(s, 0, i); + } + return substring(s, 0, 0); } String concatenate_strings(gbAllocator a, String const &x, String const &y) { diff --git a/tests/core/build.bat b/tests/core/build.bat index 2f9ba672e..1973c22aa 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -5,61 +5,61 @@ python3 download_assets.py echo --- echo Running core:image tests echo --- -%PATH_TO_ODIN% run image %COMMON% +%PATH_TO_ODIN% run image %COMMON% -out:test_image echo --- echo Running core:compress tests echo --- -%PATH_TO_ODIN% run compress %COMMON% +%PATH_TO_ODIN% run compress %COMMON% -out:test_compress echo --- echo Running core:strings tests echo --- -%PATH_TO_ODIN% run strings %COMMON% +%PATH_TO_ODIN% run strings %COMMON% -out:test_strings echo --- echo Running core:hash tests echo --- -%PATH_TO_ODIN% run hash %COMMON% -o:size +%PATH_TO_ODIN% run hash %COMMON% -o:size -out:test_hash echo --- echo Running core:odin tests echo --- -%PATH_TO_ODIN% run odin %COMMON% -o:size +%PATH_TO_ODIN% run odin %COMMON% -o:size -out:test_odin echo --- echo Running core:crypto hash tests echo --- -%PATH_TO_ODIN% run crypto %COMMON% +%PATH_TO_ODIN% run crypto %COMMON% -out:test_crypto echo --- echo Running core:encoding tests echo --- -%PATH_TO_ODIN% run encoding/hxa %COMMON% -%PATH_TO_ODIN% run encoding/json %COMMON% -%PATH_TO_ODIN% run encoding/varint %COMMON% +%PATH_TO_ODIN% run encoding/hxa %COMMON% -out:test_hxa +%PATH_TO_ODIN% run encoding/json %COMMON% -out:test_json +%PATH_TO_ODIN% run encoding/varint %COMMON% -out:test_varint echo --- echo Running core:math/noise tests echo --- -%PATH_TO_ODIN% run math/noise %COMMON% +%PATH_TO_ODIN% run math/noise %COMMON% -out:test_noise echo --- echo Running core:math tests echo --- -%PATH_TO_ODIN% run math %COMMON% +%PATH_TO_ODIN% run math %COMMON% -out:test_math echo --- echo Running core:math/linalg/glsl tests echo --- -%PATH_TO_ODIN% run math/linalg/glsl %COMMON% +%PATH_TO_ODIN% run math/linalg/glsl %COMMON% -out:test_glsl echo --- echo Running core:path/filepath tests echo --- -%PATH_TO_ODIN% run path/filepath %COMMON% +%PATH_TO_ODIN% run path/filepath %COMMON% -out:test_filepath echo --- echo Running core:reflect tests echo --- -%PATH_TO_ODIN% run reflect %COMMON% +%PATH_TO_ODIN% run reflect %COMMON% -out:test_reflect diff --git a/tests/core/math/big/build.bat b/tests/core/math/big/build.bat index 16bdbc8ca..ad199d775 100644 --- a/tests/core/math/big/build.bat +++ b/tests/core/math/big/build.bat @@ -4,7 +4,7 @@ set PATH_TO_ODIN==..\..\..\..\odin set TEST_ARGS=-fast-tests set TEST_ARGS=-no-random set TEST_ARGS= -set OUT_NAME=math_big_test_library +set OUT_NAME=math_big_test_library.dll set COMMON=-build-mode:shared -show-timings -no-bounds-check -define:MATH_BIG_EXE=false -vet -strict-style echo --- echo Running core:math/big tests diff --git a/tests/issues/run.sh b/tests/issues/run.sh index 117a9a5f1..91ec99e05 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -8,10 +8,10 @@ COMMON="-collection:tests=tests -out:tests/issues/build/test_issue" set -x ./odin build tests/issues/test_issue_829.odin $COMMON -file -tests/issues/build/test_issue +tests/issues/build/test_issue.bin ./odin build tests/issues/test_issue_1592.odin $COMMON -file -tests/issues/build/test_issue +tests/issues/build/test_issue.bin set +x From 76d48b38d394b953ea4bbe1420ecd11e6e7dd028 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 8 Apr 2022 19:02:14 +0200 Subject: [PATCH 25/43] Compiler: Allow -out: to not have an extension on *nix for executables (only). --- src/build_settings.cpp | 4 +++- tests/issues/run.sh | 4 ++-- tests/vendor/Makefile | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 0b582eac8..55d129124 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1308,7 +1308,9 @@ bool init_build_paths(String init_filename) { // Do we have an extension? We might not if the output filename was supplied. if (bc->build_paths[BuildPath_Output].ext.len == 0) { - bc->build_paths[BuildPath_Output].ext = copy_string(ha, output_extension); + if (build_context.metrics.os == TargetOs_windows || build_context.build_mode != BuildMode_Executable) { + bc->build_paths[BuildPath_Output].ext = copy_string(ha, output_extension); + } } // Check if output path is a directory. diff --git a/tests/issues/run.sh b/tests/issues/run.sh index 91ec99e05..117a9a5f1 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -8,10 +8,10 @@ COMMON="-collection:tests=tests -out:tests/issues/build/test_issue" set -x ./odin build tests/issues/test_issue_829.odin $COMMON -file -tests/issues/build/test_issue.bin +tests/issues/build/test_issue ./odin build tests/issues/test_issue_1592.odin $COMMON -file -tests/issues/build/test_issue.bin +tests/issues/build/test_issue set +x diff --git a/tests/vendor/Makefile b/tests/vendor/Makefile index 341067c6e..c508f6c50 100644 --- a/tests/vendor/Makefile +++ b/tests/vendor/Makefile @@ -10,4 +10,4 @@ endif all: botan_test botan_test: - $(ODIN) run botan -out=botan_hash -o:speed -no-bounds-check $(ODINFLAGS) + $(ODIN) run botan -out=test_botan_hash -o:speed -no-bounds-check $(ODINFLAGS) From f4723aea4cb610a8ccc7d3614f8787d638d284d6 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 8 Apr 2022 19:14:59 +0200 Subject: [PATCH 26/43] Remove redundant bit for non-Windows. --- src/build_settings.cpp | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 55d129124..212ded5c8 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1281,22 +1281,8 @@ bool init_build_paths(String init_filename) { output_name = remove_extension_from_path(output_name); output_name = copy_string(ha, string_trim_whitespace(output_name)); - /* - NOTE(Jeroen): This fallback substitution can't be made at this stage. - if (gen->output_name.len == 0) { - gen->output_name = c->info.init_scope->pkg->name; - } - */ Path output_path = path_from_string(ha, output_name); - #ifndef GB_SYSTEM_WINDOWS - char cwd[4096]; - getcwd(&cwd[0], 4096); - - const u8 * cwd_str = (const u8 *)&cwd[0]; - output_path.basename = copy_string(ha, make_string(cwd_str, strlen(cwd))); - #endif - // Replace extension. if (output_path.ext.len > 0) { gb_free(ha, output_path.ext.text); From 3d2856db31456e9a117209eccf8e1167b4401205 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 24 Apr 2022 14:19:25 +0200 Subject: [PATCH 27/43] Update tests to use new filename generation code. --- .github/workflows/ci.yml | 11 ++++++++--- core/crypto/util/util.odin | 1 + tests/core/Makefile | 29 +++++++++++++++-------------- tests/core/build.bat | 32 +++++++++++++++++--------------- tests/issues/run.bat | 21 ++++++++------------- tests/issues/run.sh | 19 +++++++++---------- tests/vendor/Makefile | 3 ++- tests/vendor/build.bat | 3 ++- 8 files changed, 62 insertions(+), 57 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3cc4283b0..989f56712 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,9 @@ jobs: make timeout-minutes: 10 - name: Odin issues tests - run: tests/issues/run.sh + run: | + cd tests/issues + run.sh timeout-minutes: 10 - name: Odin check examples/all for Linux i386 run: ./odin check examples/all -vet -strict-style -target:linux_i386 @@ -91,7 +93,9 @@ jobs: make timeout-minutes: 10 - name: Odin issues tests - run: tests/issues/run.sh + run: | + cd tests/issues + run.sh timeout-minutes: 10 - name: Odin check examples/all for Darwin arm64 run: ./odin check examples/all -vet -strict-style -target:darwin_arm64 @@ -163,7 +167,8 @@ jobs: shell: cmd run: | call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat - call tests\issues\run.bat + cd tests\issues + call build.bat timeout-minutes: 10 - name: Odin check examples/all for Windows 32bits shell: cmd diff --git a/core/crypto/util/util.odin b/core/crypto/util/util.odin index 6273a232e..83b07e546 100644 --- a/core/crypto/util/util.odin +++ b/core/crypto/util/util.odin @@ -11,6 +11,7 @@ package util */ import "core:mem" +_ :: mem // @note(bp): this can replace the other two cast_slice :: #force_inline proc "contextless" ($D: typeid/[]$DE, src: $S/[]$SE) -> D { diff --git a/tests/core/Makefile b/tests/core/Makefile index 6a92b4efb..9bb622633 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -1,5 +1,6 @@ ODIN=../../odin PYTHON=$(shell which python3) +OUT_FILE=test_binary.bin all: download_test_assets image_test compress_test strings_test hash_test crypto_test noise_test encoding_test \ math_test linalg_glsl_math_test filepath_test reflect_test os_exit_test @@ -8,39 +9,39 @@ download_test_assets: $(PYTHON) download_assets.py image_test: - $(ODIN) run image/test_core_image.odin -file + $(ODIN) run image/test_core_image.odin -out=$(OUT_FILE) -file compress_test: - $(ODIN) run compress/test_core_compress.odin -file + $(ODIN) run compress/test_core_compress.odin -out=$(OUT_FILE) -file strings_test: - $(ODIN) run strings/test_core_strings.odin -file + $(ODIN) run strings/test_core_strings.odin -out=$(OUT_FILE) -file hash_test: - $(ODIN) run hash -out=test_hash -o:speed -no-bounds-check + $(ODIN) run hash -out=$(OUT_FILE) -o:speed -no-bounds-check crypto_test: - $(ODIN) run crypto -out=test_crypto_hash -o:speed -no-bounds-check + $(ODIN) run crypto -out=$(OUT_FILE) -o:speed -no-bounds-check noise_test: - $(ODIN) run math/noise -out=test_noise + $(ODIN) run math/noise -out=$(OUT_FILE) encoding_test: - $(ODIN) run encoding/hxa -out=test_hxa -collection:tests=.. - $(ODIN) run encoding/json -out=test_json - $(ODIN) run encoding/varint -out=test_varint + $(ODIN) run encoding/hxa -out=$(OUT_FILE) -collection:tests=.. + $(ODIN) run encoding/json -out=$(OUT_FILE) + $(ODIN) run encoding/varint -out=$(OUT_FILE) math_test: - $(ODIN) run math/test_core_math.odin -out=test_core_math -file -collection:tests=.. + $(ODIN) run math/test_core_math.odin -out=$(OUT_FILE) -file -collection:tests=.. linalg_glsl_math_test: - $(ODIN) run math/linalg/glsl/test_linalg_glsl_math.odin -file -out=test_linalg_glsl_math -collection:tests=.. + $(ODIN) run math/linalg/glsl/test_linalg_glsl_math.odin -file -out=$(OUT_FILE) -collection:tests=.. filepath_test: - $(ODIN) run path/filepath/test_core_filepath.odin -file -out=test_core_filepath -collection:tests=.. + $(ODIN) run path/filepath/test_core_filepath.odin -file -out=$(OUT_FILE) -collection:tests=.. reflect_test: - $(ODIN) run reflect/test_core_reflect.odin -file -out=test_core_reflect -collection:tests=.. + $(ODIN) run reflect/test_core_reflect.odin -file -out=$(OUT_FILE) -collection:tests=.. os_exit_test: - $(ODIN) run os/test_core_os_exit.odin -file -out=test_core_os_exit && exit 1 || exit 0 + $(ODIN) run os/test_core_os_exit.odin -file -out=$(OUT_FILE) && exit 1 || exit 0 diff --git a/tests/core/build.bat b/tests/core/build.bat index 1973c22aa..331a473aa 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -1,65 +1,67 @@ @echo off -set COMMON=-show-timings -no-bounds-check -vet -strict-style -collection:tests=.. +set OUT_FILE=test_binary.exe +set COMMON=-show-timings -no-bounds-check -vet -strict-style -collection:tests=.. -out:%OUT_FILE% set PATH_TO_ODIN==..\..\odin + python3 download_assets.py echo --- echo Running core:image tests echo --- -%PATH_TO_ODIN% run image %COMMON% -out:test_image +%PATH_TO_ODIN% run image %COMMON% echo --- echo Running core:compress tests echo --- -%PATH_TO_ODIN% run compress %COMMON% -out:test_compress +%PATH_TO_ODIN% run compress %COMMON% echo --- echo Running core:strings tests echo --- -%PATH_TO_ODIN% run strings %COMMON% -out:test_strings +%PATH_TO_ODIN% run strings %COMMON% echo --- echo Running core:hash tests echo --- -%PATH_TO_ODIN% run hash %COMMON% -o:size -out:test_hash +%PATH_TO_ODIN% run hash %COMMON% -o:size echo --- echo Running core:odin tests echo --- -%PATH_TO_ODIN% run odin %COMMON% -o:size -out:test_odin +%PATH_TO_ODIN% run odin %COMMON% -o:size echo --- echo Running core:crypto hash tests echo --- -%PATH_TO_ODIN% run crypto %COMMON% -out:test_crypto +%PATH_TO_ODIN% run crypto %COMMON% echo --- echo Running core:encoding tests echo --- -%PATH_TO_ODIN% run encoding/hxa %COMMON% -out:test_hxa -%PATH_TO_ODIN% run encoding/json %COMMON% -out:test_json -%PATH_TO_ODIN% run encoding/varint %COMMON% -out:test_varint +%PATH_TO_ODIN% run encoding/hxa %COMMON% +%PATH_TO_ODIN% run encoding/json %COMMON% +%PATH_TO_ODIN% run encoding/varint %COMMON% echo --- echo Running core:math/noise tests echo --- -%PATH_TO_ODIN% run math/noise %COMMON% -out:test_noise +%PATH_TO_ODIN% run math/noise %COMMON% echo --- echo Running core:math tests echo --- -%PATH_TO_ODIN% run math %COMMON% -out:test_math +%PATH_TO_ODIN% run math %COMMON% echo --- echo Running core:math/linalg/glsl tests echo --- -%PATH_TO_ODIN% run math/linalg/glsl %COMMON% -out:test_glsl +%PATH_TO_ODIN% run math/linalg/glsl %COMMON% echo --- echo Running core:path/filepath tests echo --- -%PATH_TO_ODIN% run path/filepath %COMMON% -out:test_filepath +%PATH_TO_ODIN% run path/filepath %COMMON% echo --- echo Running core:reflect tests echo --- -%PATH_TO_ODIN% run reflect %COMMON% -out:test_reflect +%PATH_TO_ODIN% run reflect %COMMON% diff --git a/tests/issues/run.bat b/tests/issues/run.bat index a652d9694..a936bd896 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -1,17 +1,12 @@ @echo off +set PATH_TO_ODIN==..\..\odin +set COMMON=-collection:tests=.. -out:build\test_issue +if not exist "build" mkdir build -if not exist "tests\issues\build\" mkdir tests\issues\build +%PATH_TO_ODIN% build test_issue_829.odin %COMMON% -file +build\test_issue -set COMMON=-collection:tests=tests -out:tests\issues\build\test_issue +%PATH_TO_ODIN% build test_issue_1592.odin %COMMON% -file +build\test_issue -@echo on - -.\odin build tests\issues\test_issue_829.odin %COMMON% -file -tests\issues\build\test_issue - -.\odin build tests\issues\test_issue_1592.odin %COMMON% -file -tests\issues\build\test_issue - -@echo off - -rmdir /S /Q tests\issues\build +rmdir /S /Q build diff --git a/tests/issues/run.sh b/tests/issues/run.sh index 117a9a5f1..ec6d7130d 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -1,18 +1,17 @@ #!/bin/bash +ODIN=../../odin +COMMON="-collection:tests=.. -out:build/test_issue.bin" + set -eu - -mkdir -p tests/issues/build - -COMMON="-collection:tests=tests -out:tests/issues/build/test_issue" - +mkdir -p build set -x -./odin build tests/issues/test_issue_829.odin $COMMON -file -tests/issues/build/test_issue +$ODIN build test_issue_829.odin $COMMON -file +build/test_issue.bin -./odin build tests/issues/test_issue_1592.odin $COMMON -file -tests/issues/build/test_issue +$ODIN build test_issue_1592.odin $COMMON -file +build/test_issue.bin set +x -rm -rf tests/issues/build +rm -rf build diff --git a/tests/vendor/Makefile b/tests/vendor/Makefile index c508f6c50..380e64e09 100644 --- a/tests/vendor/Makefile +++ b/tests/vendor/Makefile @@ -1,5 +1,6 @@ ODIN=../../odin ODINFLAGS= +OUT_FILE=test_binary.bin OS=$(shell uname) @@ -10,4 +11,4 @@ endif all: botan_test botan_test: - $(ODIN) run botan -out=test_botan_hash -o:speed -no-bounds-check $(ODINFLAGS) + $(ODIN) run botan -out=$(OUT_FILE) -o:speed -no-bounds-check $(ODINFLAGS) diff --git a/tests/vendor/build.bat b/tests/vendor/build.bat index e70d9f1d5..4bd9a6496 100644 --- a/tests/vendor/build.bat +++ b/tests/vendor/build.bat @@ -1,5 +1,6 @@ @echo off -set COMMON=-show-timings -no-bounds-check -vet -strict-style +set OUT_FILE=test_binary.exe +set COMMON=-show-timings -no-bounds-check -vet -strict-style -out:%OUT_FILE% set PATH_TO_ODIN==..\..\odin echo --- From 9f8d90f466454f4d14e755d44e4ba47ccbf0c92e Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 24 Apr 2022 14:28:00 +0200 Subject: [PATCH 28/43] Update CI paths for issue tests. --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 989f56712..d72775636 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,7 @@ jobs: - name: Odin issues tests run: | cd tests/issues - run.sh + ./run.sh timeout-minutes: 10 - name: Odin check examples/all for Linux i386 run: ./odin check examples/all -vet -strict-style -target:linux_i386 @@ -95,7 +95,7 @@ jobs: - name: Odin issues tests run: | cd tests/issues - run.sh + ./run.sh timeout-minutes: 10 - name: Odin check examples/all for Darwin arm64 run: ./odin check examples/all -vet -strict-style -target:darwin_arm64 @@ -168,7 +168,7 @@ jobs: run: | call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat cd tests\issues - call build.bat + call run.bat timeout-minutes: 10 - name: Odin check examples/all for Windows 32bits shell: cmd From 63331ef731209ec8db65d7f26bdbebdf9459107d Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 24 Apr 2022 19:53:36 +0200 Subject: [PATCH 29/43] Revert "Merge pull request #1702 from Kelimion/filename_generation" This reverts commit a40a53b10447c9223c24cccf565a95f1773d3922, reversing changes made to 5422a3b17eae821df4adf869960995e922eb0e76. --- .github/workflows/ci.yml | 11 +- .gitignore | 1 - Makefile | 4 +- build_odin.sh | 4 +- core/crypto/util/util.odin | 1 - src/build_settings.cpp | 208 +++------------------ src/common.cpp | 257 +++++++++++++++++++++++++- src/gb/gb.h | 50 ++--- src/llvm_backend.cpp | 14 +- src/llvm_backend_general.cpp | 1 + src/main.cpp | 152 ++++++++-------- src/parser.cpp | 2 +- src/path.cpp | 333 ---------------------------------- src/string.cpp | 10 +- tests/core/Makefile | 29 ++- tests/core/build.bat | 10 +- tests/core/math/big/build.bat | 2 +- tests/issues/run.bat | 21 ++- tests/issues/run.sh | 19 +- tests/vendor/Makefile | 3 +- tests/vendor/build.bat | 3 +- 21 files changed, 436 insertions(+), 699 deletions(-) delete mode 100644 src/path.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d72775636..3cc4283b0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,9 +39,7 @@ jobs: make timeout-minutes: 10 - name: Odin issues tests - run: | - cd tests/issues - ./run.sh + run: tests/issues/run.sh timeout-minutes: 10 - name: Odin check examples/all for Linux i386 run: ./odin check examples/all -vet -strict-style -target:linux_i386 @@ -93,9 +91,7 @@ jobs: make timeout-minutes: 10 - name: Odin issues tests - run: | - cd tests/issues - ./run.sh + run: tests/issues/run.sh timeout-minutes: 10 - name: Odin check examples/all for Darwin arm64 run: ./odin check examples/all -vet -strict-style -target:darwin_arm64 @@ -167,8 +163,7 @@ jobs: shell: cmd run: | call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat - cd tests\issues - call run.bat + call tests\issues\run.bat timeout-minutes: 10 - name: Odin check examples/all for Windows 32bits shell: cmd diff --git a/.gitignore b/.gitignore index d03a86fd7..e8b3d3050 100644 --- a/.gitignore +++ b/.gitignore @@ -269,7 +269,6 @@ bin/ # - Linux/MacOS odin odin.dSYM -*.bin # shared collection shared/ diff --git a/Makefile b/Makefile index 1a1c93180..82150c6a2 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ -all: debug +all: debug demo demo: - ./odin run examples/demo + ./odin run examples/demo/demo.odin report: ./odin report diff --git a/build_odin.sh b/build_odin.sh index 4810cafd2..aef3f2836 100755 --- a/build_odin.sh +++ b/build_odin.sh @@ -102,7 +102,7 @@ build_odin() { } run_demo() { - ./odin run examples/demo + ./odin run examples/demo/demo.odin -file } case $OS in @@ -147,4 +147,4 @@ if [[ $# -eq 1 ]]; then exit 0 else panic "Too many arguments!" -fi \ No newline at end of file +fi diff --git a/core/crypto/util/util.odin b/core/crypto/util/util.odin index 83b07e546..6273a232e 100644 --- a/core/crypto/util/util.odin +++ b/core/crypto/util/util.odin @@ -11,7 +11,6 @@ package util */ import "core:mem" -_ :: mem // @note(bp): this can replace the other two cast_slice :: #force_inline proc "contextless" ($D: typeid/[]$DE, src: $S/[]$SE) -> D { diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 212ded5c8..2f3eb03a5 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -3,6 +3,7 @@ #include #endif + // #if defined(GB_SYSTEM_WINDOWS) // #define DEFAULT_TO_THREADED_CHECKER // #endif @@ -197,22 +198,6 @@ enum RelocMode : u8 { RelocMode_DynamicNoPIC, }; -enum BuildPath : u8 { - BuildPath_Main_Package, // Input Path to the package directory (or file) we're building. - BuildPath_RC, // Input Path for .rc file, can be set with `-resource:`. - BuildPath_RES, // Output Path for .res file, generated from previous. - BuildPath_Win_SDK_Root, // windows_sdk_root - BuildPath_Win_SDK_UM_Lib, // windows_sdk_um_library_path - BuildPath_Win_SDK_UCRT_Lib, // windows_sdk_ucrt_library_path - BuildPath_VS_EXE, // vs_exe_path - BuildPath_VS_LIB, // vs_library_path - - BuildPath_Output, // Output Path for .exe, .dll, .so, etc. Can be overridden with `-out:`. - BuildPath_PDB, // Output Path for .pdb file, can be overridden with `-pdb-name:`. - - BuildPathCOUNT, -}; - // This stores the information for the specify architecture of this build struct BuildContext { // Constants @@ -241,13 +226,9 @@ struct BuildContext { bool show_help; - Array build_paths; // Contains `Path` objects to output filename, pdb, resource and intermediate files. - // BuildPath enum contains the indices of paths we know *before* the work starts. - String out_filepath; String resource_filepath; String pdb_filepath; - bool has_resource; String link_flags; String extra_linker_flags; @@ -319,6 +300,8 @@ struct BuildContext { }; + + gb_global BuildContext build_context = {0}; bool global_warnings_as_errors(void) { @@ -622,6 +605,28 @@ bool allow_check_foreign_filepath(void) { // is_abs_path // has_subdir +enum TargetFileValidity : u8 { + TargetFileValidity_Invalid, + + TargetFileValidity_Writable_File, + TargetFileValidity_No_Write_Permission, + TargetFileValidity_Directory, + + TargetTargetFileValidity_COUNT, +}; + +TargetFileValidity set_output_filename(void) { + // Assembles the output filename from build_context information. + // Returns `true` if it doesn't exist or is a file. + // Returns `false` if a directory or write-protected file. + + + + + return TargetFileValidity_Writable_File; +} + + String const WIN32_SEPARATOR_STRING = {cast(u8 *)"\\", 1}; String const NIX_SEPARATOR_STRING = {cast(u8 *)"/", 1}; @@ -968,6 +973,7 @@ char *token_pos_to_string(TokenPos const &pos) { return s; } + void init_build_context(TargetMetrics *cross_target) { BuildContext *bc = &build_context; @@ -1146,166 +1152,8 @@ void init_build_context(TargetMetrics *cross_target) { bc->optimization_level = gb_clamp(bc->optimization_level, 0, 3); + + #undef LINK_FLAG_X64 #undef LINK_FLAG_386 } - -#if defined(GB_SYSTEM_WINDOWS) -// NOTE(IC): In order to find Visual C++ paths without relying on environment variables. -// NOTE(Jeroen): No longer needed in `main.cpp -> linker_stage`. We now resolve those paths in `init_build_paths`. -#include "microsoft_craziness.h" -#endif - -// NOTE(Jeroen): Set/create the output and other paths and report an error as appropriate. -// We've previously called `parse_build_flags`, so `out_filepath` should be set. -bool init_build_paths(String init_filename) { - gbAllocator ha = heap_allocator(); - BuildContext *bc = &build_context; - - // NOTE(Jeroen): We're pre-allocating BuildPathCOUNT slots so that certain paths are always at the same enumerated index. - array_init(&bc->build_paths, permanent_allocator(), BuildPathCOUNT); - - // [BuildPathMainPackage] Turn given init path into a `Path`, which includes normalizing it into a full path. - bc->build_paths[BuildPath_Main_Package] = path_from_string(ha, init_filename); - - bool produces_output_file = false; - if (bc->command_kind == Command_doc && bc->cmd_doc_flags & CmdDocFlag_DocFormat) { - produces_output_file = true; - } else if (bc->command_kind & Command__does_build) { - produces_output_file = true; - } - - if (!produces_output_file) { - // Command doesn't produce output files. We're done. - return true; - } - - #if defined(GB_SYSTEM_WINDOWS) - if (bc->resource_filepath.len > 0) { - bc->build_paths[BuildPath_RC] = path_from_string(ha, bc->resource_filepath); - bc->build_paths[BuildPath_RES] = path_from_string(ha, bc->resource_filepath); - bc->build_paths[BuildPath_RC].ext = copy_string(ha, STR_LIT("rc")); - bc->build_paths[BuildPath_RES].ext = copy_string(ha, STR_LIT("res")); - } - - if (bc->pdb_filepath.len > 0) { - bc->build_paths[BuildPath_PDB] = path_from_string(ha, bc->pdb_filepath); - } - - if ((bc->command_kind & Command__does_build) && (!bc->ignore_microsoft_magic)) { - // NOTE(ic): It would be nice to extend this so that we could specify the Visual Studio version that we want instead of defaulting to the latest. - Find_Result_Utf8 find_result = find_visual_studio_and_windows_sdk_utf8(); - - if (find_result.windows_sdk_version == 0) { - gb_printf_err("Windows SDK not found.\n"); - return false; - } - - GB_ASSERT(find_result.windows_sdk_um_library_path.len > 0); - GB_ASSERT(find_result.windows_sdk_ucrt_library_path.len > 0); - - if (find_result.windows_sdk_root.len > 0) { - bc->build_paths[BuildPath_Win_SDK_Root] = path_from_string(ha, find_result.windows_sdk_root); - } - - if (find_result.windows_sdk_um_library_path.len > 0) { - bc->build_paths[BuildPath_Win_SDK_UM_Lib] = path_from_string(ha, find_result.windows_sdk_um_library_path); - } - - if (find_result.windows_sdk_ucrt_library_path.len > 0) { - bc->build_paths[BuildPath_Win_SDK_UCRT_Lib] = path_from_string(ha, find_result.windows_sdk_ucrt_library_path); - } - - if (find_result.vs_exe_path.len > 0) { - bc->build_paths[BuildPath_VS_EXE] = path_from_string(ha, find_result.vs_exe_path); - } - - if (find_result.vs_library_path.len > 0) { - bc->build_paths[BuildPath_VS_LIB] = path_from_string(ha, find_result.vs_library_path); - } - - gb_free(ha, find_result.windows_sdk_root.text); - gb_free(ha, find_result.windows_sdk_um_library_path.text); - gb_free(ha, find_result.windows_sdk_ucrt_library_path.text); - gb_free(ha, find_result.vs_exe_path.text); - gb_free(ha, find_result.vs_library_path.text); - - } - #endif - - // All the build targets and OSes. - String output_extension; - - if (bc->command_kind == Command_doc && bc->cmd_doc_flags & CmdDocFlag_DocFormat) { - output_extension = STR_LIT("odin-doc"); - } else if (is_arch_wasm()) { - output_extension = STR_LIT("wasm"); - } else if (build_context.build_mode == BuildMode_Executable) { - // By default use a .bin executable extension. - output_extension = STR_LIT("bin"); - - if (build_context.metrics.os == TargetOs_windows) { - output_extension = STR_LIT("exe"); - } else if (build_context.cross_compiling && selected_target_metrics->metrics == &target_essence_amd64) { - output_extension = make_string(nullptr, 0); - } - } else if (build_context.build_mode == BuildMode_DynamicLibrary) { - // By default use a .so shared library extension. - output_extension = STR_LIT("so"); - - if (build_context.metrics.os == TargetOs_windows) { - output_extension = STR_LIT("dll"); - } else if (build_context.metrics.os == TargetOs_darwin) { - output_extension = STR_LIT("dylib"); - } - } else if (build_context.build_mode == BuildMode_Object) { - // By default use a .o object extension. - output_extension = STR_LIT("o"); - - if (build_context.metrics.os == TargetOs_windows) { - output_extension = STR_LIT("obj"); - } - } else if (build_context.build_mode == BuildMode_Assembly) { - // By default use a .S asm extension. - output_extension = STR_LIT("S"); - } else if (build_context.build_mode == BuildMode_LLVM_IR) { - output_extension = STR_LIT("ll"); - } else { - GB_PANIC("Unhandled build mode/target combination.\n"); - } - - if (bc->out_filepath.len > 0) { - bc->build_paths[BuildPath_Output] = path_from_string(ha, bc->out_filepath); - } else { - String output_name = remove_directory_from_path(init_filename); - output_name = remove_extension_from_path(output_name); - output_name = copy_string(ha, string_trim_whitespace(output_name)); - - Path output_path = path_from_string(ha, output_name); - - // Replace extension. - if (output_path.ext.len > 0) { - gb_free(ha, output_path.ext.text); - } - output_path.ext = copy_string(ha, output_extension); - - bc->build_paths[BuildPath_Output] = output_path; - } - - // Do we have an extension? We might not if the output filename was supplied. - if (bc->build_paths[BuildPath_Output].ext.len == 0) { - if (build_context.metrics.os == TargetOs_windows || build_context.build_mode != BuildMode_Executable) { - bc->build_paths[BuildPath_Output].ext = copy_string(ha, output_extension); - } - } - - // Check if output path is a directory. - if (path_is_directory(bc->build_paths[BuildPath_Output])) { - String output_file = path_to_string(ha, bc->build_paths[BuildPath_Output]); - defer (gb_free(ha, output_file.text)); - gb_printf_err("Output path %.*s is a directory.\n", LIT(output_file)); - return false; - } - - return true; -} \ No newline at end of file diff --git a/src/common.cpp b/src/common.cpp index 94248fb62..aaacda04b 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -675,7 +675,262 @@ wchar_t **command_line_to_wargv(wchar_t *cmd_line, int *_argc) { #endif -#include "path.cpp" + +#if defined(GB_SYSTEM_WINDOWS) + bool path_is_directory(String path) { + gbAllocator a = heap_allocator(); + String16 wstr = string_to_string16(a, path); + defer (gb_free(a, wstr.text)); + + i32 attribs = GetFileAttributesW(wstr.text); + if (attribs < 0) return false; + + return (attribs & FILE_ATTRIBUTE_DIRECTORY) != 0; + } + +#else + bool path_is_directory(String path) { + gbAllocator a = heap_allocator(); + char *copy = cast(char *)copy_string(a, path).text; + defer (gb_free(a, copy)); + + struct stat s; + if (stat(copy, &s) == 0) { + return (s.st_mode & S_IFDIR) != 0; + } + return false; + } +#endif + + +String path_to_full_path(gbAllocator a, String path) { + gbAllocator ha = heap_allocator(); + char *path_c = gb_alloc_str_len(ha, cast(char *)path.text, path.len); + defer (gb_free(ha, path_c)); + + char *fullpath = gb_path_get_full_name(a, path_c); + String res = string_trim_whitespace(make_string_c(fullpath)); +#if defined(GB_SYSTEM_WINDOWS) + for (isize i = 0; i < res.len; i++) { + if (res.text[i] == '\\') { + res.text[i] = '/'; + } + } +#endif + return res; +} + + + +struct FileInfo { + String name; + String fullpath; + i64 size; + bool is_dir; +}; + +enum ReadDirectoryError { + ReadDirectory_None, + + ReadDirectory_InvalidPath, + ReadDirectory_NotExists, + ReadDirectory_Permission, + ReadDirectory_NotDir, + ReadDirectory_Empty, + ReadDirectory_Unknown, + + ReadDirectory_COUNT, +}; + +i64 get_file_size(String path) { + char *c_str = alloc_cstring(heap_allocator(), path); + defer (gb_free(heap_allocator(), c_str)); + + gbFile f = {}; + gbFileError err = gb_file_open(&f, c_str); + defer (gb_file_close(&f)); + if (err != gbFileError_None) { + return -1; + } + return gb_file_size(&f); +} + + +#if defined(GB_SYSTEM_WINDOWS) +ReadDirectoryError read_directory(String path, Array *fi) { + GB_ASSERT(fi != nullptr); + + gbAllocator a = heap_allocator(); + + while (path.len > 0) { + Rune end = path[path.len-1]; + if (end == '/') { + path.len -= 1; + } else if (end == '\\') { + path.len -= 1; + } else { + break; + } + } + + if (path.len == 0) { + return ReadDirectory_InvalidPath; + } + { + char *c_str = alloc_cstring(a, path); + defer (gb_free(a, c_str)); + + gbFile f = {}; + gbFileError file_err = gb_file_open(&f, c_str); + defer (gb_file_close(&f)); + + switch (file_err) { + case gbFileError_Invalid: return ReadDirectory_InvalidPath; + case gbFileError_NotExists: return ReadDirectory_NotExists; + // case gbFileError_Permission: return ReadDirectory_Permission; + } + } + + if (!path_is_directory(path)) { + return ReadDirectory_NotDir; + } + + + char *new_path = gb_alloc_array(a, char, path.len+3); + defer (gb_free(a, new_path)); + + gb_memmove(new_path, path.text, path.len); + gb_memmove(new_path+path.len, "/*", 2); + new_path[path.len+2] = 0; + + String np = make_string(cast(u8 *)new_path, path.len+2); + String16 wstr = string_to_string16(a, np); + defer (gb_free(a, wstr.text)); + + WIN32_FIND_DATAW file_data = {}; + HANDLE find_file = FindFirstFileW(wstr.text, &file_data); + if (find_file == INVALID_HANDLE_VALUE) { + return ReadDirectory_Unknown; + } + defer (FindClose(find_file)); + + array_init(fi, a, 0, 100); + + do { + wchar_t *filename_w = file_data.cFileName; + i64 size = cast(i64)file_data.nFileSizeLow; + size |= (cast(i64)file_data.nFileSizeHigh) << 32; + String name = string16_to_string(a, make_string16_c(filename_w)); + if (name == "." || name == "..") { + gb_free(a, name.text); + continue; + } + + String filepath = {}; + filepath.len = path.len+1+name.len; + filepath.text = gb_alloc_array(a, u8, filepath.len+1); + defer (gb_free(a, filepath.text)); + gb_memmove(filepath.text, path.text, path.len); + gb_memmove(filepath.text+path.len, "/", 1); + gb_memmove(filepath.text+path.len+1, name.text, name.len); + + FileInfo info = {}; + info.name = name; + info.fullpath = path_to_full_path(a, filepath); + info.size = size; + info.is_dir = (file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + array_add(fi, info); + } while (FindNextFileW(find_file, &file_data)); + + if (fi->count == 0) { + return ReadDirectory_Empty; + } + + return ReadDirectory_None; +} +#elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD) + +#include + +ReadDirectoryError read_directory(String path, Array *fi) { + GB_ASSERT(fi != nullptr); + + gbAllocator a = heap_allocator(); + + char *c_path = alloc_cstring(a, path); + defer (gb_free(a, c_path)); + + DIR *dir = opendir(c_path); + if (!dir) { + switch (errno) { + case ENOENT: + return ReadDirectory_NotExists; + case EACCES: + return ReadDirectory_Permission; + case ENOTDIR: + return ReadDirectory_NotDir; + default: + // ENOMEM: out of memory + // EMFILE: per-process limit on open fds reached + // ENFILE: system-wide limit on total open files reached + return ReadDirectory_Unknown; + } + GB_PANIC("unreachable"); + } + + array_init(fi, a, 0, 100); + + for (;;) { + struct dirent *entry = readdir(dir); + if (entry == nullptr) { + break; + } + + String name = make_string_c(entry->d_name); + if (name == "." || name == "..") { + continue; + } + + String filepath = {}; + filepath.len = path.len+1+name.len; + filepath.text = gb_alloc_array(a, u8, filepath.len+1); + defer (gb_free(a, filepath.text)); + gb_memmove(filepath.text, path.text, path.len); + gb_memmove(filepath.text+path.len, "/", 1); + gb_memmove(filepath.text+path.len+1, name.text, name.len); + filepath.text[filepath.len] = 0; + + + struct stat dir_stat = {}; + + if (stat((char *)filepath.text, &dir_stat)) { + continue; + } + + if (S_ISDIR(dir_stat.st_mode)) { + continue; + } + + i64 size = dir_stat.st_size; + + FileInfo info = {}; + info.name = name; + info.fullpath = path_to_full_path(a, filepath); + info.size = size; + array_add(fi, info); + } + + if (fi->count == 0) { + return ReadDirectory_Empty; + } + + return ReadDirectory_None; +} +#else +#error Implement read_directory +#endif + + struct LoadedFile { void *handle; diff --git a/src/gb/gb.h b/src/gb/gb.h index 3b2d6434c..b72a893f7 100644 --- a/src/gb/gb.h +++ b/src/gb/gb.h @@ -6273,44 +6273,20 @@ char *gb_path_get_full_name(gbAllocator a, char const *path) { #else char *p, *result, *fullpath = NULL; isize len; - fullpath = realpath(path, NULL); - - if (fullpath == NULL) { - // NOTE(Jeroen): Path doesn't exist. - if (gb_strlen(path) > 0 && path[0] == '/') { - // But it is an absolute path, so return as-is. - - fullpath = (char *)path; - len = gb_strlen(fullpath) + 1; - result = gb_alloc_array(a, char, len + 1); - - gb_memmove(result, fullpath, len); - result[len] = 0; - - } else { - // Appears to be a relative path, so construct an absolute one relative to . - char cwd[4096]; - getcwd(&cwd[0], 4096); - - isize path_len = gb_strlen(path); - isize cwd_len = gb_strlen(cwd); - len = cwd_len + 1 + path_len + 1; - result = gb_alloc_array(a, char, len); - - gb_memmove(result, (void *)cwd, cwd_len); - result[cwd_len] = '/'; - - gb_memmove(result + cwd_len + 1, (void *)path, gb_strlen(path)); - result[len] = 0; - - } - } else { - len = gb_strlen(fullpath) + 1; - result = gb_alloc_array(a, char, len + 1); - gb_memmove(result, fullpath, len); - result[len] = 0; - free(fullpath); + p = realpath(path, NULL); + fullpath = p; + if (p == NULL) { + // NOTE(bill): File does not exist + fullpath = cast(char *)path; } + + len = gb_strlen(fullpath); + + result = gb_alloc_array(a, char, len + 1); + gb_memmove(result, fullpath, len); + result[len] = 0; + free(p); + return result; #endif } diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 7781997f7..f5cb84785 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -967,12 +967,7 @@ lbProcedure *lb_create_main_procedure(lbModule *m, lbProcedure *startup_runtime) } String lb_filepath_ll_for_module(lbModule *m) { - String path = concatenate3_strings(permanent_allocator(), - build_context.build_paths[BuildPath_Output].basename, - STR_LIT("/"), - build_context.build_paths[BuildPath_Output].name - ); - + String path = m->gen->output_base; if (m->pkg) { path = concatenate3_strings(permanent_allocator(), path, STR_LIT("-"), m->pkg->name); } else if (USE_SEPARATE_MODULES) { @@ -983,12 +978,7 @@ String lb_filepath_ll_for_module(lbModule *m) { return path; } String lb_filepath_obj_for_module(lbModule *m) { - String path = concatenate3_strings(permanent_allocator(), - build_context.build_paths[BuildPath_Output].basename, - STR_LIT("/"), - build_context.build_paths[BuildPath_Output].name - ); - + String path = m->gen->output_base; if (m->pkg) { path = concatenate3_strings(permanent_allocator(), path, STR_LIT("-"), m->pkg->name); } diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 1a431a4ac..330059622 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -87,6 +87,7 @@ bool lb_init_generator(lbGenerator *gen, Checker *c) { return false; } + String init_fullpath = c->parser->init_fullpath; if (build_context.out_filepath.len == 0) { diff --git a/src/main.cpp b/src/main.cpp index 7b0364149..fc8792ceb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -46,6 +46,7 @@ gb_global Timings global_timings = {0}; #include "checker.cpp" #include "docs.cpp" + #include "llvm_backend.cpp" #if defined(GB_SYSTEM_OSX) @@ -56,8 +57,16 @@ gb_global Timings global_timings = {0}; #endif #include "query_data.cpp" + + +#if defined(GB_SYSTEM_WINDOWS) +// NOTE(IC): In order to find Visual C++ paths without relying on environment variables. +#include "microsoft_craziness.h" +#endif + #include "bug_report.cpp" + // NOTE(bill): 'name' is used in debugging and profiling modes i32 system_exec_command_line_app(char const *name, char const *fmt, ...) { isize const cmd_cap = 64<<20; // 64 MiB should be more than enough @@ -121,35 +130,34 @@ i32 system_exec_command_line_app(char const *name, char const *fmt, ...) { } + + i32 linker_stage(lbGenerator *gen) { i32 result = 0; Timings *timings = &global_timings; - String output_filename = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]); - debugf("Linking %.*s\n", LIT(output_filename)); - - // TOOD(Jeroen): Make a `build_paths[BuildPath_Object] to avoid `%.*s.o`. + String output_base = gen->output_base; if (is_arch_wasm()) { timings_start_section(timings, str_lit("wasm-ld")); #if defined(GB_SYSTEM_WINDOWS) result = system_exec_command_line_app("wasm-ld", - "\"%.*s\\bin\\wasm-ld\" \"%.*s.o\" -o \"%.*s\" %.*s %.*s", + "\"%.*s\\bin\\wasm-ld\" \"%.*s.wasm.o\" -o \"%.*s.wasm\" %.*s %.*s", LIT(build_context.ODIN_ROOT), - LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags)); + LIT(output_base), LIT(output_base), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags)); #else result = system_exec_command_line_app("wasm-ld", - "wasm-ld \"%.*s.o\" -o \"%.*s\" %.*s %.*s", - LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags)); + "wasm-ld \"%.*s.wasm.o\" -o \"%.*s.wasm\" %.*s %.*s", + LIT(output_base), LIT(output_base), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags)); #endif return result; } if (build_context.cross_compiling && selected_target_metrics->metrics == &target_essence_amd64) { -#if defined(GB_SYSTEM_UNIX) +#ifdef GB_SYSTEM_UNIX result = system_exec_command_line_app("linker", "x86_64-essence-gcc \"%.*s.o\" -o \"%.*s\" %.*s %.*s", - LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags)); + LIT(output_base), LIT(output_base), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags)); #else gb_printf_err("Linking for cross compilation for this platform is not yet supported (%.*s %.*s)\n", LIT(target_os_names[build_context.metrics.os]), @@ -173,11 +181,28 @@ i32 linker_stage(lbGenerator *gen) { gbString lib_str = gb_string_make(heap_allocator(), ""); defer (gb_string_free(lib_str)); + char const *output_ext = "exe"; gbString link_settings = gb_string_make_reserve(heap_allocator(), 256); defer (gb_string_free(link_settings)); + + // NOTE(ic): It would be nice to extend this so that we could specify the Visual Studio version that we want instead of defaulting to the latest. + Find_Result_Utf8 find_result = find_visual_studio_and_windows_sdk_utf8(); + + if (find_result.windows_sdk_version == 0) { + gb_printf_err("Windows SDK not found.\n"); + exit(1); + } + + if (build_context.ignore_microsoft_magic) { + find_result = {}; + } + // Add library search paths. - if (build_context.build_paths[BuildPath_VS_LIB].basename.len > 0) { + if (find_result.vs_library_path.len > 0) { + GB_ASSERT(find_result.windows_sdk_um_library_path.len > 0); + GB_ASSERT(find_result.windows_sdk_ucrt_library_path.len > 0); + String path = {}; auto add_path = [&](String path) { if (path[path.len-1] == '\\') { @@ -185,9 +210,9 @@ i32 linker_stage(lbGenerator *gen) { } link_settings = gb_string_append_fmt(link_settings, " /LIBPATH:\"%.*s\"", LIT(path)); }; - add_path(build_context.build_paths[BuildPath_Win_SDK_UM_Lib].basename); - add_path(build_context.build_paths[BuildPath_Win_SDK_UCRT_Lib].basename); - add_path(build_context.build_paths[BuildPath_VS_LIB].basename); + add_path(find_result.windows_sdk_um_library_path); + add_path(find_result.windows_sdk_ucrt_library_path); + add_path(find_result.vs_library_path); } @@ -227,14 +252,14 @@ i32 linker_stage(lbGenerator *gen) { if (build_context.build_mode == BuildMode_DynamicLibrary) { + output_ext = "dll"; link_settings = gb_string_append_fmt(link_settings, " /DLL"); } else { link_settings = gb_string_append_fmt(link_settings, " /ENTRY:mainCRTStartup"); } if (build_context.pdb_filepath != "") { - String pdb_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_PDB]); - link_settings = gb_string_append_fmt(link_settings, " /PDB:%.*s", LIT(pdb_path)); + link_settings = gb_string_append_fmt(link_settings, " /PDB:%.*s", LIT(build_context.pdb_filepath)); } if (build_context.no_crt) { @@ -275,21 +300,13 @@ i32 linker_stage(lbGenerator *gen) { object_files = gb_string_append_fmt(object_files, "\"%.*s\" ", LIT(object_path)); } - String vs_exe_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_VS_EXE]); - defer (gb_free(heap_allocator(), vs_exe_path.text)); - char const *subsystem_str = build_context.use_subsystem_windows ? "WINDOWS" : "CONSOLE"; if (!build_context.use_lld) { // msvc if (build_context.has_resource) { - String rc_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_RC]); - String res_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_RES]); - defer (gb_free(heap_allocator(), rc_path.text)); - defer (gb_free(heap_allocator(), res_path.text)); - result = system_exec_command_line_app("msvc-link", - "\"rc.exe\" /nologo /fo \"%.*s\" \"%.*s\"", - LIT(res_path), - LIT(rc_path) + "\"rc.exe\" /nologo /fo \"%.*s.res\" \"%.*s.rc\"", + LIT(output_base), + LIT(build_context.resource_filepath) ); if (result) { @@ -297,13 +314,13 @@ i32 linker_stage(lbGenerator *gen) { } result = system_exec_command_line_app("msvc-link", - "\"%.*slink.exe\" %s \"%.*s\" -OUT:\"%.*s\" %s " + "\"%.*slink.exe\" %s \"%.*s.res\" -OUT:\"%.*s.%s\" %s " "/nologo /incremental:no /opt:ref /subsystem:%s " " %.*s " " %.*s " " %s " "", - LIT(vs_exe_path), object_files, LIT(res_path), LIT(output_filename), + LIT(find_result.vs_exe_path), object_files, LIT(output_base), LIT(output_base), output_ext, link_settings, subsystem_str, LIT(build_context.link_flags), @@ -312,13 +329,13 @@ i32 linker_stage(lbGenerator *gen) { ); } else { result = system_exec_command_line_app("msvc-link", - "\"%.*slink.exe\" %s -OUT:\"%.*s\" %s " + "\"%.*slink.exe\" %s -OUT:\"%.*s.%s\" %s " "/nologo /incremental:no /opt:ref /subsystem:%s " " %.*s " " %.*s " " %s " "", - LIT(vs_exe_path), object_files, LIT(output_filename), + LIT(find_result.vs_exe_path), object_files, LIT(output_base), output_ext, link_settings, subsystem_str, LIT(build_context.link_flags), @@ -333,13 +350,13 @@ i32 linker_stage(lbGenerator *gen) { } else { // lld result = system_exec_command_line_app("msvc-lld-link", - "\"%.*s\\bin\\lld-link\" %s -OUT:\"%.*s\" %s " + "\"%.*s\\bin\\lld-link\" %s -OUT:\"%.*s.%s\" %s " "/nologo /incremental:no /opt:ref /subsystem:%s " " %.*s " " %.*s " " %s " "", - LIT(build_context.ODIN_ROOT), object_files, LIT(output_filename), + LIT(build_context.ODIN_ROOT), object_files, LIT(output_base),output_ext, link_settings, subsystem_str, LIT(build_context.link_flags), @@ -398,7 +415,7 @@ i32 linker_stage(lbGenerator *gen) { } else if (string_ends_with(lib, str_lit(".so"))) { // dynamic lib, relative path to executable // NOTE(vassvik): it is the user's responsibility to make sure the shared library files are visible - // at runtime to the executable + // at runtimeto the executable lib_str = gb_string_append_fmt(lib_str, " -l:\"%s/%.*s\" ", cwd, LIT(lib)); } else { // dynamic or static system lib, just link regularly searching system library paths @@ -414,6 +431,9 @@ i32 linker_stage(lbGenerator *gen) { object_files = gb_string_append_fmt(object_files, "\"%.*s\" ", LIT(object_path)); } + // Unlike the Win32 linker code, the output_ext includes the dot, because + // typically executable files on *NIX systems don't have extensions. + String output_ext = {}; gbString link_settings = gb_string_make_reserve(heap_allocator(), 32); if (build_context.no_crt) { @@ -441,12 +461,26 @@ i32 linker_stage(lbGenerator *gen) { // correctly this way since all the other dependencies provided implicitly // by the compiler frontend are still needed and most of the command // line arguments prepared previously are incompatible with ld. + // + // Shared libraries are .dylib on MacOS and .so on Linux. + if (build_context.metrics.os == TargetOs_darwin) { + output_ext = STR_LIT(".dylib"); + } else { + output_ext = STR_LIT(".so"); + } link_settings = gb_string_appendc(link_settings, "-Wl,-init,'_odin_entry_point' "); link_settings = gb_string_appendc(link_settings, "-Wl,-fini,'_odin_exit_point' "); } else if (build_context.metrics.os != TargetOs_openbsd) { // OpenBSD defaults to PIE executable. do not pass -no-pie for it. link_settings = gb_string_appendc(link_settings, "-no-pie "); } + if (build_context.out_filepath.len > 0) { + //NOTE(thebirk): We have a custom -out arguments, so we should use the extension from that + isize pos = string_extension_position(build_context.out_filepath); + if (pos > 0) { + output_ext = substring(build_context.out_filepath, pos, build_context.out_filepath.len); + } + } gbString platform_lib_str = gb_string_make(heap_allocator(), ""); defer (gb_string_free(platform_lib_str)); @@ -473,7 +507,7 @@ i32 linker_stage(lbGenerator *gen) { defer (gb_string_free(link_command_line)); link_command_line = gb_string_appendc(link_command_line, object_files); - link_command_line = gb_string_append_fmt(link_command_line, " -o \"%.*s\" ", LIT(output_filename)); + link_command_line = gb_string_append_fmt(link_command_line, " -o \"%.*s%.*s\" ", LIT(output_base), LIT(output_ext)); link_command_line = gb_string_append_fmt(link_command_line, " %s ", platform_lib_str); link_command_line = gb_string_append_fmt(link_command_line, " %s ", lib_str); link_command_line = gb_string_append_fmt(link_command_line, " %.*s ", LIT(build_context.link_flags)); @@ -490,7 +524,9 @@ i32 linker_stage(lbGenerator *gen) { if (build_context.ODIN_DEBUG) { // NOTE: macOS links DWARF symbols dynamically. Dsymutil will map the stubs in the exe // to the symbols in the object file - result = system_exec_command_line_app("dsymutil", "dsymutil %.*s", LIT(output_filename)); + result = system_exec_command_line_app("dsymutil", + "dsymutil %.*s%.*s", LIT(output_base), LIT(output_ext) + ); if (result) { return result; @@ -1490,10 +1526,6 @@ bool parse_build_flags(Array args) { gb_printf_err("Invalid -resource path %.*s, missing .rc\n", LIT(path)); bad_flags = true; break; - } else if (!gb_file_exists((const char *)path.text)) { - gb_printf_err("Invalid -resource path %.*s, file does not exist.\n", LIT(path)); - bad_flags = true; - break; } build_context.resource_filepath = substring(path, 0, string_extension_position(path)); build_context.has_resource = true; @@ -1508,11 +1540,6 @@ bool parse_build_flags(Array args) { String path = value.value_string; path = string_trim_whitespace(path); if (is_build_flag_path_valid(path)) { - if (path_is_directory(path)) { - gb_printf_err("Invalid -pdb-name path. %.*s, is a directory.\n", LIT(path)); - bad_flags = true; - break; - } // #if defined(GB_SYSTEM_WINDOWS) // String ext = path_extension(path); // if (ext != ".pdb") { @@ -2639,8 +2666,6 @@ int main(int arg_count, char const **arg_ptr) { return 1; } - init_filename = copy_string(permanent_allocator(), init_filename); - if (init_filename == "-help" || init_filename == "--help") { build_context.show_help = true; @@ -2663,12 +2688,6 @@ int main(int arg_count, char const **arg_ptr) { gb_printf_err("Did you mean `%.*s %.*s %.*s -file`?\n", LIT(args[0]), LIT(command), LIT(init_filename)); gb_printf_err("The `-file` flag tells it to treat a file as a self-contained package.\n"); return 1; - } else { - String const ext = str_lit(".odin"); - if (!string_ends_with(init_filename, ext)) { - gb_printf_err("Expected either a directory or a .odin file, got '%.*s'\n", LIT(init_filename)); - return 1; - } } } } @@ -2690,24 +2709,13 @@ int main(int arg_count, char const **arg_ptr) { get_fullpath_relative(heap_allocator(), odin_root_dir(), str_lit("shared"))); } + init_build_context(selected_target_metrics ? selected_target_metrics->metrics : nullptr); // if (build_context.word_size == 4 && build_context.metrics.os != TargetOs_js) { // print_usage_line(0, "%.*s 32-bit is not yet supported for this platform", LIT(args[0])); // return 1; // } - // Set and check build paths... - if (!init_build_paths(init_filename)) { - return 1; - } - - if (build_context.show_debug_messages) { - for_array(i, build_context.build_paths) { - String build_path = path_to_string(heap_allocator(), build_context.build_paths[i]); - debugf("build_paths[%ld]: %.*s\n", i, LIT(build_path)); - } - } - init_global_thread_pool(); defer (thread_pool_destroy(&global_thread_pool)); @@ -2724,8 +2732,6 @@ int main(int arg_count, char const **arg_ptr) { } defer (destroy_parser(parser)); - // TODO(jeroen): Remove the `init_filename` param. - // Let's put that on `build_context.build_paths[0]` instead. if (parse_packages(parser, init_filename) != ParseFile_None) { return 1; } @@ -2804,14 +2810,16 @@ int main(int arg_count, char const **arg_ptr) { } if (run_output) { - String exe_name = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]); - defer (gb_free(heap_allocator(), exe_name.text)); - #if defined(GB_SYSTEM_WINDOWS) - return system_exec_command_line_app("odin run", "%.*s %.*s", LIT(exe_name), LIT(run_args_string)); + return system_exec_command_line_app("odin run", "%.*s.exe %.*s", LIT(gen->output_base), LIT(run_args_string)); #else - return system_exec_command_line_app("odin run", "\"%.*s\" %.*s", LIT(exe_name), LIT(run_args_string)); + //NOTE(thebirk): This whole thing is a little leaky + String output_ext = {}; + String complete_path = concatenate_strings(permanent_allocator(), gen->output_base, output_ext); + complete_path = path_to_full_path(permanent_allocator(), complete_path); + return system_exec_command_line_app("odin run", "\"%.*s\" %.*s", LIT(complete_path), LIT(run_args_string)); #endif } + return 0; } diff --git a/src/parser.cpp b/src/parser.cpp index df7f908a6..767119aa8 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -5751,7 +5751,7 @@ ParseFileError parse_packages(Parser *p, String init_filename) { } } } - + { // Add these packages serially and then process them parallel mutex_lock(&p->wait_mutex); diff --git a/src/path.cpp b/src/path.cpp deleted file mode 100644 index 8d8e532b8..000000000 --- a/src/path.cpp +++ /dev/null @@ -1,333 +0,0 @@ -/* - Path handling utilities. -*/ - -#if defined(GB_SYSTEM_WINDOWS) - bool path_is_directory(String path) { - gbAllocator a = heap_allocator(); - String16 wstr = string_to_string16(a, path); - defer (gb_free(a, wstr.text)); - - i32 attribs = GetFileAttributesW(wstr.text); - if (attribs < 0) return false; - - return (attribs & FILE_ATTRIBUTE_DIRECTORY) != 0; - } - -#else - bool path_is_directory(String path) { - gbAllocator a = heap_allocator(); - char *copy = cast(char *)copy_string(a, path).text; - defer (gb_free(a, copy)); - - struct stat s; - if (stat(copy, &s) == 0) { - return (s.st_mode & S_IFDIR) != 0; - } - return false; - } -#endif - - -String path_to_full_path(gbAllocator a, String path) { - gbAllocator ha = heap_allocator(); - char *path_c = gb_alloc_str_len(ha, cast(char *)path.text, path.len); - defer (gb_free(ha, path_c)); - - char *fullpath = gb_path_get_full_name(a, path_c); - String res = string_trim_whitespace(make_string_c(fullpath)); -#if defined(GB_SYSTEM_WINDOWS) - for (isize i = 0; i < res.len; i++) { - if (res.text[i] == '\\') { - res.text[i] = '/'; - } - } -#endif - return copy_string(a, res); -} - -struct Path { - String basename; - String name; - String ext; -}; - -// NOTE(Jeroen): Naively turns a Path into a string. -String path_to_string(gbAllocator a, Path path) { - if (path.basename.len + path.name.len + path.ext.len == 0) { - return make_string(nullptr, 0); - } - - isize len = path.basename.len + 1 + path.name.len + 1; - if (path.ext.len > 0) { - len += path.ext.len + 1; - } - - u8 *str = gb_alloc_array(a, u8, len); - - isize i = 0; - gb_memmove(str+i, path.basename.text, path.basename.len); i += path.basename.len; - gb_memmove(str+i, "/", 1); i += 1; - gb_memmove(str+i, path.name.text, path.name.len); i += path.name.len; - if (path.ext.len > 0) { - gb_memmove(str+i, ".", 1); i += 1; - gb_memmove(str+i, path.ext.text, path.ext.len); i += path.ext.len; - } - str[i] = 0; - - String res = make_string(str, i); - res = string_trim_whitespace(res); - return res; -} - -// NOTE(Jeroen): Naively turns a Path into a string, then normalizes it using `path_to_full_path`. -String path_to_full_path(gbAllocator a, Path path) { - String temp = path_to_string(heap_allocator(), path); - defer (gb_free(heap_allocator(), temp.text)); - - return path_to_full_path(a, temp); -} - -// NOTE(Jeroen): Takes a path like "odin" or "W:\Odin", turns it into a full path, -// and then breaks it into its components to make a Path. -Path path_from_string(gbAllocator a, String const &path) { - Path res = {}; - - if (path.len == 0) return res; - - String fullpath = path_to_full_path(a, path); - defer (gb_free(heap_allocator(), fullpath.text)); - - res.basename = directory_from_path(fullpath); - res.basename = copy_string(a, res.basename); - - if (string_ends_with(fullpath, '/')) { - // It's a directory. We don't need to tinker with the name and extension. - return res; - } - - isize name_start = (res.basename.len > 0) ? res.basename.len + 1 : res.basename.len; - res.name = substring(fullpath, name_start, fullpath.len); - res.name = remove_extension_from_path(res.name); - res.name = copy_string(a, res.name); - - res.ext = path_extension(fullpath, false); // false says not to include the dot. - res.ext = copy_string(a, res.ext); - return res; -} - -bool path_is_directory(Path path) { - String path_string = path_to_full_path(heap_allocator(), path); - defer (gb_free(heap_allocator(), path_string.text)); - - return path_is_directory(path_string); -} - -struct FileInfo { - String name; - String fullpath; - i64 size; - bool is_dir; -}; - -enum ReadDirectoryError { - ReadDirectory_None, - - ReadDirectory_InvalidPath, - ReadDirectory_NotExists, - ReadDirectory_Permission, - ReadDirectory_NotDir, - ReadDirectory_Empty, - ReadDirectory_Unknown, - - ReadDirectory_COUNT, -}; - -i64 get_file_size(String path) { - char *c_str = alloc_cstring(heap_allocator(), path); - defer (gb_free(heap_allocator(), c_str)); - - gbFile f = {}; - gbFileError err = gb_file_open(&f, c_str); - defer (gb_file_close(&f)); - if (err != gbFileError_None) { - return -1; - } - return gb_file_size(&f); -} - - -#if defined(GB_SYSTEM_WINDOWS) -ReadDirectoryError read_directory(String path, Array *fi) { - GB_ASSERT(fi != nullptr); - - gbAllocator a = heap_allocator(); - - while (path.len > 0) { - Rune end = path[path.len-1]; - if (end == '/') { - path.len -= 1; - } else if (end == '\\') { - path.len -= 1; - } else { - break; - } - } - - if (path.len == 0) { - return ReadDirectory_InvalidPath; - } - { - char *c_str = alloc_cstring(a, path); - defer (gb_free(a, c_str)); - - gbFile f = {}; - gbFileError file_err = gb_file_open(&f, c_str); - defer (gb_file_close(&f)); - - switch (file_err) { - case gbFileError_Invalid: return ReadDirectory_InvalidPath; - case gbFileError_NotExists: return ReadDirectory_NotExists; - // case gbFileError_Permission: return ReadDirectory_Permission; - } - } - - if (!path_is_directory(path)) { - return ReadDirectory_NotDir; - } - - - char *new_path = gb_alloc_array(a, char, path.len+3); - defer (gb_free(a, new_path)); - - gb_memmove(new_path, path.text, path.len); - gb_memmove(new_path+path.len, "/*", 2); - new_path[path.len+2] = 0; - - String np = make_string(cast(u8 *)new_path, path.len+2); - String16 wstr = string_to_string16(a, np); - defer (gb_free(a, wstr.text)); - - WIN32_FIND_DATAW file_data = {}; - HANDLE find_file = FindFirstFileW(wstr.text, &file_data); - if (find_file == INVALID_HANDLE_VALUE) { - return ReadDirectory_Unknown; - } - defer (FindClose(find_file)); - - array_init(fi, a, 0, 100); - - do { - wchar_t *filename_w = file_data.cFileName; - i64 size = cast(i64)file_data.nFileSizeLow; - size |= (cast(i64)file_data.nFileSizeHigh) << 32; - String name = string16_to_string(a, make_string16_c(filename_w)); - if (name == "." || name == "..") { - gb_free(a, name.text); - continue; - } - - String filepath = {}; - filepath.len = path.len+1+name.len; - filepath.text = gb_alloc_array(a, u8, filepath.len+1); - defer (gb_free(a, filepath.text)); - gb_memmove(filepath.text, path.text, path.len); - gb_memmove(filepath.text+path.len, "/", 1); - gb_memmove(filepath.text+path.len+1, name.text, name.len); - - FileInfo info = {}; - info.name = name; - info.fullpath = path_to_full_path(a, filepath); - info.size = size; - info.is_dir = (file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; - array_add(fi, info); - } while (FindNextFileW(find_file, &file_data)); - - if (fi->count == 0) { - return ReadDirectory_Empty; - } - - return ReadDirectory_None; -} -#elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD) - -#include - -ReadDirectoryError read_directory(String path, Array *fi) { - GB_ASSERT(fi != nullptr); - - gbAllocator a = heap_allocator(); - - char *c_path = alloc_cstring(a, path); - defer (gb_free(a, c_path)); - - DIR *dir = opendir(c_path); - if (!dir) { - switch (errno) { - case ENOENT: - return ReadDirectory_NotExists; - case EACCES: - return ReadDirectory_Permission; - case ENOTDIR: - return ReadDirectory_NotDir; - default: - // ENOMEM: out of memory - // EMFILE: per-process limit on open fds reached - // ENFILE: system-wide limit on total open files reached - return ReadDirectory_Unknown; - } - GB_PANIC("unreachable"); - } - - array_init(fi, a, 0, 100); - - for (;;) { - struct dirent *entry = readdir(dir); - if (entry == nullptr) { - break; - } - - String name = make_string_c(entry->d_name); - if (name == "." || name == "..") { - continue; - } - - String filepath = {}; - filepath.len = path.len+1+name.len; - filepath.text = gb_alloc_array(a, u8, filepath.len+1); - defer (gb_free(a, filepath.text)); - gb_memmove(filepath.text, path.text, path.len); - gb_memmove(filepath.text+path.len, "/", 1); - gb_memmove(filepath.text+path.len+1, name.text, name.len); - filepath.text[filepath.len] = 0; - - - struct stat dir_stat = {}; - - if (stat((char *)filepath.text, &dir_stat)) { - continue; - } - - if (S_ISDIR(dir_stat.st_mode)) { - continue; - } - - i64 size = dir_stat.st_size; - - FileInfo info = {}; - info.name = name; - info.fullpath = path_to_full_path(a, filepath); - info.size = size; - array_add(fi, info); - } - - if (fi->count == 0) { - return ReadDirectory_Empty; - } - - return ReadDirectory_None; -} -#else -#error Implement read_directory -#endif - diff --git a/src/string.cpp b/src/string.cpp index 3515df48e..d3dbc6904 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -245,14 +245,15 @@ gb_inline isize string_extension_position(String const &str) { return dot_pos; } -String path_extension(String const &str, bool include_dot = true) { +String path_extension(String const &str) { isize pos = string_extension_position(str); if (pos < 0) { return make_string(nullptr, 0); } - return substring(str, include_dot ? pos : pos + 1, str.len); + return substring(str, pos, str.len); } + String string_trim_whitespace(String str) { while (str.len > 0 && rune_is_whitespace(str[str.len-1])) { str.len--; @@ -327,10 +328,7 @@ String directory_from_path(String const &s) { break; } } - if (i >= 0) { - return substring(s, 0, i); - } - return substring(s, 0, 0); + return substring(s, 0, i); } String concatenate_strings(gbAllocator a, String const &x, String const &y) { diff --git a/tests/core/Makefile b/tests/core/Makefile index 9bb622633..6a92b4efb 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -1,6 +1,5 @@ ODIN=../../odin PYTHON=$(shell which python3) -OUT_FILE=test_binary.bin all: download_test_assets image_test compress_test strings_test hash_test crypto_test noise_test encoding_test \ math_test linalg_glsl_math_test filepath_test reflect_test os_exit_test @@ -9,39 +8,39 @@ download_test_assets: $(PYTHON) download_assets.py image_test: - $(ODIN) run image/test_core_image.odin -out=$(OUT_FILE) -file + $(ODIN) run image/test_core_image.odin -file compress_test: - $(ODIN) run compress/test_core_compress.odin -out=$(OUT_FILE) -file + $(ODIN) run compress/test_core_compress.odin -file strings_test: - $(ODIN) run strings/test_core_strings.odin -out=$(OUT_FILE) -file + $(ODIN) run strings/test_core_strings.odin -file hash_test: - $(ODIN) run hash -out=$(OUT_FILE) -o:speed -no-bounds-check + $(ODIN) run hash -out=test_hash -o:speed -no-bounds-check crypto_test: - $(ODIN) run crypto -out=$(OUT_FILE) -o:speed -no-bounds-check + $(ODIN) run crypto -out=test_crypto_hash -o:speed -no-bounds-check noise_test: - $(ODIN) run math/noise -out=$(OUT_FILE) + $(ODIN) run math/noise -out=test_noise encoding_test: - $(ODIN) run encoding/hxa -out=$(OUT_FILE) -collection:tests=.. - $(ODIN) run encoding/json -out=$(OUT_FILE) - $(ODIN) run encoding/varint -out=$(OUT_FILE) + $(ODIN) run encoding/hxa -out=test_hxa -collection:tests=.. + $(ODIN) run encoding/json -out=test_json + $(ODIN) run encoding/varint -out=test_varint math_test: - $(ODIN) run math/test_core_math.odin -out=$(OUT_FILE) -file -collection:tests=.. + $(ODIN) run math/test_core_math.odin -out=test_core_math -file -collection:tests=.. linalg_glsl_math_test: - $(ODIN) run math/linalg/glsl/test_linalg_glsl_math.odin -file -out=$(OUT_FILE) -collection:tests=.. + $(ODIN) run math/linalg/glsl/test_linalg_glsl_math.odin -file -out=test_linalg_glsl_math -collection:tests=.. filepath_test: - $(ODIN) run path/filepath/test_core_filepath.odin -file -out=$(OUT_FILE) -collection:tests=.. + $(ODIN) run path/filepath/test_core_filepath.odin -file -out=test_core_filepath -collection:tests=.. reflect_test: - $(ODIN) run reflect/test_core_reflect.odin -file -out=$(OUT_FILE) -collection:tests=.. + $(ODIN) run reflect/test_core_reflect.odin -file -out=test_core_reflect -collection:tests=.. os_exit_test: - $(ODIN) run os/test_core_os_exit.odin -file -out=$(OUT_FILE) && exit 1 || exit 0 + $(ODIN) run os/test_core_os_exit.odin -file -out=test_core_os_exit && exit 1 || exit 0 diff --git a/tests/core/build.bat b/tests/core/build.bat index 331a473aa..2f9ba672e 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -1,8 +1,6 @@ @echo off -set OUT_FILE=test_binary.exe -set COMMON=-show-timings -no-bounds-check -vet -strict-style -collection:tests=.. -out:%OUT_FILE% +set COMMON=-show-timings -no-bounds-check -vet -strict-style -collection:tests=.. set PATH_TO_ODIN==..\..\odin - python3 download_assets.py echo --- echo Running core:image tests @@ -37,14 +35,14 @@ echo --- echo --- echo Running core:encoding tests echo --- -%PATH_TO_ODIN% run encoding/hxa %COMMON% -%PATH_TO_ODIN% run encoding/json %COMMON% +%PATH_TO_ODIN% run encoding/hxa %COMMON% +%PATH_TO_ODIN% run encoding/json %COMMON% %PATH_TO_ODIN% run encoding/varint %COMMON% echo --- echo Running core:math/noise tests echo --- -%PATH_TO_ODIN% run math/noise %COMMON% +%PATH_TO_ODIN% run math/noise %COMMON% echo --- echo Running core:math tests diff --git a/tests/core/math/big/build.bat b/tests/core/math/big/build.bat index ad199d775..16bdbc8ca 100644 --- a/tests/core/math/big/build.bat +++ b/tests/core/math/big/build.bat @@ -4,7 +4,7 @@ set PATH_TO_ODIN==..\..\..\..\odin set TEST_ARGS=-fast-tests set TEST_ARGS=-no-random set TEST_ARGS= -set OUT_NAME=math_big_test_library.dll +set OUT_NAME=math_big_test_library set COMMON=-build-mode:shared -show-timings -no-bounds-check -define:MATH_BIG_EXE=false -vet -strict-style echo --- echo Running core:math/big tests diff --git a/tests/issues/run.bat b/tests/issues/run.bat index a936bd896..a652d9694 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -1,12 +1,17 @@ @echo off -set PATH_TO_ODIN==..\..\odin -set COMMON=-collection:tests=.. -out:build\test_issue -if not exist "build" mkdir build -%PATH_TO_ODIN% build test_issue_829.odin %COMMON% -file -build\test_issue +if not exist "tests\issues\build\" mkdir tests\issues\build -%PATH_TO_ODIN% build test_issue_1592.odin %COMMON% -file -build\test_issue +set COMMON=-collection:tests=tests -out:tests\issues\build\test_issue -rmdir /S /Q build +@echo on + +.\odin build tests\issues\test_issue_829.odin %COMMON% -file +tests\issues\build\test_issue + +.\odin build tests\issues\test_issue_1592.odin %COMMON% -file +tests\issues\build\test_issue + +@echo off + +rmdir /S /Q tests\issues\build diff --git a/tests/issues/run.sh b/tests/issues/run.sh index ec6d7130d..117a9a5f1 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -1,17 +1,18 @@ #!/bin/bash -ODIN=../../odin -COMMON="-collection:tests=.. -out:build/test_issue.bin" - set -eu -mkdir -p build + +mkdir -p tests/issues/build + +COMMON="-collection:tests=tests -out:tests/issues/build/test_issue" + set -x -$ODIN build test_issue_829.odin $COMMON -file -build/test_issue.bin +./odin build tests/issues/test_issue_829.odin $COMMON -file +tests/issues/build/test_issue -$ODIN build test_issue_1592.odin $COMMON -file -build/test_issue.bin +./odin build tests/issues/test_issue_1592.odin $COMMON -file +tests/issues/build/test_issue set +x -rm -rf build +rm -rf tests/issues/build diff --git a/tests/vendor/Makefile b/tests/vendor/Makefile index 380e64e09..341067c6e 100644 --- a/tests/vendor/Makefile +++ b/tests/vendor/Makefile @@ -1,6 +1,5 @@ ODIN=../../odin ODINFLAGS= -OUT_FILE=test_binary.bin OS=$(shell uname) @@ -11,4 +10,4 @@ endif all: botan_test botan_test: - $(ODIN) run botan -out=$(OUT_FILE) -o:speed -no-bounds-check $(ODINFLAGS) + $(ODIN) run botan -out=botan_hash -o:speed -no-bounds-check $(ODINFLAGS) diff --git a/tests/vendor/build.bat b/tests/vendor/build.bat index 4bd9a6496..e70d9f1d5 100644 --- a/tests/vendor/build.bat +++ b/tests/vendor/build.bat @@ -1,6 +1,5 @@ @echo off -set OUT_FILE=test_binary.exe -set COMMON=-show-timings -no-bounds-check -vet -strict-style -out:%OUT_FILE% +set COMMON=-show-timings -no-bounds-check -vet -strict-style set PATH_TO_ODIN==..\..\odin echo --- From e01662c13959469c9c0bc4974a3b7c03d577d7a4 Mon Sep 17 00:00:00 2001 From: Florian Behr Date: Mon, 25 Apr 2022 13:23:05 +0200 Subject: [PATCH 30/43] Make allocator in pool_add_task() explicit --- core/thread/thread_pool.odin | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/thread/thread_pool.odin b/core/thread/thread_pool.odin index 3f782cf73..fe289d8aa 100644 --- a/core/thread/thread_pool.odin +++ b/core/thread/thread_pool.odin @@ -113,9 +113,8 @@ pool_join :: proc(pool: ^Pool) { // the thread pool. You can even add tasks from inside other tasks. // // Each task also needs an allocator which it either owns, or which is thread -// safe. By default, allocations in the task are disabled by use of the -// nil_allocator. -pool_add_task :: proc(pool: ^Pool, procedure: Task_Proc, data: rawptr, user_index: int = 0, allocator := context.allocator) { +// safe. +pool_add_task :: proc(pool: ^Pool, procedure: Task_Proc, data: rawptr, user_index: int = 0, allocator: mem.Allocator) { sync.guard(&pool.mutex) append(&pool.tasks, Task{ From 1fb76ad7687bc9efe538b79ac05dc0fafad06950 Mon Sep 17 00:00:00 2001 From: Florian Behr Date: Mon, 25 Apr 2022 13:41:19 +0200 Subject: [PATCH 31/43] change usage in demo.odin --- examples/demo/demo.odin | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/demo/demo.odin b/examples/demo/demo.odin index b03345849..a36acdf18 100644 --- a/examples/demo/demo.odin +++ b/examples/demo/demo.odin @@ -1166,7 +1166,8 @@ threading_example :: proc() { for i in 0..<30 { - thread.pool_add_task(pool=&pool, procedure=task_proc, data=nil, user_index=i) + // be mindful of the allocator used for tasks. The allocator needs to be thread safe, or be owned by the task for exclusive use + thread.pool_add_task(pool=&pool, procedure=task_proc, data=nil, user_index=i, allocator=context.allocator) } thread.pool_start(&pool) From ca6a1db7576dd72f5a5b4b73fec8cbad34e5bced Mon Sep 17 00:00:00 2001 From: Florian Behr Date: Mon, 25 Apr 2022 13:41:39 +0200 Subject: [PATCH 32/43] fix doc comment for pool_init --- core/thread/thread_pool.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/thread/thread_pool.odin b/core/thread/thread_pool.odin index fe289d8aa..4fc02309e 100644 --- a/core/thread/thread_pool.odin +++ b/core/thread/thread_pool.odin @@ -44,7 +44,7 @@ Pool :: struct { } // Once initialized, the pool's memory address is not allowed to change until -// it is destroyed. If thread_count < 1, thread count 1 will be used. +// it is destroyed. // // The thread pool requires an allocator which it either owns, or which is thread safe. pool_init :: proc(pool: ^Pool, thread_count: int, allocator: mem.Allocator) { From ee67a0b9a1d83acad3e8ade9314bdad3bb0d2197 Mon Sep 17 00:00:00 2001 From: Florian Behr Date: Mon, 25 Apr 2022 14:08:09 +0200 Subject: [PATCH 33/43] reorder procedure parameters to make sure the optional argument in pool_add_task() is last, and the argument order is consistent with pool_init() --- core/thread/thread_pool.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/thread/thread_pool.odin b/core/thread/thread_pool.odin index 4fc02309e..af80da9aa 100644 --- a/core/thread/thread_pool.odin +++ b/core/thread/thread_pool.odin @@ -47,7 +47,7 @@ Pool :: struct { // it is destroyed. // // The thread pool requires an allocator which it either owns, or which is thread safe. -pool_init :: proc(pool: ^Pool, thread_count: int, allocator: mem.Allocator) { +pool_init :: proc(pool: ^Pool, allocator: mem.Allocator, thread_count: int) { context.allocator = allocator pool.allocator = allocator pool.tasks = make([dynamic]Task) @@ -114,7 +114,7 @@ pool_join :: proc(pool: ^Pool) { // // Each task also needs an allocator which it either owns, or which is thread // safe. -pool_add_task :: proc(pool: ^Pool, procedure: Task_Proc, data: rawptr, user_index: int = 0, allocator: mem.Allocator) { +pool_add_task :: proc(pool: ^Pool, allocator: mem.Allocator, procedure: Task_Proc, data: rawptr, user_index: int = 0) { sync.guard(&pool.mutex) append(&pool.tasks, Task{ From a412d34574649cceacea6a171ccd4af6119c7a0f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 26 Apr 2022 11:35:34 +0100 Subject: [PATCH 34/43] Fix #1740 --- core/mem/virtual/virtual.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/mem/virtual/virtual.odin b/core/mem/virtual/virtual.odin index 2035171a7..21ab5ef21 100644 --- a/core/mem/virtual/virtual.odin +++ b/core/mem/virtual/virtual.odin @@ -120,7 +120,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: int) do_commit_if_necessary :: proc(block: ^Memory_Block, size: uint) -> (err: Allocator_Error) { if block.committed - block.used < size { pmblock := (^Platform_Memory_Block)(block) - base_offset := uint(uintptr(block) - uintptr(pmblock)) + base_offset := uint(uintptr(pmblock.block.base) - uintptr(pmblock)) platform_total_commit := base_offset + block.used + size assert(pmblock.committed <= pmblock.reserved) From 530401e5ee84bd3346b6a3213814a904696019df Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 26 Apr 2022 11:38:32 +0100 Subject: [PATCH 35/43] Fix #1729 --- src/check_type.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/check_type.cpp b/src/check_type.cpp index 51f472961..c0ae64baa 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -1338,14 +1338,14 @@ ParameterValue handle_parameter_value(CheckerContext *ctx, Type *in_type, Type * } } } - } else if (allow_caller_location && o.mode == Addressing_Context) { + } else if (allow_caller_location && (o.mode == Addressing_Context || are_types_identical(o.type, t_source_code_location))) { param_value.kind = ParameterValue_Value; param_value.ast_value = expr; } else if (o.value.kind != ExactValue_Invalid) { param_value.kind = ParameterValue_Constant; param_value.value = o.value; } else { - error(expr, "Default parameter must be a constant, %d", o.mode); + error(expr, "Default parameter must be a constant"); } } } else { From 3bd71229596427d43551ff6612d71505bb79c796 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 26 Apr 2022 11:42:01 +0100 Subject: [PATCH 36/43] Fix #1720 --- vendor/vulkan/_gen/create_vulkan_odin_wrapper.py | 3 +++ vendor/vulkan/core.odin | 3 +++ 2 files changed, 6 insertions(+) diff --git a/vendor/vulkan/_gen/create_vulkan_odin_wrapper.py b/vendor/vulkan/_gen/create_vulkan_odin_wrapper.py index ae1bc8d64..9a32f5796 100644 --- a/vendor/vulkan/_gen/create_vulkan_odin_wrapper.py +++ b/vendor/vulkan/_gen/create_vulkan_odin_wrapper.py @@ -626,6 +626,9 @@ with open("../core.odin", 'w', encoding='utf-8') as f: f.write(BASE) f.write(""" API_VERSION_1_0 :: (1<<22) | (0<<12) | (0) +API_VERSION_1_1 :: (1<<22) | (1<<12) | (0) +API_VERSION_1_2 :: (1<<22) | (2<<12) | (0) +API_VERSION_1_3 :: (1<<22) | (3<<12) | (0) MAKE_VERSION :: proc(major, minor, patch: u32) -> u32 { return (major<<22) | (minor<<12) | (patch) diff --git a/vendor/vulkan/core.odin b/vendor/vulkan/core.odin index 94c667910..b90bfad17 100644 --- a/vendor/vulkan/core.odin +++ b/vendor/vulkan/core.odin @@ -3,6 +3,9 @@ // package vulkan API_VERSION_1_0 :: (1<<22) | (0<<12) | (0) +API_VERSION_1_1 :: (1<<22) | (1<<12) | (0) +API_VERSION_1_2 :: (1<<22) | (2<<12) | (0) +API_VERSION_1_3 :: (1<<22) | (3<<12) | (0) MAKE_VERSION :: proc(major, minor, patch: u32) -> u32 { return (major<<22) | (minor<<12) | (patch) From c81fd2e5dd82fba0d5a1eb6771b1816cdb4ba574 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 26 Apr 2022 11:45:46 +0100 Subject: [PATCH 37/43] Fix #1644 --- core/math/linalg/specific.odin | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/math/linalg/specific.odin b/core/math/linalg/specific.odin index cb007bd91..a4aaeb012 100644 --- a/core/math/linalg/specific.odin +++ b/core/math/linalg/specific.odin @@ -479,21 +479,21 @@ angle_from_quaternion_f16 :: proc(q: Quaternionf16) -> f16 { return math.asin(q.x*q.x + q.y*q.y + q.z*q.z) * 2 } - return math.cos(q.x) * 2 + return math.acos(q.w) * 2 } angle_from_quaternion_f32 :: proc(q: Quaternionf32) -> f32 { if abs(q.w) > math.SQRT_THREE*0.5 { return math.asin(q.x*q.x + q.y*q.y + q.z*q.z) * 2 } - return math.cos(q.x) * 2 + return math.acos(q.w) * 2 } angle_from_quaternion_f64 :: proc(q: Quaternionf64) -> f64 { if abs(q.w) > math.SQRT_THREE*0.5 { return math.asin(q.x*q.x + q.y*q.y + q.z*q.z) * 2 } - return math.cos(q.x) * 2 + return math.acos(q.w) * 2 } angle_from_quaternion :: proc{ angle_from_quaternion_f16, From a5342a01267f55dec5a5b9f775cec8c8379139b1 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 26 Apr 2022 13:14:09 +0200 Subject: [PATCH 38/43] Address edge cases. --- .github/workflows/ci.yml | 11 ++---- .gitignore | 1 - Makefile | 4 +-- build_odin.sh | 4 +-- core/crypto/util/util.odin | 1 - src/build_settings.cpp | 42 ++++++++++++++++++---- src/path.cpp | 67 +++++++++++++++++++++++++++++++++-- src/string.cpp | 35 ------------------ tests/core/Makefile | 29 ++++++++------- tests/core/build.bat | 10 +++--- tests/core/math/big/build.bat | 2 +- tests/issues/run.bat | 21 ++++++----- tests/issues/run.sh | 19 +++++----- tests/vendor/Makefile | 3 +- tests/vendor/build.bat | 3 +- 15 files changed, 150 insertions(+), 102 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d72775636..3cc4283b0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,9 +39,7 @@ jobs: make timeout-minutes: 10 - name: Odin issues tests - run: | - cd tests/issues - ./run.sh + run: tests/issues/run.sh timeout-minutes: 10 - name: Odin check examples/all for Linux i386 run: ./odin check examples/all -vet -strict-style -target:linux_i386 @@ -93,9 +91,7 @@ jobs: make timeout-minutes: 10 - name: Odin issues tests - run: | - cd tests/issues - ./run.sh + run: tests/issues/run.sh timeout-minutes: 10 - name: Odin check examples/all for Darwin arm64 run: ./odin check examples/all -vet -strict-style -target:darwin_arm64 @@ -167,8 +163,7 @@ jobs: shell: cmd run: | call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat - cd tests\issues - call run.bat + call tests\issues\run.bat timeout-minutes: 10 - name: Odin check examples/all for Windows 32bits shell: cmd diff --git a/.gitignore b/.gitignore index d03a86fd7..e8b3d3050 100644 --- a/.gitignore +++ b/.gitignore @@ -269,7 +269,6 @@ bin/ # - Linux/MacOS odin odin.dSYM -*.bin # shared collection shared/ diff --git a/Makefile b/Makefile index 1a1c93180..82150c6a2 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ -all: debug +all: debug demo demo: - ./odin run examples/demo + ./odin run examples/demo/demo.odin report: ./odin report diff --git a/build_odin.sh b/build_odin.sh index 4810cafd2..aef3f2836 100755 --- a/build_odin.sh +++ b/build_odin.sh @@ -102,7 +102,7 @@ build_odin() { } run_demo() { - ./odin run examples/demo + ./odin run examples/demo/demo.odin -file } case $OS in @@ -147,4 +147,4 @@ if [[ $# -eq 1 ]]; then exit 0 else panic "Too many arguments!" -fi \ No newline at end of file +fi diff --git a/core/crypto/util/util.odin b/core/crypto/util/util.odin index 83b07e546..6273a232e 100644 --- a/core/crypto/util/util.odin +++ b/core/crypto/util/util.odin @@ -11,7 +11,6 @@ package util */ import "core:mem" -_ :: mem // @note(bp): this can replace the other two cast_slice :: #force_inline proc "contextless" ($D: typeid/[]$DE, src: $S/[]$SE) -> D { diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 212ded5c8..89d370144 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1276,16 +1276,44 @@ bool init_build_paths(String init_filename) { if (bc->out_filepath.len > 0) { bc->build_paths[BuildPath_Output] = path_from_string(ha, bc->out_filepath); + if (build_context.metrics.os == TargetOs_windows) { + String output_file = path_to_string(ha, bc->build_paths[BuildPath_Output]); + defer (gb_free(ha, output_file.text)); + if (path_is_directory(bc->build_paths[BuildPath_Output])) { + gb_printf_err("Output path %.*s is a directory.\n", LIT(output_file)); + return false; + } else if (bc->build_paths[BuildPath_Output].ext.len == 0) { + gb_printf_err("Output path %.*s must have an appropriate extension.\n", LIT(output_file)); + return false; + } + } } else { - String output_name = remove_directory_from_path(init_filename); - output_name = remove_extension_from_path(output_name); - output_name = copy_string(ha, string_trim_whitespace(output_name)); + Path output_path; - Path output_path = path_from_string(ha, output_name); + if (str_eq(init_filename, str_lit("."))) { + // We must name the output file after the current directory. + debugf("Output name will be created from current base name %.*s.\n", LIT(bc->build_paths[BuildPath_Main_Package].basename)); + String last_element = last_path_element(bc->build_paths[BuildPath_Main_Package].basename); - // Replace extension. - if (output_path.ext.len > 0) { - gb_free(ha, output_path.ext.text); + if (last_element.len == 0) { + gb_printf_err("The output name is created from the last path element. `%.*s` has none. Use `-out:output_name.ext` to set it.\n", LIT(bc->build_paths[BuildPath_Main_Package].basename)); + return false; + } + output_path.basename = copy_string(ha, bc->build_paths[BuildPath_Main_Package].basename); + output_path.name = copy_string(ha, last_element); + + } else { + // Init filename was not 'current path'. + // Contruct the output name from the path elements as usual. + String output_name = remove_directory_from_path(init_filename); + output_name = remove_extension_from_path(output_name); + output_name = copy_string(ha, string_trim_whitespace(output_name)); + output_path = path_from_string(ha, output_name); + + // Replace extension. + if (output_path.ext.len > 0) { + gb_free(ha, output_path.ext.text); + } } output_path.ext = copy_string(ha, output_extension); diff --git a/src/path.cpp b/src/path.cpp index 8d8e532b8..6f83c39ea 100644 --- a/src/path.cpp +++ b/src/path.cpp @@ -1,6 +1,46 @@ /* Path handling utilities. */ +String remove_extension_from_path(String const &s) { + for (isize i = s.len-1; i >= 0; i--) { + if (s[i] == '.') { + return substring(s, 0, i); + } + } + return s; +} + +String remove_directory_from_path(String const &s) { + isize len = 0; + for (isize i = s.len-1; i >= 0; i--) { + if (s[i] == '/' || + s[i] == '\\') { + break; + } + len += 1; + } + return substring(s, s.len-len, s.len); +} + +bool path_is_directory(String path); + +String directory_from_path(String const &s) { + if (path_is_directory(s)) { + return s; + } + + isize i = s.len-1; + for (; i >= 0; i--) { + if (s[i] == '/' || + s[i] == '\\') { + break; + } + } + if (i >= 0) { + return substring(s, 0, i); + } + return substring(s, 0, 0); +} #if defined(GB_SYSTEM_WINDOWS) bool path_is_directory(String path) { @@ -98,11 +138,15 @@ Path path_from_string(gbAllocator a, String const &path) { String fullpath = path_to_full_path(a, path); defer (gb_free(heap_allocator(), fullpath.text)); - res.basename = directory_from_path(fullpath); - res.basename = copy_string(a, res.basename); + res.basename = directory_from_path(fullpath); + res.basename = copy_string(a, res.basename); - if (string_ends_with(fullpath, '/')) { + if (path_is_directory(fullpath)) { // It's a directory. We don't need to tinker with the name and extension. + // It could have a superfluous trailing `/`. Remove it if so. + if (res.basename.len > 0 && res.basename.text[res.basename.len - 1] == '/') { + res.basename.len--; + } return res; } @@ -116,6 +160,23 @@ Path path_from_string(gbAllocator a, String const &path) { return res; } +// NOTE(Jeroen): Takes a path String and returns the last path element. +String last_path_element(String const &path) { + isize count = 0; + u8 * start = (u8 *)(&path.text[path.len - 1]); + for (isize length = path.len; length > 0 && path.text[length - 1] != '/'; length--) { + count++; + start--; + } + if (count > 0) { + start++; // Advance past the `/` and return the substring. + String res = make_string(start, count); + return res; + } + // Must be a root path like `/` or `C:/`, return empty String. + return STR_LIT(""); +} + bool path_is_directory(Path path) { String path_string = path_to_full_path(heap_allocator(), path); defer (gb_free(heap_allocator(), path_string.text)); diff --git a/src/string.cpp b/src/string.cpp index 3515df48e..616761265 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -298,41 +298,6 @@ String filename_from_path(String s) { return make_string(nullptr, 0); } -String remove_extension_from_path(String const &s) { - for (isize i = s.len-1; i >= 0; i--) { - if (s[i] == '.') { - return substring(s, 0, i); - } - } - return s; -} - -String remove_directory_from_path(String const &s) { - isize len = 0; - for (isize i = s.len-1; i >= 0; i--) { - if (s[i] == '/' || - s[i] == '\\') { - break; - } - len += 1; - } - return substring(s, s.len-len, s.len); -} - -String directory_from_path(String const &s) { - isize i = s.len-1; - for (; i >= 0; i--) { - if (s[i] == '/' || - s[i] == '\\') { - break; - } - } - if (i >= 0) { - return substring(s, 0, i); - } - return substring(s, 0, 0); -} - String concatenate_strings(gbAllocator a, String const &x, String const &y) { isize len = x.len+y.len; u8 *data = gb_alloc_array(a, u8, len+1); diff --git a/tests/core/Makefile b/tests/core/Makefile index 9bb622633..6a92b4efb 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -1,6 +1,5 @@ ODIN=../../odin PYTHON=$(shell which python3) -OUT_FILE=test_binary.bin all: download_test_assets image_test compress_test strings_test hash_test crypto_test noise_test encoding_test \ math_test linalg_glsl_math_test filepath_test reflect_test os_exit_test @@ -9,39 +8,39 @@ download_test_assets: $(PYTHON) download_assets.py image_test: - $(ODIN) run image/test_core_image.odin -out=$(OUT_FILE) -file + $(ODIN) run image/test_core_image.odin -file compress_test: - $(ODIN) run compress/test_core_compress.odin -out=$(OUT_FILE) -file + $(ODIN) run compress/test_core_compress.odin -file strings_test: - $(ODIN) run strings/test_core_strings.odin -out=$(OUT_FILE) -file + $(ODIN) run strings/test_core_strings.odin -file hash_test: - $(ODIN) run hash -out=$(OUT_FILE) -o:speed -no-bounds-check + $(ODIN) run hash -out=test_hash -o:speed -no-bounds-check crypto_test: - $(ODIN) run crypto -out=$(OUT_FILE) -o:speed -no-bounds-check + $(ODIN) run crypto -out=test_crypto_hash -o:speed -no-bounds-check noise_test: - $(ODIN) run math/noise -out=$(OUT_FILE) + $(ODIN) run math/noise -out=test_noise encoding_test: - $(ODIN) run encoding/hxa -out=$(OUT_FILE) -collection:tests=.. - $(ODIN) run encoding/json -out=$(OUT_FILE) - $(ODIN) run encoding/varint -out=$(OUT_FILE) + $(ODIN) run encoding/hxa -out=test_hxa -collection:tests=.. + $(ODIN) run encoding/json -out=test_json + $(ODIN) run encoding/varint -out=test_varint math_test: - $(ODIN) run math/test_core_math.odin -out=$(OUT_FILE) -file -collection:tests=.. + $(ODIN) run math/test_core_math.odin -out=test_core_math -file -collection:tests=.. linalg_glsl_math_test: - $(ODIN) run math/linalg/glsl/test_linalg_glsl_math.odin -file -out=$(OUT_FILE) -collection:tests=.. + $(ODIN) run math/linalg/glsl/test_linalg_glsl_math.odin -file -out=test_linalg_glsl_math -collection:tests=.. filepath_test: - $(ODIN) run path/filepath/test_core_filepath.odin -file -out=$(OUT_FILE) -collection:tests=.. + $(ODIN) run path/filepath/test_core_filepath.odin -file -out=test_core_filepath -collection:tests=.. reflect_test: - $(ODIN) run reflect/test_core_reflect.odin -file -out=$(OUT_FILE) -collection:tests=.. + $(ODIN) run reflect/test_core_reflect.odin -file -out=test_core_reflect -collection:tests=.. os_exit_test: - $(ODIN) run os/test_core_os_exit.odin -file -out=$(OUT_FILE) && exit 1 || exit 0 + $(ODIN) run os/test_core_os_exit.odin -file -out=test_core_os_exit && exit 1 || exit 0 diff --git a/tests/core/build.bat b/tests/core/build.bat index 331a473aa..2f9ba672e 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -1,8 +1,6 @@ @echo off -set OUT_FILE=test_binary.exe -set COMMON=-show-timings -no-bounds-check -vet -strict-style -collection:tests=.. -out:%OUT_FILE% +set COMMON=-show-timings -no-bounds-check -vet -strict-style -collection:tests=.. set PATH_TO_ODIN==..\..\odin - python3 download_assets.py echo --- echo Running core:image tests @@ -37,14 +35,14 @@ echo --- echo --- echo Running core:encoding tests echo --- -%PATH_TO_ODIN% run encoding/hxa %COMMON% -%PATH_TO_ODIN% run encoding/json %COMMON% +%PATH_TO_ODIN% run encoding/hxa %COMMON% +%PATH_TO_ODIN% run encoding/json %COMMON% %PATH_TO_ODIN% run encoding/varint %COMMON% echo --- echo Running core:math/noise tests echo --- -%PATH_TO_ODIN% run math/noise %COMMON% +%PATH_TO_ODIN% run math/noise %COMMON% echo --- echo Running core:math tests diff --git a/tests/core/math/big/build.bat b/tests/core/math/big/build.bat index ad199d775..16bdbc8ca 100644 --- a/tests/core/math/big/build.bat +++ b/tests/core/math/big/build.bat @@ -4,7 +4,7 @@ set PATH_TO_ODIN==..\..\..\..\odin set TEST_ARGS=-fast-tests set TEST_ARGS=-no-random set TEST_ARGS= -set OUT_NAME=math_big_test_library.dll +set OUT_NAME=math_big_test_library set COMMON=-build-mode:shared -show-timings -no-bounds-check -define:MATH_BIG_EXE=false -vet -strict-style echo --- echo Running core:math/big tests diff --git a/tests/issues/run.bat b/tests/issues/run.bat index a936bd896..a652d9694 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -1,12 +1,17 @@ @echo off -set PATH_TO_ODIN==..\..\odin -set COMMON=-collection:tests=.. -out:build\test_issue -if not exist "build" mkdir build -%PATH_TO_ODIN% build test_issue_829.odin %COMMON% -file -build\test_issue +if not exist "tests\issues\build\" mkdir tests\issues\build -%PATH_TO_ODIN% build test_issue_1592.odin %COMMON% -file -build\test_issue +set COMMON=-collection:tests=tests -out:tests\issues\build\test_issue -rmdir /S /Q build +@echo on + +.\odin build tests\issues\test_issue_829.odin %COMMON% -file +tests\issues\build\test_issue + +.\odin build tests\issues\test_issue_1592.odin %COMMON% -file +tests\issues\build\test_issue + +@echo off + +rmdir /S /Q tests\issues\build diff --git a/tests/issues/run.sh b/tests/issues/run.sh index ec6d7130d..117a9a5f1 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -1,17 +1,18 @@ #!/bin/bash -ODIN=../../odin -COMMON="-collection:tests=.. -out:build/test_issue.bin" - set -eu -mkdir -p build + +mkdir -p tests/issues/build + +COMMON="-collection:tests=tests -out:tests/issues/build/test_issue" + set -x -$ODIN build test_issue_829.odin $COMMON -file -build/test_issue.bin +./odin build tests/issues/test_issue_829.odin $COMMON -file +tests/issues/build/test_issue -$ODIN build test_issue_1592.odin $COMMON -file -build/test_issue.bin +./odin build tests/issues/test_issue_1592.odin $COMMON -file +tests/issues/build/test_issue set +x -rm -rf build +rm -rf tests/issues/build diff --git a/tests/vendor/Makefile b/tests/vendor/Makefile index 380e64e09..341067c6e 100644 --- a/tests/vendor/Makefile +++ b/tests/vendor/Makefile @@ -1,6 +1,5 @@ ODIN=../../odin ODINFLAGS= -OUT_FILE=test_binary.bin OS=$(shell uname) @@ -11,4 +10,4 @@ endif all: botan_test botan_test: - $(ODIN) run botan -out=$(OUT_FILE) -o:speed -no-bounds-check $(ODINFLAGS) + $(ODIN) run botan -out=botan_hash -o:speed -no-bounds-check $(ODINFLAGS) diff --git a/tests/vendor/build.bat b/tests/vendor/build.bat index 4bd9a6496..e70d9f1d5 100644 --- a/tests/vendor/build.bat +++ b/tests/vendor/build.bat @@ -1,6 +1,5 @@ @echo off -set OUT_FILE=test_binary.exe -set COMMON=-show-timings -no-bounds-check -vet -strict-style -out:%OUT_FILE% +set COMMON=-show-timings -no-bounds-check -vet -strict-style set PATH_TO_ODIN==..\..\odin echo --- From 5e11ad2e1e6d0e335f47197c63a5ffdd8f405dd7 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 26 Apr 2022 14:23:23 +0200 Subject: [PATCH 39/43] Update test paths. --- .github/workflows/ci.yml | 11 ++++++++--- core/crypto/util/util.odin | 1 + tests/core/Makefile | 28 ++++++++++++++-------------- tests/core/build.bat | 28 ++++++++++++++-------------- tests/issues/run.bat | 14 +++++++------- tests/issues/run.sh | 16 ++++++++-------- tests/vendor/Makefile | 2 +- tests/vendor/build.bat | 4 ++-- 8 files changed, 55 insertions(+), 49 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3cc4283b0..d72775636 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,9 @@ jobs: make timeout-minutes: 10 - name: Odin issues tests - run: tests/issues/run.sh + run: | + cd tests/issues + ./run.sh timeout-minutes: 10 - name: Odin check examples/all for Linux i386 run: ./odin check examples/all -vet -strict-style -target:linux_i386 @@ -91,7 +93,9 @@ jobs: make timeout-minutes: 10 - name: Odin issues tests - run: tests/issues/run.sh + run: | + cd tests/issues + ./run.sh timeout-minutes: 10 - name: Odin check examples/all for Darwin arm64 run: ./odin check examples/all -vet -strict-style -target:darwin_arm64 @@ -163,7 +167,8 @@ jobs: shell: cmd run: | 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 - name: Odin check examples/all for Windows 32bits shell: cmd diff --git a/core/crypto/util/util.odin b/core/crypto/util/util.odin index 6273a232e..83b07e546 100644 --- a/core/crypto/util/util.odin +++ b/core/crypto/util/util.odin @@ -11,6 +11,7 @@ package util */ import "core:mem" +_ :: mem // @note(bp): this can replace the other two cast_slice :: #force_inline proc "contextless" ($D: typeid/[]$DE, src: $S/[]$SE) -> D { diff --git a/tests/core/Makefile b/tests/core/Makefile index 6a92b4efb..2c24fef75 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -8,39 +8,39 @@ download_test_assets: $(PYTHON) download_assets.py image_test: - $(ODIN) run image/test_core_image.odin -file + $(ODIN) run image/test_core_image.odin -file -out:test_core_image compress_test: - $(ODIN) run compress/test_core_compress.odin -file + $(ODIN) run compress/test_core_compress.odin -file -out:test_core_compress strings_test: - $(ODIN) run strings/test_core_strings.odin -file + $(ODIN) run strings/test_core_strings.odin -file -out:test_core_strings hash_test: - $(ODIN) run hash -out=test_hash -o:speed -no-bounds-check + $(ODIN) run hash -o:speed -no-bounds-check -out:test_hash crypto_test: - $(ODIN) run crypto -out=test_crypto_hash -o:speed -no-bounds-check + $(ODIN) run crypto -o:speed -no-bounds-check -out:test_crypto_hash noise_test: - $(ODIN) run math/noise -out=test_noise + $(ODIN) run math/noise -out:test_noise encoding_test: - $(ODIN) run encoding/hxa -out=test_hxa -collection:tests=.. - $(ODIN) run encoding/json -out=test_json - $(ODIN) run encoding/varint -out=test_varint + $(ODIN) run encoding/hxa -collection:tests=.. -out:test_hxa + $(ODIN) run encoding/json -out:test_json + $(ODIN) run encoding/varint -out:test_varint math_test: - $(ODIN) run math/test_core_math.odin -out=test_core_math -file -collection:tests=.. + $(ODIN) run math/test_core_math.odin -file -collection:tests=.. -out:test_core_math linalg_glsl_math_test: - $(ODIN) run math/linalg/glsl/test_linalg_glsl_math.odin -file -out=test_linalg_glsl_math -collection:tests=.. + $(ODIN) run math/linalg/glsl/test_linalg_glsl_math.odin -file -collection:tests=.. -out:test_linalg_glsl_math filepath_test: - $(ODIN) run path/filepath/test_core_filepath.odin -file -out=test_core_filepath -collection:tests=.. + $(ODIN) run path/filepath/test_core_filepath.odin -file -collection:tests=.. -out:test_core_filepath reflect_test: - $(ODIN) run reflect/test_core_reflect.odin -file -out=test_core_reflect -collection:tests=.. + $(ODIN) run reflect/test_core_reflect.odin -file -collection:tests=.. -out:test_core_reflect os_exit_test: - $(ODIN) run os/test_core_os_exit.odin -file -out=test_core_os_exit && exit 1 || exit 0 + $(ODIN) run os/test_core_os_exit.odin -file -out:test_core_os_exit && exit 1 || exit 0 \ No newline at end of file diff --git a/tests/core/build.bat b/tests/core/build.bat index 2f9ba672e..b03fef4bb 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -5,61 +5,61 @@ python3 download_assets.py echo --- echo Running core:image tests echo --- -%PATH_TO_ODIN% run image %COMMON% +%PATH_TO_ODIN% run image %COMMON% -out:test_core_image.exe echo --- echo Running core:compress tests echo --- -%PATH_TO_ODIN% run compress %COMMON% +%PATH_TO_ODIN% run compress %COMMON% -out:test_core_compress.exe echo --- echo Running core:strings tests echo --- -%PATH_TO_ODIN% run strings %COMMON% +%PATH_TO_ODIN% run strings %COMMON% -out:test_core_strings.exe echo --- echo Running core:hash tests echo --- -%PATH_TO_ODIN% run hash %COMMON% -o:size +%PATH_TO_ODIN% run hash %COMMON% -o:size -out:test_core_hash.exe echo --- echo Running core:odin tests echo --- -%PATH_TO_ODIN% run odin %COMMON% -o:size +%PATH_TO_ODIN% run odin %COMMON% -o:size -out:test_core_odin.exe echo --- echo Running core:crypto hash tests echo --- -%PATH_TO_ODIN% run crypto %COMMON% +%PATH_TO_ODIN% run crypto %COMMON% -out:test_crypto_hash.exe echo --- echo Running core:encoding tests echo --- -%PATH_TO_ODIN% run encoding/hxa %COMMON% -%PATH_TO_ODIN% run encoding/json %COMMON% -%PATH_TO_ODIN% run encoding/varint %COMMON% +%PATH_TO_ODIN% run encoding/hxa %COMMON% -out:test_hxa.exe +%PATH_TO_ODIN% run encoding/json %COMMON% -out:test_json.exe +%PATH_TO_ODIN% run encoding/varint %COMMON% -out:test_varint.exe echo --- echo Running core:math/noise tests echo --- -%PATH_TO_ODIN% run math/noise %COMMON% +%PATH_TO_ODIN% run math/noise %COMMON% -out:test_noise.exe echo --- echo Running core:math tests echo --- -%PATH_TO_ODIN% run math %COMMON% +%PATH_TO_ODIN% run math %COMMON% -out:test_core_math.exe echo --- echo Running core:math/linalg/glsl tests echo --- -%PATH_TO_ODIN% run math/linalg/glsl %COMMON% +%PATH_TO_ODIN% run math/linalg/glsl %COMMON% -out:test_linalg_glsl.exe echo --- echo Running core:path/filepath tests echo --- -%PATH_TO_ODIN% run path/filepath %COMMON% +%PATH_TO_ODIN% run path/filepath %COMMON% -out:test_core_filepath.exe echo --- echo Running core:reflect tests echo --- -%PATH_TO_ODIN% run reflect %COMMON% +%PATH_TO_ODIN% run reflect %COMMON% -out:test_core_reflect.exe \ No newline at end of file diff --git a/tests/issues/run.bat b/tests/issues/run.bat index a652d9694..a7078ae0f 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -1,17 +1,17 @@ @echo off -if not exist "tests\issues\build\" mkdir tests\issues\build +if not exist "build\" mkdir build -set COMMON=-collection:tests=tests -out:tests\issues\build\test_issue +set COMMON=-collection:tests=.. -out:build\test_issue.exe @echo on -.\odin build tests\issues\test_issue_829.odin %COMMON% -file -tests\issues\build\test_issue +..\..\odin build test_issue_829.odin %COMMON% -file +build\test_issue -.\odin build tests\issues\test_issue_1592.odin %COMMON% -file -tests\issues\build\test_issue +..\..\odin build test_issue_1592.odin %COMMON% -file +build\test_issue @echo off -rmdir /S /Q tests\issues\build +rmdir /S /Q build diff --git a/tests/issues/run.sh b/tests/issues/run.sh index 117a9a5f1..ec0804bac 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -1,18 +1,18 @@ #!/bin/bash set -eu -mkdir -p tests/issues/build - -COMMON="-collection:tests=tests -out:tests/issues/build/test_issue" +mkdir -p build +ODIN=../../odin +COMMON="-collection:tests=.. -out:build/test_issue" set -x -./odin build tests/issues/test_issue_829.odin $COMMON -file -tests/issues/build/test_issue +$ODIN build test_issue_829.odin $COMMON -file +./build/test_issue -./odin build tests/issues/test_issue_1592.odin $COMMON -file -tests/issues/build/test_issue +$ODIN build test_issue_1592.odin $COMMON -file +./build/test_issue set +x -rm -rf tests/issues/build +rm -rf build diff --git a/tests/vendor/Makefile b/tests/vendor/Makefile index 341067c6e..6c68d7908 100644 --- a/tests/vendor/Makefile +++ b/tests/vendor/Makefile @@ -10,4 +10,4 @@ endif all: botan_test botan_test: - $(ODIN) run botan -out=botan_hash -o:speed -no-bounds-check $(ODINFLAGS) + $(ODIN) run botan -o:speed -no-bounds-check $(ODINFLAGS) -out=vendor_botan diff --git a/tests/vendor/build.bat b/tests/vendor/build.bat index e70d9f1d5..d92a5eaea 100644 --- a/tests/vendor/build.bat +++ b/tests/vendor/build.bat @@ -5,9 +5,9 @@ set PATH_TO_ODIN==..\..\odin echo --- echo Running vendor:botan tests echo --- -%PATH_TO_ODIN% run botan %COMMON% +%PATH_TO_ODIN% run botan %COMMON% -out:vendor_botan.exe echo --- echo Running vendor:glfw tests echo --- -%PATH_TO_ODIN% run glfw %COMMON% \ No newline at end of file +%PATH_TO_ODIN% run glfw %COMMON% -out:vendor_glfw.exe \ No newline at end of file From ba5e33bc3518c327057102f56ac2c1cce55dc76f Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 26 Apr 2022 14:51:16 +0200 Subject: [PATCH 40/43] Update CI workflow. --- .github/workflows/ci.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3cc4283b0..a5a6eddb1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,9 @@ jobs: make timeout-minutes: 10 - name: Odin issues tests - run: tests/issues/run.sh + run: | + cd tests/issues + ./run.sh timeout-minutes: 10 - name: Odin check examples/all for Linux i386 run: ./odin check examples/all -vet -strict-style -target:linux_i386 @@ -91,7 +93,8 @@ jobs: make timeout-minutes: 10 - name: Odin issues tests - run: tests/issues/run.sh + cd tests/issues + ./run.sh timeout-minutes: 10 - name: Odin check examples/all for Darwin arm64 run: ./odin check examples/all -vet -strict-style -target:darwin_arm64 @@ -163,7 +166,8 @@ jobs: shell: cmd run: | 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 - name: Odin check examples/all for Windows 32bits shell: cmd From 1c03e6805775c58044d417eea2504312b4e916ab Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 26 Apr 2022 14:56:28 +0200 Subject: [PATCH 41/43] Update CI. --- .github/workflows/ci.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a5a6eddb1..d72775636 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,8 +40,8 @@ jobs: timeout-minutes: 10 - name: Odin issues tests run: | - cd tests/issues - ./run.sh + cd tests/issues + ./run.sh timeout-minutes: 10 - name: Odin check examples/all for Linux i386 run: ./odin check examples/all -vet -strict-style -target:linux_i386 @@ -93,8 +93,9 @@ jobs: make timeout-minutes: 10 - name: Odin issues tests - cd tests/issues - ./run.sh + run: | + cd tests/issues + ./run.sh timeout-minutes: 10 - name: Odin check examples/all for Darwin arm64 run: ./odin check examples/all -vet -strict-style -target:darwin_arm64 From 40f0f5ad8dd56691ae42e9dd4b9a2f5e5395f3ee Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 26 Apr 2022 15:01:09 +0200 Subject: [PATCH 42/43] Update CI for math library. --- tests/core/math/big/build.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/math/big/build.bat b/tests/core/math/big/build.bat index 16bdbc8ca..ad199d775 100644 --- a/tests/core/math/big/build.bat +++ b/tests/core/math/big/build.bat @@ -4,7 +4,7 @@ set PATH_TO_ODIN==..\..\..\..\odin set TEST_ARGS=-fast-tests set TEST_ARGS=-no-random set TEST_ARGS= -set OUT_NAME=math_big_test_library +set OUT_NAME=math_big_test_library.dll set COMMON=-build-mode:shared -show-timings -no-bounds-check -define:MATH_BIG_EXE=false -vet -strict-style echo --- echo Running core:math/big tests From d262eda91c527880a95f70a3d6fa20346c1d20d6 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 26 Apr 2022 15:10:31 +0200 Subject: [PATCH 43/43] Update Makefile --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 82150c6a2..91010a620 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ -all: debug demo +all: debug demo: - ./odin run examples/demo/demo.odin + ./odin run examples/demo/demo.odin -file report: ./odin report

+ Odin logo +
+ The Data-Oriented Language for Sane Software Development. +
+
+
+ + + + + +
+ + + + + + +