From 8fcc38ce404f16b2809a73de7bdf778bb84becb4 Mon Sep 17 00:00:00 2001 From: Danny van Kooten Date: Sun, 15 Mar 2020 20:49:20 +0100 Subject: add basic infix expressions --- src/template.c | 67 ++++++++++++++++++++++++++++++++++----------- tests/test.h | 1 + tests/test_template.c | 75 +++++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 120 insertions(+), 23 deletions(-) diff --git a/src/template.c b/src/template.c index a5486df..15ce246 100644 --- a/src/template.c +++ b/src/template.c @@ -40,9 +40,30 @@ char *trim_leading_whitespace(char *str) { return str; } +char *eval_expression(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 ""; + } + + char *key = node->contents; + /* 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; + } + + return NULL; +} + 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|")) { // maybe eat whitespace going backward if (strstr(t->children[1]->contents, "-")) { @@ -54,22 +75,35 @@ int eval(char *dest, mpc_ast_t* t, struct hashmap *ctx) { trim_whitespace = 1; } - char *value = NULL; - if (ctx != NULL && strstr(t->children[2]->tag, "symbol")) { - char *key = t->children[2]->contents; - value = hashmap_resolve(ctx, key); - - // TODO: Handle unexisting keys - if (value == NULL) { - return 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); } - } else if(strstr(t->children[2]->tag, "number")) { - value = t->children[2]->contents; - } else if(strstr(t->children[2]->tag, "string")) { - value = t->children[2]->children[1]->contents; + strcat(dest, buf); + return 0; } - - strcat(dest, value); + + strcat(dest, lvalue); return 0; } @@ -106,6 +140,7 @@ mpc_parser_t *parser_init() { 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 *expression = mpc_new("expression"); mpc_parser_t *comment = mpc_new("comment"); @@ -123,8 +158,9 @@ mpc_parser_t *parser_init() { " symbol : /[a-zA-Z_.]+/ ;" " number : /[0-9]+/ ;" " string : '\"' /([^\"])*/ '\"' ;" + " op : '+' | '-' | '*' | '/' | '>' | '<';" " text : /[^{][^{%#]*/ ;" - " expression : \"{{\" /-? */ ( | | ) / *-?/ \"}}\" ;" + " expression : \"{{\" /-? */ ( | | ) (/ */ / */ ( | | ))? / *-?/ \"}}\" ;" " comment : \"{#\" /[^#][^#}]*/ \"#}\" ;" " statement_open: \"{%\" /-? */;" " statement_close: / *-?/ \"%}\";" @@ -138,6 +174,7 @@ mpc_parser_t *parser_init() { " body : * ;" " template : /^/ /$/ ;", symbol, + op, number, string, expression, diff --git a/tests/test.h b/tests/test.h index 5c76448..f860ee2 100644 --- a/tests/test.h +++ b/tests/test.h @@ -8,6 +8,7 @@ #define TEST(name) strcpy(current_test, #name); #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] /* used to store the running test name */ char current_test[256] = {'\0'}; diff --git a/tests/test_template.c b/tests/test_template.c index 5c17dd4..61a027f 100644 --- a/tests/test_template.c +++ b/tests/test_template.c @@ -34,22 +34,81 @@ TEST(expr_symbol) { free(output); } -TEST(var_whitespace) { - char *input = "Hello \n{{-name -}}\n."; +TEST(expr_add) { + struct { + char *input; + char *expected_output; + } tests[] = { + {"{{ 5 + 5 }}.", "10."}, + {"{{ 5 + foo }}.", "15."}, + {"{{ \"foo\" + \"bar\" }}", "foobar"}, + {"{{ \"Hello \" + name }}", "Hello Danny"}, + }; + struct hashmap *ctx = hashmap_new(); - hashmap_insert(ctx, "name", "world"); - char *output = template(input, ctx); - assert_str(output, "Helloworld."); + hashmap_insert(ctx, "foo", "10"); + hashmap_insert(ctx, "name", "Danny"); + + 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); +} + +TEST(expr_subtract) { + char *input = "Hello {{ 5 - 5 }}."; + char *output = template(input, NULL); + assert_str(output, "Hello 0."); + free(output); +} + +TEST(expr_divide) { + char *input = "Hello {{ 5 / 5 }}."; + char *output = template(input, NULL); + assert_str(output, "Hello 1."); + free(output); +} + +TEST(expr_multiply) { + char *input = "Hello {{ 5 * 5 }}."; + char *output = template(input, NULL); + assert_str(output, "Hello 25."); + free(output); +} + +TEST(expr_gt) { + char *input = "Hello {{ 5 > 4 }}."; + char *output = template(input, NULL); + assert_str(output, "Hello 1."); + free(output); + + input = "Hello {{ 5 > 6 }}."; + output = template(input, NULL); + assert_str(output, "Hello 0."); free(output); } -TEST(multiline) { - char *input = "Hello {{name}}.\nL2"; +TEST(expr_lt) { + char *input = "Hello {{ 5 < 4 }}."; + char *output = template(input, NULL); + assert_str(output, "Hello 0."); + free(output); + + input = "Hello {{ 4 < 5 }}."; + output = template(input, NULL); + assert_str(output, "Hello 1."); + free(output); +} + +TEST(expr_whitespace) { + char *input = "Hello \n{{-name -}}\n."; struct hashmap *ctx = hashmap_new(); hashmap_insert(ctx, "name", "world"); char *output = template(input, ctx); - assert_str(output, "Hello world.\nL2"); + assert_str(output, "Helloworld."); hashmap_free(ctx); free(output); } -- cgit v1.2.3