From 92be46c849a53968e469e544f84820e2fedf6dac Mon Sep 17 00:00:00 2001 From: Danny van Kooten Date: Tue, 17 Mar 2020 15:12:06 +0100 Subject: allocate precisely for output buffer --- src/template.c | 98 ++++++++++++++++++++++++++++++--------------------- tests/test.h | 2 +- tests/test_template.c | 34 +++++++++++++----- 3 files changed, 83 insertions(+), 51 deletions(-) diff --git a/src/template.c b/src/template.c index d3765a1..0529c62 100644 --- a/src/template.c +++ b/src/template.c @@ -3,6 +3,35 @@ #include "vendor/mpc.h" #include "template.h" +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 buffer { + int size; + int cap; + char *string; +}; + +void buffer_reserve(struct buffer *buf, int l) { + if (buf->size + l >= buf->cap) { + buf->cap *= 2; + buf->string = realloc(buf->string, buf->size + l); + } +} + char *read_file(char *filename) { char *input = malloc(BUFSIZ); unsigned int size = 0; @@ -40,23 +69,6 @@ char *trim_leading_whitespace(char *str) { return str; } -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; @@ -82,18 +94,20 @@ struct unja_object *make_int_object(int value) { } -void object_to_str(char *dest, struct unja_object *obj) { - char buf[64]; +void eval_object(struct buffer *buf, struct unja_object *obj) { + char tmp[64]; switch (obj->type) { case OBJ_NULL: break; case OBJ_STRING: - strcat(dest, obj->string); + buffer_reserve(buf, strlen(obj->string)); + strcat(buf->string, obj->string); break; case OBJ_INT: - sprintf(buf, "%d", obj->integer); - strcat(dest, buf); + sprintf(tmp, "%d", obj->integer); + buffer_reserve(buf, strlen(tmp)); + strcat(buf->string, tmp); break; } } @@ -134,7 +148,7 @@ 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 null_object; + return &null_object; } char *key = node->contents; @@ -142,7 +156,7 @@ struct unja_object *eval_expression_value(mpc_ast_t* node, struct hashmap *ctx) /* TODO: Handle unexisting symbols (returns NULL currently) */ if (value == NULL) { - return null_object; + return &null_object; } return make_string_object(value, NULL); } else if(strstr(node->tag, "number|")) { @@ -151,7 +165,7 @@ struct unja_object *eval_expression_value(mpc_ast_t* node, struct hashmap *ctx) return make_string_object(node->children[1]->contents, NULL); } - return null_object; + return &null_object; } @@ -192,14 +206,14 @@ struct unja_object *eval_expression(mpc_ast_t* expr, struct hashmap *ctx) { return eval_expression_value(left_node, ctx); } -int eval(char *dest, mpc_ast_t* t, struct hashmap *ctx) { +int eval(struct buffer *buf, mpc_ast_t* t, struct hashmap *ctx) { static int trim_whitespace = 0; // 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); + buf->string = trim_trailing_whitespace(buf->string); } /* set flag for next eval() to trim leading whitespace from text */ @@ -209,7 +223,7 @@ int eval(char *dest, mpc_ast_t* t, struct hashmap *ctx) { mpc_ast_t *expr = t->children[2]; struct unja_object *obj = eval_expression(expr, ctx); - object_to_str(dest, obj); + eval_object(buf, obj); object_free(obj); return 0; } @@ -220,7 +234,7 @@ int eval(char *dest, mpc_ast_t* t, struct hashmap *ctx) { struct vector *list = hashmap_resolve(ctx, iterator_key); for (int i=0; i < list->size; i++) { hashmap_insert(ctx, tmp_key, list->values[i]); - eval(dest, t->children[6], ctx); + eval(buf, t->children[6], ctx); } return 0; } @@ -230,10 +244,10 @@ int eval(char *dest, mpc_ast_t* t, struct hashmap *ctx) { struct unja_object *result = eval_expression(expr, ctx); if (object_is_truthy(result)) { - eval(dest, t->children[4], ctx); + eval(buf, t->children[4], ctx); } else { if (t->children_num > 8) { - eval(dest, t->children[8], ctx); + eval(buf, t->children[8], ctx); } } @@ -248,12 +262,13 @@ int eval(char *dest, mpc_ast_t* t, struct hashmap *ctx) { trim_whitespace = 0; } - strcat(dest, str); + buffer_reserve(buf, strlen(str)); + strcat(buf->string, str); return 0; } for (int i=0; i < t->children_num; i++) { - eval(dest, t->children[i], ctx); + eval(buf, t->children[i], ctx); } return 0; @@ -295,8 +310,8 @@ mpc_parser_t *parser_init() { " statement_open: \"{%\" /-? */;" " statement_close: / *-?/ \"%}\";" " for : \"for \" \"in\" \"endfor\" ;" - " block : \"block \" \"endblock\" ;" - " extends : \"extends \" ;" + " block : \"block \" \"endblock\" ;" + " extends : \"extends \" ;" " if : \"if \" ( \"else\" )? \"endif\" ;" " statement : | | | ;" " content : | | | ;" @@ -341,13 +356,14 @@ char *template(char *tmpl, struct hashmap *ctx) { printf("\n"); #endif - // FIXME: Allocate precisely - char *output = malloc(strlen(tmpl) * 4); - output[0] = '\0'; + struct buffer buf; + buf.size = 0; + buf.cap = strlen(tmpl) + 1; + buf.string = malloc(buf.size); + buf.string[0] = '\0'; + eval(&buf, r.output, ctx); - eval(output, r.output, ctx); mpc_ast_delete(r.output); - - return output; + return buf.string; } diff --git a/tests/test.h b/tests/test.h index dec4ba4..44fd7fb 100644 --- a/tests/test.h +++ b/tests/test.h @@ -7,7 +7,7 @@ #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_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 0567995..c13ef4c 100644 --- a/tests/test_template.c +++ b/tests/test_template.c @@ -114,18 +114,18 @@ TEST(expr_whitespace) { } TEST(for_block) { - char *input = "{% for n in numbers %}{{ n }}, {% endfor %}"; + char *input = "{% for n in names %}{{ n }}, {% endfor %}"; struct hashmap *ctx = hashmap_new(); - struct vector *numbers = vector_new(3); - vector_push(numbers, "1"); - vector_push(numbers, "2"); - vector_push(numbers, "3"); - hashmap_insert(ctx, "numbers", numbers); + 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(input, ctx); - assert_str(output, "1, 2, 3, "); - vector_free(numbers); + assert_str(output, "John, Sally, Eric, "); + vector_free(names); hashmap_free(ctx); free(output); } @@ -201,4 +201,20 @@ TEST(if_else_block) { hashmap_free(ctx); } -END_TESTS +TEST(buffer_alloc) { + /* Output a string so that output buffer is longer than template buffer, + to test dynamic allocation */ + char *input = "{{ n }}"; + struct hashmap *ctx = hashmap_new(); + char *text = "Lorem ipsum dolor sit amet."; + hashmap_insert(ctx, "n", text); + + char *output = template(input, ctx); + assert_str(output, text); + hashmap_free(ctx); + free(output); +} + + + +END_TESTS \ No newline at end of file -- cgit v1.2.3