Browse Source

Merge branch 'master' into sync-cond-futex

gingerBill 3 years ago
parent
commit
beb698f31d

+ 8 - 3
.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

+ 1 - 1
LICENSE

@@ -1,4 +1,4 @@
-Copyright (c) 2016-2021 Ginger Bill. All rights reserved.
+Copyright (c) 2016-2022 Ginger Bill. All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:

+ 2 - 2
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

+ 173 - 0
core/container/intrusive/list/intrusive_list.odin

@@ -0,0 +1,173 @@
+package container_intrusive_list
+
+import "core:intrinsics"
+
+// An intrusive doubly-linked list
+//
+// As this is an intrusive container, a `Node` must be embedded in your own
+// structure which is conventionally called a "link". The use of `push_front`
+// and `push_back` take the address of this node. Retrieving the data
+// associated with the node requires finding the relative offset of the node
+// of the parent structure. The parent type and field name are given to
+// `iterator_*` procedures, or to the built-in `container_of` procedure.
+//
+// This data structure is two-pointers in size:
+// 	8 bytes on 32-bit platforms and 16 bytes on 64-bit platforms
+List :: struct {
+	head: ^Node,
+	tail: ^Node,
+}
+
+
+Node :: struct {
+	next, prev: ^Node,
+}
+
+push_front :: proc(list: ^List, node: ^Node) {
+	if list.head != nil {
+		list.head.prev = node
+		node.prev, node.next = nil, list.head
+		list.head = node
+	} else {
+		list.head, list.tail = node, node
+		node.prev, node.next = nil, nil
+	}
+}
+
+push_back :: proc(list: ^List, node: ^Node) {
+	if list.tail != nil {
+		list.tail.next = node
+		node.prev, node.next = list.tail, nil
+		list.tail = node
+	} else {
+		list.head, list.tail = node, node
+		node.prev, node.next = nil, nil
+	}
+}
+
+remove :: proc(list: ^List, node: ^Node) {
+	if node != nil {
+		if node.next != nil {
+			node.next.prev = node.prev
+		}
+		if node.prev != nil {
+			node.prev.next = node.next
+		}
+		if list.head == node {
+			list.head = node.next
+		}
+		if list.tail == node {
+			list.tail = node.prev
+		}
+	}
+}
+
+remove_by_proc :: proc(list: ^List, to_erase: proc(^Node) -> bool) {
+	for node := list.head; node != nil; {
+		next := node.next
+		if to_erase(node) {
+			if node.next != nil {
+				node.next.prev = node.prev
+			}
+			if node.prev != nil {
+				node.prev.next = node.next
+			}
+			if list.head == node {
+				list.head = node.next
+			}
+			if list.tail == node {
+				list.tail = node.prev
+			}
+		}
+		node = next
+	}
+}
+
+
+is_empty :: proc(list: ^List) -> bool {
+	return list.head == nil
+}
+
+pop_front :: proc(list: ^List) -> ^Node {
+	link := list.head
+	if link == nil {
+		return nil
+	}
+	if link.next != nil {
+		link.next.prev = link.prev
+	}
+	if link.prev != nil {
+		link.prev.next = link.next
+	}
+	if link == list.head {
+		list.head = link.next
+	}
+	if link == list.tail {
+		list.tail = link.prev
+	}
+	return link
+
+}
+pop_back :: proc(list: ^List) -> ^Node {
+	link := list.tail
+	if link == nil {
+		return nil
+	}
+	if link.next != nil {
+		link.next.prev = link.prev
+	}
+	if link.prev != nil {
+		link.prev.next = link.next
+	}
+	if link == list.head {
+		list.head = link.next
+	}
+	if link == list.tail {
+		list.tail = link.prev
+	}
+	return link
+}
+
+
+Iterator :: struct($T: typeid) {
+	curr:   ^Node,
+	offset: uintptr,
+}
+
+iterator_head :: proc(list: List, $T: typeid, $field_name: string) -> Iterator(T)
+	where intrinsics.type_has_field(T, field_name),
+	      intrinsics.type_field_type(T, field_name) == Node {
+	return {list.head, offset_of_by_string(T, field_name)}
+}
+
+iterator_tail :: proc(list: List, $T: typeid, $field_name: string) -> Iterator(T)
+	where intrinsics.type_has_field(T, field_name),
+	      intrinsics.type_field_type(T, field_name) == Node {
+	return {list.tail, offset_of_by_string(T, field_name)}
+}
+
+iterator_from_node :: proc(node: ^Node, $T: typeid, $field_name: string) -> Iterator(T)
+	where intrinsics.type_has_field(T, field_name),
+	      intrinsics.type_field_type(T, field_name) == Node {
+	return {node, offset_of_by_string(T, field_name)}
+}
+
+iterate_next :: proc(it: ^Iterator($T)) -> (ptr: ^T, ok: bool) {
+	node := it.curr
+	if node == nil {
+		return nil, false
+	}
+	it.curr = node.next
+
+	return (^T)(uintptr(node) - it.offset), true
+}
+
+iterate_prev :: proc(it: ^Iterator($T)) -> (ptr: ^T, ok: bool) {
+	node := it.curr
+	if node == nil {
+		return nil, false
+	}
+	it.curr = node.prev
+
+	return (^T)(uintptr(node) - it.offset), true
+}

+ 42 - 13
core/intrinsics/intrinsics.odin

@@ -41,6 +41,10 @@ mem_copy_non_overlapping :: proc(dst, src: rawptr, len: int) ---
 mem_zero                 :: proc(ptr: rawptr, len: int) ---
 mem_zero_volatile        :: proc(ptr: rawptr, len: int) ---
 
+// prefer [^]T operations if possible
+ptr_offset :: proc(ptr: ^$T, offset: int) -> ^T ---
+ptr_sub    :: proc(a, b: ^$T) -> int ---
+
 unaligned_load           :: proc(src: ^$T) -> T ---
 unaligned_store          :: proc(dst: ^$T, val: T) -> T ---
 
@@ -82,6 +86,7 @@ atomic_store_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) ---
 atomic_load           :: proc(dst: ^$T) -> T ---
 atomic_load_explicit  :: proc(dst: ^$T, order: Atomic_Memory_Order) -> T ---
 
+// fetch then operator
 atomic_add               :: proc(dst; ^$T, val: T) -> T ---
 atomic_add_explicit      :: proc(dst; ^$T, val: T, order: Atomic_Memory_Order) -> T ---
 atomic_sub               :: proc(dst; ^$T, val: T) -> T ---
@@ -119,19 +124,20 @@ type_is_string     :: proc($T: typeid) -> bool ---
 type_is_typeid     :: proc($T: typeid) -> bool ---
 type_is_any        :: proc($T: typeid) -> bool ---
 
-type_is_endian_platform :: proc($T: typeid) -> bool ---
-type_is_endian_little   :: proc($T: typeid) -> bool ---
-type_is_endian_big      :: proc($T: typeid) -> bool ---
-type_is_unsigned        :: proc($T: typeid) -> bool ---
-type_is_numeric         :: proc($T: typeid) -> bool ---
-type_is_ordered         :: proc($T: typeid) -> bool ---
-type_is_ordered_numeric :: proc($T: typeid) -> bool ---
-type_is_indexable       :: proc($T: typeid) -> bool ---
-type_is_sliceable       :: proc($T: typeid) -> bool ---
-type_is_comparable      :: proc($T: typeid) -> bool ---
-type_is_simple_compare  :: proc($T: typeid) -> bool --- // easily compared using memcmp (== and !=)
-type_is_dereferenceable :: proc($T: typeid) -> bool ---
-type_is_valid_map_key   :: proc($T: typeid) -> bool ---
+type_is_endian_platform       :: proc($T: typeid) -> bool ---
+type_is_endian_little         :: proc($T: typeid) -> bool ---
+type_is_endian_big            :: proc($T: typeid) -> bool ---
+type_is_unsigned              :: proc($T: typeid) -> bool ---
+type_is_numeric               :: proc($T: typeid) -> bool ---
+type_is_ordered               :: proc($T: typeid) -> bool ---
+type_is_ordered_numeric       :: proc($T: typeid) -> bool ---
+type_is_indexable             :: proc($T: typeid) -> bool ---
+type_is_sliceable             :: proc($T: typeid) -> bool ---
+type_is_comparable            :: proc($T: typeid) -> bool ---
+type_is_simple_compare        :: proc($T: typeid) -> bool --- // easily compared using memcmp (== and !=)
+type_is_dereferenceable       :: proc($T: typeid) -> bool ---
+type_is_valid_map_key         :: proc($T: typeid) -> bool ---
+type_is_valid_matrix_elements :: proc($T: typeid) -> bool ---
 
 type_is_named            :: proc($T: typeid) -> bool ---
 type_is_pointer          :: proc($T: typeid) -> bool ---
