Compare commits
1 Commits
788a5a1f6f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 37f32fa280 |
40
DOCS.md
Normal file
40
DOCS.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# Docs
|
||||||
|
|
||||||
|
This file is meant to explain how it's working. I'll try to keep it up to date.
|
||||||
|
|
||||||
|
## General idea
|
||||||
|
|
||||||
|
Meta programming is really useful, in a lots of ways, that's why I'm trying to implement this in C, in the most pleasant way.
|
||||||
|
Header-only libraries are also a must imo, so let's try to implement meta programming within a single header! The same header for both compilation and runtime btw.
|
||||||
|
|
||||||
|
## Compilation time
|
||||||
|
|
||||||
|
`cmeta.h` contains the code to parse structs, lexing is handled by `stb_c_lexer` (for now).
|
||||||
|
The `bool process_file(const char *file_path)` function calls `gcc -E <file_path>` (so we handle macros etc), then parses the result. It then generates all `Struct_Info`s in a special place in the header, delimited by two `// AUTO GENERATED CODE //`.
|
||||||
|
If anything has failed, nothing will be generated between the two comments, so cmeta remains intact.
|
||||||
|
|
||||||
|
This struct:
|
||||||
|
```c
|
||||||
|
typedef struct {
|
||||||
|
int int_field;
|
||||||
|
} Foo_Struct;
|
||||||
|
```
|
||||||
|
|
||||||
|
Will be converted to this:
|
||||||
|
```c
|
||||||
|
// cmeta.h
|
||||||
|
|
||||||
|
// AUTO GENERATED CODE //
|
||||||
|
static Struct_Info foo_struct_info = {
|
||||||
|
.name = "Foo_Struct",
|
||||||
|
.fields_count = 1,
|
||||||
|
.fields = (Field_Info[1]) {
|
||||||
|
{ .type = "int", .name = "int_field" },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// AUTO GENERATED CODE //
|
||||||
|
```
|
||||||
|
|
||||||
|
## Runtime
|
||||||
|
|
||||||
|
`cmeta.h` now contains both type infos and useful functions to interact with them.
|
||||||
8
example/01_simple_struct/Makefile
Normal file
8
example/01_simple_struct/Makefile
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
all: cmeta main
|
||||||
|
|
||||||
|
cmeta: main.c cmeta.h cmeta.c
|
||||||
|
gcc cmeta.c -I../../third_party/ -o cmeta
|
||||||
|
|
||||||
|
main: cmeta.h main.c
|
||||||
|
./cmeta
|
||||||
|
gcc main.c -o main
|
||||||
8
example/01_simple_struct/cmeta.c
Normal file
8
example/01_simple_struct/cmeta.c
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#define CMETA_COMPTIME
|
||||||
|
#include "cmeta.h"
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
if (!process_file("./main.c")) return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
413
example/01_simple_struct/cmeta.h
Normal file
413
example/01_simple_struct/cmeta.h
Normal file
@@ -0,0 +1,413 @@
|
|||||||
|
#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 //
|
||||||
|
// AUTO GENERATED CODE //
|
||||||
|
|
||||||
|
#ifdef CMETA_COMPTIME
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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';
|
||||||
|
}
|
||||||
|
|
||||||
|
void sb_append_ch(String_Builder* sb, char ch) {
|
||||||
|
char buf[2] = {ch, '\0'};
|
||||||
|
sb_append(sb, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
#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};
|
||||||
|
String_Builder field = {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;
|
||||||
|
|
||||||
|
field.length = 0;
|
||||||
|
while (stb_c_lexer_get_token(&lexer) && lexer.token != ';') {
|
||||||
|
if (lexer.token <= 255) {
|
||||||
|
// TODO: parse arrays
|
||||||
|
if(lexer.token == '[') goto fail;
|
||||||
|
|
||||||
|
sb_append_ch(&field, (char)lexer.token);
|
||||||
|
sb_append_ch(&field, ' ');
|
||||||
|
} else {
|
||||||
|
// TODO: parse unions
|
||||||
|
if(strcmp(lexer.string, "union") == 0) goto fail;
|
||||||
|
|
||||||
|
// TODO: parse attributes
|
||||||
|
sb_append(&field, lexer.string);
|
||||||
|
sb_append_ch(&field, ' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
field.data[field.length - 1] = '\0';
|
||||||
|
|
||||||
|
char* last_space = strrchr(field.data, ' ');
|
||||||
|
char* field_name = strdup(last_space + 1);
|
||||||
|
|
||||||
|
field.data[last_space - field.data] = '\0';
|
||||||
|
|
||||||
|
char* field_type = strdup(field.data);
|
||||||
|
|
||||||
|
da_append(&fields, ((Parsed_Field_Info) {
|
||||||
|
.type = field_type,
|
||||||
|
.name = field_name,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
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:
|
||||||
|
free(field.data);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: read original file at line_num, to check for comments (e.g annotations)
|
||||||
|
} 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
|
||||||
19
example/01_simple_struct/main.c
Normal file
19
example/01_simple_struct/main.c
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include "cmeta.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int int_field;
|
||||||
|
} Foo_Struct;
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
printf("Foo_Struct infos:\n");
|
||||||
|
printf(" name = %s\n", foo_struct_info.name);
|
||||||
|
printf(" fields_count = %zu\n", foo_struct_info.fields_count);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < foo_struct_info.fields_count; i += 1) {
|
||||||
|
Field_Info field = foo_struct_info.fields[i];
|
||||||
|
printf(" .%s: %s\n", field.name, field.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user