V1 file read done
This commit is contained in:
		| @@ -71,6 +71,7 @@ $compiler_args += $flag_nologo | ||||
|  | ||||
| # Constraints on interpeting all files as C code | ||||
| $compiler_args += $flag_all_c | ||||
| $compiler_args += $flag_c11 | ||||
| # Constraints on C program code-gen  | ||||
| $compiler_args += $flag_exceptions_disabled | ||||
| $compiler_args += $flag_RTTI_disabled | ||||
|   | ||||
							
								
								
									
										211
									
								
								demo.str_cache.c
									
									
									
									
									
								
							
							
						
						
									
										211
									
								
								demo.str_cache.c
									
									
									
									
									
								
							| @@ -1,13 +1,18 @@ | ||||
| /* | ||||
| A introduction to C11 with a str cache demo. | ||||
| Attempting to showcase better conventions and constructs in C; Discovered to me as of 2025 from scouring the internet. | ||||
|  | ||||
| "C is old and flawed, but your use of it is most likely more flawed. You must have calouses to write with barbed syntax & semantics." | ||||
| */ | ||||
|  | ||||
| /* | ||||
| The below will be implemented within this single file. | ||||
| Because of this, definitions will be kept on a need-to-have basis to target only one vendor target and toolchain. | ||||
| We will not use nearly any libraries and will be targeting only Windows 11 x64 using MSVC. | ||||
|  | ||||
| Even so the constructs defined and their dependencies can be properly abstracted into a ergonomic library for multiple targets with enough time and pain. | ||||
| The difference is just more preprocess conditionals, and how far a library is trying to support a larger range of targets and their age discrpancy. | ||||
| The more minimal the less cruft. | ||||
|  | ||||
| Definitions are defined linearly on the file on-demand as needed. Since the file is to be read linearly. | ||||
| This will cause non-categorical organization so it will be more difficult to sift through if you wanted | ||||
| @@ -66,7 +71,7 @@ So we'll setup the the minimum for that when dealing with immutable constructs. | ||||
| #include <wmmintrin.h> | ||||
|  | ||||
| #include <assert.h> | ||||
| #include <stdbool.h> | ||||
| // #include <stdbool.h> | ||||
|  | ||||
| typedef unsigned __int8  U8; | ||||
| typedef signed   __int8  S8; | ||||
| @@ -80,6 +85,17 @@ typedef signed   __int64 S64; | ||||
| typedef size_t    USIZE; | ||||
| typedef ptrdiff_t SSIZE; | ||||
|  | ||||
|  | ||||
| enum { | ||||
| 	false, | ||||
| 	true, | ||||
| 	true_overflow, | ||||
| }; | ||||
|  | ||||
| typedef S8  B8; | ||||
| typedef S16 B16; | ||||
| typedef S32 B32; | ||||
|  | ||||
| // Functional style cast | ||||
| #define cast(type, data) ((type)(data)) | ||||
|  | ||||
| @@ -101,7 +117,7 @@ struct Str8 { | ||||
| }; | ||||
|  | ||||
| // String iterals in C include null-terminators, we aren't interested in preserving that. | ||||
| #define lit(string_literal) (Str8){ string_literal, size_of(string_literal) - 1 }; | ||||
| #define lit(string_literal) (Str8){ string_literal, size_of(string_literal) - 1 } | ||||
|  | ||||
| // For now this string can visualized using a debugger. | ||||
| // #define DEMO__STR_SLICE | ||||
| @@ -132,17 +148,7 @@ typedef struct Opts__read_file_contents Opts__read_file_contents; | ||||
| void         api_file_read_contents(FileOpResult* result, Str8 path, Opts__read_file_contents* opts); | ||||
| FileOpResult file__read_contents   (                      Str8 path, Opts__read_file_contents* opts); | ||||
|  | ||||
| #define file_read_contents(path, opts) file__read_contents(path, & (Opts__read_file_contents){0} ) | ||||
|  | ||||
| /* | ||||
| The above is a pattern that can be provided so that whether or not the result is formatted and provided to the user via the stack is entirely optional. | ||||
| It also allows for default parameters to be defined conviently. | ||||
| */ | ||||
|  | ||||
| // Now for our "Version 1" | ||||
|  | ||||
| #define DEMO__FILE_READ_CONTENTS_V1 | ||||
| #ifdef  DEMO__FILE_READ_CONTENTS_V1 | ||||
| #define file_read_contents(path, ...) file__read_contents(path, & (Opts__read_file_contents){__VA_ARGS__} ) | ||||
|  | ||||
| /* | ||||
| The file contents will be returned in bytes. | ||||
| @@ -163,20 +169,34 @@ struct SliceMem { | ||||
| 	SSIZE len; | ||||
| }; | ||||
|  | ||||
| struct FileOpResult | ||||
| { | ||||
| 	// For now we'll just have the content | ||||
| 	SliceByte content; | ||||
| }; | ||||
|  | ||||
| struct Opts__read_file_contents | ||||
| { | ||||
| 	// For now we'll just have the backing memory provided as a slice. | ||||
| 	SliceMem backing; | ||||
| }; | ||||
| /* | ||||
| The above is a pattern that can be provided so that whether or not the result is formatted and provided to the user via the stack is entirely optional. | ||||
| It also allows for default parameters to be defined conviently. | ||||
| */ | ||||
|  | ||||
| // We'll utilize the ReadFile procedure within the WinAPI: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile | ||||
| #include "fileapi.h" | ||||
| #define NOMINMAX | ||||
| #define WIN32_LEAN_AND_MEAN | ||||
| #define WIN32_MEAN_AND_LEAN | ||||
| #define VC_EXTRALEAN | ||||
| #include <windows.h> | ||||
| #include <windowsx.h> | ||||
| #include <timeapi.h> | ||||
| #include <tlhelp32.h> | ||||
| #include <Shlobj.h> | ||||
| #include <processthreadsapi.h> | ||||
| #pragma comment(lib, "user32") | ||||
| #pragma comment(lib, "winmm") | ||||
| #pragma comment(lib, "shell32") | ||||
| #pragma comment(lib, "advapi32") | ||||
| #pragma comment(lib, "rpcrt4") | ||||
| #pragma comment(lib, "shlwapi") | ||||
| #pragma comment(lib, "comctl32") | ||||
| #pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") // this is required for loading correct comctl32 dll file | ||||
| #undef NOMINMAX | ||||
| #undef WIN32_LEAN_AND_MEAN | ||||
| #undef WIN32_MEAN_AND_LEAN | ||||
| #undef VC_EXTRALEAN | ||||
| #if 0 | ||||
| BOOL ReadFile( | ||||
|   [in]                HANDLE       hFile, | ||||
| @@ -213,51 +233,142 @@ typedef U8 FMem_<size>KB [ <U8 amount> ]; | ||||
| */ | ||||
|  | ||||
| typedef U8 FMem_16KB [ KILOBTYES(16) ]; | ||||
| typedef U8 FMem_64KB [ KILOBTYES(64) ]; | ||||
|  | ||||
| #define typeof          __typeof__ | ||||
| #define fmem_slice(mem) (SliceMem) { mem, size_of(mem) } | ||||
|  | ||||
| // We'll be using an intrinsic for copying memory: | ||||
| void* memory_copy(void* dest, const void* src, size_t count)  | ||||
| void* memory_copy(void* dest, void const* src, SSIZE length) | ||||
| { | ||||
| 	if (dest == NULL || src == NULL || count == 0) { | ||||
| 		return NULL; | ||||
| 	if (dest == nullptr || src == nullptr || length == 0) { | ||||
| 		return nullptr; | ||||
| 	} | ||||
|  | ||||
| 	__movsb((unsigned char*)dest, (const unsigned char*)src, count); | ||||
| 	// https://learn.microsoft.com/en-us/cpp/intrinsics/movsb?view=msvc-170 | ||||
| 	__movsb((unsigned char*)dest, (const unsigned char*)src, length); | ||||
| 	return dest; | ||||
| } | ||||
|  | ||||
| // Often we'll want to check validity of a slice: | ||||
| #define slice_assert(slice) do {  \ | ||||
| 	assert(slice.ptr != nullptr); \ | ||||
| 	assert(slice.len > 0);        \ | ||||
| } while(0) | ||||
|  | ||||
| void slice_copy(SliceMem dest, SliceMem src) { | ||||
| 	assert(dest.len >= src.len); | ||||
| 	slice_assert(dest); | ||||
| 	slice_assert(src); | ||||
| 	memory_copy(dest.ptr, src.ptr, src.len); | ||||
| } | ||||
|  | ||||
| // Assumes memory is zeroed. | ||||
| char const* str8_to_cstr_capped(Str8 content, SliceMem mem) | ||||
| { | ||||
| char const* str8_to_cstr_capped(Str8 content, SliceMem mem) { | ||||
| 	assert(mem.len >= content.len); | ||||
| 	memory_copy(mem.ptr, content.ptr, content.ptr); | ||||
| 	memory_copy(mem.ptr, content.ptr, content.len); | ||||
| 	return mem.ptr; | ||||
| } | ||||
|  | ||||
| // To support zeroing slices we'll utilize an intrinisc. | ||||
| B32 memory_zero(void* dest, SSIZE length) { | ||||
| 	if (dest == nullptr || length <= 0) { | ||||
| 		return false; | ||||
| 	} | ||||
| 	__stosd((unsigned long*)dest, 0, length); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| void slice_zero(SliceMem mem) { | ||||
| 	slice_assert(mem); | ||||
| 	memory_zero(mem.ptr, mem.len); | ||||
| } | ||||
|  | ||||
| // Now for our "Version 1" | ||||
|  | ||||
| #define DEMO__FILE_READ_CONTENTS_V1 | ||||
| #ifdef  DEMO__FILE_READ_CONTENTS_V1 | ||||
|  | ||||
| struct FileOpResult | ||||
| { | ||||
| 	// For now we'll just have the content | ||||
| 	SliceByte content; | ||||
| }; | ||||
|  | ||||
| struct Opts__read_file_contents | ||||
| { | ||||
| 	// For now we'll just have the backing memory provided as a slice. | ||||
| 	SliceMem backing; | ||||
| 	// And whether we should zero the backing. | ||||
| 	B32 zero_backing; | ||||
| }; | ||||
|  | ||||
| void api_file_read_contents(FileOpResult* result, Str8 path, Opts__read_file_contents* opts) | ||||
| { | ||||
| 	assert(result != nullptr); | ||||
| 	assert(opts   != nullptr); | ||||
| 	slice_assert(path); | ||||
| 	// Backing is required at this point | ||||
| 	slice_assert(opts->backing); | ||||
|  | ||||
| 	FMem_16KB   scratch   = {0}; | ||||
| 	char const* path_cstr = str8_to_cstr_capped(path, fmem_slice(scratch) ); | ||||
|  | ||||
| 	HANDLE id_file = CreateFileA( | ||||
| 		path_cstr, | ||||
|  | ||||
| 		GENERIC_READ, | ||||
| 		FILE_SHARE_READ, | ||||
| 		NULL, | ||||
| 		OPEN_EXISTING, | ||||
| 		FILE_ATTRIBUTE_NORMAL, | ||||
| 		NULL | ||||
| 	); | ||||
| 	B32 open_failed = id_file == INVALID_HANDLE_VALUE; | ||||
| 	if (open_failed) { | ||||
| 		DWORD  error_code = GetLastError(); | ||||
| 		assert(error_code != 0); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	// BOOL op_result = ReadFile( | ||||
| 	// 	id_file, | ||||
| 	// 	buffer, | ||||
| 	// 	to_read, | ||||
| 	// 	read_amount, | ||||
| 	// 	nullptr | ||||
| 	// ); | ||||
| 	LARGE_INTEGER file_size = {0}; | ||||
| 	DWORD get_size_failed = ! GetFileSizeEx(id_file, & file_size); | ||||
| 	if   (get_size_failed) { | ||||
| 		assert(get_size_failed == INVALID_FILE_SIZE); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	return  | ||||
| 	/* | ||||
| 	TODO(Ed): You are here | ||||
| 	*/ | ||||
| 	// Because we are currently using fixed size memory, we need to confirm that we can hold this content. | ||||
| 	B32 not_enough_backing = opts->backing.len < file_size.QuadPart; | ||||
| 	if (not_enough_backing) { | ||||
| 		assert(not_enough_backing); | ||||
| 		// Otherwise we don't provide a result. | ||||
| 		result->content = (SliceByte){0}; | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	if (opts->zero_backing) { | ||||
| 		slice_zero(opts->backing); | ||||
| 	} | ||||
|  | ||||
| 	DWORD amount_read = 0; | ||||
| 	BOOL read_result = ReadFile( | ||||
| 		id_file, | ||||
| 		opts->backing.ptr, | ||||
| 		file_size.QuadPart, | ||||
| 		& amount_read, | ||||
| 		nullptr | ||||
| 	); | ||||
| 	CloseHandle(id_file); | ||||
| 	 | ||||
| 	B32 read_failed  = ! read_result; | ||||
| 	    read_failed |= amount_read != file_size.QuadPart; | ||||
| 	if (read_failed) { | ||||
| 		assert(read_failed); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	result->content.ptr = opts->backing.ptr; | ||||
| 	result->content.len = file_size.QuadPart; | ||||
| 	return; | ||||
| } | ||||
|  | ||||
| #endif DEMO__FILE_READ_CONTENTS_V1 | ||||
| @@ -269,3 +380,13 @@ FileOpResult file__read_contents(Str8 path, Opts__read_file_contents* opts) { | ||||
| 	api_file_read_contents(& result, path, opts); | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| // And now to put it all together into a test run in the debugger. Content should be properly formatted if the code is correct. | ||||
| #ifdef DEMO__FILE_READ_CONTENTS_V1 | ||||
| int main() | ||||
| { | ||||
| 	FMem_64KB    read_mem = {0}; | ||||
| 	FileOpResult res      = file_read_contents(lit("demo.str_cache.c"), .backing = fmem_slice(read_mem) ); | ||||
| 	return 0; | ||||
| } | ||||
| #endif | ||||
|   | ||||
		Reference in New Issue
	
	Block a user