feat: generate code in itself

This commit is contained in:
2026-01-25 18:02:30 +01:00
parent 58467cde4e
commit 7f131397e7
4 changed files with 127 additions and 83 deletions

3
.gitignore vendored
View File

@@ -1 +1,2 @@
build **/cmeta
**/main

View File

@@ -1,10 +0,0 @@
all: build/cmeta
build:
mkdir -p build
build/cmeta: build third_party/stb_c_lexer.h src/cmeta.h src/main.c
gcc -Wall -Wextra -pedantic -o build/cmeta -isystem third_party/ third_party/stb_c_lexer.h src/main.c
run: build/cmeta
build/cmeta

View File

@@ -1,3 +1,40 @@
#ifndef CMETA_H
#define CMETA_H
#include <stddef.h>
typedef struct {
const char* type;
const char* name;
} Field_Info;
typedef struct {
const char* name;
size_t fields_count;
Field_Info *fields;
} Struct_Info;
// AUTO GENERATED CODE //
static Struct_Info foo_struct_info = { // cmeta.h:214
.name = "Foo_Struct", // cmeta.h:215
.fields_count = 1, // cmeta.h:216
.fields = (Field_Info[1]) { // cmeta.h:217
{ .type = "int", .name = "int_field" }, // cmeta.h:219
}, // cmeta.h:221
}; // cmeta.h:222
static Struct_Info bar_struct_info = { // cmeta.h:214
.name = "Bar_Struct", // cmeta.h:215
.fields_count = 2, // cmeta.h:216
.fields = (Field_Info[2]) { // cmeta.h:217
{ .type = "int", .name = "int_field" }, // cmeta.h:219
{ .type = "long", .name = "long_fields" }, // cmeta.h:219
}, // cmeta.h:221
}; // cmeta.h:222
// AUTO GENERATED CODE //
// #define CMETA_COMPTIME
#ifdef CMETA_COMPTIME
#include <ctype.h> #include <ctype.h>
#include <errno.h> #include <errno.h>
#include <stdbool.h> #include <stdbool.h>
@@ -7,10 +44,43 @@
#define STB_C_LEXER_IMPLEMENTATION #define STB_C_LEXER_IMPLEMENTATION
#include "stb_c_lexer.h" #include "stb_c_lexer.h"
#include "cmeta.h"
stb_lexer lexer = {0}; stb_lexer lexer = {0};
typedef struct {
char* type;
char* name;
} Parsed_Field_Info;
typedef struct {
char* name;
size_t fields_count;
Parsed_Field_Info *fields;
} Parsed_Struct_Info;
typedef struct {
char* data;
size_t capacity;
size_t length;
} String_Builder;
void sb_append(String_Builder* sb, const char* data) {
size_t data_len = strlen(data);
size_t total_len = sb->length + data_len;
if(total_len + 1 > sb->capacity) {
size_t new_capacity = sb->capacity == 0 ? 64 : sb->capacity * 2;
while(new_capacity < total_len + 1) {
new_capacity *= 2;
}
sb->data = (char*) realloc(sb->data, new_capacity);
sb->capacity = new_capacity;
}
memcpy(sb->data + sb->length, data, data_len);
sb->length = total_len;
sb->data[sb->length] = '\0';
}
bool lexer_expect_keyword(const char* expected) { bool lexer_expect_keyword(const char* expected) {
stb_c_lexer_get_token(&lexer); stb_c_lexer_get_token(&lexer);
@@ -58,16 +128,17 @@ long lexer_peek() {
} }
// parses typedef struct { FIELDS } TYPE_NAME // parses typedef struct { FIELDS } TYPE_NAME
bool parse_struct(Struct_Info* info) { bool parse_struct(Parsed_Struct_Info* info) {
bool result = false; bool result = false;
char* name = NULL;
size_t fields_count = 0;
Parsed_Field_Info fields[16] = {0}; // TODO: use dynamic array
if (!lexer_expect_keyword("typedef")) goto fail; if (!lexer_expect_keyword("typedef")) goto fail;
if (!lexer_expect_keyword("struct")) goto fail; if (!lexer_expect_keyword("struct")) goto fail;
if (!lexer_expect('{', NULL)) goto fail; if (!lexer_expect('{', NULL)) goto fail;
size_t fields_count = 0;
Field_Info fields[16] = {0}; // TODO: use dynamic array
while (true) { while (true) {
char* mark = lexer.parse_point; char* mark = lexer.parse_point;
if (!stb_c_lexer_get_token(&lexer)) { if (!stb_c_lexer_get_token(&lexer)) {
@@ -92,19 +163,19 @@ bool parse_struct(Struct_Info* info) {
} }
if (!lexer_expect(CLEX_id, "type name")) goto fail; if (!lexer_expect(CLEX_id, "type name")) goto fail;
char* name = strdup(lexer.string); name = strdup(lexer.string);
info->name = name;
info->fields_count = fields_count;
info->fields = (Parsed_Field_Info*)calloc(fields_count, sizeof(Parsed_Field_Info));
for(size_t i = 0; i < fields_count; i += 1) {
info->fields[i].type = fields[i].type;
info->fields[i].name = fields[i].name;
}
result = true; result = true;
fail: fail:
if(result) { if(!result) {
info->name = name;
info->fields_count = fields_count;
info->fields = (Field_Info*)calloc(fields_count, sizeof(Field_Info));
for(size_t i = 0; i < fields_count; i += 1) {
info->fields[i].type = fields[i].type;
info->fields[i].name = fields[i].name;
}
} else {
free(name); free(name);
for(size_t i = 0; i < fields_count; i += 1) { for(size_t i = 0; i < fields_count; i += 1) {
free(fields[i].name); free(fields[i].name);
@@ -115,7 +186,7 @@ fail:
return result; return result;
} }
void print_struct(Struct_Info info) { void print_struct(Parsed_Struct_Info info) {
printf("struct_name = %s\n", info.name); printf("struct_name = %s\n", info.name);
printf("fields[%zu] = [\n", info.fields_count); printf("fields[%zu] = [\n", info.fields_count);
for (size_t i = 0; i < info.fields_count; i += 1) { for (size_t i = 0; i < info.fields_count; i += 1) {
@@ -137,7 +208,7 @@ char* to_lowercase(char* str) {
return str; return str;
} }
void generate_struct_info(FILE* stream, Struct_Info info) { void generate_struct_info(FILE* stream, Parsed_Struct_Info info) {
char* lowercase_name = to_lowercase(strdup(info.name)); char* lowercase_name = to_lowercase(strdup(info.name));
gen("static Struct_Info %s_info = {", lowercase_name); gen("static Struct_Info %s_info = {", lowercase_name);
@@ -176,21 +247,31 @@ bool read_entire_file(const char* file_path, char** content) {
result = true; result = true;
fail: fail:
if (!result) { if (!result) {
free(content); free(*content);
fprintf(stderr, "ERROR: Could not read `%s`: %s\n", file_path, strerror(errno)); fprintf(stderr, "ERROR: Could not read `%s`: %s\n", file_path, strerror(errno));
} }
if (file) fclose(file); if (file) fclose(file);
return result; return result;
} }
bool generate_output_file(const char* output_path, Struct_Info *struct_infos, size_t struct_infos_count) { bool generate_output_file(const char* output_path, Parsed_Struct_Info *struct_infos, size_t struct_infos_count) {
const char* GENERATION_MARK = "// AUTO GENERATED CODE //\n";
const size_t GENERATION_MARK_LEN = strlen(GENERATION_MARK);
bool result = false; bool result = false;
char* header_content; char* header_content;
if (!read_entire_file("src/cmeta.h", &header_content)) goto fail; if (!read_entire_file(__FILE__, &header_content)) goto fail;
char* generate_at = strstr(header_content, "// GENERATE_HERE //"); // 1. find BEGIN an END
if (generate_at == NULL) { char* generate_begin = strstr(header_content, GENERATION_MARK);
if (generate_begin == NULL) {
fprintf(stderr, "ERROR: could not found generation mark in cmeta.h\n");
goto fail;
}
char* generate_end = strstr(generate_begin + GENERATION_MARK_LEN, GENERATION_MARK);
if (generate_end == NULL) {
fprintf(stderr, "ERROR: could not found generation mark in cmeta.h\n"); fprintf(stderr, "ERROR: could not found generation mark in cmeta.h\n");
goto fail; goto fail;
} }
@@ -203,17 +284,14 @@ bool generate_output_file(const char* output_path, Struct_Info *struct_infos, si
FILE* stream = output_file; FILE* stream = output_file;
// write up to the generation mark // write up to the generation mark, including it
fwrite(header_content, generate_at - header_content, 1, stream); fwrite(header_content, generate_begin + GENERATION_MARK_LEN - header_content, 1, stream);
gen("// Generated by cmeta");
for (size_t i = 0; i < struct_infos_count; i += 1) { for (size_t i = 0; i < struct_infos_count; i += 1) {
generate_struct_info(stream, struct_infos[i]); generate_struct_info(stream, struct_infos[i]);
} }
// write the rest after the \n fwrite(generate_end, strlen(generate_end), 1, stream);
generate_at = strchr(generate_at, '\n');
fwrite(generate_at, strlen(generate_at), 1, stream);
result = true; result = true;
fail: fail:
@@ -223,7 +301,7 @@ fail:
return result; return result;
} }
bool preprocess_file(const char* file_path, char** result) { bool preprocess_file(const char* file_path, String_Builder* result) {
char command[256] = {0}; char command[256] = {0};
sprintf(command, "gcc -E %s", file_path); sprintf(command, "gcc -E %s", file_path);
@@ -239,7 +317,7 @@ bool preprocess_file(const char* file_path, char** result) {
// TODO: use dynamic array instead // TODO: use dynamic array instead
size_t cursor = 0; size_t cursor = 0;
*result = (char*) malloc(2048 * sizeof(char)); result->length = 0;
bool collecting_content = false; bool collecting_content = false;
@@ -250,12 +328,11 @@ bool preprocess_file(const char* file_path, char** result) {
if (strcmp(file_name, file_path) == 0) { if (strcmp(file_name, file_path) == 0) {
collecting_content = true; collecting_content = true;
continue;; continue;
} }
} else if(collecting_content) { } else if(collecting_content) {
size_t line_len = strlen(line); sb_append(result, line);
memcpy(*result + cursor, line, line_len); cursor += strlen(line);
cursor += line_len;
} }
} }
@@ -264,32 +341,23 @@ bool preprocess_file(const char* file_path, char** result) {
return true; return true;
} }
int main(int argc, char** argv) bool process_file(const char* input_file) {
{
const char* program_name = argv[0];
if (argc == 1) {
fprintf(stderr, "ERROR: missing required argument <input_file>\n");
fprintf(stderr, "Usage: %s <input_file>\n", program_name);
return 1;
}
// read input file // read input file
char* input_content; String_Builder input_content = {0};
if (!preprocess_file(argv[1], &input_content)) return 1; if (!preprocess_file(input_file, &input_content)) return false;
// init lexer // init lexer
char string_store[1024]; char string_store[1024] = {0};
stb_c_lexer_init(&lexer, input_content, input_content + strlen(input_content), string_store, sizeof(string_store) / sizeof(char)); stb_c_lexer_init(&lexer, input_content.data, input_content.data + input_content.length, string_store, sizeof(string_store) / sizeof(char));
// find and parse all structs // find and parse all structs
size_t struct_infos_count = 0; size_t struct_infos_count = 0;
Struct_Info struct_infos[16]; // TODO: use dynamic array Parsed_Struct_Info struct_infos[16]; // TODO: use dynamic array
while (true) { while (true) {
char* mark = lexer.parse_point; char* mark = lexer.parse_point;
if (!stb_c_lexer_get_token(&lexer)) break; if (!stb_c_lexer_get_token(&lexer)) break;
if (lexer.token == CLEX_id && strcmp(lexer.string, "typedef") == 0) { if (lexer.token == CLEX_id && strcmp(lexer.string, "typedef") == 0) {
lexer.parse_point = mark; lexer.parse_point = mark;
@@ -299,7 +367,11 @@ int main(int argc, char** argv)
} }
} }
if (!generate_output_file("test/01_simple_struct/cmeta.h", struct_infos, struct_infos_count)) return 1; if (!generate_output_file(__FILE__, struct_infos, struct_infos_count)) return false;
return 0; return true;
} }
#endif // CMETA_COMPTIME
#endif // CMETA_H

View File

@@ -1,19 +0,0 @@
#ifndef CMETA_H
#define CMETA_H
#include <stddef.h>
typedef struct {
char* type;
char* name;
} Field_Info;
typedef struct {
char* name;
size_t fields_count;
Field_Info *fields;
} Struct_Info;
// GENERATE_HERE //
#endif // CMETA_H