aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDanny van Kooten <dannyvankooten@users.noreply.github.com>2020-03-15 20:49:20 +0100
committerDanny van Kooten <dannyvankooten@users.noreply.github.com>2020-03-15 20:49:20 +0100
commit8fcc38ce404f16b2809a73de7bdf778bb84becb4 (patch)
treeeb3da105ae8dfb4df697e972b95d0b10abd134e9
parent74a547aa407ff8555f40dc38b18dee0bdb708836 (diff)
downloadunja-8fcc38ce404f16b2809a73de7bdf778bb84becb4.tar.gz
unja-8fcc38ce404f16b2809a73de7bdf778bb84becb4.zip
add basic infix expressions
-rw-r--r--src/template.c67
-rw-r--r--tests/test.h1
-rw-r--r--tests/test_template.c75
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 : \"{{\" /-? */ (<symbol> | <number> | <string> ) / *-?/ \"}}\" ;"
+ " expression : \"{{\" /-? */ (<symbol> | <number> | <string> ) (/ */ <op> / */ (<symbol> | <number> | <string> ))? / *-?/ \"}}\" ;"
" comment : \"{#\" /[^#][^#}]*/ \"#}\" ;"
" statement_open: \"{%\" /-? */;"
" statement_close: / *-?/ \"%}\";"
@@ -138,6 +174,7 @@ mpc_parser_t *parser_init() {
" body : <content>* ;"
" template : /^/ <body> /$/ ;",
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);
}