|
// initially generated with https://aistudio.google.com/ |
|
|
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <stdbool.h> |
|
#include <sys/stat.h> |
|
|
|
char const* filename = NULL; |
|
|
|
void print(char c) { |
|
if (c == '\"') printf("\\\""); |
|
else if (c == '\r') printf("\\r"); |
|
else if (c == '\n') printf("\\n"); |
|
else if (c == '\\') printf("\\\\"); |
|
else printf("%c", c); |
|
} |
|
|
|
void process(char const* text) { |
|
char const* cursor = text; |
|
char const* start_tag; |
|
|
|
while ((start_tag = strstr(cursor, "{{")) != NULL) { |
|
// Print text before tag |
|
if (start_tag != cursor) { |
|
printf(" sb_append(sb, \""); |
|
while (cursor < start_tag) { |
|
print(*cursor++); |
|
} |
|
printf("\");\n"); |
|
} |
|
|
|
// Find end of tag |
|
const char* end_tag = strstr(start_tag, "}}"); |
|
if (!end_tag) break; |
|
|
|
// Extract and print code |
|
printf(" "); |
|
const char* code_ptr = start_tag + 2; |
|
while (code_ptr < end_tag) { |
|
printf("%c", *code_ptr++); |
|
} |
|
|
|
cursor = end_tag + 2; |
|
} |
|
|
|
// Print remaining line content |
|
if (*cursor != '\0') { |
|
printf(" sb_append(sb, \""); |
|
while (*cursor) { |
|
print(*cursor++); |
|
} |
|
printf("\");\n"); |
|
} |
|
} |
|
|
|
int main(int argc, char** argv) { |
|
FILE* in; |
|
struct stat st; |
|
char *buffer; |
|
|
|
if (argc == 2) { |
|
filename = argv[1]; |
|
in = fopen(filename, "r"); |
|
if (!in) { |
|
perror("Can't open specified file"); |
|
return EXIT_FAILURE; |
|
} |
|
if (fstat(fileno(in), &st)) { |
|
perror("Can't get file stat"); |
|
return EXIT_FAILURE; |
|
} |
|
buffer = malloc(st.st_size + 1); |
|
if (fread(buffer, 1, st.st_size, in) == 0) { |
|
perror("Can't read specified file"); |
|
return EXIT_FAILURE; |
|
}; |
|
buffer[st.st_size] = '\0'; |
|
} |
|
|
|
printf("#include <string-builder.h>\n\n"); |
|
printf("struct Context;\n\n"); |
|
printf("void render_template(struct Context* ctx, struct StringBuilder* sb);\n\n"); |
|
printf("#ifdef IMPLEMENTATION\n\n"); |
|
printf("void render_template(struct Context* ctx, struct StringBuilder* sb) {\n"); |
|
|
|
process(buffer); |
|
|
|
printf("}\n\n"); |
|
printf("#endif // IMPLEMENTATION\n"); |
|
|
|
free(buffer); |
|
return EXIT_SUCCESS; |
|
} |
WARNING: following is generated by a clanker, though, it is not that bad.
Rule 1: The "Leading Quote" Rule (Format Mode)
If the first non-whitespace character is a double quote (
"), the block is interpreted as Format Mode.ctx->to every token following the first comma that is a valid C identifier and not followed by a function parenthesis.{{ "%d", user->age }}sb_appendf(sb, "%d", ctx->user->age);Rule 2: The "Simple Path" Rule (String Mode)
If the content consists entirely of a single C-style "path" (alphanumerics, underscores,
->, or.) and contains no spaces, commas, or semicolons, it is interpreted as String Mode.sb_appendf(sb, "%s", ctx->[content]);.{{ user->name }}sb_appendf(sb, "%s", ctx->user->name);Rule 3: The "Statement/Logic" Rule (Raw Mode)
If the content does not meet Rule 1 or Rule 2, or if it contains specific C triggers, it is interpreted as Raw Mode.
A block is Raw Mode if it meets any of these criteria:
;).{or}).if,for,while,switch,do,else).=,+=, etc).Summary Table for the Preprocessor
"sb_appendf(sb, content);sb_appendf(sb, "%s", ctx->content);;,{,=, orif/for/whilecontentEdge Case Handling
1. What if I want to print a local variable (not in
ctx)?Since the "String Mode" rule automatically adds
ctx->, you would use Raw Mode syntax to bypass it:ctx:{{ user->name }}ctx->user->name{{ sb_appendf(sb, "%s", my_local); }}(Raw mode because it's a function call).2. What if I want to print a simple integer without a format string?
A simple identifier like
{{ age }}would trigger String Mode and result in"%s", which would crash/warn for an integer.{{ "%d", age }}. Because it starts with a quote, it correctly triggers Rule 1.3. Ambiguity with function calls:
{{ get_name() }}sb_appendf(sb, "%s", get_name());. This is safe and prevents the preprocessor from making wrong assumptions about return types.