From 5d66c96a190a396a1535c89bed4e33c2a005fe8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yaroslav=20de=20la=20Pe=C3=B1a=20Smirnov?= Date: Thu, 24 Mar 2022 01:04:02 +0300 Subject: Initial commit Basically it works, just needs some polishing and maybe a couple of features that I could actually use. Also probably better docs. Not sure if it will be of use to anybody besides me. --- src/ast.c | 322 ++++++++++++++++++++++++ src/hmap.c | 222 +++++++++++++++++ src/lexer.c | 264 ++++++++++++++++++++ src/object.c | 242 ++++++++++++++++++ src/parser.c | 700 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/roscha.c | 632 +++++++++++++++++++++++++++++++++++++++++++++++ src/slice.c | 63 +++++ src/tests/lexer.c | 246 +++++++++++++++++++ src/tests/parser.c | 542 +++++++++++++++++++++++++++++++++++++++++ src/tests/roscha.c | 189 +++++++++++++++ src/tests/slice.c | 41 ++++ src/token.c | 144 +++++++++++ src/vector.c | 49 ++++ 13 files changed, 3656 insertions(+) create mode 100644 src/ast.c create mode 100644 src/hmap.c create mode 100644 src/lexer.c create mode 100644 src/object.c create mode 100644 src/parser.c create mode 100644 src/roscha.c create mode 100644 src/slice.c create mode 100644 src/tests/lexer.c create mode 100644 src/tests/parser.c create mode 100644 src/tests/roscha.c create mode 100644 src/tests/slice.c create mode 100644 src/token.c create mode 100644 src/vector.c (limited to 'src') diff --git a/src/ast.c b/src/ast.c new file mode 100644 index 0000000..2de718a --- /dev/null +++ b/src/ast.c @@ -0,0 +1,322 @@ +#include "ast.h" +#include "slice.h" +#include "vector.h" + +#include +#include + +static inline sds +subblocks_string(struct vector *subblocks, sds str) +{ + size_t i; + struct block *subblk; + vector_foreach(subblocks, i, subblk) { + str = block_string(subblk, str); + str = sdscat(str, "\n"); + } + + return str; +} + +static inline sds +ident_string(struct ident *ident, sds str) +{ + return slice_string(&ident->token.literal, str); +} + +static inline sds +integer_string(struct integer *i, sds str) +{ + return sdscatfmt(str, "%I", i->value); +} + +static inline sds +boolean_string(struct boolean *b, sds str) +{ + char *sval = b->value ? "true" : "false"; + return sdscatfmt(str, "%s", sval); +} + +static inline sds +string_string(struct string *s, sds str) +{ + return slice_string(&s->value, str); +} + +static inline sds +prefix_string(struct prefix *pref, sds str) +{ + str = sdscat(str, "("); + str = slice_string(&pref->operator, str); + str = expression_string(pref->right, str); + str = sdscat(str, ")"); + return str; +} + +static inline sds +infix_string(struct infix *inf, sds str) +{ + str = sdscat(str, "("); + str = expression_string(inf->left, str); + str = sdscat(str, " "); + str = slice_string(&inf->operator, str); + str = sdscat(str, " "); + str = expression_string(inf->right, str); + str = sdscat(str, ")"); + return str; +} + +static inline sds +mapkey_string(struct indexkey *map, sds str) +{ + str = expression_string(map->left, str); + str = sdscat(str, "."); + str = ident_string(&map->key->ident, str); + + return str; +} + +static inline sds +index_string(struct indexkey *index, sds str) +{ + str = expression_string(index->left, str); + str = sdscat(str, "["); + str = expression_string(index->key, str); + str = sdscat(str, "]"); + + return str; +} + +sds +expression_string(struct expression *expr, sds str) +{ + switch (expr->type) { + case EXPRESSION_IDENT: + return ident_string(&expr->ident, str); + case EXPRESSION_INT: + return integer_string(&expr->integer, str); + case EXPRESSION_BOOL: + return boolean_string(&expr->boolean, str); + case EXPRESSION_STRING: + return string_string(&expr->string, str); + case EXPRESSION_PREFIX: + return prefix_string(&expr->prefix, str); + case EXPRESSION_INFIX: + return infix_string(&expr->infix, str); + case EXPRESSION_MAPKEY: + return mapkey_string(&expr->indexkey, str); + case EXPRESSION_INDEX: + return index_string(&expr->indexkey, str); + } + return str; +} + +static inline sds +branch_string(struct branch *brnch, sds str) +{ + str = sdscat(str, "{% "); + str = slice_string(&brnch->token.literal, str); + str = sdscat(str, " "); + str = expression_string(brnch->condition, str); + str = sdscat(str, " "); + str = sdscat(str, " %}\n"); + str = subblocks_string(brnch->subblocks, str); + if (brnch->next) { + str = branch_string(brnch->next, str); + } + return str; +} + +static inline sds +cond_string(struct cond *cond, sds str) +{ + str = branch_string(cond->root, str); + str = sdscat(str, "{% endif %}"); + return str; +} + +static sds +loop_string(struct loop *loop, sds str) +{ + str = sdscat(str, "{% for "); + str = ident_string(&loop->item, str); + str = sdscat(str, " in "); + str = expression_string(loop->seq, str); + str = sdscat(str, " %}\n"); + str = subblocks_string(loop->subblocks, str); + str = sdscat(str, "\n{% endfor %}"); + return str; +} + +static inline sds +tblock_string(struct tblock *blk, sds str) +{ + str = sdscat(str, "{% block "); + str = ident_string(&blk->name, str); + str = subblocks_string(blk->subblocks, str); + str = sdscat(str, " %}\n"); + str = sdscat(str, "\n{% endblock %}"); + return str; +} + +static inline sds +parent_string(struct parent *prnt, sds str) +{ + str = sdscat(str, "{% extends "); + str = string_string(prnt->name, str); + str = sdscat(str, " %}"); + return str; +} + +sds +tag_string(struct tag *tag, sds str) +{ + switch (tag->type) { + case TAG_IF: + return cond_string(&tag->cond, str); + case TAG_FOR: + return loop_string(&tag->loop, str); + case TAG_BLOCK: + return tblock_string(&tag->tblock, str); + case TAG_EXTENDS: + return parent_string(&tag->parent, str); + case TAG_BREAK: + str = sdscat(str, "{% "); + str = slice_string(&tag->token.literal, str); + return sdscat(str, " %}"); + default: + break; + } + return str; +} + +sds +variable_string(struct variable *var, sds str) +{ + str = sdscat(str, "{{ "); + str = expression_string(var->expression, str); + str = sdscat(str, " }}"); + return str; +} + +sds +content_string(struct content *cnt, sds str) +{ + return slice_string(&cnt->token.literal, str); +} + +sds +block_string(struct block *blk, sds str) +{ + switch (blk->type) { + case BLOCK_CONTENT: + return content_string(&blk->content, str); + case BLOCK_VARIABLE: + return variable_string(&blk->variable, str); + case BLOCK_TAG: + return tag_string(&blk->tag, str); + default: + break; + } + return str; +} + +sds +template_string(struct template *tmpl, sds str) +{ + return subblocks_string(tmpl->blocks, str); +} + +void +expression_destroy(struct expression *expr) +{ + switch (expr->type) { + case EXPRESSION_PREFIX: + expression_destroy(expr->prefix.right); + break; + case EXPRESSION_INFIX: + expression_destroy(expr->infix.left); + expression_destroy(expr->infix.right); + break; + case EXPRESSION_INDEX: + case EXPRESSION_MAPKEY: + expression_destroy(expr->indexkey.left); + expression_destroy(expr->indexkey.key); + case EXPRESSION_IDENT: + case EXPRESSION_INT: + case EXPRESSION_BOOL: + case EXPRESSION_STRING: + default: + break; + } + free(expr); +} + +static inline void +subblocks_destroy(struct vector *subblks) +{ + size_t i; + struct block *blk; + vector_foreach(subblks, i, blk){ + block_destroy(blk); + } + vector_free(subblks); +} + +void +branch_destroy(struct branch *brnch) +{ + if (brnch->condition) expression_destroy(brnch->condition); + subblocks_destroy(brnch->subblocks); + if (brnch->next) branch_destroy(brnch->next); + free(brnch); +} + +void +tag_destroy(struct tag *tag) +{ + switch (tag->type) { + case TAG_IF: + branch_destroy(tag->cond.root); + break; + case TAG_FOR: + expression_destroy(tag->loop.seq); + subblocks_destroy(tag->loop.subblocks); + break; + case TAG_BLOCK: + subblocks_destroy(tag->tblock.subblocks); + break; + case TAG_EXTENDS: + free(tag->parent.name); + break; + case TAG_BREAK: + default: + break; + } +} + +void +block_destroy(struct block *blk) +{ + switch (blk->type) { + case BLOCK_VARIABLE: + expression_destroy(blk->variable.expression); + break; + case BLOCK_TAG: + tag_destroy(&blk->tag); + break; + case BLOCK_CONTENT: + default: + break; + } + free(blk); +} + +void +template_destroy(struct template *tmpl) +{ + free(tmpl->name); + subblocks_destroy(tmpl->blocks); + hmap_free(tmpl->tblocks); + free(tmpl); +} diff --git a/src/hmap.c b/src/hmap.c new file mode 100644 index 0000000..fb58a88 --- /dev/null +++ b/src/hmap.c @@ -0,0 +1,222 @@ +#include "hmap.h" +#include "slice.h" + +#include +#include +#include +#include + +#if SIZE_MAX == 0xFFFFFFFF +/* If size_t is 32bit */ +static const size_t fnv_prime = 16777619u; +static const size_t fnv_offsetb = 2166136261u; +#elif SIZE_MAX == 0xFFFFFFFFFFFFFFFF +/* If size_t is 64bit */ +static const size_t fnv_prime = 1099511628211u; +static const size_t fnv_offsetb = 14695981039346656037u; +#else +/* If size_t is 128bit. Maybe this is overdoing it? */ +static const size_t fnv_prime = 309485009821345068724781371u; +static const size_t fnv_offsetb = 144066263297769815596495629667062367629u; +#endif + +struct hnode { + struct slice key; + void *value; + struct hnode *next; +}; + +struct hmap_iter { + struct hmap *map; + size_t index; + size_t count; + struct hnode *cur; +}; + +/* FNV1a */ +static size_t +hash_slice(const struct slice *slice) +{ + size_t hash = fnv_offsetb; + size_t i = slice->start; + while (i < slice->end) { + hash ^= slice->str[i]; + hash *= fnv_prime; + i++; + } + + return hash; +} + +struct hmap * +hmap_new_with_cap(size_t cap) +{ + struct hmap *hm = malloc(sizeof *hm); + hm->cap = cap; + hm->size = 0; + if (hm == NULL) return NULL; + hm->buckets = calloc(cap, sizeof hm->buckets); + if (hm->buckets == NULL) { + free(hm); + return NULL; + } + + return hm; +} + +void * +hmap_sets(struct hmap *hm, struct slice key, void *value) +{ + int pos = hash_slice(&key) % hm->cap; + struct hnode *head = hm->buckets[pos]; + struct hnode *node = head; + void *old_value = NULL; + + while (node) { + if (slice_cmp(&node->key, &key) == 0) { + old_value = node->value; + node->value = value; + return old_value; + } + node = node->next; + } + + node = malloc(sizeof *node); + node->key = key; + node->value = value; + node->next = head; + hm->buckets[pos] = node; + hm->size++; + return old_value; +} + +void * +hmap_gets(struct hmap *hm, const struct slice *key) +{ + size_t pos = hash_slice(key) % hm->cap; + struct hnode *node = hm->buckets[pos]; + while (node != NULL) { + if (slice_cmp(&node->key, key) == 0) { + return node->value; + } + + node = node->next; + } + + return NULL; +} + +void * +hmap_get(struct hmap *hm, const char *k) +{ + struct slice key = slice_whole(k); + return hmap_gets(hm, &key); +} + +void * +hmap_removes(struct hmap *hm, const struct slice *key) +{ + int pos = hash_slice(key) % hm->cap; + struct hnode *node = hm->buckets[pos]; + struct hnode *prev = NULL; + void *old_value; + + while (node) { + if (slice_cmp(&node->key, key) == 0) { + if (prev) { + prev->next = node->next; + } else { + hm->buckets[pos] = node->next; + } + old_value = node->value; + free(node); + hm->size--; + return old_value; + } + + prev = node; + node = node->next; + } + + return NULL; +} + +void * +hmap_remove(struct hmap *hm, const char *k) +{ + struct slice key = slice_whole(k); + return hmap_removes(hm, &key); +} + +#define HMAP_WALK(hm, ...) \ + struct hnode *node; \ + struct hnode *next; \ + for (size_t i = 0; i < hm->cap; i++) { \ + node = hm->buckets[i]; \ + while (node) { \ + next = node->next; \ + __VA_ARGS__; \ + node = next; \ + } \ + } + +void +hmap_walk(struct hmap *hm, hmap_cb cb) +{ + HMAP_WALK(hm, cb(&node->key, node->value)); +} + +struct hmap_iter * +hmap_iter_new(struct hmap *hm) +{ + struct hmap_iter *iter = malloc(sizeof(*iter)); + iter->map = hm; + iter->index = 0; + iter->count = hm->size; + iter->cur = NULL; + + return iter; +} + +bool +hmap_iter_next(struct hmap_iter *iter, const struct slice **key, void **value) +{ + if (iter->count < 1) return false; + + if (!iter->cur || !iter->cur->next) { + do { + iter->cur = iter->map->buckets[iter->index++]; + } while (!iter->cur); + } else { + iter->cur = iter->cur->next; + } + *key = &iter->cur->key; + *value = iter->cur->value; + iter->count--; + + return true; +} + +void +hmap_iter_free(struct hmap_iter *iter) +{ + free(iter); +} + +void +hmap_destroy(struct hmap *hm, hmap_cb cb) +{ + HMAP_WALK(hm, cb(&node->key, node->value), free(node)); + + free(hm->buckets); + free(hm); +} + +void +hmap_free(struct hmap *hm) +{ + HMAP_WALK(hm, free(node)); + + free(hm->buckets); + free(hm); +} diff --git a/src/lexer.c b/src/lexer.c new file mode 100644 index 0000000..1ba9912 --- /dev/null +++ b/src/lexer.c @@ -0,0 +1,264 @@ +#include "lexer.h" +#include "token.h" + +#include +#include +#include +#include + +static bool +isidentc(char c) +{ + return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '_'; +} + +static void +set_token(struct token *token, enum token_type t, const struct slice *s) +{ + token->type = t; + if (s == NULL) { + token->literal.str = ""; + token->literal.start = 0; + token->literal.end = 0; + } else { + slice_cpy(&token->literal, s); + } +} + +static char +lexer_peek_prev_char(struct lexer *lexer) +{ + if (lexer->word.start <= 1) { + return 0; + } + return lexer->input[lexer->word.start - 1]; +} + +static char +lexer_peek_char(struct lexer *lexer) +{ + if (lexer->word.start >= lexer->len) { + return 0; + } + return lexer->input[lexer->word.start + 1]; +} + +static inline void +lexer_read_char(struct lexer *lexer) +{ + lexer->word.start = lexer->word.end; + if (lexer->word.end > lexer->len) { + lexer->word.end = 0; + return; + } + char prevc = lexer_peek_prev_char(lexer); + if (prevc == '\n') { + lexer->line++; + lexer->column = 0; + } + lexer->column++; + lexer->word.end++; +} + +static void +lexer_read_ident(struct lexer *lexer, struct token *token) +{ + size_t start = lexer->word.start; + token->literal.str = lexer->input; + while (isidentc(lexer->input[lexer->word.start]) + || isdigit(lexer->input[lexer->word.start])) { + lexer_read_char(lexer); + } + token->literal.start = start; + token->literal.end = lexer->word.start; +} + +static void +lexer_read_num(struct lexer *lexer, struct token *token) +{ + size_t start = lexer->word.start; + token->literal.str = lexer->input; + while (isdigit(lexer->input[lexer->word.start])) { + lexer_read_char(lexer); + } + token->literal.start = start; + token->literal.end = lexer->word.start; +} + +static void +lexer_read_string(struct lexer *lexer, struct token *token) +{ + size_t start = lexer->word.start; + token->literal.str = lexer->input; + lexer_read_char(lexer); + while(lexer->input[lexer->word.start] != '"' && + lexer->input[lexer->word.start] != '\0') { + lexer_read_char(lexer); + } + lexer_read_char(lexer); + token->literal.start = start; + token->literal.end = lexer->word.start; +} + +static void +lexer_read_content(struct lexer *lexer, struct token *token) +{ + size_t start = lexer->word.start; + token->literal.str = lexer->input; + while(lexer->input[lexer->word.start] != '{' && + lexer->input[lexer->word.start] != '\0') { + lexer_read_char(lexer); + } + token->literal.start = start; + token->literal.end = lexer->word.start; +} + +static void +lexer_eatspace(struct lexer *lexer) +{ + while(isspace(lexer->input[lexer->word.start])) { + lexer_read_char(lexer); + } +} + +struct lexer * +lexer_new(const char *input) +{ + struct lexer *lexer = malloc(sizeof(*lexer)); + lexer->input = input; + lexer->len = strlen(lexer->input); + lexer->word.str = lexer->input; + lexer->word.start = 0; + lexer->word.end = 0; + lexer->in_content = true; + lexer->line = 1; + lexer->column = 0; + lexer_read_char(lexer); + + return lexer; +} + +struct token +lexer_next_token(struct lexer *lexer) +{ + struct token token = { .line = lexer->line, .column = lexer->column }; + char c = lexer->input[lexer->word.start]; + + if (c == '\0') { + set_token(&token, TOKEN_EOF, NULL); + return token; + } + + if (lexer->in_content && c != '{') { + lexer_read_content(lexer, &token); + token.type = TOKEN_CONTENT; + return token; + } + + lexer_eatspace(lexer); + c = lexer->input[lexer->word.start]; + switch (c) { + case '=': + if (lexer_peek_char(lexer) == '=') { + lexer->word.end++; + set_token(&token, TOKEN_EQ, &lexer->word); + } else { + set_token(&token, TOKEN_ILLEGAL, &lexer->word); + } + break; + case '+': + set_token(&token, TOKEN_PLUS, &lexer->word); + break; + case '-': + set_token(&token, TOKEN_MINUS, &lexer->word); + break; + case '!': + if (lexer_peek_char(lexer) == '=') { + lexer->word.end++; + set_token(&token, TOKEN_NOTEQ, &lexer->word); + } else { + set_token(&token, TOKEN_BANG, &lexer->word); + } + break; + case '/': + set_token(&token, TOKEN_SLASH, &lexer->word); + break; + case '*': + set_token(&token, TOKEN_ASTERISK, &lexer->word); + break; + case '<': + if (lexer_peek_char(lexer) == '=') { + lexer->word.end++; + set_token(&token, TOKEN_LTE, &lexer->word); + } else { + set_token(&token, TOKEN_LT, &lexer->word); + } + break; + case '>': + if (lexer_peek_char(lexer) == '=') { + lexer->word.end++; + set_token(&token, TOKEN_GTE, &lexer->word); + } else { + set_token(&token, TOKEN_GT, &lexer->word); + } + break; + case '(': + set_token(&token, TOKEN_LPAREN, &lexer->word); + break; + case ')': + set_token(&token, TOKEN_RPAREN, &lexer->word); + break; + case '.': + set_token(&token, TOKEN_DOT, &lexer->word); + break; + case ',': + set_token(&token, TOKEN_COMMA, &lexer->word); + break; + case '[': + set_token(&token, TOKEN_LBRACKET, &lexer->word); + break; + case ']': + set_token(&token, TOKEN_RBRACKET, &lexer->word); + break; + case '{': + lexer->in_content = false; + set_token(&token, TOKEN_LBRACE, &lexer->word); + break; + case '}':{ + char prevc = lexer_peek_prev_char(lexer); + if (prevc == '}' || prevc == '%') { + lexer->in_content = true; + } + set_token(&token, TOKEN_RBRACE, &lexer->word); + break; + } + case '%': + set_token(&token, TOKEN_PERCENT, &lexer->word); + break; + default: + if (c == '"') { + lexer_read_string(lexer, &token); + token.type = TOKEN_STRING; + return token; + } else if (isidentc(c)) { + lexer_read_ident(lexer, &token); + token.type = token_lookup_ident(&token.literal); + return token; + } else if (isdigit(c)) { + lexer_read_num(lexer, &token); + token.type = TOKEN_INT; + return token; + } + set_token(&token, TOKEN_ILLEGAL, &lexer->word); + } + + lexer_read_char(lexer); + + return token; +} + +void +lexer_destroy(struct lexer *lexer) +{ + free(lexer); +} diff --git a/src/object.c b/src/object.c new file mode 100644 index 0000000..e7311b5 --- /dev/null +++ b/src/object.c @@ -0,0 +1,242 @@ +#include "object.h" + +static const char *roscha_types[] = { + "null", + "int", + "bool", + "string", + "vector", + "hashmap", +}; + +static void +roscha_object_destroy_hmap_cb(const struct slice *key, void *val) +{ + struct roscha_object *obj = val; + roscha_object_unref(obj); +} + +extern inline const char * +roscha_type_print(enum roscha_type type) +{ + return roscha_types[type]; +} + +static inline sds +bool_string(bool val, sds str) +{ + sdscat(str, val ? "true" : "false"); + return str; +} + +static inline sds +vector_string(struct vector *vec, sds str) +{ + size_t i; + struct roscha_object *obj; + str = sdscat(str, "[ "); + vector_foreach(vec, i, obj) { + str = roscha_object_string(obj, str); + str = sdscat(str, ", "); + } + str = sdscat(str, "]"); + return str; +} + +static inline sds +hmap_string(struct hmap *map, sds str) +{ + str = sdscat(str, "{ "); + const struct slice *key; + void *val; + struct hmap_iter *iter = hmap_iter_new(map); + hmap_iter_foreach(iter, &key, &val) { + str = sdscatfmt(str, "\"%s\": ", key->str); + const struct roscha_object *obj = val; + str = roscha_object_string(obj, str); + str = sdscat(str, ", "); + } + str = sdscat(str, "}"); + + return str; +} + +sds +roscha_object_string(const struct roscha_object *obj, sds str) +{ + switch (obj->type) { + case ROSCHA_NULL: + return sdscat(str, "null"); + case ROSCHA_BOOL: + return bool_string(obj->boolean, str); + case ROSCHA_INT: + return sdscatfmt(str, "%I", obj->integer); + case ROSCHA_STRING: + return sdscat(str, obj->string); + case ROSCHA_SLICE: + return slice_string(&obj->slice, str); + case ROSCHA_VECTOR: + return vector_string(obj->vector, str); + case ROSCHA_HMAP: + return hmap_string(obj->hmap, str); + } + return str; +} + +struct roscha_object * +roscha_object_new_int(int64_t val) +{ + struct roscha_object *obj = malloc(sizeof(*obj)); + obj->type = ROSCHA_INT; + obj->refcount = 1; + obj->integer = val; + return obj; +} + +struct roscha_object * +roscha_object_new_slice(struct slice s) +{ + struct roscha_object *obj = malloc(sizeof(*obj)); + obj->type = ROSCHA_SLICE; + obj->refcount = 1; + obj->slice = s; + return obj; +} + +struct roscha_object * +roscha_object_new_string(sds str) +{ + struct roscha_object *obj = malloc(sizeof(*obj)); + obj->type = ROSCHA_STRING; + obj->refcount = 1; + obj->string = str; + return obj; +} + +struct roscha_object * +roscha_object_new_vector(struct vector *vec) +{ + struct roscha_object *obj = malloc(sizeof(*obj)); + obj->type = ROSCHA_VECTOR; + obj->refcount = 1; + obj->vector = vec; + return obj; +} + +struct roscha_object * +roscha_object_new_hmap(struct hmap *map) +{ + struct roscha_object *obj = malloc(sizeof(*obj)); + obj->type = ROSCHA_HMAP; + obj->refcount = 1; + obj->hmap = map; + return obj; +} + +static inline void +roscha_object_vector_destroy(struct roscha_object *obj) +{ + size_t i; + struct roscha_object *subobj; + vector_foreach(obj->vector, i, subobj) { + roscha_object_unref(subobj); + } + vector_free(obj->vector); +} + +inline void +roscha_object_ref(struct roscha_object *obj) +{ + obj->refcount++; +} + +void +roscha_object_unref(struct roscha_object *obj) +{ + if (obj == NULL) return; + if (obj->type == ROSCHA_NULL || obj->type == ROSCHA_BOOL) return; + if (--obj->refcount < 1) { + switch (obj->type) { + case ROSCHA_STRING: + sdsfree(obj->string); + break; + case ROSCHA_VECTOR: + roscha_object_vector_destroy(obj); + break; + case ROSCHA_HMAP: + hmap_destroy(obj->hmap, roscha_object_destroy_hmap_cb); + break; + default: + break; + } + free(obj); + } +} + +void +roscha_vector_push(struct roscha_object *vec, struct roscha_object *val) +{ + roscha_object_ref(val); + vector_push(vec->vector, val); +} + +struct roscha_object * +roscha_vector_pop(struct roscha_object *vec) +{ + return (struct roscha_object *)vector_pop(vec->vector); +} + +struct roscha_object * +roscha_hmap_sets(struct roscha_object *hmap, struct slice key, + struct roscha_object *value) +{ + roscha_object_ref(value); + return hmap_sets(hmap->hmap, key, value); +} + +struct roscha_object * +roscha_hmap_setstr(struct roscha_object *hmap, const char *key, + struct roscha_object *value) +{ + roscha_object_ref(value); + return hmap_set(hmap->hmap, key, value); +} + +struct roscha_object * +roscha_hmap_gets(struct roscha_object *hmap, const struct slice *key) +{ + return (struct roscha_object *)hmap_gets(hmap->hmap, key); +} + +struct roscha_object * +roscha_hmap_getstr(struct roscha_object *hmap, const char *key) +{ + return (struct roscha_object *)hmap_get(hmap->hmap, key); +} + +struct roscha_object * +roscha_hmap_pops(struct roscha_object *hmap, const struct slice *key) +{ + return (struct roscha_object *)hmap_removes(hmap->hmap, key); +} + +struct roscha_object * +roscha_hmap_popstr(struct roscha_object *hmap, + const char *key) +{ + return (struct roscha_object *)hmap_remove(hmap->hmap, key); +} + +void +roscha_hmap_unsets(struct roscha_object *hmap, const struct slice *key) +{ + struct roscha_object *obj = hmap_removes(hmap->hmap, key); + if (obj) roscha_object_unref(obj); +} + +void +roscha_hmap_unsetstr(struct roscha_object *hmap, const char *key) +{ + struct roscha_object *obj = hmap_remove(hmap->hmap, key); + if (obj) roscha_object_unref(obj); +} diff --git a/src/parser.c b/src/parser.c new file mode 100644 index 0000000..4ccba1b --- /dev/null +++ b/src/parser.c @@ -0,0 +1,700 @@ +#include "parser.h" +#include "ast.h" +#include "token.h" +#include "vector.h" + +#include +#include + +enum precedence { + PRE_LOWEST = 1, + PRE_EQUALS, + PRE_LG, + PRE_SUM, + PRE_PROD, + PRE_PREFIX, + PRE_CALL, + PRE_INDEX, +}; + +static enum precedence precedence_values[] = { + 0, + PRE_LOWEST, + PRE_EQUALS, + PRE_LG, + PRE_SUM, + PRE_PROD, + PRE_PREFIX, + PRE_CALL, + PRE_INDEX, +}; + +static struct hmap *prefix_fns = NULL; +static struct hmap *infix_fns = NULL; +static struct hmap *precedences = NULL; + +static struct block *parser_parse_block(struct parser *, struct block *opening); + +static inline void +parser_register_prefix(enum token_type t, prefix_parse_f fn) +{ + hmap_set(prefix_fns, token_type_print(t), fn); +} + +static inline void +parser_register_infix(enum token_type t, infix_parse_f fn) +{ + hmap_set(infix_fns, token_type_print(t), fn); +} + +static inline void +parser_register_precedence(enum token_type t, enum precedence pre) +{ + hmap_set(precedences, token_type_print(t), &precedence_values[pre]); +} + +static inline prefix_parse_f +parser_get_prefix(struct parser *parser, enum token_type t) +{ + return hmap_get(prefix_fns, token_type_print(t)); +} + +static inline infix_parse_f +parser_get_infix(struct parser *parser, enum token_type t) +{ + return hmap_get(infix_fns, token_type_print(t)); +} + +static inline enum precedence +parser_get_precedence(struct parser *parser, enum token_type t) +{ + enum precedence *pre = hmap_get(precedences, token_type_print(t)); + if (!pre) return PRE_LOWEST; + return *pre; +} + +static inline void +parser_next_token(struct parser *parser) +{ + parser->cur_token = parser->peek_token; + parser->peek_token = lexer_next_token(parser->lexer); +} + +static inline bool +parser_cur_token_is(struct parser *parser, enum token_type t) +{ + return parser->cur_token.type == t; +} + +static inline bool +parser_peek_token_is(struct parser *parser, enum token_type t) +{ + return parser->peek_token.type == t; +} + +static inline enum precedence +parser_peek_precedence(struct parser *parser) +{ + return parser_get_precedence(parser, parser->peek_token.type); +} + +static inline enum precedence +parser_cur_precedence(struct parser *parser) +{ + return parser_get_precedence(parser, parser->cur_token.type); +} + +#define parser_error(p, t, fmt, ...) \ + sds err = sdscatfmt(sdsempty(), "%s:%U:%U: "fmt, parser->name, \ + t.line, t.column, __VA_ARGS__); \ + vector_push(p->errors, err) + +static inline void +parser_peek_error(struct parser *parser, enum token_type t) +{ + parser_error(parser, parser->peek_token, "expected token %s, got %s", + token_type_print(t), token_type_print(parser->peek_token.type)); +} + +static inline bool +parser_expect_peek(struct parser *parser, enum token_type t) +{ + if (parser_peek_token_is(parser, t)) { + parser_next_token(parser); + return true; + } + parser_peek_error(parser, t); + return false; +} + +static inline void +parser_no_prefix_fn_error(struct parser *parser, enum token_type t) +{ + parser_error(parser, parser->cur_token, "%s not recognized as prefix", + token_type_print(t)); +} + +static struct expression * +parser_parse_expression(struct parser *parser, enum precedence pre) +{ + prefix_parse_f prefix = parser_get_prefix(parser, parser->cur_token.type); + if (!prefix) { + parser_no_prefix_fn_error(parser, parser->cur_token.type); + return NULL; + } + struct expression *lexpr = prefix(parser); + + while (pre < parser_peek_precedence(parser) + && !parser_peek_token_is(parser, TOKEN_PERCENT) + && !parser_peek_token_is(parser, TOKEN_RBRACE)) { + infix_parse_f infix = parser_get_infix(parser, parser->peek_token.type); + if (!infix) { return lexpr; } + + parser_next_token(parser); + lexpr = infix(parser, lexpr); + } + + return lexpr; +} + +static struct expression * +parser_parse_identifier(struct parser *parser) +{ + struct expression *expr = malloc(sizeof(*expr)); + expr->type = EXPRESSION_IDENT; + expr->token = parser->cur_token; + + return expr; +} + +static struct expression * +parser_parse_integer(struct parser *parser) +{ + struct expression *expr = malloc(sizeof(*expr)); + expr->type = EXPRESSION_INT; + expr->token = parser->cur_token; + + char *end; + expr->integer.value = + strtol(expr->token.literal.str + expr->token.literal.start, &end, 0); + if (*end != '\0' + && (expr->token.literal.str + expr->token.literal.end) < end) { + sds istr = slice_string(&expr->token.literal, sdsempty()); + parser_error(parser, parser->cur_token, "%s is not a valid integer", + istr); + sdsfree(istr); + free(expr); + return NULL; + } + + return expr; +} + +static struct expression * +parser_parse_boolean(struct parser *parser) +{ + struct expression *expr = malloc(sizeof(*expr)); + expr->type = EXPRESSION_BOOL; + expr->token = parser->cur_token; + expr->boolean.value = expr->token.type == TOKEN_TRUE; + + return expr; +} + +static struct expression * +parser_parse_string(struct parser *parser) +{ + struct expression *expr = malloc(sizeof(*expr)); + expr->type = EXPRESSION_STRING; + expr->token = parser->cur_token; + expr->string.value = parser->cur_token.literal; + expr->string.value.start++; + expr->string.value.end--; + + return expr; +} + +static struct expression * +parser_parse_grouped(struct parser *parser) +{ + parser_next_token(parser); + struct expression *expr = parser_parse_expression(parser, PRE_LOWEST); + if (!parser_expect_peek(parser, TOKEN_RPAREN)) { return NULL; } + + return expr; +} + +static struct expression * +parser_parse_prefix(struct parser *parser) +{ + struct expression *expr = malloc(sizeof(*expr)); + expr->type = EXPRESSION_PREFIX; + expr->token = parser->cur_token; + expr->prefix.operator= parser->cur_token.literal; + + parser_next_token(parser); + expr->prefix.right = parser_parse_expression(parser, PRE_PREFIX); + + return expr; +} + +static struct expression * +parser_parse_infix(struct parser *parser, struct expression *lexpr) +{ + struct expression *expr = malloc(sizeof(*expr)); + expr->type = EXPRESSION_INFIX; + expr->token = parser->cur_token; + expr->infix.operator= parser->cur_token.literal; + expr->infix.left = lexpr; + + enum precedence pre = parser_cur_precedence(parser); + parser_next_token(parser); + expr->infix.right = parser_parse_expression(parser, pre); + + return expr; +} + +static struct expression * +parser_parse_mapkey(struct parser *parser, struct expression *lexpr) +{ + if (lexpr->type != EXPRESSION_IDENT + && lexpr->type != EXPRESSION_MAPKEY + && lexpr->type != EXPRESSION_INDEX) { + sds got = expression_string(lexpr, sdsempty()); + parser_error(parser, parser->cur_token, + "expected a map identifier, key or index; got %s", got); + sdsfree(got); + return NULL; + } + struct expression *expr = malloc(sizeof(*expr)); + expr->type = EXPRESSION_MAPKEY; + expr->token = parser->cur_token; + expr->indexkey.left = lexpr; + + parser_next_token(parser); + expr->indexkey.key = parser_parse_expression(parser, PRE_INDEX); + if (expr->indexkey.key->type != EXPRESSION_IDENT) { + sds got = expression_string(expr->indexkey.key, sdsempty()); + parser_error(parser, parser->cur_token, + "expected a map key identifier, got %s", got); + sdsfree(got); + expression_destroy(expr); + return NULL; + } + + return expr; +} + +static struct expression * +parser_parse_index(struct parser *parser, struct expression *lexpr) +{ + if (lexpr->type != EXPRESSION_IDENT + && lexpr->type != EXPRESSION_MAPKEY + && lexpr->type != EXPRESSION_INDEX) { + sds got = expression_string(lexpr, sdsempty()); + parser_error(parser, parser->cur_token, + "expected a vector identifier, key or index; got %s", got); + sdsfree(got); + return NULL; + } + struct expression *expr = malloc(sizeof(*expr)); + expr->type = EXPRESSION_INDEX; + expr->token = parser->cur_token; + expr->indexkey.left = lexpr; + + parser_next_token(parser); + expr->indexkey.key = parser_parse_expression(parser, PRE_LOWEST); + + if (!parser_expect_peek(parser, TOKEN_RBRACKET)) { + expression_destroy(expr); + return NULL; + } + + return expr; +} + +static inline bool +parser_parse_loop(struct parser *parser, struct block *blk) +{ + blk->tag.type = TAG_FOR; + + if (!parser_expect_peek(parser, TOKEN_IDENT)) return false; + blk->tag.loop.item.token = parser->cur_token; + if (!parser_expect_peek(parser, TOKEN_IN)) return false; + if (!parser_expect_peek(parser, TOKEN_IDENT)) return false; + blk->tag.loop.seq = parser_parse_expression(parser, PRE_LOWEST); + + if (!parser_expect_peek(parser, TOKEN_PERCENT)) return false; + if (!parser_expect_peek(parser, TOKEN_RBRACE)) return false; + + parser_next_token(parser); + blk->tag.loop.subblocks = vector_new(); + while (!parser_cur_token_is(parser, TOKEN_EOF)) { + struct block *subblk = parser_parse_block(parser, blk); + if (subblk == NULL) { + return false; + } + vector_push(blk->tag.loop.subblocks, subblk); + parser_next_token(parser); + if (subblk->type == BLOCK_TAG && subblk->tag.type == TAG_CLOSE) { + break; + } + } + + return true; +} + +static inline struct branch * +parser_parse_branch(struct parser *parser, struct block *opening) +{ + struct branch *brnch = calloc(1, sizeof(*brnch)); + brnch->token = parser->cur_token; + + if (brnch->token.type == TOKEN_IF || brnch->token.type == TOKEN_ELIF) { + parser_next_token(parser); + brnch->condition = parser_parse_expression(parser, PRE_LOWEST); + } + + if (!parser_expect_peek(parser, TOKEN_PERCENT)) return false; + if (!parser_expect_peek(parser, TOKEN_RBRACE)) return false; + + parser_next_token(parser); + brnch->subblocks = vector_new(); + while (!parser_cur_token_is(parser, TOKEN_EOF)) { + struct block *subblk = parser_parse_block(parser, opening); + if (subblk == NULL) { + branch_destroy(brnch); + return NULL; + } + if (subblk->type == BLOCK_TAG && subblk->tag.type == TAG_IF) { + brnch->next = subblk->tag.cond.root; + free(subblk); + break; + } + vector_push(brnch->subblocks, subblk); + parser_next_token(parser); + if (subblk->type == BLOCK_TAG && subblk->tag.type == TAG_CLOSE) { + break; + } + } + + return brnch; +} + +static inline bool +parser_parse_cond(struct parser *parser, struct block *blk) +{ + blk->tag.type = TAG_IF; + blk->tag.cond.root = parser_parse_branch(parser, blk); + if (!blk->tag.cond.root) { + return false; + } + + return true; +} + +static inline bool +parser_parse_cond_alt(struct parser *parser, struct block *blk, + struct block *opening) +{ + if (opening == NULL || opening->type != BLOCK_TAG + || opening->tag.type != TAG_IF) { + parser_error(parser, parser->cur_token, "unexpected token %s", + token_type_print(parser->cur_token.type)); + return false; + } + blk->tag.type = TAG_IF; + blk->tag.cond.root = parser_parse_branch(parser, blk); + if (!blk->tag.cond.root) { + return false; + } + + return true; +} + +static inline bool +parser_parse_parent(struct parser *parser, struct block *blk) +{ + blk->tag.type = TAG_EXTENDS; + if (!parser_expect_peek(parser, TOKEN_STRING)) return false; + + blk->tag.parent.name = malloc(sizeof(*blk->tag.parent.name)); + blk->tag.parent.name->token = parser->cur_token; + blk->tag.parent.name->value = parser->cur_token.literal; + blk->tag.parent.name->value.start++; + blk->tag.parent.name->value.end--; + + if (!parser_expect_peek(parser, TOKEN_PERCENT)) return false; + if (!parser_expect_peek(parser, TOKEN_RBRACE)) return false; + + return true; +} + +static inline bool +parser_parse_tblock(struct parser *parser, struct block *blk) +{ + blk->tag.type = TAG_BLOCK; + + if (!parser_expect_peek(parser, TOKEN_IDENT)) return false; + blk->tag.tblock.name.token = parser->cur_token; + + if (!parser_expect_peek(parser, TOKEN_PERCENT)) return false; + if (!parser_expect_peek(parser, TOKEN_RBRACE)) return false; + + parser_next_token(parser); + blk->tag.tblock.subblocks = vector_new(); + while (!parser_cur_token_is(parser, TOKEN_EOF)) { + struct block *subblk = parser_parse_block(parser, blk); + if (subblk == NULL) { + return false; + } + vector_push(blk->tag.tblock.subblocks, subblk); + if (subblk->type == BLOCK_TAG && subblk->tag.type == TAG_CLOSE) { + break; + } + parser_next_token(parser); + } + + hmap_sets(parser->tblocks, blk->tag.tblock.name.token.literal, blk); + return true; +} + +static inline struct block * +parser_parse_tag(struct parser *parser, struct block *opening) +{ + struct block *blk = malloc(sizeof(*blk)); + blk->type = BLOCK_TAG; + + parser_next_token(parser); + parser_next_token(parser); + + blk->token = parser->cur_token; + + bool res = true; + switch (parser->cur_token.type) { + case TOKEN_FOR: + res = parser_parse_loop(parser, blk); + break; + case TOKEN_BREAK: + blk->tag.type = TAG_BREAK; + goto onetoken; + case TOKEN_IF: + res = parser_parse_cond(parser, blk); + break; + case TOKEN_ELIF: + case TOKEN_ELSE: + res = parser_parse_cond_alt(parser, blk, opening); + break; + case TOKEN_EXTENDS: + res = parser_parse_parent(parser, blk); + break; + case TOKEN_BLOCK: + res = parser_parse_tblock(parser, blk); + break; + case TOKEN_ENDFOR: + if (opening == NULL) goto noopening; + if (opening->tag.type != TAG_FOR) goto noopening; + goto closing; + case TOKEN_ENDIF: + if (opening == NULL) goto noopening; + if (opening->tag.type != TAG_IF) goto noopening; + goto closing; + case TOKEN_ENDBLOCK: + if (opening == NULL) goto noopening; + if (opening->tag.type != TAG_BLOCK) goto noopening; + goto closing; + default:; + parser_error(parser, parser->cur_token, "expected keyword, got %s", + token_type_print(parser->cur_token.type)); + return NULL; + } + + if (!res) { + free(blk); + return NULL; + } + + return blk; +closing: + blk->tag.type = TAG_CLOSE; +onetoken: + if (!parser_expect_peek(parser, TOKEN_PERCENT)) goto fail; + if (!parser_expect_peek(parser, TOKEN_RBRACE)) goto fail; + return blk; +noopening:; + parser_error(parser, parser->cur_token, "unexpected closing tag %s", + token_type_print(parser->cur_token.type)); +fail: + free(blk); + return NULL; +} + +static inline struct block * +parser_parse_variable(struct parser *parser) +{ + struct block *blk = malloc(sizeof(*blk)); + blk->type = BLOCK_VARIABLE; + blk->token = parser->peek_token; + + parser_next_token(parser); + parser_next_token(parser); + + blk->variable.expression = parser_parse_expression(parser, PRE_LOWEST); + if (!blk->variable.expression) goto fail; + if (!parser_expect_peek(parser, TOKEN_RBRACE)) goto fail; + if (!parser_expect_peek(parser, TOKEN_RBRACE)) goto fail; + + return blk; +fail: + block_destroy(blk); + return NULL; +} + +static inline struct block * +parser_parse_content(struct parser *parser) +{ + struct block *blk = malloc(sizeof(*blk)); + blk->type = BLOCK_CONTENT; + blk->token = parser->cur_token; + + return blk; +} + +static struct block * +parser_parse_block(struct parser *parser, struct block *opening) +{ + switch (parser->cur_token.type) { + case TOKEN_LBRACE: + switch (parser->peek_token.type) { + case TOKEN_LBRACE: + return parser_parse_variable(parser); + case TOKEN_PERCENT: + return parser_parse_tag(parser, opening); + default:{ + parser_error(parser, parser->cur_token, + "expected token %s or %s, got %s", + token_type_print(TOKEN_LBRACE), + token_type_print(TOKEN_PERCENT), + token_type_print(parser->peek_token.type)); + return NULL; + } + } + case TOKEN_CONTENT: + default: + return parser_parse_content(parser); + } +} + +struct parser * +parser_new(char *name, char *input) +{ + struct parser *parser = calloc(1, sizeof(*parser)); + parser->name = name; + + struct lexer *lex = lexer_new(input); + parser->lexer = lex; + + parser->errors = vector_new(); + + parser_next_token(parser); + parser_next_token(parser); + + return parser; +} + +struct template * +parser_parse_template(struct parser *parser) +{ + struct template *tmpl = malloc(sizeof(*tmpl)); + tmpl->name = parser->name; + tmpl->source = (char *)parser->lexer->input; + parser->tblocks = hmap_new(); + tmpl->child = NULL; + tmpl->blocks = vector_new(); + + while (!parser_cur_token_is(parser, TOKEN_EOF)) { + struct block *blk = parser_parse_block(parser, NULL); + if (blk == NULL) { + break; + } + vector_push(tmpl->blocks, blk); + + parser_next_token(parser); + } + + tmpl->tblocks = parser->tblocks; + return tmpl; +} + +void +parser_destroy(struct parser *parser) +{ + size_t i; + char *val; + vector_foreach(parser->errors, i, val) { + sdsfree(val); + } + vector_free(parser->errors); + lexer_destroy(parser->lexer); + free(parser); +} + +void +parser_init(void) +{ + token_init_keywords(); + + prefix_fns = hmap_new(); + parser_register_prefix(TOKEN_IDENT, parser_parse_identifier); + parser_register_prefix(TOKEN_INT, parser_parse_integer); + parser_register_prefix(TOKEN_BANG, parser_parse_prefix); + parser_register_prefix(TOKEN_MINUS, parser_parse_prefix); + parser_register_prefix(TOKEN_NOT, parser_parse_prefix); + parser_register_prefix(TOKEN_TRUE, parser_parse_boolean); + parser_register_prefix(TOKEN_FALSE, parser_parse_boolean); + parser_register_prefix(TOKEN_STRING, parser_parse_string); + parser_register_prefix(TOKEN_LPAREN, parser_parse_grouped); + parser_register_prefix(TOKEN_RPAREN, parser_parse_grouped); + + infix_fns = hmap_new(); + parser_register_infix(TOKEN_PLUS, parser_parse_infix); + parser_register_infix(TOKEN_MINUS, parser_parse_infix); + parser_register_infix(TOKEN_SLASH, parser_parse_infix); + parser_register_infix(TOKEN_ASTERISK, parser_parse_infix); + parser_register_infix(TOKEN_EQ, parser_parse_infix); + parser_register_infix(TOKEN_NOTEQ, parser_parse_infix); + parser_register_infix(TOKEN_LT, parser_parse_infix); + parser_register_infix(TOKEN_GT, parser_parse_infix); + parser_register_infix(TOKEN_LTE, parser_parse_infix); + parser_register_infix(TOKEN_GTE, parser_parse_infix); + parser_register_infix(TOKEN_AND, parser_parse_infix); + parser_register_infix(TOKEN_OR, parser_parse_infix); + + parser_register_infix(TOKEN_DOT, parser_parse_mapkey); + parser_register_infix(TOKEN_LBRACKET, parser_parse_index); + + precedences = hmap_new(); + parser_register_precedence(TOKEN_EQ, PRE_EQUALS); + parser_register_precedence(TOKEN_NOTEQ, PRE_EQUALS); + parser_register_precedence(TOKEN_LT, PRE_LG); + parser_register_precedence(TOKEN_GT, PRE_LG); + parser_register_precedence(TOKEN_LTE, PRE_LG); + parser_register_precedence(TOKEN_GTE, PRE_LG); + parser_register_precedence(TOKEN_AND, PRE_LG); + parser_register_precedence(TOKEN_OR, PRE_LG); + parser_register_precedence(TOKEN_PLUS, PRE_SUM); + parser_register_precedence(TOKEN_MINUS, PRE_SUM); + parser_register_precedence(TOKEN_ASTERISK, PRE_PROD); + parser_register_precedence(TOKEN_SLASH, PRE_PROD); + parser_register_precedence(TOKEN_DOT, PRE_INDEX); + parser_register_precedence(TOKEN_LBRACKET, PRE_INDEX); +} + +void +parser_deinit(void) +{ + token_free_keywords(); + hmap_free(infix_fns); + hmap_free(prefix_fns); + hmap_free(precedences); +} diff --git a/src/roscha.c b/src/roscha.c new file mode 100644 index 0000000..3f315ce --- /dev/null +++ b/src/roscha.c @@ -0,0 +1,632 @@ +#include "roscha.h" + +#include "ast.h" +#include "hmap.h" +#include "vector.h" +#include "parser.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUFSIZE 8912 + +struct roscha_ { + /* hmap of template */ + struct hmap *templates; + /* template currently being evaluated */ + const struct template *eval_tmpl; + /* Set when a break tag was encountered */ + bool brk; +}; + +static struct roscha_object obj_null = { + ROSCHA_NULL, + 0, + .boolean = false, +}; +static struct roscha_object obj_true = { + ROSCHA_BOOL, + 0, + .boolean = true, +}; +static struct roscha_object obj_false = { + ROSCHA_BOOL, + 0, + .boolean = false, +}; + +static inline struct roscha_object * +get_bool_object(bool val) +{ + struct roscha_object *obj = val ? &obj_true : &obj_false; + return obj; +} + +static void +roscha_env_destroy_templates_cb(const struct slice *key, void *val) +{ + struct template *tmpl = val; + template_destroy(tmpl); +} + +#define eval_error(e, t, fmt, ...) \ + sds err = sdscatfmt(sdsempty(), "%s:%U:%U: "fmt, \ + e->internal->eval_tmpl->name, t.line, t.column, __VA_ARGS__); \ + vector_push(e->errors, err) + +#define THERES_ERRORS env->errors->len > 0 + +static inline struct roscha_object *eval_expression(struct roscha_env *, + struct expression *); + +static inline struct roscha_object * +eval_prefix(struct roscha_env *env, struct prefix *pref) +{ + struct roscha_object *right = eval_expression(env, pref->right); + if (!right) { + return NULL; + } + struct roscha_object *res = NULL; + switch (pref->token.type) { + case TOKEN_BANG: + case TOKEN_NOT: + res = get_bool_object(!right->boolean); + break; + case TOKEN_MINUS: + if (right->type != ROSCHA_INT) { + eval_error(env, pref->token, + "operator '%s' can only be used with integer types", + token_type_print(pref->token.type)); + } else { + res = roscha_object_new(-right->integer); + } + break; + default:{ + eval_error(env, pref->token, "invalid prefix operator '%s'", + token_type_print(pref->token.type)); + res = NULL; + } + } + roscha_object_unref(right); + + return res; +} + +static inline struct roscha_object * +eval_boolean_infix(struct roscha_env *env, struct token *op, + struct roscha_object *left, struct roscha_object *right) +{ + struct roscha_object *res = NULL; + switch (op->type) { + case TOKEN_LT: + res = get_bool_object(left->boolean < right->boolean); + break; + case TOKEN_GT: + res = get_bool_object(left->boolean > right->boolean); + break; + case TOKEN_LTE: + res = get_bool_object(left->boolean <= right->boolean); + break; + case TOKEN_GTE: + res = get_bool_object(left->boolean >= right->boolean); + break; + case TOKEN_EQ: + res = get_bool_object(left->boolean == right->boolean); + break; + case TOKEN_NOTEQ: + res = get_bool_object(left->boolean != right->boolean); + break; + case TOKEN_AND: + res = get_bool_object(left->boolean && right->boolean); + break; + case TOKEN_OR: + res = get_bool_object(left->boolean || right->boolean); + break; + default: + if (left->type != right->type) { + eval_error(env, (*op), "types mismatch: %s %s %s", + roscha_type_print(left->type), token_type_print(op->type), + roscha_type_print(right->type)); + break; + } + eval_error(env, (*op), "bad operator: %s %s %s", + roscha_type_print(left->type), token_type_print(op->type), + roscha_type_print(right->type)); + break; + } + roscha_object_unref(left); + roscha_object_unref(right); + + return res; +} + +static inline struct roscha_object * +eval_integer_infix(struct roscha_env *env, struct token *op, + struct roscha_object *left, struct roscha_object *right) +{ + struct roscha_object *res; + switch (op->type) { + case TOKEN_PLUS: + res = roscha_object_new(left->integer + right->integer); + break; + case TOKEN_MINUS: + res = roscha_object_new(left->integer - right->integer); + break; + case TOKEN_ASTERISK: + res = roscha_object_new(left->integer * right->integer); + break; + case TOKEN_SLASH: + res = roscha_object_new(left->integer / right->integer); + break; + default: + return eval_boolean_infix(env, op, left, right); + } + roscha_object_unref(left); + roscha_object_unref(right); + + return res; +} + +static inline struct roscha_object * +eval_infix(struct roscha_env *env, struct infix *inf) +{ + struct roscha_object *left = eval_expression(env, inf->left); + if (!left) { + return NULL; + } + struct roscha_object *right = eval_expression(env, inf->right); + if (!right) { + roscha_object_unref(left); + return NULL; + } + if (left->type == ROSCHA_INT && right->type == ROSCHA_INT) + { + return eval_integer_infix(env, &inf->token, left, right); + } + + return eval_boolean_infix(env, &inf->token, left, right); +} + +static inline struct roscha_object * +eval_mapkey(struct roscha_env *env, struct indexkey *mkey) +{ + struct roscha_object *res = NULL; + struct roscha_object *map = eval_expression(env, mkey->left); + if (!map) return NULL; + if (map->type != ROSCHA_HMAP) { + eval_error(env, mkey->token, "expected %s type got %s", + roscha_type_print(ROSCHA_HMAP), + roscha_type_print(map->type)); + goto out; + } + if (mkey->key->type != EXPRESSION_IDENT) { + eval_error(env, mkey->key->token, "bad map key '%s'", + token_type_print(mkey->key->token.type)); + goto out; + } + res = hmap_gets(map->hmap, &mkey->key->token.literal); + if (!res) { + res = &obj_null; + } else { + roscha_object_ref(res); + } + +out: + roscha_object_unref(map); + return res; +} + +static inline struct roscha_object * +eval_index(struct roscha_env *env, struct indexkey *index) +{ + struct roscha_object *res = NULL; + struct roscha_object *vec = eval_expression(env, index->left); + if (!vec) return NULL; + if (vec->type != ROSCHA_VECTOR) { + eval_error(env, index->token, "expected %s type got %s", + roscha_type_print(ROSCHA_VECTOR), + roscha_type_print(vec->type)); + goto out; + } + struct roscha_object *i = eval_expression(env, index->key); + if (i->type != ROSCHA_INT) { + eval_error(env, index->key->token, "bad vector key type %s", + roscha_type_print(ROSCHA_INT)); + goto out2; + } + if (i->integer > (vec->vector->len - 1)) { + res = &obj_null; + } else { + res = vec->vector->values[i->integer]; + roscha_object_ref(res); + } + +out2: + roscha_object_unref(i); +out: + roscha_object_unref(vec); + return res; +} + +static inline struct roscha_object * +eval_expression(struct roscha_env *env, struct expression *expr) +{ + struct roscha_object *obj = NULL; + switch (expr->type) { + case EXPRESSION_IDENT: + obj = roscha_hmap_get(env->vars, &expr->ident.token.literal); + if (!obj) { + obj = &obj_null; + } else { + roscha_object_ref(obj); + } + break; + case EXPRESSION_INT: + obj = roscha_object_new(expr->integer.value); + break; + case EXPRESSION_BOOL: + obj = get_bool_object(expr->boolean.value); + break; + case EXPRESSION_STRING: + obj = roscha_object_new(expr->string.value); + break; + case EXPRESSION_PREFIX: + obj = eval_prefix(env, &expr->prefix); + break; + case EXPRESSION_INFIX: + obj = eval_infix(env, &expr->infix); + break; + case EXPRESSION_MAPKEY: + obj = eval_mapkey(env, &expr->indexkey); + break; + case EXPRESSION_INDEX: + obj = eval_index(env, &expr->indexkey); + break; + } + + return obj; +} + +static inline sds +eval_variable(struct roscha_env *env, sds r, struct variable *var) +{ + struct roscha_object *obj = eval_expression(env, var->expression); + if (!obj) { + return r; + } + r = roscha_object_string(obj, r); + roscha_object_unref(obj); + + return r; +} + +static inline sds eval_subblocks(struct roscha_env *, sds r, + struct vector *blks); + +static inline sds +eval_branch(struct roscha_env *env, sds r, struct branch *br) +{ + if (br->condition) { + struct roscha_object *cond = eval_expression(env, br->condition); + if (cond->boolean) { + r = eval_subblocks(env, r, br->subblocks); + } else if (br->next) { + r = eval_branch(env, r, br->next); + } + roscha_object_unref(cond); + } else { + r = eval_subblocks(env, r, br->subblocks); + } + return r; +} + +static inline sds +eval_loop(struct roscha_env *env, sds r, struct loop *loop) +{ + struct roscha_object *loopv = roscha_object_new(hmap_new()); + struct roscha_object *indexv = roscha_object_new(0); + roscha_hmap_set(loopv, "index", indexv); + struct slice loopk = slice_whole("loop"); + struct roscha_object *outerloop = roscha_hmap_set(env->vars, "loop", loopv); + struct slice it = loop->item.token.literal; + struct roscha_object *outeritem = roscha_hmap_get(env->vars, &it); + struct roscha_object *seq = eval_expression(env, loop->seq); + if (!seq) return r; + if (seq->type == ROSCHA_VECTOR) { + struct roscha_object *item; + vector_foreach(seq->vector, indexv->integer, item) { + roscha_hmap_set(env->vars, it, item); + r = eval_subblocks(env, r, loop->subblocks); + roscha_hmap_unset(env->vars, &it); + if (THERES_ERRORS) break; + if (env->internal->brk) { + env->internal->brk = false; + break; + } + } + } else if(seq->type == ROSCHA_HMAP) { + struct hmap_iter *iter = hmap_iter_new(seq->hmap); + const struct slice *key; + void *val; + hmap_iter_foreach(iter, &key, &val) { + struct roscha_object *item = val; + indexv->integer++; + roscha_hmap_set(env->vars, it, item); + r = eval_subblocks(env, r, loop->subblocks); + roscha_hmap_unset(env->vars, &it); + if (THERES_ERRORS) break; + if (env->internal->brk) { + env->internal->brk = false; + break; + } + } + } else { + eval_error(env, loop->seq->token, + "sequence should be of type %s or %s, got %s", + roscha_type_print(ROSCHA_VECTOR), + roscha_type_print(ROSCHA_HMAP), roscha_type_print(seq->type)); + } + + if (outerloop) { + roscha_hmap_set(env->vars, loopk, outerloop); + roscha_object_unref(outerloop); + roscha_object_unref(loopv); + } else { + roscha_hmap_unset(env->vars, &loopk); + } + if (outeritem) { + roscha_hmap_set(env->vars, it, outeritem); + roscha_object_unref(outeritem); + } + roscha_object_unref(indexv); + roscha_object_unref(loopv); + roscha_object_unref(seq); + + return r; +} + +static inline struct tblock * +get_child_tblock(struct roscha_env *env, struct slice *name, + const struct template *tmpl) +{ + struct tblock *tblk = NULL; + if (tmpl->child) { + tblk = get_child_tblock(env, name, tmpl->child); + } + if (!tblk) { + return hmap_gets(tmpl->tblocks, name); + } + + return tblk; +} + +static inline sds +eval_tblock(struct roscha_env *env, sds r, struct tblock *tblk) +{ + struct tblock *child = get_child_tblock(env, &tblk->name.token.literal, + env->internal->eval_tmpl); + if (child) { + tblk = child; + } + + return eval_subblocks(env, r, tblk->subblocks); +} + +static inline sds +eval_tag(struct roscha_env *env, sds r, struct tag *tag) +{ + switch (tag->type) { + case TAG_IF: + return eval_branch(env, r, tag->cond.root); + case TAG_FOR: + return eval_loop(env, r, &tag->loop); + case TAG_BLOCK: + return eval_tblock(env, r, &tag->tblock); + case TAG_EXTENDS:{ + eval_error(env, tag->token, "extends tag can only be the first tag", + tag->token); + break; + } + case TAG_BREAK: + env->internal->brk = true; + break; + default: + break; + } + + return r; +} + +static inline sds +eval_block(struct roscha_env *env, sds r, struct block *blk) +{ + switch (blk->type) { + case BLOCK_CONTENT: + return slice_string(&blk->token.literal, r); + case BLOCK_VARIABLE: + return eval_variable(env, r, &blk->variable); + case BLOCK_TAG: + return eval_tag(env, r, &blk->tag); + } +} + +static inline sds +eval_subblocks(struct roscha_env *env, sds r, struct vector *blks) +{ + size_t i; + struct block *blk; + vector_foreach (blks, i, blk) { + r = eval_block(env, r, blk); + if (THERES_ERRORS) return r; + if (env->internal->brk) return r; + } + + return r; +} + +static inline sds +eval_template(struct roscha_env *env, const struct slice *name, + struct template *child) +{ + struct template *tmpl = hmap_gets(env->internal->templates, name); + if (!tmpl) { + sds errmsg = sdscat(sdsempty(), "template \""); + errmsg = slice_string(name, errmsg); + errmsg = sdscat(errmsg, "\" not found"); + vector_push(env->errors, errmsg); + return NULL; + } + + tmpl->child = child; + env->internal->eval_tmpl = tmpl; + + struct block *blk = tmpl->blocks->values[0]; + if (blk->type == BLOCK_TAG && blk->tag.type == TAG_EXTENDS) { + struct slice name = blk->tag.parent.name->value; + return eval_template(env, &name, tmpl); + } + + sds r = sdsempty(); + r = eval_subblocks(env, r, tmpl->blocks); + + env->internal->eval_tmpl = NULL; + + return r; +} + +void +roscha_init(void) +{ + parser_init(); +} + +void +roscha_deinit(void) +{ + parser_deinit(); +} + +struct roscha_env * +roscha_env_new(void) +{ + struct roscha_env *env = calloc(1, sizeof(*env)); + env->internal = calloc(1, sizeof(*env->internal)); + env->vars = roscha_object_new(hmap_new()); + env->internal->templates = hmap_new(); + env->errors = vector_new(); + + return env; +} + +bool +roscha_env_add_template(struct roscha_env *env, char *name, char *body) +{ + struct parser *parser = parser_new(name, body); + struct template *tmpl = parser_parse_template(parser); + if (parser->errors->len > 0) { + sds errmsg = NULL; + while ((errmsg = vector_pop(parser->errors)) != NULL) { + vector_push(env->errors, errmsg); + } + parser_destroy(parser); + template_destroy(tmpl); + return false; + } + parser_destroy(parser); + hmap_sets(env->internal->templates, slice_whole(name), tmpl); + return true; +} + +bool +roscha_env_load_dir(struct roscha_env *env, const char *path) +{ + DIR *dir = opendir(path); + if (!dir) { + sds errmsg = sdscatfmt(sdsempty(), "unable to open dir %s, error %s", + path, strerror(errno)); + vector_push(env->errors, errmsg); + return false; + } + struct dirent *ent; + while ((ent = readdir(dir))) { + if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) { + continue; + } + + struct stat fstats; + sds fpath = sdscatfmt(sdsempty(), "%s/%s", path, ent->d_name); + if (stat(fpath, &fstats)) { + sds errmsg = sdscatfmt(sdsempty(), + "unable to stat file %s, error %s", fpath, strerror(errno)); + vector_push(env->errors, errmsg); + closedir(dir); + return false; + } + if (S_ISDIR(fstats.st_mode)) continue; + + char *name = malloc(strlen(ent->d_name) + 1); + strcpy(name, ent->d_name); + + int fd = open(fpath, O_RDONLY); + char buf[BUFSIZE]; + sds body = sdsempty(); + size_t nread; + while ((nread = read(fd, buf, BUFSIZE)) > 0) { + buf[nread] = '\0'; + body = sdscat(body, buf); + } + close(fd); + if (nread < 0) { + sds errmsg = sdscatfmt(sdsempty(), + "unable to read file %s, error %s", fpath, strerror(errno)); + vector_push(env->errors, errmsg); + goto fileerr; + } + if (!roscha_env_add_template(env, name, body)) { + goto fileerr; + } + continue; +fileerr: + closedir(dir); + sdsfree(body); + return false; + } + + closedir(dir); + return true; +} + +sds +roscha_env_render(struct roscha_env *env, const char *name) +{ + struct slice sname = slice_whole(name); + return eval_template(env, &sname, NULL); +} + +struct vector * +roscha_env_check_errors(struct roscha_env *env) +{ + if (env->errors->len > 0) return env->errors; + return NULL; +} + +void +roscha_env_destroy(struct roscha_env *env) +{ + size_t i; + sds errmsg; + vector_foreach (env->errors, i, errmsg) { + sdsfree(errmsg); + } + vector_free(env->errors); + roscha_object_unref(env->vars); + hmap_destroy(env->internal->templates, roscha_env_destroy_templates_cb); + free(env->internal); + free(env); +} diff --git a/src/slice.c b/src/slice.c new file mode 100644 index 0000000..5d89d9e --- /dev/null +++ b/src/slice.c @@ -0,0 +1,63 @@ +#include "slice.h" + +#include +#include + +struct slice +slice_new(const char *str, size_t start, size_t end) +{ + struct slice slice = { + .str = str, + .start = start, + .end = end, + }; + + return slice; +} + +void +slice_set(struct slice *slice, const char *str, size_t start, size_t end) +{ + slice->str = str; + slice->start = start; + slice->end = end; +} + +size_t +slice_len(const struct slice *slice) +{ + return slice->end - slice->start; +} + +int +slice_cmp(const struct slice *restrict a, const struct slice *restrict b) +{ + size_t lena = slice_len(a), lenb = slice_len(b); + int lencmp = (lena > lenb) - (lena < lenb); + if (lencmp) { + return lencmp; + } + + for (size_t i = 0; i < lena; i++) { + char ca = a->str[a->start + i], cb = b->str[b->start + i]; + int cmp = (ca > cb) - (ca < cb); + if (cmp) return cmp; + } + + return 0; +} + +void +slice_cpy(struct slice *dst, const struct slice *src) +{ + dst->str = src->str; + dst->start = src->start; + dst->end = src->end; +} + +sds +slice_string(const struct slice *slice, sds str) +{ + size_t len = slice->end - slice->start; + return sdscatlen(str, slice->str + slice->start, len); +} diff --git a/src/tests/lexer.c b/src/tests/lexer.c new file mode 100644 index 0000000..a9cb142 --- /dev/null +++ b/src/tests/lexer.c @@ -0,0 +1,246 @@ +#include "tests/tests.h" +#include "lexer.h" + +#include + +#include "slice.h" +#include "token.h" + +static void +test_next_token(void) +{ + char *input = "{% extends \"template\" %}\n" + "{% block rooster %}\n" + "{% if true %}\n" + "some content\n" + "{% elif not false %}\n" + "other content\n" + "{% else %}\n" + "yet something else\n" + "{% endif %}\n" + "{% for v in list %}\n" + "{{ v+(1-2)*4/5 }}\n" + "{% break %}\n" + "{% endfor %}\n" + "{% endblock %}\n" + "{{ list[1] }}\n" + "{{ map.value }}\n" + "{{ 5 < 10 }}\n" + "{{ 10 > 5 }}\n" + "{{ 5 <= 10 }}\n" + "{{ 10 >= 5 }}\n" + "{{ 10 != 5 }}\n" + "{{ 5 == 5 }}\n" + "{{ 5 and 5 }}\n" + "{{ 5 or 5 }}\n"; + + token_init_keywords(); + struct lexer *lexer = lexer_new(input); + struct token expected[] = { + { TOKEN_LBRACE, slice_whole("{"), 1, 1 }, + { TOKEN_PERCENT, slice_whole("%"), 1, 2 }, + { TOKEN_EXTENDS, slice_whole("extends"), 1, 4 }, + { TOKEN_STRING, slice_whole("\"template\""), 1, 12 }, + { TOKEN_PERCENT, slice_whole("%"), 1, 23 }, + { TOKEN_RBRACE, slice_whole("}"), 1, 24 }, + { TOKEN_CONTENT, slice_whole("\n"), 1, 25 }, + + { TOKEN_LBRACE, slice_whole("{"), 2, 1 }, + { TOKEN_PERCENT, slice_whole("%"), 2, 2 }, + { TOKEN_BLOCK, slice_whole("block"), 2, 4 }, + { TOKEN_IDENT, slice_whole("rooster"), 2, 10 }, + { TOKEN_PERCENT, slice_whole("%"), 2, 18 }, + { TOKEN_RBRACE, slice_whole("}"), 2, 19 }, + { TOKEN_CONTENT, slice_whole("\n"), 2, 20 }, + + { TOKEN_LBRACE, slice_whole("{"), 3, 1 }, + { TOKEN_PERCENT, slice_whole("%"), 3, 2 }, + { TOKEN_IF, slice_whole("if"), 3, 4 }, + { TOKEN_TRUE, slice_whole("true"), 3, 7 }, + { TOKEN_PERCENT, slice_whole("%"), 3, 12 }, + { TOKEN_RBRACE, slice_whole("}"), 3, 13 }, + { TOKEN_CONTENT, slice_whole("\nsome content\n"), 3, 14 }, + + { TOKEN_LBRACE, slice_whole("{"), 5, 1 }, + { TOKEN_PERCENT, slice_whole("%"), 5, 2 }, + { TOKEN_ELIF, slice_whole("elif"), 5, 4 }, + { TOKEN_NOT, slice_whole("not"), 5, 9 }, + { TOKEN_FALSE, slice_whole("false"), 5, 12 }, + { TOKEN_PERCENT, slice_whole("%"), 5, 18 }, + { TOKEN_RBRACE, slice_whole("}"), 5, 19 }, + { TOKEN_CONTENT, slice_whole("\nother content\n"), 5, 20 }, + + { TOKEN_LBRACE, slice_whole("{"), 7, 1 }, + { TOKEN_PERCENT, slice_whole("%"), 7, 2 }, + { TOKEN_ELSE, slice_whole("else"), 7, 4 }, + { TOKEN_PERCENT, slice_whole("%"), 7, 9 }, + { TOKEN_RBRACE, slice_whole("}"), 7, 10 }, + { TOKEN_CONTENT, slice_whole("\nyet something else\n"), 7, 11 }, + + { TOKEN_LBRACE, slice_whole("{"), 9, 1 }, + { TOKEN_PERCENT, slice_whole("%"), 9, 2 }, + { TOKEN_ENDIF, slice_whole("endif"), 9, 4 }, + { TOKEN_PERCENT, slice_whole("%"), 9, 10 }, + { TOKEN_RBRACE, slice_whole("}"), 9, 11 }, + { TOKEN_CONTENT, slice_whole("\n"), 9, 12 }, + + { TOKEN_LBRACE, slice_whole("{"), 10, 1 }, + { TOKEN_PERCENT, slice_whole("%"), 10, 2 }, + { TOKEN_FOR, slice_whole("for"), 10, 4 }, + { TOKEN_IDENT, slice_whole("v"), 10, 8 }, + { TOKEN_IN, slice_whole("in"), 10, 10 }, + { TOKEN_IDENT, slice_whole("list"), 10, 13 }, + { TOKEN_PERCENT, slice_whole("%"), 10, 18 }, + { TOKEN_RBRACE, slice_whole("}"), 10, 19 }, + { TOKEN_CONTENT, slice_whole("\n"), 10, 20 }, + + { TOKEN_LBRACE, slice_whole("{"), 11, 1 }, + { TOKEN_LBRACE, slice_whole("{"), 11, 2 }, + { TOKEN_IDENT, slice_whole("v"), 11, 4 }, + { TOKEN_PLUS, slice_whole("+"), 11, 5 }, + { TOKEN_LPAREN, slice_whole("("), 11, 6 }, + { TOKEN_INT, slice_whole("1"), 11, 7 }, + { TOKEN_MINUS, slice_whole("-"), 11, 8 }, + { TOKEN_INT, slice_whole("2"), 11, 9 }, + { TOKEN_RPAREN, slice_whole(")"), 11, 10 }, + { TOKEN_ASTERISK, slice_whole("*"), 11, 11 }, + { TOKEN_INT, slice_whole("4"), 11, 12 }, + { TOKEN_SLASH, slice_whole("/"), 11, 13 }, + { TOKEN_INT, slice_whole("5"), 11, 14 }, + { TOKEN_RBRACE, slice_whole("}"), 11, 16 }, + { TOKEN_RBRACE, slice_whole("}"), 11, 17 }, + { TOKEN_CONTENT, slice_whole("\n"), 11, 18 }, + + { TOKEN_LBRACE, slice_whole("{"), 12, 1 }, + { TOKEN_PERCENT, slice_whole("%"), 12, 2 }, + { TOKEN_BREAK, slice_whole("break"), 12, 4 }, + { TOKEN_PERCENT, slice_whole("%"), 12, 10 }, + { TOKEN_RBRACE, slice_whole("}"), 12, 11 }, + { TOKEN_CONTENT, slice_whole("\n"), 12, 12 }, + + { TOKEN_LBRACE, slice_whole("{"), 13, 1 }, + { TOKEN_PERCENT, slice_whole("%"), 13, 2 }, + { TOKEN_ENDFOR, slice_whole("endfor"), 13, 4 }, + { TOKEN_PERCENT, slice_whole("%"), 13, 11 }, + { TOKEN_RBRACE, slice_whole("}"), 13, 12 }, + { TOKEN_CONTENT, slice_whole("\n"), 13, 13 }, + + { TOKEN_LBRACE, slice_whole("{"), 14, 1 }, + { TOKEN_PERCENT, slice_whole("%"), 14, 2 }, + { TOKEN_ENDBLOCK, slice_whole("endblock"), 14, 4 }, + { TOKEN_PERCENT, slice_whole("%"), 14, 13 }, + { TOKEN_RBRACE, slice_whole("}"), 14, 14 }, + { TOKEN_CONTENT, slice_whole("\n"), 14, 15 }, + + { TOKEN_LBRACE, slice_whole("{"), 15, 1 }, + { TOKEN_LBRACE, slice_whole("{"), 15, 2 }, + { TOKEN_IDENT, slice_whole("list"), 15, 4 }, + { TOKEN_LBRACKET, slice_whole("["), 15, 8 }, + { TOKEN_INT, slice_whole("1"), 15, 9 }, + { TOKEN_RBRACKET, slice_whole("]"), 15, 10 }, + { TOKEN_RBRACE, slice_whole("}"), 15, 12 }, + { TOKEN_RBRACE, slice_whole("}"), 15, 13 }, + { TOKEN_CONTENT, slice_whole("\n"), 15, 14 }, + + { TOKEN_LBRACE, slice_whole("{"), 16, 1 }, + { TOKEN_LBRACE, slice_whole("{"), 16, 2 }, + { TOKEN_IDENT, slice_whole("map"), 16, 4 }, + { TOKEN_DOT, slice_whole("."), 16, 7 }, + { TOKEN_IDENT, slice_whole("value"), 16, 8 }, + { TOKEN_RBRACE, slice_whole("}"), 16, 10 }, + { TOKEN_RBRACE, slice_whole("}"), 16, 11 }, + { TOKEN_CONTENT, slice_whole("\n"), 16, 12 }, + + { TOKEN_LBRACE, slice_whole("{"), 17, 1 }, + { TOKEN_LBRACE, slice_whole("{"), 17, 2 }, + { TOKEN_INT, slice_whole("5"), 17, 4 }, + { TOKEN_LT, slice_whole("<"), 17, 6 }, + { TOKEN_INT, slice_whole("10"), 17, 8 }, + { TOKEN_RBRACE, slice_whole("}"), 17, 11 }, + { TOKEN_RBRACE, slice_whole("}"), 17, 12 }, + { TOKEN_CONTENT, slice_whole("\n"), 17, 13 }, + + { TOKEN_LBRACE, slice_whole("{"), 18, 1 }, + { TOKEN_LBRACE, slice_whole("{"), 18, 2 }, + { TOKEN_INT, slice_whole("10"), 18, 4 }, + { TOKEN_GT, slice_whole(">"), 18, 7 }, + { TOKEN_INT, slice_whole("5"), 18, 9 }, + { TOKEN_RBRACE, slice_whole("}"), 18, 11 }, + { TOKEN_RBRACE, slice_whole("}"), 18, 12 }, + { TOKEN_CONTENT, slice_whole("\n"), 18, 13 }, + + { TOKEN_LBRACE, slice_whole("{"), 19, 1 }, + { TOKEN_LBRACE, slice_whole("{"), 19, 2 }, + { TOKEN_INT, slice_whole("5"), 19, 4 }, + { TOKEN_LTE, slice_whole("<="), 19, 6 }, + { TOKEN_INT, slice_whole("10"), 19, 9 }, + { TOKEN_RBRACE, slice_whole("}"), 19, 12 }, + { TOKEN_RBRACE, slice_whole("}"), 19, 13 }, + { TOKEN_CONTENT, slice_whole("\n"), 19, 14 }, + + { TOKEN_LBRACE, slice_whole("{"), 20, 1 }, + { TOKEN_LBRACE, slice_whole("{"), 20, 2 }, + { TOKEN_INT, slice_whole("10"), 20, 4 }, + { TOKEN_GTE, slice_whole(">="), 20, 7 }, + { TOKEN_INT, slice_whole("5"), 20, 10 }, + { TOKEN_RBRACE, slice_whole("}"), 20, 12 }, + { TOKEN_RBRACE, slice_whole("}"), 20, 13 }, + { TOKEN_CONTENT, slice_whole("\n"), 20, 14 }, + + { TOKEN_LBRACE, slice_whole("{"), 21, 1 }, + { TOKEN_LBRACE, slice_whole("{"), 21, 2 }, + { TOKEN_INT, slice_whole("10"), 21, 4 }, + { TOKEN_NOTEQ, slice_whole("!="), 21, 7 }, + { TOKEN_INT, slice_whole("5"), 21, 10 }, + { TOKEN_RBRACE, slice_whole("}"), 21, 12 }, + { TOKEN_RBRACE, slice_whole("}"), 21, 13 }, + { TOKEN_CONTENT, slice_whole("\n"), 21, 14 }, + + { TOKEN_LBRACE, slice_whole("{"), 22, 1 }, + { TOKEN_LBRACE, slice_whole("{"), 22, 2 }, + { TOKEN_INT, slice_whole("5"), 22, 4 }, + { TOKEN_EQ, slice_whole("=="), 22, 6 }, + { TOKEN_INT, slice_whole("5"), 22, 9 }, + { TOKEN_RBRACE, slice_whole("}"), 22, 11 }, + { TOKEN_RBRACE, slice_whole("}"), 22, 12 }, + { TOKEN_CONTENT, slice_whole("\n"), 22, 13 }, + + { TOKEN_LBRACE, slice_whole("{"), 23, 1 }, + { TOKEN_LBRACE, slice_whole("{"), 23, 2 }, + { TOKEN_INT, slice_whole("5"), 23, 4 }, + { TOKEN_AND, slice_whole("and"), 23, 6 }, + { TOKEN_INT, slice_whole("5"), 23, 10 }, + { TOKEN_RBRACE, slice_whole("}"), 23, 12 }, + { TOKEN_RBRACE, slice_whole("}"), 23, 13 }, + { TOKEN_CONTENT, slice_whole("\n"), 23, 14 }, + + { TOKEN_LBRACE, slice_whole("{"), 24, 1 }, + { TOKEN_LBRACE, slice_whole("{"), 24, 2 }, + { TOKEN_INT, slice_whole("5"), 24, 4 }, + { TOKEN_OR, slice_whole("or"), 24, 6 }, + { TOKEN_INT, slice_whole("5"), 24, 9 }, + { TOKEN_RBRACE, slice_whole("}"), 24, 11 }, + { TOKEN_RBRACE, slice_whole("}"), 24, 12 }, + { TOKEN_CONTENT, slice_whole("\n"), 24, 13 }, + + { TOKEN_EOF, }, + }; + size_t i = 0; + + do { + struct token token = lexer_next_token(lexer); + asserteq(token.type, expected[i].type); + asserteq(slice_cmp(&token.literal, &expected[i].literal), 0); + i++; + } while (expected[i].type != TOKEN_EOF); + + lexer_destroy(lexer); + token_free_keywords(); +} + +int +main(void) +{ + INIT_TESTS(); + RUN_TEST(test_next_token); +} diff --git a/src/tests/parser.c b/src/tests/parser.c new file mode 100644 index 0000000..eb5c2a1 --- /dev/null +++ b/src/tests/parser.c @@ -0,0 +1,542 @@ +#define _POSIX_C_SOURCE 200809L +#include "parser.h" +#include "tests/tests.h" + +#include "ast.h" +#include "slice.h" + +#include + +enum value_type { + VALUE_IDENT, + VALUE_INT, + VALUE_BOOL, + VALUE_STRING, +}; + +struct value { + union { + struct slice ident; + struct slice string; + int64_t integer; + bool boolean; + }; + enum value_type type; +}; + +static void +check_parser_errors(struct parser *parser, const char *file, int line, + const char *func) +{ + if (parser->errors->len > 0) { + printf("\n"); + size_t i; + sds val; + vector_foreach (parser->errors, i, val) { + printf("parser error %lu: %s\n", i, val); + } + printf(TBLD TRED "FAIL!\n" TRST); + printf("%s:%d: %s: ", file, line, func); + printf("parser encountered errors\n"); + abort(); + } +} + +#define check_parser_errors(p) \ + check_parser_errors(p, __FILE__, __LINE__, __func__) + +static void +test_integer_literal(struct expression *expr, int64_t val) +{ + char buf[128]; + struct slice sval; + asserteq(expr->type, EXPRESSION_INT); + asserteq(expr->integer.value, val); + sprintf(buf, "%ld", val); + sval = slice_whole(buf); + asserteq(slice_cmp(&expr->token.literal, &sval), 0); +} + +static void +test_identifier(struct expression *expr, struct slice *ident) +{ + asserteq(expr->type, EXPRESSION_IDENT); + asserteq(slice_cmp(&expr->token.literal, ident), 0); +} + +static void +test_boolean_literal(struct expression *expr, bool val) +{ + char *str = val ? "true" : "false"; + struct slice sval = slice_whole(str); + asserteq(expr->type, EXPRESSION_BOOL); + asserteq(expr->boolean.value, val); + asserteq(slice_cmp(&expr->token.literal, &sval), 0); +} + +static void +test_string_literal(struct expression *expr, const struct slice *val) +{ + asserteq(expr->type, EXPRESSION_STRING); + asserteq(slice_cmp(&expr->string.value, val), 0); +} + + +static inline void +test_expected(struct expression *expr, struct value v) +{ + switch(v.type) { + case VALUE_IDENT: + test_identifier(expr, &v.ident); + break; + case VALUE_INT: + test_integer_literal(expr, v.integer); + break; + case VALUE_BOOL: + test_boolean_literal(expr, v.boolean); + break; + case VALUE_STRING: + test_string_literal(expr, &v.string); + break; + } +} + +#define VIDENT(v) \ + (struct value){ .type = VALUE_IDENT, .ident = slice_whole(v) } + +#define VINT(v) \ + (struct value){ .type = VALUE_INT, .integer = v } + +#define VBOOL(v) \ + (struct value){ .type = VALUE_BOOL, .boolean = v } + +#define VSTR(v) \ + (struct value){ .type = VALUE_STRING, .string = slice_whole(v) } + +static inline void +test_infix(struct infix *expr, struct value lval, struct slice *op, + struct value rval) +{ + test_expected(expr->left, lval); + asserteq(slice_cmp(&expr->operator, op), 0); + test_expected(expr->right, rval); +} + +static inline void +test_literal_variables(void) +{ + struct { + char *input; + struct value val; + } tests[] = { + { "{{ foo }}", VIDENT("foo"), }, + { "{{ 20 }}", VINT(20), }, + { "{{ true }}", VBOOL(true), }, + { "{{ false }}", VBOOL(false), }, + { 0 }, + }; + for (size_t i = 0; tests[i].input != NULL; i++) { + struct parser *parser = parser_new(strdup("test"), tests[i].input); + struct template *tmpl = parser_parse_template(parser); + check_parser_errors(parser); + assertneq(tmpl, NULL); + asserteq(tmpl->blocks->len, 1); + struct block *blk = tmpl->blocks->values[0]; + asserteq(blk->type, BLOCK_VARIABLE); + test_expected(blk->variable.expression, tests[i].val); + parser_destroy(parser); + template_destroy(tmpl); + } +} + +static inline void +test_prefix_variables(void) +{ + struct { + char *input; + struct slice operator; + struct value val; + } tests[] = { + { "{{ !foo }}", slice_whole("!"), VIDENT("foo"), }, + { "{{ -bar }}", slice_whole("-"), VIDENT("bar"), }, + { "{{ -20 }}", slice_whole("-"), VINT(20), }, + { "{{ !true }}", slice_whole("!"), VBOOL(true), }, + { "{{ !false }}", slice_whole("!"), VBOOL(false), }, + { "{{ not false }}", slice_whole("not"), VBOOL(false), }, + { 0 }, + }; + for (size_t i = 0; tests[i].input != NULL; i++) { + struct parser *parser = parser_new(strdup("test"), tests[i].input); + struct template *tmpl = parser_parse_template(parser); + check_parser_errors(parser); + assertneq(tmpl, NULL); + asserteq(tmpl->blocks->len, 1); + struct block *blk = tmpl->blocks->values[0]; + asserteq(blk->type, BLOCK_VARIABLE); + asserteq(blk->variable.expression->type, EXPRESSION_PREFIX); + struct expression *pref = blk->variable.expression; + asserteq(slice_cmp(&pref->prefix.operator, &tests[i].operator), 0); + test_expected(pref->prefix.right, tests[i].val); + parser_destroy(parser); + template_destroy(tmpl); + } +} + +static inline void +test_infix_variables(void) +{ + struct { + char *input; + struct value left; + struct slice operator; + struct value right; + } tests[] = { + { "{{ foo + bar }}", VIDENT("foo"), slice_whole("+"), VIDENT("bar"), }, + { "{{ 6 - 9 }}", VINT(6),slice_whole("-"), VINT(9), }, + { "{{ 4 * 20 }}", VINT(4), slice_whole("*"), VINT(20), }, + { "{{ foo / 20 }}", VIDENT("foo"), slice_whole("/"), VINT(20), }, + { "{{ \"str\" == \"str\" }}", VSTR("str"), slice_whole("=="), VSTR("str"), }, + { "{{ true != false }}", VBOOL(true), slice_whole("!="), VBOOL(false), }, + { "{{ 4 < 20 }}", VINT(4), slice_whole("<"), VINT(20), }, + { "{{ 4 <= 20 }}", VINT(4), slice_whole("<="), VINT(20), }, + { "{{ 100 > 20 }}", VINT(100), slice_whole(">"), VINT(20), }, + { "{{ 100 >= 20 }}", VINT(100), slice_whole(">="), VINT(20), }, + { "{{ true and true }}", VBOOL(true), slice_whole("and"), VBOOL(true), }, + { "{{ true or false }}", VBOOL(true), slice_whole("or"), VBOOL(false), }, + { 0 }, + }; + for (size_t i = 0; tests[i].input != NULL; i++) { + struct parser *parser = parser_new(strdup("test"), tests[i].input); + struct template *tmpl = parser_parse_template(parser); + check_parser_errors(parser); + assertneq(tmpl, NULL); + asserteq(tmpl->blocks->len, 1); + struct block *blk = tmpl->blocks->values[0]; + asserteq(blk->type, BLOCK_VARIABLE); + asserteq(blk->variable.expression->type, EXPRESSION_INFIX); + test_infix(&blk->variable.expression->infix, + tests[i].left, &tests[i].operator, tests[i].right); + parser_destroy(parser); + template_destroy(tmpl); + } +} + +static inline void +test_map_variables(void) +{ + char *input = "{{ map.key }}"; + struct value left = VIDENT("map"); + struct value key = VIDENT("key"); + struct parser *parser = parser_new(strdup("test"), input); + struct template *tmpl = parser_parse_template(parser); + check_parser_errors(parser); + assertneq(tmpl, NULL); + asserteq(tmpl->blocks->len, 1); + struct block *blk = tmpl->blocks->values[0]; + asserteq(blk->type, BLOCK_VARIABLE); + asserteq(blk->variable.expression->type, EXPRESSION_MAPKEY); + struct expression *map = blk->variable.expression; + test_expected(map->indexkey.left, left); + test_expected(map->indexkey.key, key); + parser_destroy(parser); + template_destroy(tmpl); +} + +static inline void +test_index_variables(void) +{ + char *input = "{{ arr[1 + 2] }}"; + struct value left = VIDENT("arr"); + struct value ileft = VINT(1); + struct slice iop = slice_whole("+"); + struct value iright = VINT(2); + struct parser *parser = parser_new(strdup("test"), input); + struct template *tmpl = parser_parse_template(parser); + check_parser_errors(parser); + + assertneq(tmpl, NULL); + asserteq(tmpl->blocks->len, 1); + struct block *blk = tmpl->blocks->values[0]; + asserteq(blk->type, BLOCK_VARIABLE); + asserteq(blk->variable.expression->type, EXPRESSION_INDEX); + struct expression *map = blk->variable.expression; + test_expected(map->indexkey.left, left); + test_infix(&map->indexkey.key->infix, ileft, &iop, iright); + + parser_destroy(parser); + template_destroy(tmpl); +} + +static inline void +test_operator_precedence(void) +{ + struct { + char *input; + char *expected; + } tests[] = { + { + "{{ -a * b }}", + "{{ ((-a) * b) }}", + }, + { + "{{ 1 + 2 * 3 }}", + "{{ (1 + (2 * 3)) }}", + }, + { + "{{ !-a }}", + "{{ (!(-a)) }}", + }, + { + "{{ a + b + c }}", + "{{ ((a + b) + c) }}", + }, + { + "{{ a + b - c }}", + "{{ ((a + b) - c) }}", + }, + { + "{{ a * b * c }}", + "{{ ((a * b) * c) }}", + }, + { + "{{ a * b / c }}", + "{{ ((a * b) / c) }}", + }, + { + "{{ a + b / c }}", + "{{ (a + (b / c)) }}", + }, + { + "{{ a + b * c + d / e - f }}", + "{{ (((a + (b * c)) + (d / e)) - f) }}", + }, + { + "{{ 5 > 4 == 3 < 4 }}", + "{{ ((5 > 4) == (3 < 4)) }}", + }, + { + "{{ 5 < 4 != 3 > 4 }}", + "{{ ((5 < 4) != (3 > 4)) }}", + }, + { + "{{ 3 + 4 * 5 == 3 * 1 + 4 * 5 }}", + "{{ ((3 + (4 * 5)) == ((3 * 1) + (4 * 5))) }}", + }, + { + "{{ (5 + 5) * 2 }}", + "{{ ((5 + 5) * 2) }}", + }, + { + "{{ 2 / (5 + 5) }}", + "{{ (2 / (5 + 5)) }}", + }, + { + "{{ (5 + 5) * 2 * (5 + 5) }}", + "{{ (((5 + 5) * 2) * (5 + 5)) }}", + }, + { + "{{ -(5 + 5) }}", + "{{ (-(5 + 5)) }}", + }, + { + "{{ foo[0] + bar[1 + 2] }}", + "{{ (foo[0] + bar[(1 + 2)]) }}", + }, + { + "{{ foo.bar + foo.baz }}", + "{{ (foo.bar + foo.baz) }}", + }, + { + "{{ foo.bar + bar[0].baz * foo.bar.baz }}", + "{{ (foo.bar + (bar[0].baz * foo.bar.baz)) }}", + }, + { 0 }, + }; + for (size_t i = 0; tests[i].input != NULL; i++) { + struct parser *parser = parser_new(strdup("test"), tests[i].input); + struct template *tmpl = parser_parse_template(parser); + check_parser_errors(parser); + assertneq(tmpl, NULL); + asserteq(tmpl->blocks->len, 1); + struct block *blk = tmpl->blocks->values[0]; + asserteq(blk->type, BLOCK_VARIABLE); + sds output = block_string(blk, sdsempty()); + asserteq(strcmp(output, tests[i].expected), 0); + sdsfree(output); + parser_destroy(parser); + template_destroy(tmpl); + } +} + +static inline void +test_loop_tag(void) +{ + char *input = "{% for v in seq %}" + "{% break %}" + "{% endfor %}"; + struct slice item = slice_whole("v"); + struct slice seq = slice_whole("seq"); + struct parser *parser = parser_new(strdup("test"), input); + struct template *tmpl = parser_parse_template(parser); + check_parser_errors(parser); + + assertneq(tmpl, NULL); + asserteq(tmpl->blocks->len, 1); + struct block *blk = tmpl->blocks->values[0]; + asserteq(blk->type, BLOCK_TAG); + asserteq(blk->tag.type, TAG_FOR); + asserteq(slice_cmp(&blk->tag.loop.item.token.literal, &item), 0); + struct expression *seqexpr = blk->tag.loop.seq; + asserteq(seqexpr->type, EXPRESSION_IDENT); + asserteq(slice_cmp(&seqexpr->ident.token.literal, &seq), 0); + asserteq(blk->tag.loop.subblocks->len, 2); + struct block *sub1 = blk->tag.loop.subblocks->values[0]; + struct block *sub2 = blk->tag.loop.subblocks->values[1]; + asserteq(sub1->type, BLOCK_TAG); + asserteq(sub2->type, BLOCK_TAG); + asserteq(sub1->tag.type, TAG_BREAK); + asserteq(sub2->tag.type, TAG_CLOSE); + asserteq(sub2->tag.token.type, TOKEN_ENDFOR); + + parser_destroy(parser); + template_destroy(tmpl); +} + +static inline void +test_cond_tag(void) +{ + char *input = "{% if false %}" + "{{ foo }}" + "{% elif 1 > 2 %}" + "{{ bar }}" + "{% else %}" + "baz" + "{% endif %}"; + struct value ifexp = VIDENT("foo"); + struct value elifl = VINT(1); + struct slice elifop = slice_whole(">"); + struct value elifr = VINT(2); + struct value elifexp = VIDENT("bar"); + struct slice elsecont = slice_whole("baz"); + struct parser *parser = parser_new(strdup("test"), input); + struct template *tmpl = parser_parse_template(parser); + check_parser_errors(parser); + + assertneq(tmpl, NULL); + asserteq(tmpl->blocks->len, 1); + struct block *blk = tmpl->blocks->values[0]; + asserteq(blk->type, BLOCK_TAG); + asserteq(blk->tag.type, TAG_IF); + + struct branch *b1 = blk->tag.cond.root; + assertneq(b1->condition, NULL); + asserteq(b1->condition->type, EXPRESSION_BOOL); + asserteq(b1->condition->token.type, TOKEN_FALSE); + asserteq(b1->subblocks->len, 1); + struct block *b1sub1 = b1->subblocks->values[0]; + asserteq(b1sub1->type, BLOCK_VARIABLE); + test_expected(b1sub1->variable.expression, ifexp); + + struct branch *b2 = b1->next; + assertneq(b2->condition, NULL); + test_infix(&b2->condition->infix, elifl, &elifop, elifr); + asserteq(b2->subblocks->len, 1); + struct block *b2sub1 = b2->subblocks->values[0]; + asserteq(b2sub1->type, BLOCK_VARIABLE); + test_expected(b2sub1->variable.expression, elifexp); + + struct branch *b3 = b2->next; + asserteq(b3->condition, NULL); + asserteq(b3->subblocks->len, 2); + struct block *b3sub1 = b3->subblocks->values[0]; + asserteq(b3sub1->type, BLOCK_CONTENT); + asserteq(slice_cmp(&b3sub1->content.token.literal, &elsecont), 0); + struct block *b3sub2 = b3->subblocks->values[1]; + asserteq(b3sub2->tag.type, TAG_CLOSE); + asserteq(b3sub2->tag.token.type, TOKEN_ENDIF); + + parser_destroy(parser); + template_destroy(tmpl); +} + +static inline void +test_parent_tag(void) +{ + char *input = "{% extends \"base.html\" %}"; + struct slice name = slice_whole("base.html"); + struct parser *parser = parser_new(strdup("test"), input); + struct template *tmpl = parser_parse_template(parser); + check_parser_errors(parser); + + assertneq(tmpl, NULL); + asserteq(tmpl->blocks->len, 1); + struct block *blk = tmpl->blocks->values[0]; + asserteq(blk->type, BLOCK_TAG); + asserteq(blk->tag.type, TAG_EXTENDS); + asserteq(slice_cmp(&blk->tag.parent.name->value, &name), 0); + + parser_destroy(parser); + template_destroy(tmpl); +} + +static inline void +test_tblock_tag(void) +{ + char *input = "{% block cock %}" + "{% endblock %}"; + struct slice name = slice_whole("cock"); + struct parser *parser = parser_new(strdup("test"), input); + struct template *tmpl = parser_parse_template(parser); + check_parser_errors(parser); + + assertneq(tmpl, NULL); + asserteq(tmpl->blocks->len, 1); + struct block *blk = tmpl->blocks->values[0]; + asserteq(blk->type, BLOCK_TAG); + asserteq(blk->tag.type, TAG_BLOCK); + asserteq(slice_cmp(&blk->tag.tblock.name.token.literal, &name), 0); + asserteq(blk->tag.tblock.subblocks->len, 1); + struct block *sub1 = blk->tag.tblock.subblocks->values[0]; + asserteq(sub1->type, BLOCK_TAG); + asserteq(sub1->tag.type, TAG_CLOSE); + asserteq(sub1->tag.token.type, TOKEN_ENDBLOCK); + + blk = hmap_gets(tmpl->tblocks, &name); + assertneq(blk, NULL); + asserteq(blk->type, BLOCK_TAG); + asserteq(blk->tag.type, TAG_BLOCK); + asserteq(slice_cmp(&blk->tag.tblock.name.token.literal, &name), 0); + + parser_destroy(parser); + template_destroy(tmpl); +} + +static void +init(void) +{ + parser_init(); +} + +static void +cleanup(void) +{ + parser_deinit(); +} + +int +main(void) +{ + init(); + INIT_TESTS(); + RUN_TEST(test_literal_variables); + RUN_TEST(test_prefix_variables); + RUN_TEST(test_infix_variables); + RUN_TEST(test_map_variables); + RUN_TEST(test_index_variables); + RUN_TEST(test_operator_precedence); + RUN_TEST(test_loop_tag); + RUN_TEST(test_cond_tag); + RUN_TEST(test_parent_tag); + RUN_TEST(test_tblock_tag); + cleanup(); +} diff --git a/src/tests/roscha.c b/src/tests/roscha.c new file mode 100644 index 0000000..92b8bc4 --- /dev/null +++ b/src/tests/roscha.c @@ -0,0 +1,189 @@ +#define _POSIX_C_SOURCE 200809L +#include "tests/tests.h" +#include "roscha.h" + +#include + +static void +check_env_errors(struct roscha_env *env, const char *file, int line, + const char *func) +{ + struct vector *errors = roscha_env_check_errors(env); + if (!errors) return; + printf("\n"); + size_t i; + sds val; + vector_foreach (errors, i, val) { + printf("parser error %lu: %s\n", i, val); + } + printf(TBLD TRED "FAIL!\n" TRST); + printf("%s:%d: %s: ", file, line, func); + printf("parser encountered errors\n"); + abort(); +} + +#define check_env_errors(p) \ + check_env_errors(p, __FILE__, __LINE__, __func__) + +static void +test_eval_variable(void) +{ + char *input = "{{ foo.bar }}" + "{{ foo.bar + foo.baz }}" + "{{ foo.bar - foo.baz }}" + "{{ foo.bar * foo.baz }}" + "{{ foo.bar / foo.baz }}" + "{{ l }}" + "{{ l[0] }}, {{ l[1] }}" + ""; + char *expected = "8" + "12" + "4" + "32" + "2" + "[ hello, world, ]" + "hello, world" + ""; + struct roscha_object *foo = roscha_object_new(hmap_new()); + roscha_hmap_set_new(foo, "bar", 8); + roscha_hmap_set_new(foo, "baz", 4); + struct roscha_object *l = roscha_object_new(vector_new()); + roscha_vector_push_new(l, (slice_whole("hello"))); + roscha_vector_push_new(l, (slice_whole("world"))); + struct roscha_env *env = roscha_env_new(); + roscha_env_add_template(env, strdup("test"), input); + check_env_errors(env); + roscha_hmap_set(env->vars, "foo", foo); + roscha_hmap_set(env->vars, "l", l); + + sds got = roscha_env_render(env, "test"); + check_env_errors(env); + asserteq(strcmp(got, expected), 0); + roscha_env_destroy(env); + sdsfree(got); + roscha_object_unref(foo); + roscha_object_unref(l); +} + +static void +test_eval_cond(void) +{ + char *input = "{% if foo > bar %}" + "Yes" + "{% elif baz %}" + "Maybe" + "{% else %}" + "No" + "{% endif %}"; + char *expected1 = "No"; + char *expected2 = "Maybe"; + char *expected3 = "Yes"; + struct roscha_object *foo = roscha_object_new(10); + struct roscha_object *bar = roscha_object_new(20); + struct roscha_object *baz = roscha_object_new(69); + + struct roscha_env *env = roscha_env_new(); + roscha_env_add_template(env, strdup("test"), input); + check_env_errors(env); + roscha_hmap_set(env->vars, "foo", foo); + roscha_hmap_set(env->vars, "bar", bar); + + sds got = roscha_env_render(env, "test"); + check_env_errors(env); + asserteq(strcmp(got, expected1), 0); + sdsfree(got); + + roscha_hmap_set(env->vars, "baz", baz); + got = roscha_env_render(env, "test"); + check_env_errors(env); + asserteq(strcmp(got, expected2), 0); + sdsfree(got); + + foo->integer = 420; + got = roscha_env_render(env, "test"); + check_env_errors(env); + asserteq(strcmp(got, expected3), 0); + sdsfree(got); + + roscha_env_destroy(env); + roscha_object_unref(foo); + roscha_object_unref(bar); + roscha_object_unref(baz); +} + +static void +test_eval_loop(void) +{ + char *input = "{% for v in foo %}" + "{{ loop.index }}" + "{{ v }}" + "{% endfor %}"; + char *expected = "0hello1world"; + + struct roscha_object *foo = roscha_object_new(vector_new()); + roscha_vector_push_new(foo, (slice_whole("hello"))); + roscha_vector_push_new(foo, (slice_whole("world"))); + + struct roscha_env *env = roscha_env_new(); + roscha_env_add_template(env, strdup("test"), input); + check_env_errors(env); + roscha_hmap_set(env->vars, "foo", foo); + sds got = roscha_env_render(env, "test"); + check_env_errors(env); + asserteq(strcmp(got, expected), 0); + + sdsfree(got); + roscha_env_destroy(env); + roscha_object_unref(foo); +} + +static void +test_eval_child(void) +{ + char *parent = "hello{% block title %}{% endblock %}" + "{% block content %}Content{% endblock %}" + "{% block foot %}Foot{% endblock %}"; + char *child = "{% extends \"parent\" %}" + "{% block title %}, world{% endblock %}" + "{% block content %}" + "In a beautiful place out in the country." + "{% endblock %}"; + char *expected = "hello, world" + "In a beautiful place out in the country." + "Foot"; + struct roscha_env *env = roscha_env_new(); + roscha_env_add_template(env, strdup("parent"), parent); + check_env_errors(env); + roscha_env_add_template(env, strdup("child"), child); + check_env_errors(env); + sds got = roscha_env_render(env, "child"); + check_env_errors(env); + asserteq(strcmp(got, expected), 0); + + sdsfree(got); + roscha_env_destroy(env); +} + +static void +init(void) +{ + roscha_init(); +} + +static void +cleanup(void) +{ + roscha_deinit(); +} + +int +main(void) +{ + init(); + INIT_TESTS(); + RUN_TEST(test_eval_variable); + RUN_TEST(test_eval_cond); + RUN_TEST(test_eval_loop); + RUN_TEST(test_eval_child); + cleanup(); +} diff --git a/src/tests/slice.c b/src/tests/slice.c new file mode 100644 index 0000000..1f3875a --- /dev/null +++ b/src/tests/slice.c @@ -0,0 +1,41 @@ +#include "tests/tests.h" +#include "slice.h" + +#include + +static void +test_slice_cmp(void) +{ + struct slice s1a = { + .str = "hello world", + .start = 6, + .end = 11, + }; + struct slice s1b = { + .str = "world", + .start = 0, + .end = 5, + }; + asserteq(slice_cmp(&s1a, &s1b), 0); +} + +static void +test_slice_string(void) +{ + struct slice slice = { + .str = "hello world", + .start = 6, + .end = 11, + }; + sds str = sdsempty(); + slice_string(&slice, str); + asserteq(strcmp("world", str), 0); +} + +int +main(void) +{ + INIT_TESTS(); + RUN_TEST(test_slice_cmp); + RUN_TEST(test_slice_string); +} diff --git a/src/token.c b/src/token.c new file mode 100644 index 0000000..0d0092d --- /dev/null +++ b/src/token.c @@ -0,0 +1,144 @@ +#include "token.h" + +#include +#include + +#include "hmap.h" + +static struct hmap *keywords = NULL; +static const char *keys[] = { + "and", + "or", + "not", + "for", + "in", + "break", + "endfor", + "true", + "false", + "if", + "elif", + "else", + "endif", + "extends", + "block", + "endblock", + NULL, +}; +enum token_type values[] = { + TOKEN_AND, + TOKEN_OR, + TOKEN_NOT, + TOKEN_FOR, + TOKEN_IN, + TOKEN_BREAK, + TOKEN_ENDFOR, + TOKEN_TRUE, + TOKEN_FALSE, + TOKEN_IF, + TOKEN_ELIF, + TOKEN_ELSE, + TOKEN_ENDIF, + TOKEN_EXTENDS, + TOKEN_BLOCK, + TOKEN_ENDBLOCK, + TOKEN_EOF, +}; + +const char *token_types[] = { + "ILLEGAL", + "EOF", + /* Identifiers/Literals */ + "IDENTIFIER", + "INTEGER", + "STRING", + /* Operators */ + "=", + "+", + "-", + "!", + "*", + "/", + "<", + ">", + "<=", + ">=", + "==", + "!=", + "and", + "or", + "not", + /* Delimiters */ + ".", + ",", + "(", + ")", + "{", + "}", + "[", + "]", + "#", + "%", + /* Keywords */ + "for", + "in", + "break", + "endfor", + "true", + "false", + "if", + "elif", + "else", + "endif", + "extends", + "block", + "endblock", + /* The document content */ + "CONTENT", +}; + +void +token_init_keywords(void) +{ + if (keywords == NULL) { + keywords = hmap_new(); + for (size_t i = 0; keys[i] != NULL; i++) { + hmap_set(keywords, keys[i], values + i); + } + } + +} + +enum token_type +token_lookup_ident(const struct slice *ident) +{ + enum token_type *t = hmap_gets(keywords, ident); + if (t) { + return *t; + } + + return TOKEN_IDENT; +} + +extern inline const char * +token_type_print(enum token_type t) +{ + return token_types[t]; +} + +sds +token_string(struct token *token, sds str) +{ + const char *type = token_type_print(token->type); + sds slicebuf = sdsempty(); + sdscatfmt(str, "TOKEN: type: %s, literal: %s", type, + slice_string(&token->literal, slicebuf)); + sdsfree(slicebuf); + return str; +} + +void +token_free_keywords(void) +{ + hmap_free(keywords); +} diff --git a/src/vector.c b/src/vector.c new file mode 100644 index 0000000..2962780 --- /dev/null +++ b/src/vector.c @@ -0,0 +1,49 @@ +#include "vector.h" + +static inline bool +vector_grow(struct vector *vec) +{ + vec->cap *= 2; + vec->values = realloc(vec->values, sizeof(vec->values) * vec->cap); + return vec->values != NULL; +} + +struct vector * +vector_new_with_cap(size_t cap) +{ + struct vector *vec = malloc(sizeof(*vec)); + if (!vec) return NULL; + vec->values = malloc(sizeof(vec->values) * cap); + if (!vec->values) { + free(vec); + return NULL; + } + vec->cap = cap; + vec->len = 0; + + return vec; +} + +ssize_t +vector_push(struct vector *vec, void *val) +{ + if (vec->len >= vec->cap && !vector_grow(vec)) return -1; + vec->values[vec->len] = val; + vec->len++; + + return vec->len - 1; +} + +void * +vector_pop(struct vector *vec) +{ + if (vec->len == 0) return NULL; + return vec->values[--vec->len]; +} + +void +vector_free(struct vector *vec) +{ + free(vec->values); + free(vec); +} -- cgit v1.2.3