aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--.vscode/settings.json13
-rw-r--r--src/template.c218
-rw-r--r--tests/test.h1
-rw-r--r--tests/test_template.c30
5 files changed, 201 insertions, 65 deletions
diff --git a/.gitignore b/.gitignore
index 4157400..d19a272 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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