C_Intro/demo.str_cache.c
2025-05-04 11:24:16 -04:00

272 lines
8.0 KiB
C

/*
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.
*/
/*
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.
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
to see definitions related to a sepecific kind of data or operation (strings, memory, etc).
*/
#if 0
int main()
{
VArena cache_arena; varena_init(cache_arena);
StrCache cache = strcache_init(varena_ainfo(cache));
VArena file_arena; varena_init(file_arena);
Str path_text = lit("../demo.strcache.c");
FileContent text_file = file_read_contents(varena_ainfo(file_arena), path_text);
Arena ast_arena; arena_init(ast_arena);
WATL_ParseOps ops = { .str_cache = &cache, .node_backing = arena_ainfo(ast_arena) }
WATL_ParsedInfo parsed = watl_parse(text_file.content, ops);
watl_dbg_dump(parsed.root);
strcache_dbg_listing(cache);
return 0;
}
#endif
/*
The above makes use of the following core concepts to achieve its net result:
* Slices
* Arenas
* Generic Runtime Allocator Interface
* Hashing
Secondarily for the purposes of using the above sufficiently the following are also utilized:
* Virtual Address Space
* Read/Write Files
* Lexing & Parsing
* Debug printing
TODO(Ed): Do we introduce gencpp in this?
*/
/*
First thing we'll problably want is a way to deal with text effectively.
So we'll setup the the minimum for that when dealing with immutable constructs.
*/
// We'll need some minimum set of dependencies to adequately define the constructs.
// ASSUMING MODERN MSVC TOOLCHAIN.
#include <stdarg.h>
#include <stddef.h>
#include <intrin.h>
#include <tmmintrin.h>
#include <wmmintrin.h>
#include <assert.h>
#include <stdbool.h>
typedef unsigned __int8 U8;
typedef signed __int8 S8;
typedef unsigned __int16 U16;
typedef signed __int16 S16;
typedef unsigned __int32 U32;
typedef signed __int32 S32;
typedef unsigned __int64 U64;
typedef signed __int64 S64;
typedef size_t USIZE;
typedef ptrdiff_t SSIZE;
// Functional style cast
#define cast(type, data) ((type)(data))
#define nullptr cast(void*, 0)
// Enforces size querying uses SSIZE type.
#define size_of(data) cast(SSIZE, sizeof(data))
/*
The first construct we'll utilize is a String Slice.
In modern programming with the memory sizes utilized, it is more ergonomic to track the length of strings with their pointer.
Most strings are not stored in some immutable table tracked statically, performance loss in doing so is negligble on modern hardware constraints.
*/
typedef struct Str8 Str8;
struct Str8 {
char const* ptr;
SSIZE len;
};
// 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 };
// For now this string can visualized using a debugger.
// #define DEMO__STR_SLICE
#ifdef DEMO__STR_SLICE
int main()
{
Str8 first = lit("Our first string as a slice");
return 0;
}
#endif DEMO__STR_SLICE
/*
We now want to be able to read a file. This will be a heavy rabbit-hole as we'll need to setup a basic file interface
and related definitions for handling the memory.
For the purposes of the initial definition we'll introduced fixed-sized memory handling statically allocated onto the stack.
*/
/*
First off we need to find out how to aquire the contents of a file on Windows.
We'll be wrapping the operation in a procedure called file_read_contents. We'll have it take a path and optional arguments (Opts__read_file_contents).
It will return a result in a composite struct: FileOpResult; which may be expanded as needed in the future.
*/
typedef struct FileOpResult FileOpResult;
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
/*
The file contents will be returned in bytes.
To view or manage any slice of bytes we'll be utilizing a byte slice.
*/
typedef struct SliceByte SliceByte;
struct SliceByte {
U8* ptr;
SSIZE len;
};
/*
To address memory we'll use a memory slice.
*/
typedef struct SliceMem SliceMem;
struct SliceMem {
void* ptr;
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;
};
// 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"
#if 0
BOOL ReadFile(
[in] HANDLE hFile,
[out] LPVOID lpBuffer,
[in] DWORD nNumberOfBytesToRead,
[out, optional] LPDWORD lpNumberOfBytesRead,
[in, out, optional] LPOVERLAPPED lpOverlapped
);
// In order to read a file we need a handle to a valid filesystem entity to read from: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
HANDLE CreateFileA(
[in] LPCSTR lpFileName,
[in] DWORD dwDesiredAccess,
[in] DWORD dwShareMode,
[in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes,
[in] DWORD dwCreationDisposition,
[in] DWORD dwFlagsAndAttributes,
[in, optional] HANDLE hTemplateFile
);
#endif
// We need to covert our string slice to a c-string for CreateFileA's path input.
#define KILOBTYES(n) (cast(SSIZE, n) << 10)
#define MEGABYTES(n) (cast(SSIZE, n) << 20)
#define GIGABYTES(n) (cast(SSIZE, n) << 30)
#define TERABYTES(n) (cast(SSIZE, n) << 40)
/*
We'll be defining here Fixed-sized memory blocks using typedefs on-demand
They will having the following format:
typedef U8 FMem_<size>KB [ <U8 amount> ];
*/
typedef U8 FMem_16KB [ KILOBTYES(16) ];
#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)
{
if (dest == NULL || src == NULL || count == 0) {
return NULL;
}
__movsb((unsigned char*)dest, (const unsigned char*)src, count);
return dest;
}
// Assumes memory is zeroed.
char const* str8_to_cstr_capped(Str8 content, SliceMem mem)
{
assert(mem.len >= content.len);
memory_copy(mem.ptr, content.ptr, content.ptr);
return mem.ptr;
}
void api_file_read_contents(FileOpResult* result, Str8 path, Opts__read_file_contents* opts)
{
FMem_16KB scratch = {0};
char const* path_cstr = str8_to_cstr_capped(path, fmem_slice(scratch) );
HANDLE id_file = CreateFileA(
path_cstr,
);
// BOOL op_result = ReadFile(
// id_file,
// buffer,
// to_read,
// read_amount,
// nullptr
// );
return
/*
TODO(Ed): You are here
*/
}
#endif DEMO__FILE_READ_CONTENTS_V1
// Version agnostic code:
inline
FileOpResult file__read_contents(Str8 path, Opts__read_file_contents* opts) {
FileOpResult result;
api_file_read_contents(& result, path, opts);
return result;
}