@@ -146,6 +152,7 @@ type_is_enum             :: proc($T: typeid) -> bool ---
 type_is_proc             :: proc($T: typeid) -> bool ---
 type_is_bit_set          :: proc($T: typeid) -> bool ---
 type_is_simd_vector      :: proc($T: typeid) -> bool ---
+type_is_matrix           :: proc($T: typeid) -> bool ---
 
 type_has_nil :: proc($T: typeid) -> bool ---
 
@@ -153,6 +160,7 @@ type_is_specialization_of :: proc($T, $S: typeid) -> bool ---
 type_is_variant_of :: proc($U, $V: typeid) -> bool where type_is_union(U) ---
 
 type_has_field :: proc($T: typeid, $name: string) -> bool ---
+type_field_type :: proc($T: typeid, $name: string) -> typeid ---
 
 type_proc_parameter_count :: proc($T: typeid) -> int where type_is_proc(T) ---
 type_proc_return_count    :: proc($T: typeid) -> int where type_is_proc(T) ---
@@ -160,20 +168,41 @@ type_proc_return_count    :: proc($T: typeid) -> int where type_is_proc(T) ---
 type_proc_parameter_type  :: proc($T: typeid, index: int) -> typeid where type_is_proc(T) ---
 type_proc_return_type     :: proc($T: typeid, index: int) -> typeid where type_is_proc(T) ---
 
+type_struct_field_count :: proc($T: typeid) -> int where type_is_struct(T) ---
+
 type_polymorphic_record_parameter_count :: proc($T: typeid) -> typeid ---
 type_polymorphic_record_parameter_value :: proc($T: typeid, index: int) -> $V ---
 
+type_is_specialized_polymorphic_record   :: proc($T: typeid) -> bool ---
+type_is_unspecialized_polymorphic_record :: proc($T: typeid) -> bool ---
+
+type_is_subtype_of :: proc($T, $U: typeid) -> bool ---
 
 type_field_index_of :: proc($T: typeid, $name: string) -> uintptr ---
 
 type_equal_proc  :: proc($T: typeid) -> (equal:  proc "contextless" (rawptr, rawptr) -> bool)                 where type_is_comparable(T) ---
 type_hasher_proc :: proc($T: typeid) -> (hasher: proc "contextless" (data: rawptr, seed: uintptr) -> uintptr) where type_is_comparable(T) ---
 
+constant_utf16_cstring :: proc($literal: string) -> [^]u16 ---
 
 // WASM targets only
 wasm_memory_grow :: proc(index, delta: uintptr) -> int ---
 wasm_memory_size :: proc(index: uintptr)        -> int ---
 
+
+// Darwin targets only
+objc_object   :: struct{}
+objc_selector :: struct{}
+objc_class    :: struct{}
+objc_id    :: ^objc_object
+objc_SEL   :: ^objc_selector
+objc_Class :: ^objc_class
+
+objc_find_selector     :: proc($name: string) -> objc_SEL   ---
+objc_register_selector :: proc($name: string) -> objc_SEL   ---
+objc_find_class        :: proc($name: string) -> objc_Class ---
+objc_register_class    :: proc($name: string) -> objc_Class ---
+
 // Internal compiler use only
 
 __entry_point :: proc() ---

+ 10 - 0
core/runtime/core_builtin.odin

@@ -5,6 +5,16 @@ import "core:intrinsics"
 @builtin
 Maybe :: union($T: typeid) #maybe {T}
 
+
+@builtin
+container_of :: #force_inline proc "contextless" (ptr: $P/^$Field_Type, $T: typeid, $field_name: string) -> ^T
+	where intrinsics.type_has_field(T, field_name),
+	      intrinsics.type_field_type(T, field_name) == Field_Type {
+	offset :: offset_of_by_string(T, field_name)
+	return (^T)(uintptr(ptr) - offset) if ptr != nil else nil
+}
+
+
 @thread_local global_default_temp_allocator_data: Default_Temp_Allocator
 
 @builtin

+ 4 - 4
core/sync/sema_internal.odin

@@ -6,19 +6,19 @@ import "core:time"
 
 when #config(ODIN_SYNC_SEMA_USE_FUTEX, true) {
 	_Sema :: struct {
-		sema: Atomic_Sema,
+		atomic: Atomic_Sema,
 	}
 
 	_sema_post :: proc(s: ^Sema, count := 1) {
-		atomic_sema_post(&s.impl.sema, count)
+		atomic_sema_post(&s.impl.atomic, count)
 	}
 
 	_sema_wait :: proc(s: ^Sema) {
-		atomic_sema_wait(&s.impl.sema)
+		atomic_sema_wait(&s.impl.atomic)
 	}
 
 	_sema_wait_with_timeout :: proc(s: ^Sema, duration: time.Duration) -> bool {
-		return atomic_sema_wait_with_timeout(&s.impl.sema, duration)
+		return atomic_sema_wait_with_timeout(&s.impl.atomic, duration)
 	}
 } else {
 	_Sema :: struct {

+ 4 - 5
core/thread/thread_pool.odin

@@ -44,10 +44,10 @@ 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) {
+pool_init :: proc(pool: ^Pool, allocator: mem.Allocator, thread_count: int) {
 	context.allocator = allocator
 	pool.allocator = allocator
 	pool.tasks      = make([dynamic]Task)
@@ -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, allocator: mem.Allocator, procedure: Task_Proc, data: rawptr, user_index: int = 0) {
 	sync.guard(&pool.mutex)
 
 	append(&pool.tasks, Task{

+ 2 - 1
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)

+ 208 - 28
src/build_settings.cpp

@@ -3,7 +3,6 @@
 #include <sys/sysctl.h>
 #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<Path> 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,194 @@ 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);
+		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 {
+		Path output_path;
+
+		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);
+
+			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);
+
+		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;
+}

+ 33 - 0
src/check_builtin.cpp

@@ -29,6 +29,7 @@ BuiltinTypeIsProc *builtin_type_is_procs[BuiltinProc__type_simple_boolean_end -
 
 	is_type_named,
 	is_type_pointer,
+	is_type_multi_pointer,
 	is_type_array,
 	is_type_enumerated_array,
 	is_type_slice,
@@ -3866,6 +3867,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
 	case BuiltinProc_type_is_valid_matrix_elements:
 	case BuiltinProc_type_is_named:
 	case BuiltinProc_type_is_pointer:
+	case BuiltinProc_type_is_multi_pointer:
 	case BuiltinProc_type_is_array:
 	case BuiltinProc_type_is_enumerated_array:
 	case BuiltinProc_type_is_slice:
@@ -3926,6 +3928,37 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
 			break;
 		}
 		break;
+	case BuiltinProc_type_field_type:
+		{
+			Operand op = {};
+			Type *bt = check_type(c, ce->args[0]);
+			Type *type = base_type(bt);
+			if (type == nullptr || type == t_invalid) {
+				error(ce->args[0], "Expected a type for '%.*s'", LIT(builtin_name));
+				return false;
+			}
+			Operand x = {};
+			check_expr(c, &x, ce->args[1]);
+
+			if (!is_type_string(x.type) || x.mode != Addressing_Constant || x.value.kind != ExactValue_String) {
+				error(ce->args[1], "Expected a const string for field argument");
+				return false;
+			}
+
+			String field_name = x.value.value_string;
+
+			Selection sel = lookup_field(type, field_name, false);
+			if (sel.index.count == 0) {
+				gbString t = type_to_string(type);
+				error(ce->args[1], "'%.*s' is not a field of type %s", LIT(field_name), t);
+				gb_string_free(t);
+				return false;
+			}
+			operand->mode = Addressing_Type;
+			operand->type = sel.entity->type;
+			break;
+		}
+		break;
 
 	case BuiltinProc_type_is_specialization_of:
 		{

+ 2 - 2
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 || are_types_identical(o.type, t_source_code_location))) {
+				} else if (allow_caller_location && o.mode == Addressing_Context) {
 					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");
+					error(expr, "Default parameter must be a constant, %d", o.mode);
 				}
 			}
 		} else {

+ 4 - 0
src/checker_builtin_procs.hpp

@@ -158,6 +158,7 @@ BuiltinProc__type_simple_boolean_begin,
 
 	BuiltinProc_type_is_named,
 	BuiltinProc_type_is_pointer,
+	BuiltinProc_type_is_multi_pointer,
 	BuiltinProc_type_is_array,
 	BuiltinProc_type_is_enumerated_array,
 	BuiltinProc_type_is_slice,
@@ -179,6 +180,7 @@ BuiltinProc__type_simple_boolean_begin,
 BuiltinProc__type_simple_boolean_end,
 
 	BuiltinProc_type_has_field,
+	BuiltinProc_type_field_type,
 
 	BuiltinProc_type_is_specialization_of,
 
@@ -375,6 +377,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
 
 	{STR_LIT("type_is_named"),             1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("type_is_pointer"),           1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+	{STR_LIT("type_is_multi_pointer"),      1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("type_is_array"),             1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("type_is_enumerated_array"),  1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 	{STR_LIT("type_is_slice"),             1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
@@ -395,6 +398,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
 	{STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
 
 	{STR_LIT("type_has_field"),            2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+	{STR_LIT("type_field_type"),           2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 
 	{STR_LIT("type_is_specialization_of"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
 

+ 1 - 256
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<FileInfo> *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 <dirent.h>
-
-ReadDirectoryError read_directory(String path, Array<FileInfo> *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;

+ 35 - 11
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 <cwd>.
+			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);
 
-	len = gb_strlen(fullpath);
+			gb_memmove(result, (void *)cwd, cwd_len);
+			result[cwd_len] = '/';
 
-	result = gb_alloc_array(a, char, len + 1);
-	gb_memmove(result, fullpath, len);
-	result[len] = 0;
-	free(p);
+			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);
+	}
 	return result;
 #endif
 }

+ 12 - 2
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);
 	}

+ 0 - 1
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) {

+ 72 - 80
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<String> 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<String> 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;
 }

+ 1 - 1
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);

+ 394 - 0
src/path.cpp

@@ -0,0 +1,394 @@
+/*
+	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) {
+		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 (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;
+	}
+
+	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;
+}
+
+// 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));
+
+	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<FileInfo> *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 <dirent.h>
+
+ReadDirectoryError read_directory(String path, Array<FileInfo> *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
+

+ 2 - 35
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--;
@@ -299,38 +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;
-		}
-	}
-	return substring(s, 0, i);
-}
-
 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);

+ 14 - 14
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

+ 14 - 14
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

+ 18 - 15
tests/core/download_assets.py

@@ -5,8 +5,9 @@ import sys
 import os
 import zipfile
 
+TEST_SUITES        = ['PNG', 'XML']
 DOWNLOAD_BASE_PATH = "assets/{}"
-ASSETS_BASE_URL    = "https://raw.githubusercontent.com/Kelimion/compress-odin/master/tests/assets/{}/{}"
+ASSETS_BASE_URL    = "https://raw.githubusercontent.com/odin-lang/test-assets/master/{}/{}"
 PNG_IMAGES         = [
 	"basi0g01.png", "basi0g02.png", "basi0g04.png", "basi0g08.png", "basi0g16.png", "basi2c08.png",
 	"basi2c16.png", "basi3p01.png", "basi3p02.png", "basi3p04.png", "basi3p08.png", "basi4a08.png",
@@ -73,25 +74,27 @@ def try_download_and_unpack_zip(suite):
 		print("Could not extract ZIP file")
 		return 2
 
-
 def main():
-	print("Downloading PNG assets")
+	for suite in TEST_SUITES:
+		print("Downloading {} assets".format(suite))
 
-	# Make PNG assets path
-	try:
-		path = DOWNLOAD_BASE_PATH.format("PNG")
-		os.makedirs(path)
-	except FileExistsError:
-		pass
+		# Make assets path
+		try:
+			path = DOWNLOAD_BASE_PATH.format(suite)
+			os.makedirs(path)
+		except FileExistsError:
+			pass
+
+		# Try downloading and unpacking the assets
+		r = try_download_and_unpack_zip(suite)
+		if r is not None:
+			return r
+
+		# We could fall back on downloading the PNG files individually, but it's slow
+		print("Done downloading {} assets.".format(suite))
 
-	# Try downloading and unpacking the PNG assets
-	r = try_download_and_unpack_zip("PNG")
-	if r is not None:
-		return r
 
-	# We could fall back on downloading the PNG files individually, but it's slow
 
-	print("Done downloading PNG assets")
 	return 0
 
 if __name__ == '__main__':

+ 1 - 1
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

+ 7 - 7
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

+ 8 - 8
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

+ 1 - 1
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

+ 2 - 2
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%
+%PATH_TO_ODIN% run glfw %COMMON% -out:vendor_glfw.exe