diff options
-rw-r--r-- | src/template.c | 232 | ||||
-rw-r--r-- | src/template.h | 6 | ||||
-rw-r--r-- | tests/test_template.c | 40 |
3 files changed, 161 insertions, 117 deletions
diff --git a/src/template.c b/src/template.c index c43827a..d3066c3 100644 --- a/src/template.c +++ b/src/template.c @@ -32,6 +32,13 @@ struct env { struct hashmap *templates; }; +struct template { + char *name; + mpc_ast_t *ast; + struct hashmap *blocks; + char *parent; +}; + /* ensure buffer has room for a string sized l, grows buffer capacity if needed */ void buffer_reserve(struct buffer *buf, int l) { int req_size = buf->size + l; @@ -47,6 +54,96 @@ void buffer_reserve(struct buffer *buf, int l) { } } + +mpc_parser_t *parser_init() { + static mpc_parser_t *template; + if (template != NULL) { + return template; + } + + mpc_parser_t *symbol = mpc_new("symbol"); + mpc_parser_t *number = mpc_new("number"); + mpc_parser_t *string = mpc_new("string"); + mpc_parser_t *op = mpc_new("op"); + mpc_parser_t *text = mpc_new("text"); + mpc_parser_t *print = mpc_new("print"); + mpc_parser_t *expression = mpc_new("expression"); + mpc_parser_t *comment = mpc_new("comment"); + mpc_parser_t *statement = mpc_new("statement"); + mpc_parser_t *statement_open = mpc_new("statement_open"); + mpc_parser_t *statement_close = mpc_new("statement_close"); + mpc_parser_t *statement_for = mpc_new("for"); + mpc_parser_t *statement_if = mpc_new("if"); + mpc_parser_t *statement_block = mpc_new("block"); + mpc_parser_t *statement_extends = mpc_new("extends"); + mpc_parser_t *body = mpc_new("body"); + mpc_parser_t *content = mpc_new("content"); + template = mpc_new("template"); + mpca_lang(MPCA_LANG_DEFAULT, + " symbol : /[a-zA-Z][a-zA-Z0-9_.]*/ ;" + " number : /[0-9]+/ ;" + " string : '\"' /([^\"])*/ '\"' ;" + " op : '+' | '-' | '*' | '/' | '>' | '<';" + " text : /[^{][^{%#]*/ ;" + " expression: (<symbol> | <number> | <string>) (<op> (<symbol> | <number> | <string>))* ;" + " print : \"{{\" /-? */ <expression> / *-?/ \"}}\" ;" + " comment : \"{#\" /[^#][^#}]*/ \"#}\" ;" + " statement_open: \"{%\" /-? */;" + " statement_close: / *-?/ \"%}\";" + " for : <statement_open> \"for \" <symbol> \"in\" <symbol> <statement_close> <body> <statement_open> \"endfor\" <statement_close> ;" + " block : <statement_open> \"block \" <symbol> <statement_close> <body> <statement_open> \"endblock\" <statement_close>;" + " extends : <statement_open> \"extends \" <string> <statement_close>;" + " if : <statement_open> \"if \" <expression> <statement_close> <body> (<statement_open> \"else\" <statement_close> <body>)? <statement_open> \"endif\" <statement_close> ;" + " statement : <for> | <block> | <extends> | <if> ;" + " content : <print> | <statement> | <text> | <comment>;" + " body : <content>* ;" + " template : /^/ <body> /$/ ;", + symbol, + op, + number, + string, + print, + expression, + text, + comment, + statement_open, + statement_close, + statement, + statement_if, + statement_for, + statement_block, + statement_extends, + content, + body, + template, + NULL); + return template; +} + +mpc_ast_t *parse(char *tmpl) { + mpc_parser_t *parser = parser_init(); + mpc_result_t r; + + if (!mpc_parse("input", tmpl, parser, &r)) { + mpc_err_print(r.error); + mpc_err_delete(r.error); + return NULL; + } + + return r.output; +} + +struct hashmap *find_blocks_in_ast(mpc_ast_t *node, struct hashmap *map) { + if (strstr(node->tag, "content|statement|block")) { + char *name = node->children[2]->contents; + hashmap_insert(map, name, node); + } + + for (int i=0; i < node->children_num; i++) { + find_blocks_in_ast(node->children[i], map); + } +} + struct env *env_new(char *dirname) { DIR *dr = opendir(dirname); if (dr == NULL) { @@ -66,7 +163,19 @@ struct env *env_new(char *dirname) { char *name = de->d_name; char *tmpl = read_file(name); - hashmap_insert(env->templates, name, tmpl); + mpc_ast_t *ast = parse(tmpl); + + struct template *t = malloc(sizeof *t); + t->ast = ast; + t->name = name; + t->blocks = find_blocks_in_ast(ast, hashmap_new()); + t->parent = NULL; + + if (ast->children_num > 1 && ast->children[1]->children_num > 0 && strstr(ast->children[1]->children[0]->tag, "content|statement|extends")) { + t->parent = ast->children[1]->children[0]->children[2]->children[1]->contents; + } + + hashmap_insert(env->templates, name, t); } closedir(dr); @@ -326,110 +435,45 @@ int eval(struct buffer *buf, mpc_ast_t* t, struct hashmap *ctx) { return 0; } - - if (strstr(t->tag, ">")) { - for (int i=0; i < t->children_num; i++) { - eval(buf, t->children[i], ctx); - } - } - - return 0; -} - -mpc_parser_t *parser_init() { - static mpc_parser_t *template; - if (template != NULL) { - return template; + for (int i=0; i < t->children_num; i++) { + eval(buf, t->children[i], ctx); } - mpc_parser_t *symbol = mpc_new("symbol"); - mpc_parser_t *number = mpc_new("number"); - mpc_parser_t *string = mpc_new("string"); - mpc_parser_t *op = mpc_new("op"); - mpc_parser_t *text = mpc_new("text"); - mpc_parser_t *print = mpc_new("print"); - mpc_parser_t *expression = mpc_new("expression"); - mpc_parser_t *comment = mpc_new("comment"); - mpc_parser_t *statement = mpc_new("statement"); - mpc_parser_t *statement_open = mpc_new("statement_open"); - mpc_parser_t *statement_close = mpc_new("statement_close"); - mpc_parser_t *statement_for = mpc_new("for"); - mpc_parser_t *statement_if = mpc_new("if"); - mpc_parser_t *statement_block = mpc_new("block"); - mpc_parser_t *statement_extends = mpc_new("extends"); - mpc_parser_t *body = mpc_new("body"); - mpc_parser_t *content = mpc_new("content"); - template = mpc_new("template"); - mpca_lang(MPCA_LANG_DEFAULT, - " symbol : /[a-zA-Z][a-zA-Z0-9_.]*/ ;" - " number : /[0-9]+/ ;" - " string : '\"' /([^\"])*/ '\"' ;" - " op : '+' | '-' | '*' | '/' | '>' | '<';" - " text : /[^{][^{%#]*/ ;" - " expression: (<symbol> | <number> | <string>) (<op> (<symbol> | <number> | <string>))* ;" - " print : \"{{\" /-? */ <expression> / *-?/ \"}}\" ;" - " comment : \"{#\" /[^#][^#}]*/ \"#}\" ;" - " statement_open: \"{%\" /-? */;" - " statement_close: / *-?/ \"%}\";" - " for : <statement_open> \"for \" <symbol> \"in\" <symbol> <statement_close> <body> <statement_open> \"endfor\" <statement_close> ;" - " block : <statement_open> \"block \" <symbol> <statement_close> <body> <statement_open> \"endblock\" <statement_close>;" - " extends : <statement_open> \"extends \" <string> <statement_close>;" - " if : <statement_open> \"if \" <expression> <statement_close> <body> (<statement_open> \"else\" <statement_close> <body>)? <statement_open> \"endif\" <statement_close> ;" - " statement : <for> | <block> | <extends> | <if> ;" - " content : <print> | <statement> | <text> | <comment>;" - " body : <content>* ;" - " template : /^/ <body> /$/ ;", - symbol, - op, - number, - string, - print, - expression, - text, - comment, - statement_open, - statement_close, - statement, - statement_if, - statement_for, - statement_block, - statement_extends, - content, - body, - template, - NULL); - return template; + return 0; } -char *template(char *tmpl, struct hashmap *ctx) { - mpc_parser_t *parser = parser_init(); - mpc_result_t r; - - if (!mpc_parse("input", tmpl, parser, &r)) { - mpc_err_print(r.error); - mpc_err_delete(r.error); - return NULL; - } - +char *render_ast(mpc_ast_t *ast, struct hashmap *ctx) { #if DEBUG - printf("Template: %s\n", tmpl); - printf("AST: "); - mpc_ast_print(r.output); + printf("AST: \n"); + mpc_ast_print(ast); printf("\n"); #endif - + struct buffer buf; buf.size = 0; - buf.cap = strlen(tmpl) + 1; - buf.string = malloc(buf.size); + buf.cap = 256; + buf.string = malloc(buf.cap); buf.string[0] = '\0'; - eval(&buf, r.output, ctx); - - mpc_ast_delete(r.output); + eval(&buf, ast, ctx); return buf.string; } -char *render(struct env *env, char *template_name, struct hashmap *ctx) { - char *tmpl = hashmap_get(env->templates, template_name); - return template(tmpl, ctx); +char *template_string(char *tmpl, struct hashmap *ctx) { + #if DEBUG + printf("Template: %s\n", tmpl); + #endif + mpc_ast_t *ast = parse(tmpl); + char *output = render_ast(ast, ctx); + mpc_ast_delete(ast); + return output; +} + +char *template(struct env *env, char *template_name, struct hashmap *ctx) { + struct template *t = hashmap_get(env->templates, template_name); + + #if DEBUG + printf("Template name: %s\n", t->name); + printf("Parent: %s\n", t->parent ? t->parent : "None"); + #endif + return render_ast(t->ast, ctx); }
\ No newline at end of file diff --git a/src/template.h b/src/template.h index f65da47..45a58fc 100644 --- a/src/template.h +++ b/src/template.h @@ -1,10 +1,10 @@ #include "hashmap.h" #include "vector.h" -char *read_file(char *filename); -char *template(char *tmpl, struct hashmap *ctx); struct env; struct env *env_new(); void env_free(struct env *env); -char *render(struct env *env, char *template_name, struct hashmap *ctx);
\ No newline at end of file +char *template(struct env *env, char *template_name, struct hashmap *ctx); +char *template_string(char *tmpl, struct hashmap *ctx); +char *read_file(char *filename);
\ No newline at end of file diff --git a/tests/test_template.c b/tests/test_template.c index cc57ba5..d4f4e7f 100644 --- a/tests/test_template.c +++ b/tests/test_template.c @@ -5,21 +5,21 @@ START_TESTS TEST(text_only) { char *input = "Hello world."; - char *output = template(input, NULL); + char *output = template_string(input, NULL); assert_str(output, "Hello world."); free(output); } TEST(expr_number) { char *input = "Hello {{ 5 }}."; - char *output = template(input, NULL); + char *output = template_string(input, NULL); assert_str(output, "Hello 5."); free(output); } TEST(expr_string) { char *input = "Hello {{ \"world\" }}."; - char *output = template(input, NULL); + char *output = template_string(input, NULL); assert_str(output, "Hello world."); free(output); } @@ -28,7 +28,7 @@ TEST(expr_symbol) { char *input = "Hello {{name}}."; struct hashmap *ctx = hashmap_new(); hashmap_insert(ctx, "name", "world"); - char *output = template(input, ctx); + char *output = template_string(input, ctx); assert_str(output, "Hello world."); hashmap_free(ctx); free(output); @@ -50,7 +50,7 @@ TEST(expr_add) { hashmap_insert(ctx, "name", "Danny"); for (int i=0; i < ARRAY_SIZE(tests); i++) { - char *output = template(tests[i].input, ctx); + char *output = template_string(tests[i].input, ctx); assert_str(output, tests[i].expected_output); free(output); } @@ -60,45 +60,45 @@ TEST(expr_add) { TEST(expr_subtract) { char *input = "Hello {{ 5 - 5 }}."; - char *output = template(input, NULL); + char *output = template_string(input, NULL); assert_str(output, "Hello 0."); free(output); } TEST(expr_divide) { char *input = "Hello {{ 5 / 5 }}."; - char *output = template(input, NULL); + char *output = template_string(input, NULL); assert_str(output, "Hello 1."); free(output); } TEST(expr_multiply) { char *input = "Hello {{ 5 * 5 }}."; - char *output = template(input, NULL); + char *output = template_string(input, NULL); assert_str(output, "Hello 25."); free(output); } TEST(expr_gt) { char *input = "Hello {{ 5 > 4 }}."; - char *output = template(input, NULL); + char *output = template_string(input, NULL); assert_str(output, "Hello 1."); free(output); input = "Hello {{ 5 > 6 }}."; - output = template(input, NULL); + output = template_string(input, NULL); assert_str(output, "Hello 0."); free(output); } TEST(expr_lt) { char *input = "Hello {{ 5 < 4 }}."; - char *output = template(input, NULL); + char *output = template_string(input, NULL); assert_str(output, "Hello 0."); free(output); input = "Hello {{ 4 < 5 }}."; - output = template(input, NULL); + output = template_string(input, NULL); assert_str(output, "Hello 1."); free(output); } @@ -107,7 +107,7 @@ TEST(expr_whitespace) { char *input = "Hello \n{{-name -}}\n."; struct hashmap *ctx = hashmap_new(); hashmap_insert(ctx, "name", "world"); - char *output = template(input, ctx); + char *output = template_string(input, ctx); assert_str(output, "Helloworld."); hashmap_free(ctx); free(output); @@ -123,7 +123,7 @@ TEST(for_block) { vector_push(names, "Eric"); hashmap_insert(ctx, "names", names); - char *output = template(input, ctx); + char *output = template_string(input, ctx); assert_str(output, "John, Sally, Eric, "); vector_free(names); hashmap_free(ctx); @@ -138,7 +138,7 @@ TEST(var_dot_notation) { struct hashmap *ctx = hashmap_new(); hashmap_insert(ctx, "user", user); - char *output = template(input, ctx); + char *output = template_string(input, ctx); assert_str(output, "Hello Danny!"); hashmap_free(ctx); free(output); @@ -146,7 +146,7 @@ TEST(var_dot_notation) { TEST(comments) { char *input = "Hello {# comment here #}world."; - char *output = template(input, NULL); + char *output = template_string(input, NULL); assert_str(output, "Hello world."); free(output); } @@ -168,7 +168,7 @@ TEST(if_block) { hashmap_insert(ctx, "name", "Danny"); hashmap_insert(ctx, "age", "29"); for (int i=0; i < ARRAY_SIZE(tests); i++) { - char *output = template(tests[i].input, ctx); + char *output = template_string(tests[i].input, ctx); assert_str(output, tests[i].expected_output); free(output); } @@ -193,7 +193,7 @@ TEST(if_else_block) { hashmap_insert(ctx, "name", "Danny"); hashmap_insert(ctx, "age", "29"); for (int i=0; i < ARRAY_SIZE(tests); i++) { - char *output = template(tests[i].input, ctx); + char *output = template_string(tests[i].input, ctx); assert_str(output, tests[i].expected_output); free(output); } @@ -209,7 +209,7 @@ TEST(buffer_alloc) { char *text = "Lorem ipsum dolor sit amet."; hashmap_insert(ctx, "n", text); - char *output = template(input, ctx); + char *output = template_string(input, ctx); assert_str(output, text); hashmap_free(ctx); free(output); @@ -217,7 +217,7 @@ TEST(buffer_alloc) { TEST(directory) { struct env *env = env_new("./tests/data/01/"); - char *output = render(env, "child.tmpl", NULL); + char *output = template(env, "child.tmpl", NULL); assert_str(output, "Header\n\nChild content\n\nFooter"); free(output); env_free(env); |