#ifndef CMETA_H #define CMETA_H #include 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 #include #include #include #include #define STB_C_LEXER_IMPLEMENTATION #include "stb_c_lexer.h" 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) { stb_c_lexer_get_token(&lexer); if (lexer.token != CLEX_id) { // TODO: map lexer.token to readable name fprintf(stderr, "ERROR: expected `%s` but got `%ld`\n", expected, lexer.token); return false; } if (strcmp(lexer.string, expected) != 0) { fprintf(stderr, "ERROR: expected `%s` but got `%s`\n", expected, lexer.string); return false; } return true; } bool lexer_expect(long expected, const char* expected_str) { stb_c_lexer_get_token(&lexer); if (lexer.token != expected) { // TODO: map lexer.token to readable name if(expected_str != NULL) { fprintf(stderr, "ERROR: expected %s but got `%ld`\n", expected_str, lexer.token); } else { fprintf(stderr, "ERROR: expected `%ld` but got `%ld`\n", expected, lexer.token); } return false; } return true; } long lexer_peek() { char* mark = lexer.parse_point; if (!stb_c_lexer_get_token(&lexer)) { lexer.parse_point = mark; return CLEX_eof; } long token = lexer.token; lexer.parse_point = mark; return token; } // parses typedef struct { FIELDS } TYPE_NAME bool parse_struct(Parsed_Struct_Info* info) { 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("struct")) goto fail; if (!lexer_expect('{', NULL)) goto fail; while (true) { char* mark = lexer.parse_point; if (!stb_c_lexer_get_token(&lexer)) { fprintf(stderr, "ERROR: expected struct fields but got EOF\n"); goto fail; } if (lexer.token == '}') break; lexer.parse_point = mark; if (!lexer_expect(CLEX_id, "field type")) goto fail; char* field_type = strdup(lexer.string); if (!lexer_expect(CLEX_id, "field name")) goto fail; char* field_name = strdup(lexer.string); fields[fields_count].type = field_type; fields[fields_count].name = field_name; fields_count += 1; if(!lexer_expect(';', NULL)) goto fail; } if (!lexer_expect(CLEX_id, "type name")) goto fail; 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; fail: if(!result) { free(name); for(size_t i = 0; i < fields_count; i += 1) { free(fields[i].name); free(fields[i].type); } } return result; } void print_struct(Parsed_Struct_Info info) { printf("struct_name = %s\n", info.name); printf("fields[%zu] = [\n", info.fields_count); for (size_t i = 0; i < info.fields_count; i += 1) { printf(" { type = %s, name = %s },\n", info.fields[i].type, info.fields[i].name); } printf("]\n"); } #define gen(...) do { \ fprintf(stream, __VA_ARGS__); \ fprintf(stream, " // %s:%d\n", __FILE__, __LINE__); \ } while(0) char* to_lowercase(char* str) { size_t len = strlen(str); for(size_t i = 0; i < len; i += 1) { str[i] = tolower(str[i]); } return str; } void generate_struct_info(FILE* stream, Parsed_Struct_Info info) { char* lowercase_name = to_lowercase(strdup(info.name)); gen("static Struct_Info %s_info = {", lowercase_name); gen(" .name = \"%s\",", info.name); gen(" .fields_count = %zu,", info.fields_count); gen(" .fields = (Field_Info[%zu]) {", info.fields_count); for (size_t i = 0; i < info.fields_count; i += 1) { gen(" { .type = \"%s\", .name = \"%s\" },", info.fields[i].type, info.fields[i].name); } gen(" },"); gen("};"); free(lowercase_name); } bool read_entire_file(const char* file_path, char** content) { bool result = false; FILE* file = fopen(file_path, "rb"); if(file == NULL) goto fail; if(fseek(file, 0, SEEK_END) < 0) goto fail; long length = ftell(file); if(length < 0) goto fail; if(fseek(file, 0, SEEK_SET) < 0) goto fail; *content = (char*) malloc((length + 1) * sizeof(char)); fread(*content, 1, length, file); // TODO: will not set errno if (ferror(file)) goto fail; (*content)[length] = '\0'; result = true; fail: if (!result) { free(*content); fprintf(stderr, "ERROR: Could not read `%s`: %s\n", file_path, strerror(errno)); } if (file) fclose(file); return result; } 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; char* header_content; if (!read_entire_file(__FILE__, &header_content)) goto fail; // 1. find BEGIN an END 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"); goto fail; } FILE* output_file = fopen(output_path, "wb"); if (!output_file) { fprintf(stderr, "ERROR: could not write to %s: %s\n", output_path, strerror(errno)); goto fail; } FILE* stream = output_file; // write up to the generation mark, including it fwrite(header_content, generate_begin + GENERATION_MARK_LEN - header_content, 1, stream); for (size_t i = 0; i < struct_infos_count; i += 1) { generate_struct_info(stream, struct_infos[i]); } fwrite(generate_end, strlen(generate_end), 1, stream); result = true; fail: free(header_content); if (output_file) fclose(output_file); return result; } bool preprocess_file(const char* file_path, String_Builder* result) { char command[256] = {0}; sprintf(command, "gcc -E %s", file_path); FILE* fp = popen(command, "r"); if (fp == NULL) { fprintf(stderr, "ERROR: Failed to run command: %s\n", strerror(errno)); return false; } char line[512]; size_t line_num = 0; char file_name[512]; // TODO: use dynamic array instead size_t cursor = 0; result->length = 0; bool collecting_content = false; while (fgets(line, sizeof(line), fp) != NULL) { if (sscanf(line, "# %zu \"%s\"", &line_num, file_name) == 2) { // remove trailing " file_name[strlen(file_name) - 1] = '\0'; if (strcmp(file_name, file_path) == 0) { collecting_content = true; continue; } } else if(collecting_content) { sb_append(result, line); cursor += strlen(line); } } pclose(fp); return true; } bool process_file(const char* input_file) { // read input file String_Builder input_content = {0}; if (!preprocess_file(input_file, &input_content)) return false; // init lexer char string_store[1024] = {0}; 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 size_t struct_infos_count = 0; Parsed_Struct_Info struct_infos[16]; // TODO: use dynamic array while (true) { char* mark = lexer.parse_point; if (!stb_c_lexer_get_token(&lexer)) break; if (lexer.token == CLEX_id && strcmp(lexer.string, "typedef") == 0) { lexer.parse_point = mark; if (parse_struct(&struct_infos[struct_infos_count])) { struct_infos_count += 1; } } } if (!generate_output_file(__FILE__, struct_infos, struct_infos_count)) return false; return true; } #endif // CMETA_COMPTIME #endif // CMETA_H