diff options
| author | Danny van Kooten <dannyvankooten@users.noreply.github.com> | 2020-03-16 16:24:56 +0100 | 
|---|---|---|
| committer | Danny van Kooten <dannyvankooten@users.noreply.github.com> | 2020-03-16 16:24:56 +0100 | 
| commit | c3ebcee2541d7dcc515fba929aa83555ccbdc030 (patch) | |
| tree | 69c9ffaea1a653f6876363cf4699a00136dd23d3 | |
| parent | 31e98ced014b9326ddfc990b003864df69cd4dcc (diff) | |
| download | unja-c3ebcee2541d7dcc515fba929aa83555ccbdc030.tar.gz unja-c3ebcee2541d7dcc515fba929aa83555ccbdc030.zip  | |
add if statements & use object values internally
| -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   | 
