diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/ast.c | 322 | ||||
| -rw-r--r-- | src/hmap.c | 222 | ||||
| -rw-r--r-- | src/lexer.c | 264 | ||||
| -rw-r--r-- | src/object.c | 242 | ||||
| -rw-r--r-- | src/parser.c | 700 | ||||
| -rw-r--r-- | src/roscha.c | 632 | ||||
| -rw-r--r-- | src/slice.c | 63 | ||||
| -rw-r--r-- | src/tests/lexer.c | 246 | ||||
| -rw-r--r-- | src/tests/parser.c | 542 | ||||
| -rw-r--r-- | src/tests/roscha.c | 189 | ||||
| -rw-r--r-- | src/tests/slice.c | 41 | ||||
| -rw-r--r-- | src/token.c | 144 | ||||
| -rw-r--r-- | src/vector.c | 49 | 
13 files changed, 3656 insertions, 0 deletions
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 <stdio.h> +#include <string.h> + +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 <inttypes.h> +#include <string.h> +#include <stdlib.h> +#include <err.h> + +#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 <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> + +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 <stdio.h> +#include <stdlib.h> + +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 <errno.h> +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <dirent.h> +#include <sys/stat.h> + +#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 <stdlib.h> +#include <string.h> + +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 <string.h> + +#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 <string.h> + +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 <string.h> + +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 <string.h> + +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 <stdio.h> +#include <stddef.h> + +#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); +}  | 
