Skip to content

Instantly share code, notes, and snippets.

@silverweed
Last active September 8, 2024 12:44
Show Gist options
  • Save silverweed/0d1a029a21eb364f55e1a3c61772a4d1 to your computer and use it in GitHub Desktop.
Save silverweed/0d1a029a21eb364f55e1a3c61772a4d1 to your computer and use it in GitHub Desktop.
resolve_includes.c
// Given the string `input`, replaces every instance of `#include "file"` with the contents
// of `file` (relative to `include_dir`). The returned string aliases `input` if no includes were found,
// otherwise it's a new copy with the same lifetime as `arena`.
String8 file_resolve_includes(Arena *arena, String8 input, String8 include_dir)
{
String8 output = {};
String8 include_str = str8("#include");
Temp scratch = scratch_begin(&arena, 1);
// locate all include directives
struct Include {
usz pos;
usz len; // length of the include directive, up to the last '"'
String8 file;
};
struct Include includes[256] = {};
u64 n_includes = 0;
u64 pos = 0;
while (pos < input.size - include_str.size) {
const char *include_pos = strstr(cstr(input) + pos, cstr(include_str));
if (!include_pos)
break;
if (n_includes == countof(includes)) {
ERR_TAG("Files", "Too many #include directives.");
return output;
}
struct Include inc;
inc.pos = (u8 *)include_pos - input.str;
// parse file name
pos = inc.pos + include_str.size;
u8 c;
do {
c = input.str[pos++];
} while (pos < input.size && (c == ' ' || c == '\t'));
if (c != '"') {
ERR_TAG("Files", "Error resolving includes: expected '\"' after #include, found %c (at pos %zu).\n", c, pos - 1);
u8 code[50] = {};
memcpy(code, input.str + pos - 20, countof(code));
ERR_TAG("Files", "Code around pos:\n%s\n", code);
assert(false);
return output;
}
u8 *included_file_start = input.str + pos;
usz included_file_size = 0;
b8 found_closing_quote = false;
while (pos < input.size) {
c = input.str[pos++];
if (c == '"') {
found_closing_quote = true;
break;
}
++included_file_size;
}
if (!found_closing_quote) {
ERR_TAG("Files", "Error resolving includes: unterminated string after #include directive.\n");
assert(false);
return output;
}
if (included_file_size == 0) {
ERR_TAG("Files", "Error resolving includes: empty string in #include directive.\n");
assert(false);
return output;
}
String8 included_file = str8_from_buf(scratch.arena, included_file_start, included_file_size);
inc.file = push_str8f(scratch.arena, "%s/%s", cstr(include_dir), cstr(included_file));
inc.len = pos - inc.pos;
includes[n_includes++] = inc;
}
// Copy content into output
if (n_includes == 0) {
output = input;
return output;
}
u64 output_tot_bytes = input.size;
for (u64 i = 0; i < n_includes; ++i) {
FILE *f = fopen(cstr(includes[i].file), "r");
if (!f) {
ERR_TAG("Files", "Failed to open included file %s", cstr(includes[i].file));
return output;
}
output_tot_bytes += file_size(f);
output_tot_bytes -= includes[i].len;
fclose(f);
}
output.size = output_tot_bytes;
output.str = arena_push_array_nozero(u8, arena, output.size + 1);
output.str[output.size] = 0;
usz input_pos = 0;
usz output_pos = 0;
for (u64 inc_idx = 0; inc_idx < n_includes; ++inc_idx) {
struct Include *inc = &includes[inc_idx];
assert(inc->pos >= input_pos);
usz input_len = inc->pos - input_pos;
if (input_len > 0) {
memcpy(output.str + output_pos, input.str + input_pos, input_len);
input_pos += input_len;
output_pos += input_len;
}
u64 arena_pre_pos = arena_pos(scratch.arena);
String8 inc_file_content = file_read_to_string(scratch.arena, inc->file);
if (inc_file_content.size > 0) {
memcpy(output.str + output_pos, inc_file_content.str, inc_file_content.size);
input_pos += inc->len;
output_pos += inc_file_content.size;
}
arena_pop_to(scratch.arena, arena_pre_pos);
assert(output_pos < output.size);
}
usz rem = input.size - input_pos;
assert(rem == output.size - output_pos);
if (rem > 0)
memcpy(output.str + output_pos, input.str + input_pos, rem);
return output;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment