diff options
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | .vscode/settings.json | 13 | ||||
-rw-r--r-- | src/template.c | 218 | ||||
-rw-r--r-- | tests/test.h | 1 | ||||
-rw-r--r-- | tests/test_template.c | 30 |
5 files changed, 201 insertions, 65 deletions
@@ -1,3 +1,5 @@ hyde *.dSYM -bin/
\ No newline at end of file +bin/ +.vscode +vgcore.*
\ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 420aa6e..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "files.associations": { - "cstdio": "cpp", - "mpc.h": "c", - "stdio.h": "c", - "typeinfo": "c", - "template.h": "c", - "test.h": "c", - "stdlib.h": "c", - "list": "c", - "hashmap.h": "c" - } -}
\ No newline at end of file diff --git a/src/template.c b/src/template.c index 85db75a..9343ec1 100644 --- a/src/template.c +++ b/src/template.c @@ -40,31 +40,163 @@ char *trim_leading_whitespace(char *str) { return str; } -char *eval_expression(mpc_ast_t* node, struct hashmap *ctx) { - if (strstr(node->tag, "symbol")) { +enum unja_object_type { + OBJ_NULL, + OBJ_INT, + OBJ_STRING, +}; + +struct unja_object { + enum unja_object_type type; + int integer; + char *string; +}; + +struct unja_object _null_object = { + .type = OBJ_NULL, +}; +struct unja_object *null_object = &_null_object; + +struct unja_object *make_string_object(char *value, char *value2) { + struct unja_object *obj = malloc(sizeof *obj); + obj->type = OBJ_STRING; + int l = strlen(value) + 1; + if (value2) { + l += strlen(value2); + } + + obj->string = malloc(l); + strcpy(obj->string, value); + + if(value2) { + strcat(obj->string, value2); + } + return obj; +} + +struct unja_object *make_int_object(int value) { + struct unja_object *obj = malloc(sizeof *obj); + obj->type = OBJ_INT; + obj->integer = value; + return obj; +} + + +void object_to_str(char *dest, struct unja_object *obj) { + char buf[64]; + + switch (obj->type) { + case OBJ_NULL: + break; + case OBJ_STRING: + strcat(dest, obj->string); + break; + case OBJ_INT: + sprintf(buf, "%d", obj->integer); + strcat(dest, buf); + break; + } +} + +int object_to_int(struct unja_object *obj) { + switch (obj->type) { + case OBJ_NULL: return 0; + case OBJ_STRING: return atoi(obj->string); + case OBJ_INT: return obj->integer; + } + + return 0; +} + +void object_free(struct unja_object *obj) { + switch(obj->type) { + case OBJ_NULL: return; + case OBJ_STRING: + free(obj->string); + break; + case OBJ_INT: break; + } + + free(obj); +} + +int object_is_truthy(struct unja_object *obj) { + switch (obj->type) { + case OBJ_NULL: return 0; + case OBJ_STRING: return strlen(obj->string) > 0; + case OBJ_INT: return obj->integer > 0; + } + + return 0; +} + +struct unja_object *eval_expression_value(mpc_ast_t* node, struct hashmap *ctx) { + if (strstr(node->tag, "symbol|")) { /* Return empty string if no context was passed. Should probably signal error here. */ if (ctx == NULL) { - return ""; + return null_object; } char *key = node->contents; + char *value = hashmap_resolve(ctx, key); + /* TODO: Handle unexisting symbols (returns NULL currently) */ - return hashmap_resolve(ctx, key); - } else if(strstr(node->tag, "number")) { - return node->contents; - } else if(strstr(node->tag, "string")) { - return node->children[1]->contents; + if (value == NULL) { + return null_object; + } + return make_string_object(value, NULL); + } else if(strstr(node->tag, "number|")) { + return make_int_object(atoi(node->contents)); + } else if(strstr(node->tag, "string|")) { + return make_string_object(node->children[1]->contents, NULL); } - return NULL; + return null_object; +} + + +struct unja_object *eval_expression(mpc_ast_t* expr, struct hashmap *ctx) { + if (expr->children_num >= 2 && strstr(expr->children[1]->tag, "op")) { + mpc_ast_t *left_node = expr->children[0]; + mpc_ast_t *op = expr->children[1]; + mpc_ast_t *right_node = expr->children[2]; + + struct unja_object *left = eval_expression_value(left_node, ctx); + struct unja_object *right = eval_expression_value(right_node, ctx); + + /* if operator is + and either left or right node is of type string: concat */ + if (op->contents[0] == '+' && (left->type == OBJ_STRING && right->type == OBJ_STRING)) { + struct unja_object *result = make_string_object(left->string, right->string); + object_free(left); + object_free(right); + return result; + } + + /* eval int infix expression */ + int result; + switch (op->contents[0]) { + case '+': result = object_to_int(left) + object_to_int(right); break; + case '-': result = object_to_int(left) - object_to_int(right); break; + case '/': result = object_to_int(left) / object_to_int(right); break; + case '*': result = object_to_int(left) * object_to_int(right); break; + case '>': result = object_to_int(left) > object_to_int(right); break; + case '<': result = object_to_int(left) < object_to_int(right); break; + } + + object_free(left); + object_free(right); + return make_int_object(result); + } + + mpc_ast_t *left_node = expr; + return eval_expression_value(left_node, ctx); } int eval(char *dest, mpc_ast_t* t, struct hashmap *ctx) { static int trim_whitespace = 0; - char buf[64]; - // eval expression - if (strstr(t->tag, "content|expression|")) { + // eval print statement + if (strstr(t->tag, "content|print")) { // maybe eat whitespace going backward if (strstr(t->children[1]->contents, "-")) { dest = trim_trailing_whitespace(dest); @@ -74,36 +206,11 @@ int eval(char *dest, mpc_ast_t* t, struct hashmap *ctx) { if (strstr(t->children[3]->contents, "-")) { trim_whitespace = 1; } - - mpc_ast_t *left_node = t->children[2]; - char *lvalue = eval_expression(left_node, ctx); - - mpc_ast_t *op = t->children[4]; - if (op != NULL && strstr(op->tag, "op")) { - mpc_ast_t *right_node = t->children[6]; - char *rvalue = eval_expression(right_node, ctx); - - /* if operator is + and either left or right node is of type string: concat */ - if (op->contents[0] == '+' && (strstr(left_node->tag, "string") || strstr(right_node->tag, "string"))) { - sprintf(buf, "%s%s", lvalue, rvalue); - } else { - /* eval int infix expression */ - int result; - switch (op->contents[0]) { - case '+': result = atoi(lvalue) + atoi(rvalue); break; - case '-': result = atoi(lvalue) - atoi(rvalue); break; - case '/': result = atoi(lvalue) / atoi(rvalue); break; - case '*': result = atoi(lvalue) * atoi(rvalue); break; - case '>': result = atoi(lvalue) > atoi(rvalue); break; - case '<': result = atoi(lvalue) < atoi(rvalue); break; - } - sprintf(buf, "%d", result); - } - strcat(dest, buf); - return 0; - } - strcat(dest, lvalue); + mpc_ast_t *expr = t->children[2]; + struct unja_object *obj = eval_expression(expr, ctx); + object_to_str(dest, obj); + object_free(obj); return 0; } @@ -118,6 +225,18 @@ int eval(char *dest, mpc_ast_t* t, struct hashmap *ctx) { return 0; } + if (strstr(t->tag, "content|statement|if")) { + mpc_ast_t *expr = t->children[2]; + struct unja_object *result = eval_expression(expr, ctx); + + if (object_is_truthy(result)) { + eval(dest, t->children[4], ctx); + } + + object_free(result); + return 0; + } + if (strstr(t->tag, "content|text")) { char *str = t->contents; if (trim_whitespace) { @@ -142,6 +261,7 @@ mpc_parser_t *parser_init() { 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"); @@ -154,29 +274,31 @@ mpc_parser_t *parser_init() { mpc_parser_t *body = mpc_new("body"); mpc_parser_t *content = mpc_new("content"); mpc_parser_t *template = mpc_new("template"); - mpca_lang(MPCA_LANG_WHITESPACE_SENSITIVE, - " symbol : /[a-zA-Z_.]+/ ;" + 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> ))? / *-?/ \"}}\" ;" + " 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> ;" + " for : <statement_open> \"for \" <symbol> \"in\" <symbol> <statement_close> <body> <statement_open> \"endfor\" <statement_close> ;" " block : <statement_open> \"block \" <statement_close> <statement_open> \"endblock\" <statement_close>;" " extends : <statement_open> \"extends \" <statement_close>;" /* TODO: Extend parser to include expression */ - " if : <statement_open> \"if \" <statement_close> <body> <statement_open> \"endif\" <statement_close> ;" + " if : <statement_open> \"if \" <expression> <statement_close> <body> <statement_open> \"endif\" <statement_close> ;" " statement : <for> | <block> | <extends> | <if> ;" - " content : <expression> | <statement> | <text> | <comment>;" + " content : <print> | <statement> | <text> | <comment>;" " body : <content>* ;" " template : /^/ <body> /$/ ;", symbol, op, number, string, + print, expression, text, comment, @@ -212,7 +334,7 @@ char *template(char *tmpl, struct hashmap *ctx) { #endif // FIXME: Allocate precisely - char *output = malloc(strlen(tmpl) * 2); + char *output = malloc(strlen(tmpl) * 4); output[0] = '\0'; eval(output, r.output, ctx); diff --git a/tests/test.h b/tests/test.h index 7a82d08..dec4ba4 100644 --- a/tests/test.h +++ b/tests/test.h @@ -7,7 +7,6 @@ #define END_TESTS } #define TEST(name) strcpy(current_test, #name); #define assert_null(actual) _assert(actual == NULL, __FILE__, __LINE__, "invalid value: expected NULL, got %s", actual) - #define assert_str(actual, expected) _assert(actual != NULL && strcmp(actual, expected) == 0, __FILE__, __LINE__, "invalid string: expected %s, got %s", expected, actual) #define assert(assertion, format, ...) _assert(assertion, __FILE__, __LINE__, format, ##__VA_ARGS__) #define ARRAY_SIZE(arr) sizeof arr / sizeof arr[0] diff --git a/tests/test_template.c b/tests/test_template.c index 61a027f..329075e 100644 --- a/tests/test_template.c +++ b/tests/test_template.c @@ -145,10 +145,36 @@ TEST(var_dot_notation) { } TEST(comments) { - char *input = "Hello {# comment here #} world."; + char *input = "Hello {# comment here #}world."; char *output = template(input, NULL); - assert_str(output, "Hello world."); + assert_str(output, "Hello world."); free(output); } + +TEST(if_block) { + + struct { + char *input; + char *expected_output; + } tests[] = { + {"{% if 5 > 10 %}OK{% endif %}.", "."}, + {"{% if 10 > 5 %}OK{% endif %}.", "OK."}, + {"{% if foobar %}OK{% endif %}.", "."}, + {"{% if name %}OK{% endif %}.", "OK."}, + {"{% if age > 10 %}OK{% endif %}.", "OK."}, + }; + + struct hashmap *ctx = hashmap_new(); + 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); + assert_str(output, tests[i].expected_output); + free(output); + } + + hashmap_free(ctx); +} + END_TESTS |