#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 // // 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 { Parsed_Field_Info* items; size_t count; size_t capacity; } Parsed_Field_Infos; typedef struct { char* name; size_t fields_count; Parsed_Field_Info *fields; } Parsed_Struct_Info; typedef struct { Parsed_Struct_Info* items; size_t count; size_t capacity; } Parsed_Struct_Infos; 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'; } #define da_append(da, item) \ do { \ if ((da)->count + 1 > (da)->capacity) { \ size_t new_capacity = (da)->capacity == 0 ? 64 : (da)->capacity * 2; \ while ((da)->count + 1 > new_capacity) { \ new_capacity *= 2; \ } \ (da)->items = realloc((da)->items, new_capacity * sizeof(*(da)->items)); \ } \ (da)->items[(da)->count++] = (item); \ } while (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; Parsed_Field_Infos fields = {0}; 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); Parsed_Field_Info field = { .type = field_type, .name = field_name, }; da_append(&fields, field); 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(info->fields_count, sizeof(Parsed_Field_Info)); for(size_t i = 0; i < info->fields_count; i += 1) { info->fields[i].type = fields.items[i].type; info->fields[i].name = fields.items[i].name; } result = true; fail: if(!result) { free(name); for(size_t i = 0; i < fields.count; i += 1) { free(fields.items[i].name); free(fields.items[i].type); } free(fields.items); } 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_Infos struct_infos) { 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.items[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]; 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 Parsed_Struct_Infos struct_infos = {0}; 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; Parsed_Struct_Info struct_info = {0}; if (parse_struct(&struct_info)) { da_append(&struct_infos, struct_info); } } } if (!generate_output_file(__FILE__, struct_infos)) return false; return true; } #endif // CMETA_COMPTIME #endif // CMETA_H