From 9f4d4ab24e7a539621bcd810b84201b0127c0870 Mon Sep 17 00:00:00 2001 From: Danny van Kooten Date: Fri, 20 Mar 2020 14:01:49 +0100 Subject: allow negating singular terms & add loop variables --- src/template.c | 39 +++++++++++++++++++++++++++++++++++---- tests/test_template.c | 28 +++++++++++++++++++++++----- 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/src/template.c b/src/template.c index a1439c4..e8fcf36 100644 --- a/src/template.c +++ b/src/template.c @@ -90,7 +90,7 @@ mpc_parser_t *parser_init() { " number : /[0-9]+/ ;" " text : /[^{][^{%#]*/;" " string : '\"' /([^\"])*/ '\"' ;" - " factor : '(' ')' | | | ;" + " factor : | | ;" " term : ( ('*' | '/' | '%') )* ;" " lexp : ( ('+' | '-') )* ;" " expression: '>' " @@ -99,6 +99,7 @@ mpc_parser_t *parser_init() { " | \"<=\" " " | \"!=\" " " | \"==\" " + " | \"not\" " " | ;" " print : /{{2}-? */ / *-?}}/ ;" " comment : \"{#\" /[^#][^#}]*/ \"#}\" ;" @@ -329,7 +330,7 @@ void object_free(struct unja_object *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_STRING: return strlen(obj->string) > 0 && strcmp(obj->string, "0") != 0; case OBJ_INT: return obj->integer > 0; } @@ -356,6 +357,7 @@ struct unja_object *eval_expression_value(mpc_ast_t* node, struct context *ctx) 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)); @@ -434,9 +436,18 @@ struct unja_object *eval_expression(mpc_ast_t* expr, struct context *ctx) { return eval_expression_value(expr, ctx); } - /* otherwise: with operator */ - int offset = 0; struct unja_object *result; + + /* singular negated term */ + if (strcmp(expr->children[0]->contents, "not") == 0) { + result = eval_expression_value(expr->children[2], ctx); + struct unja_object *negated = make_int_object(!object_to_int(result)); + object_free(result); + return negated; + } + + /* otherwise: with operator */ + unsigned int offset = 0; mpc_ast_t *left_node = expr->children[0]; struct unja_object *left = eval_expression(left_node, ctx); @@ -511,12 +522,32 @@ int eval(struct buffer *buf, mpc_ast_t* t, struct context *ctx) { char *tmp_key = t->children[2]->contents; char *iterator_key = t->children[4]->contents; struct vector *list = hashmap_resolve(ctx->vars, iterator_key); + + /* add "loop" variable to context */ + struct hashmap *loop = hashmap_new(); + char index[8], first[2], last[2]; + hashmap_insert(loop, "index", index); + hashmap_insert(loop, "first", first); + hashmap_insert(loop, "last", last); + hashmap_insert(ctx->vars, "loop", loop); + + /* loop over values in vector */ for (int i=0; i < list->size; i++) { + /* set loop variable values */ + sprintf(index, "%d", i); + sprintf(first, "%d", i == 0); + sprintf(last, "%d", i == (list->size - 1)); hashmap_insert(ctx->vars, tmp_key, list->values[i]); trim_whitespace = strstr(t->children[5]->contents, "-") ? 1 : 0; + + /* evaluate body */ eval(buf, t->children[6], ctx); } + /* remove "loop" variable from context */ + hashmap_remove(ctx->vars, "loop"); + hashmap_free(loop); + /* trim trailing whitespace if closing tag has minus sign */ if (strstr(t->children[7]->contents, "-")) { buf->string = trim_trailing_whitespace(buf->string); diff --git a/tests/test_template.c b/tests/test_template.c index d94ddd6..69ff7fe 100644 --- a/tests/test_template.c +++ b/tests/test_template.c @@ -3,7 +3,7 @@ START_TESTS -TEST(text_only) { +TEST(textvc_only) { char *input = "Hello world."; char *output = template_string(input, NULL); assert_str(output, "Hello world."); @@ -89,7 +89,6 @@ TEST(expr_add) { hashmap_free(ctx); } - TEST(expr_op_precedence) { struct { char *input; @@ -162,6 +161,28 @@ TEST(for_block) { free(output); } + +TEST(for_block_vars) { + char *input = "{% for n in names %}" + "{{loop.index + 1}}: {{ n }}" + "{% if loop.first %} <--{% endif %}" + "{% if not loop.last %}\n{% endif %}" + "{% endfor %}"; + struct hashmap *ctx = hashmap_new(); + + struct vector *names = vector_new(9); + vector_push(names, "John"); + vector_push(names, "Sally"); + vector_push(names, "Eric"); + hashmap_insert(ctx, "names", names); + + char *output = template_string(input, ctx); + assert_str(output, "1: John <--\n2: Sally\n3: Eric"); + vector_free(names); + hashmap_free(ctx); + free(output); +} + TEST(for_block_whitespace) { char *input = "\n{%- for n in names -%}\n{{ n }}\n{%- endfor -%}\n"; struct hashmap *ctx = hashmap_new(); @@ -292,7 +313,6 @@ TEST(expr_lte) { } } - TEST(expr_eq) { struct { char *input; @@ -412,7 +432,6 @@ TEST(buffer_alloc) { } TEST(inheritance_depth_1) { - /* TODO: Check why this fails with files names 1.tmpl */ struct env *env = env_new("./tests/data/inheritance-depth-1/"); char *output = template(env, "one.tmpl", NULL); assert_str(output, "Header\nChild content\nFooter\n"); @@ -420,7 +439,6 @@ TEST(inheritance_depth_1) { env_free(env); } - TEST(inheritance_depth_2) { struct env *env = env_new("./tests/data/inheritance-depth-2/"); char *output = template(env, "two.tmpl", NULL); -- cgit v1.2.3