diff --git a/src/common/stack.c b/src/common/stack.c index 63f7741..2e8eb0f 100644 --- a/src/common/stack.c +++ b/src/common/stack.c @@ -14,8 +14,10 @@ * along with this program. If not, see . */ #include "stack.h" +#include #include "logging.h" + static void increase_capacity(Stack *stack) { if (!stack) { return; @@ -49,6 +51,51 @@ void Stack_Deinit(Stack *stack) { stack->capacity = 0; } +Stack *Stack_Create() { + Stack *stack = malloc(sizeof(Stack)); + if (!stack) { + logFatal("Failed to allocate memory for stack."); + } + Stack_Init(stack); + return stack; +} + +void Stack_Destroy(Stack *stack) { + if (!stack) { + return; + } + Stack_Deinit(stack); + free(stack); +} + +Stack *Stack_Copy(const Stack *src) { + if (!src) { + return NULL; + } + Stack *copy = malloc(sizeof(Stack)); + if (!copy) { + logFatal("Failed to allocate memory for stack copy."); + } + + size_t cap = src->capacity ? src->capacity : STACK_INITIAL_CAPACITY; // handle capacity == 0 + + // allocate memory + copy->items = malloc(cap * sizeof(void *)); + if (!copy->items) { + logFatal("Failed to allocate memory for stack copy items."); + } + // only copy items + memcpy(copy->items, src->items, src->size * sizeof(void *)); + // set remainder to 0 + if (cap > src->size) { + memset(copy->items + src->size, 0, (cap - src->size) * sizeof(void *)); + } + copy->size = src->size; + copy->capacity = src->capacity; + + return copy; +} + void Stack_Push(Stack *stack, void *item) { if (!stack) { return; @@ -86,6 +133,10 @@ bool Stack_Has(const Stack *stack, const void *item) { return false; } +bool Stack_IsEmpty(const Stack *stack) { + return stack->size == 0; +} + void Stack_Clear(Stack *stack) { if (!stack) { return; diff --git a/src/common/stack.h b/src/common/stack.h index f982e4f..1e0b09c 100644 --- a/src/common/stack.h +++ b/src/common/stack.h @@ -32,12 +32,19 @@ typedef struct _Stack { void Stack_Init(Stack *stack); void Stack_Deinit(Stack *stack); +Stack *Stack_Create(); +void Stack_Destroy(Stack *stack); + +Stack *Stack_Copy(const Stack *stack); + void Stack_Push(Stack *stack, void *item); void *Stack_Pop(Stack *stack); void *Stack_Peek(const Stack *stack); bool Stack_Has(const Stack *stack, const void *item); +bool Stack_IsEmpty(const Stack *stack); + void Stack_Clear(Stack *stack); diff --git a/src/common/table.c b/src/common/table.c index 9b78e30..8608c54 100644 --- a/src/common/table.c +++ b/src/common/table.c @@ -27,12 +27,12 @@ void TableSlot_Init(TableSlot *slot) { slot->state = TABLE_SLOT_EMPTY; } -void TableSlot_Deinit(TableSlot *slot) { +void TableSlot_Deinit(TableSlot *slot, void (*free_key_func)(void *key)) { if (!slot) { return; } - if (slot->key) { - free(slot->key); + if (slot->key && free_key_func) { + free_key_func(slot->key); } if (slot->destructor) { slot->destructor(slot->value); @@ -51,7 +51,20 @@ static uint32_t hash_string(const char *str) { return hash; } -static TableSlot *find_slot(const Table *table, const char *key) { +static uint32_t hash_ptr(const void *p) { + uintptr_t x = (uintptr_t)p; + return (uint32_t)(x ^ (x >> 32)); +} + +static int cmp_ptr(const void *a, const void *b) { + return a != b; +} + +static void *cpy_ptr(const void *p) { + return (void*)p; +} + +static TableSlot *find_slot(const Table *table, const void *key) { if (!table || !key) { logFatal("Invalid table or key in find_slot()"); } @@ -59,7 +72,7 @@ static TableSlot *find_slot(const Table *table, const char *key) { if (table->used >= table->capacity * TABLE_MAX_LOAD_FACTOR + 1) { logFatal("Table too loaded."); // this can only happen there is an error in the code } - uint32_t hash = hash_string(key); + uint32_t hash = table->hash_func(key); size_t index = hash % table->capacity; TableSlot *tombstone = NULL; for (;;) { @@ -73,7 +86,7 @@ static TableSlot *find_slot(const Table *table, const char *key) { else if (slot->state == TABLE_SLOT_TOMBSTONE && tombstone == NULL) { tombstone = slot; } - else if (slot->state == TABLE_SLOT_USED && strcmp(slot->key, key) == 0) { + else if (slot->state == TABLE_SLOT_USED && table->key_cmp_func(slot->key, key) == 0) { return slot; } @@ -130,8 +143,8 @@ static void increase_capacity(Table *table) { // free old table keys for (size_t i=0; icapacity; i++) { TableSlot *slot = &table->slots[i]; - if (slot->state == TABLE_SLOT_USED) { - free(slot->key); + if (slot->state == TABLE_SLOT_USED && table->key_free_func) { + table->key_free_func(slot->key); } } // free old table slots @@ -164,7 +177,7 @@ void Table_Deinit(Table *table) { if (table->slots) { for (size_t i=0; icapacity; i++) { TableSlot *entry = &table->slots[i]; - TableSlot_Deinit(entry); + TableSlot_Deinit(entry, table->key_free_func); } free(table->slots); } @@ -174,8 +187,36 @@ void Table_Deinit(Table *table) { } Table *Table_Create() { + return Table_CreateCustom( + (uint32_t(*)(const void*))hash_string, + (int(*)(const void*, const void*))strcmp, + (void*(*)(const void*))strdup, + (void(*)(void*))free); +} + +Table *Table_CreatePtr() { + return Table_CreateCustom( + hash_ptr, + cmp_ptr, + cpy_ptr, + NULL); +} + +Table *Table_CreateCustom( + uint32_t (*hash_func)(const void *p), + int (*key_cmp_func)(const void*, const void *), + void *(*key_copy_func)(const void *p), + void (*key_free_func)(void *p) +) { Table *table = malloc(sizeof(Table)); + if (!table) { + logFatal("Failed to allocate memory for table."); + } Table_Init(table); + table->hash_func = hash_func; + table->key_cmp_func = key_cmp_func; + table->key_copy_func = key_copy_func; + table->key_free_func = key_free_func; return table; } @@ -188,7 +229,7 @@ void Table_Destroy(Table *table) { } -void Table_Set(Table *table, const char*key, void *value, void (*destructor)(void *value)) { +void Table_Set(Table *table, const void *key, void *value, void (*destructor)(void *value)) { if (!table || !key) { return; } @@ -202,8 +243,8 @@ void Table_Set(Table *table, const char*key, void *value, void (*destructor)(voi if (slot->state == TABLE_SLOT_EMPTY) { slot->state = TABLE_SLOT_USED; - slot->key = strdup(key); - if (slot->key == NULL) { + slot->key = table->key_copy_func(key); + if (!slot->key) { logFatal("No memory for string copy in Table_Set()."); } table->used++; @@ -216,7 +257,7 @@ void Table_Set(Table *table, const char*key, void *value, void (*destructor)(voi slot->value = value; // potential pointers in value are copied and ownership is taken } -void *Table_Get(const Table *table, const char *key) { +void *Table_Get(const Table *table, const void *key) { if (!table) { logFatal("Invalid table in Table_Get()."); } @@ -235,7 +276,7 @@ void *Table_Get(const Table *table, const char *key) { return slot->value; } -void Table_Delete(Table *table, const char *key) { +void Table_Delete(Table *table, const void *key) { if (!table) { logFatal("Invalid table in Table_Delete()."); } @@ -250,14 +291,14 @@ void Table_Delete(Table *table, const char *key) { if (slot->state != TABLE_SLOT_USED) { return; } - TableSlot_Deinit(slot); + TableSlot_Deinit(slot, table->key_free_func); slot->state = TABLE_SLOT_TOMBSTONE; // used is not decremented intentionally! // tombstones are counted to trigger rehash earlier and decrease probing } -bool Table_Has(const Table *table, const char *key) { +bool Table_Has(const Table *table, const void *key) { if (!table) { logFatal("Invalid table in Table_Has()."); } @@ -272,7 +313,7 @@ bool Table_Has(const Table *table, const char *key) { return (slot->state == TABLE_SLOT_USED); } -bool Table_HasOwnership(const Table *table, const char *key) { +bool Table_HasOwnership(const Table *table, const void *key) { if (!table) { logFatal("Invalid table in Table_HasOwnership()."); } diff --git a/src/common/table.h b/src/common/table.h index 1ec71ee..3b62330 100644 --- a/src/common/table.h +++ b/src/common/table.h @@ -20,6 +20,8 @@ * Simple implementation of a hashtable using the open addressing * with linear probing. For hashing the djb2 hash algorithm is used. * + * Custom keys are supported via the Table_CreateCustom() function. + * * The load factor is calculated from all slots that are not empty * (tombstones included). It's assumed that there not too many deletions * occure. Otherwise the worst that can happen is that the used memory @@ -28,6 +30,7 @@ #ifndef TABLE_H #define TABLE_H +#include #include #include @@ -42,25 +45,52 @@ typedef enum { } TableSlotState; typedef struct _TableSlot { - char *key; + void *key; void *value; void (*destructor)(void *value); TableSlotState state; } TableSlot; void TableSlot_Init(TableSlot *slot); -void TableSlot_Deinit(TableSlot *slot); +void TableSlot_Deinit(TableSlot *slot, void (*free_key_func)(void *key)); typedef struct _Table { TableSlot *slots; size_t capacity; size_t used; + + uint32_t (*hash_func)(const void *p); + int (*key_cmp_func)(const void*, const void *); + void *(*key_copy_func)(const void *p); + void (*key_free_func)(void *p); } Table; void Table_Init(Table *table); void Table_Deinit(Table *table); +/** + * @brief Create a `Table` with char* keys. + */ Table *Table_Create(); + +/** + * @brief Create a `Table` with pointer addresses as keys. + */ +Table *Table_CreatePtr(); + +/** + * @brief Create a `Table`with custom keys. + */ +Table *Table_CreateCustom( + uint32_t (*hash_func)(const void *p), + int (*key_cmp_func)(const void*, const void *), + void *(*key_copy_func)(const void *p), + void (*key_free_func)(void *p) +); + +/** + * @brief Destroy `table` + */ void Table_Destroy(Table *table); /** @@ -70,14 +100,14 @@ void Table_Destroy(Table *table); * for destroying it (in the case of overwriting or deleting it). If destructor is NULL the * ownership stays by the owner and he need to take care of freeing value. */ -void Table_Set(Table *table, const char *key, void *value, void (*destructor)(void *value)); +void Table_Set(Table *table, const void *key, void *value, void (*destructor)(void *value)); /** * @brief Return the value for key or NULL if key is not found. * * If NULL is a ligit value for key use Table_Has() to check if the key exists in the table. */ -void *Table_Get(const Table *table, const char *key); +void *Table_Get(const Table *table, const void *key); /** * @brief Delete the entry for key in table. @@ -85,17 +115,17 @@ void *Table_Get(const Table *table, const char *key); * Delete the entry of key if it exists. If table has the ownership for value (a destructor is present) * value will be destructed. */ -void Table_Delete(Table *table, const char *key); +void Table_Delete(Table *table, const void *key); /** * @brief Return true if the key exists in table. */ -bool Table_Has(const Table *table, const char *key); +bool Table_Has(const Table *table, const void *key); /** * @brief Return true if table has the ownership for the entry of key */ -bool Table_HasOwnership(const Table *table, const char *key); +bool Table_HasOwnership(const Table *table, const void *key); /** * @brief Return the current number of non-free table slots. diff --git a/src/syntax/definition.c b/src/syntax/definition.c index cf83044..f8e23a4 100644 --- a/src/syntax/definition.c +++ b/src/syntax/definition.c @@ -59,9 +59,8 @@ void SyntaxBlockDef_Destroy(SyntaxBlockDef *block) { free(block->name); } regfree(&block->start); - if (!block->only_start) { - regfree(&block->end); - } + regfree(&block->end); + free(block); } @@ -75,6 +74,13 @@ SyntaxBlockDef *SyntaxBlockDef_FromTable(const char *name, const Table *table, S block->name = strdup(name); block->only_start = true; block->color = (uint8_t)TypedTable_GetNumber(table, "color"); + + if (strcmp(name, "root") == 0) { + regcomp(&block->start, "^", REG_EXTENDED); // matches always without consuming + regcomp(&block->end, "a^", REG_EXTENDED); // matches never + block->only_start = false; + return block; + } if (!start_regex) { set_error(error, @@ -104,14 +110,18 @@ SyntaxBlockDef *SyntaxBlockDef_FromTable(const char *name, const Table *table, S regerror(ret, &block->end, errbuf, sizeof(errbuf)); set_error(error, SYNTAXDEFINITION_REGEX_ERROR_END, - String_Format("Error in end regex \"%s\" in block \"%s\": %s", start_regex, name, errbuf) + String_Format("Error in end regex \"%s\" in block \"%s\": %s", end_regex, name, errbuf) ); - block->only_start = true; // must be set that SyntaxBlockDef_Destroy() does not try to free end regex - SyntaxBlockDef_Destroy(block); // frees everything including start regex + regfree(&block->start); + free(block->name); + free(block); return NULL; } block->only_start = false; } + else { + regcomp(&block->end, "^", REG_EXTENDED); // matches everything without consuming + } return block; } diff --git a/src/syntax/definition.h b/src/syntax/definition.h index c81e0ce..a5c343b 100644 --- a/src/syntax/definition.h +++ b/src/syntax/definition.h @@ -30,7 +30,22 @@ * name = TEST * * [block:root] - * start = . + * ``` + * + * ### Another example + * ``` + * [meta] + * name = TEST + * + * [block:root] + * allowed_blocks = string, keyword + * + * [block:string] + * start = "'" + * end = "'" + * + * [block:keyword] + * start = if|then|else * ``` * * Each block must define a `start` regex (an extended POSIX regular expression). @@ -42,6 +57,9 @@ * Blocks that define an `end` expression may additionally define a list of * `allowed_blocks`, which specifies which block types are permitted to appear * inside the block. + * + * The root block does not need defined `start` or `end` patterns but should + * define an `allowed_blocks` list (otherwise nothing will be found). */ #ifndef SYNTAX_DEFINITION_H diff --git a/src/syntax/highlighting.c b/src/syntax/highlighting.c new file mode 100644 index 0000000..6d75c63 --- /dev/null +++ b/src/syntax/highlighting.c @@ -0,0 +1,207 @@ +#include "highlighting.h" +#include "common/logging.h" + +/*****************************************************************************/ +/* SyntaxHighlightingTag */ + +void SyntaxHighlightingTag_Init(SyntaxHighlightingTag *tag) { + tag->text = NULL; + tag->byte_offset = 0; + tag->block = NULL; +} + +void SyntaxHighlightingTag_Deinit(SyntaxHighlightingTag *tag) { + SyntaxHighlightingTag_Init(tag); +} + +/*****************************************************************************/ +/* SyntaxHighlightingString */ + +SyntaxHighlightingString *SyntaxHighlightingString_Create(const String *text) { + SyntaxHighlightingString *shs = malloc(sizeof(SyntaxHighlightingString)); + if (!shs) { + logFatal("Cannot allocate memory for SyntaxHighlightingString."); + } + shs->text = text; + shs->tags = NULL; + shs->tags_count = 0; + shs->tags_capacity = 0; + Stack_Init(&shs->open_blocks_at_end); + + shs->tags = malloc(sizeof(SyntaxHighlightingTag) * SHS_TAGS_INITIAL_CAPACITY); + if (!shs->tags) { + logFatal("Cannot allocate memory for SyntaxHighlightingString tags."); + } + + return shs; +} + +void SyntaxHighlightingString_Destroy(SyntaxHighlightingString *shs) { + if (shs->tags) { + free(shs->tags); + } + shs->tags = NULL; + shs->tags_count = 0; + shs->tags_capacity = 0; + shs->text = NULL; + Stack_Deinit(&shs->open_blocks_at_end); + free(shs); +} + +static void increase_tags_capacity(SyntaxHighlightingString *shs) { + if (!shs) { + return; + } + if (shs->tags_capacity == 0) { + shs->tags_capacity = SHS_TAGS_INITIAL_CAPACITY; + } + else { + shs->tags_capacity *= SHS_TAGS_GROW_FACTOR; + } + shs->tags = realloc(shs->tags, shs->tags_capacity * sizeof(SyntaxHighlightingTag)); + if (!shs->tags) { + logFatal("Failed to reallocate SyntaxHighlightingString tags."); + } +} + +void SyntaxHighlightingString_AddTag(SyntaxHighlightingString *shs, SyntaxHighlightingTag tag) { + if (!shs) { + return; + } + if (shs->tags_count + 1 >= shs->tags_capacity) { + increase_tags_capacity(shs); + } + shs->tags[shs->tags_count++] = tag; +} + +void SyntaxHighlightingString_Clear(SyntaxHighlightingString *shs) { + if (!shs) { + return; + } + shs->tags_count = 0; + Stack_Clear(&shs->open_blocks_at_end); +} + + +/*****************************************************************************/ +/* SyntaxHighlighting */ + +void SyntaxHighlighting_Init(SyntaxHighlighting *hl, const SyntaxDefinition *def) { + hl->def = def; + hl->strings = Table_CreatePtr(); +} + +void SyntaxHighlighting_Deinit(SyntaxHighlighting *hl) { + if (hl->strings) { + Table_Destroy(hl->strings); + } + hl->strings = NULL; + hl->def = NULL; +} + +static const SyntaxBlockDef *find_first_child(const char *str, const SyntaxBlockDef *current, regmatch_t *match) { + bool had_match = false; + regmatch_t first_match; + const SyntaxBlockDef *first_block; + for (size_t i=0; ichildren_count; i++) { + const SyntaxBlockDef *child = current->children[i]; + regmatch_t curr_match; + if (regexec(&child->start, str, 1, &curr_match, 0) == 0) { + if (!had_match || curr_match.rm_so < first_match.rm_so) { + first_match = curr_match; + first_block = child; + had_match = true; + } + } + } + if (had_match) { + *match = first_match; + return first_block; + } + return NULL; +} + +static bool find_end_of_block(const char *str, const SyntaxBlockDef *current, regmatch_t *match) { + regmatch_t end_match; + if (regexec(¤t->end, str, 1, &end_match, 0) == 0) { + *match = end_match; + return true; + } + return false; +} + +Stack *SyntaxHighlighting_HighlightString(SyntaxHighlighting *sh, const String *text, const Stack *open_blocks_at_begin) { + // check if there is already old infomation about text in the table + SyntaxHighlightingString *shs = Table_Get(sh->strings, text); + if (shs) { + // clear it if so + SyntaxHighlightingString_Clear(shs); + } + else { + // otherwise create and store + shs = SyntaxHighlightingString_Create(text); + Table_Set(sh->strings, text, shs, (void(*)(void*))SyntaxHighlightingString_Destroy); + } + + // create a working copy of the stack + Stack *open_blocks; + if (open_blocks_at_begin) { + open_blocks = Stack_Copy(open_blocks_at_begin); + } + else { + open_blocks = Stack_Create(); + Stack_Push(open_blocks, sh->def->root); + } + + // iterate over the string + size_t offset = 0; + for (;;) { + // take the current block from the stack (but keep it there) + const SyntaxBlockDef *current_block = (SyntaxBlockDef*)Stack_Peek(open_blocks); + + // find the first child block + regmatch_t child_match; + const SyntaxBlockDef *child = find_first_child(text->bytes + offset, current_block, &child_match); + regmatch_t end_match; + bool end_found = find_end_of_block(text->bytes + offset, current_block, &end_match); + + if (child && (!end_found || child_match.rm_so < end_match.rm_so)) { + // if there is a child block found create and add a tag to SyntaxHighlightingString tag list + SyntaxHighlightingTag tag; + tag.text = text; + tag.byte_offset = offset + child_match.rm_so; + tag.block = child; + SyntaxHighlightingString_AddTag(shs, tag); + + // increase the offset to the end of the match + offset += child_match.rm_eo; + + // push the child to the stack to continue with it in the next iteration + Stack_Push(open_blocks, (void*)child); + continue; + } + // no child block found + // so check if the last block ends + if (end_found) { + // end of block found so remove it from stack + Stack_Pop(open_blocks); // removes current (which was just peeked before) + + // and add a tag for the (new) begin of the surrounding block + SyntaxHighlightingTag tag; + tag.text = text; + tag.byte_offset = offset + end_match.rm_eo; // the current block goes behind the match of its end + tag.block = Stack_Peek(open_blocks); // new current block + SyntaxHighlightingString_AddTag(shs, tag); + + // increase offset + offset += end_match.rm_eo; + continue; + } + // neither the beginning of a new block nor the end of the current block found + return open_blocks; + } + + // if this happens there is an error in the SyntaxDefinition. + // the end pattern of the root block must *never* match + return NULL; +} diff --git a/src/syntax/highlighting.h b/src/syntax/highlighting.h new file mode 100644 index 0000000..5c1739e --- /dev/null +++ b/src/syntax/highlighting.h @@ -0,0 +1,108 @@ +/* Copyright (C) 2025 defname + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * @file highlighting.h + * @brief System to store the information about the highlighting, like text positions and colors. + * + * ### Limitations + * Regular expressions that mark the begin or the end of an block are not tested across different + * Strings. If for example a keyword starts in one string and continues in another one, even if + * they are parsed in the correct order, the keyword will not be recognized. + * So make sure the `text` parts are sperarated in a compatible way (like by newlines or similar). + */ +#ifndef SYNTAX_HIGHLIGHTING_H +#define SYNTAX_HIGHLIGHTING_H + +#include +#include "definition.h" +#include "common/stack.h" +#include "common/table.h" +#include "common/string.h" + +/** + * @brief Holds information about the beginning of a block. + * + * Instead of keeping track of the end of a block it is just treated + * as a new beginning of the surrounding block. + * + * Note that this struct does not have the ownership of any of the members. + */ +typedef struct _SyntaxHighlightingTag { + const String *text; //< pointer to the string this tag is for + size_t byte_offset; //< byte offset where the tag occurs + const SyntaxBlockDef *block; //< pointer to the block this tag is for +} SyntaxHighlightingTag; + +void SyntaxHighlightingTag_Init(SyntaxHighlightingTag *tag); +void SyntaxHighlightingTag_Deinit(SyntaxHighlightingTag *tag); + + +/** + * @brief Holds the complete highlighting information for the String `text`. + */ +typedef struct _SyntaxHighlightingString { + const String *text; //< pointer to the text information are stored for + SyntaxHighlightingTag *tags; //< list of tags for `text` + size_t tags_count; //< number of tags in `tags` + size_t tags_capacity; //< capacity of `tags` + + Stack open_blocks_at_end; //< Stack of (SyntaxBlockDef*) elements that are open at the end of `text` +} SyntaxHighlightingString; + +#define SHS_TAGS_INITIAL_CAPACITY 16 +#define SHS_TAGS_GROW_FACTOR 2 + +SyntaxHighlightingString *SyntaxHighlightingString_Create(const String *text); +void SyntaxHighlightingString_Destroy(SyntaxHighlightingString *shs); + +void SyntaxHighlightingString_AddTag(SyntaxHighlightingString *shs, SyntaxHighlightingTag tag); +void SyntaxHighlightingString_Clear(SyntaxHighlightingString *shs); + +/**match + * @brief Holds the highlighting information for text of multiple `Strings`. + */ +typedef struct _SyntaxHighlighting { + const SyntaxDefinition *def; //< SyntaxDefinition to use for highlighting + Table *strings; //< Table of (String -> SyntaxHighlightingString*) elements (holds the ownership of the SyntaxHighlightingString's) +} SyntaxHighlighting; + + +/** + * @brief Initialize syntax highlighting using def for definitions. + */ +void SyntaxHighlighting_Init(SyntaxHighlighting *sh, const SyntaxDefinition *def); + +/** + * @brief Deinitialize sh. + */ +void SyntaxHighlighting_Deinit(SyntaxHighlighting *sh); + +/** + * @brief Highlight a string according to given context. + * + * Add a `SyntaxHighlightingString`to `hl->strings` or update an existing one. + * + * @param hl The `SyntaxHighlighting` instance to use. + * @param text The text to hightlight. + * @param open_blocks An pointer to a `Stack` instance that holds the open blocks at the beginning of string. If NULL it's assumed that the root block is current. + * + * @returns + * A new created `Stack` containing all open blocks at the end of `text`. + */ +Stack *SyntaxHighlighting_HighlightString(SyntaxHighlighting *sh, const String *text, const Stack *open_blocks); + +#endif \ No newline at end of file diff --git a/tests/test_syntax_definition.c b/tests/test_syntax_definition.c index fa97531..58d8250 100644 --- a/tests/test_syntax_definition.c +++ b/tests/test_syntax_definition.c @@ -134,9 +134,19 @@ void test_errors(void) { "[meta]\n" "name = MINI\n" "[block:root]\n" + "[block:error]\n" "start=\".*)(\"\n", SYNTAXDEFINITION_REGEX_ERROR_START }, + { + "[meta]\n" + "name = MINI\n" + "[block:root]\n" + "[block:error]\n" + "start=.*\n" + "end=\".*)(\"\n", + SYNTAXDEFINITION_REGEX_ERROR_END + }, { "block:root = foo\n" "[meta]\n" @@ -166,6 +176,7 @@ void test_errors(void) { int cases_num = sizeof(cases) / sizeof(cases[0]); for (int i=0; i +#include +#include +#include +#include "syntax/highlighting.h" +#include "syntax/definition.h" +#include "common/iniparser.h" + +static SyntaxDefinition *create_definition(const char *ini) { + IniParser parser; + IniParser_Init(&parser); + IniParser_SetText(&parser, ini); + Table *table = IniParser_Parse(&parser); + TEST_ASSERT(table != NULL); + SyntaxDefinitionError error; + SyntaxDefinition *def = SyntaxDefinition_FromTable(table, &error); + TEST_ASSERT(def != NULL); + IniParser_Deinit(&parser); + Table_Destroy(table); + return def; +} + +Table *build_blocks_table(SyntaxDefinition *def) { + Table *table = Table_Create(); + for (size_t i=0; iblocks_count; i++) { + SyntaxBlockDef *block = def->blocks[i]; + Table_Set(table, block->name, block, NULL); + } + return table; +} + +typedef struct { + const char *ini; + const char *str; + size_t tags_count; + size_t tag_offsets[32]; + const char *tag_blocks[32]; + size_t open_blocks_count; + const char *open_blocks[32]; +} TagTestCase; + +static void assert_highlight_tags( + TagTestCase testcase +) { + const char *ini = testcase.ini; + String str = String_Format(testcase.str); + size_t tags_count = testcase.tags_count; + size_t *tag_offsets = testcase.tag_offsets; + const char **tag_blocks = testcase.tag_blocks; + size_t open_blocks_count_expected = testcase.open_blocks_count; + const char **open_blocks_expected = testcase.open_blocks; + + + SyntaxDefinition *def = create_definition(ini); + SyntaxHighlighting hl; + SyntaxHighlighting_Init(&hl, def); + + Stack *open_blocks_at_begin = Stack_Create(); + Stack_Push(open_blocks_at_begin, def->root); + Stack *open_blocks = SyntaxHighlighting_HighlightString(&hl, &str, open_blocks_at_begin); + + TEST_CHECK(!Stack_IsEmpty(open_blocks)); + TEST_MSG("Expected open_blocks to not be not empty."); + + for (size_t i=0; iname) == 0); + TEST_MSG("Expected open block #%zu to be '%s' but got '%s'.", i, open_blocks_expected[i], block->name); + } + + SyntaxHighlightingString *shs = Table_Get(hl.strings, &str); + + TEST_CHECK(shs->tags_count == tags_count); + TEST_MSG("Expected %zu tags but got %zu.", tags_count, shs->tags_count); + + for (size_t i=0; itags[i].byte_offset == tag_offsets[i]); + TEST_MSG("Expected offset #%zu to be %zu but got %zu.", i, tag_offsets[i], shs->tags[i].byte_offset); + + TEST_CHECK(strcmp(shs->tags[i].block->name, tag_blocks[i]) == 0); + TEST_MSG("Expected block #%zu to be '%s' but got '%s'.", i, tag_blocks[i], shs->tags[i].block->name); + } + + Stack_Destroy(open_blocks); + Stack_Destroy(open_blocks_at_begin); + String_Deinit(&str); + SyntaxHighlighting_Deinit(&hl); + SyntaxDefinition_Destroy(def); +} + +const char *test_ini0 = +"[meta]\n" +"name = TEST\n" +"[block:root]\n" +"start=^\n" +"end=a^\n" +"allowed_blocks=string\n" +"\n" +"[block:string]\n" +"start=\"'\"\n" +"end=\"'\"\n"; + + +void test_highlight_string_simple(void) { + SyntaxDefinition *def = create_definition(test_ini0); + SyntaxHighlighting hl; + SyntaxHighlighting_Init(&hl, def); + + String test1 = String_Format("root 'string' root"); + Stack *open_blocks_at_begin = Stack_Create(); + Stack_Push(open_blocks_at_begin, def->root); + Stack *open_blocks = SyntaxHighlighting_HighlightString(&hl, &test1, open_blocks_at_begin); + + TEST_CHECK(open_blocks != NULL); + TEST_CHECK(Stack_Peek(open_blocks) == def->root); + SyntaxHighlightingString *shs = Table_Get(hl.strings, &test1); + TEST_CHECK(shs->tags_count == 2); + TEST_CHECK(shs->tags[0].byte_offset == 5); // start of 'string' + TEST_CHECK(strcmp(shs->tags[0].block->name, "string") == 0); + TEST_CHECK(shs->tags[1].byte_offset == 13); // end of 'string' + TEST_CHECK(shs->tags[1].block == def->root); + + Stack_Destroy(open_blocks); + Stack_Destroy(open_blocks_at_begin); + String_Deinit(&test1); + SyntaxHighlighting_Deinit(&hl); + SyntaxDefinition_Destroy(def); +} + +const char *test_ini1 = R"( +[meta] +name = TEST + +[block:root] +allowed_blocks=string, comment, keyword, brackets + +[block:brackets] +start=\( +end=\) +allowed_blocks=string, keyword, brackets + +[block:keyword] +start=keyword + +[block:string] +start=' +end=' + +[block:comment] +start = // +end = $ +)"; + +void test_basics(void) { + TagTestCase cases[] = { + { + test_ini1, + "foobar // blabla ' bla", // comment + 2, + {7, 22}, + {"comment", "root"}, + 1, + {"root"} + }, + { + test_ini1, + "foo 'bar' foo", // string + 2, + {4, 9}, + {"string", "root"}, + 1, + {"root"} + }, + { + test_ini1, + "foo keyword foo", // keyword + 2, + {4, 11}, + {"keyword", "root"}, + 1, + {"root"} + }, + { + test_ini1, + "foo '//not a comment' foo", // string with comment inside (should be ignored) + 2, + {4, 21}, + {"string", "root"}, + 1, + {"root"} + }, + { + test_ini1, + "foo 'not a keyword' foo", // keyword inside string + 2, + {4, 19}, + {"string", "root"}, + 1, + {"root"} + }, + { + test_ini1, + "foo (no brackets) foo", + 2, + {4, 17}, + {"brackets", "root"}, + 1, + {"root"} + }, + { + test_ini1, + "()", + 2, + {0, 2}, + {"brackets", "root"}, + 1, + {"root"} + }, + { + test_ini1, + "(keyword)keyword", + 6, + {0, 1, 8, 9, 9, 16}, + {"brackets", "keyword", "brackets", "root", "keyword", "root"}, + 1, + {"root"} + }, + { + test_ini1, + "", + 0, + {}, + {}, + 1, + {"root"} + }, + { + test_ini1, + "(('not a keyword'))", + 6, + {0, 1, 2, 17, 18, 19}, + {"brackets", "brackets", "string", "brackets", "brackets", "root"}, + 1, + {"root"} + }, + + }; + size_t count = sizeof(cases) / sizeof(cases[0]); + + for (size_t i=0; i= max_len) { + break; + } + memcpy(out + l, token, token_len); + l += token_len; + } + else { + out[l++] = random_char(); + } + } + out[l] = '\0'; +} + + +void test_stress(void) { + // function should just not crash on random input + const char *tokens[] = { + "(", ")", "keyword", "'", "//" + }; + + srand(time(NULL)); + + for (int i=0; i<1000; i++) { + SyntaxDefinition *def = create_definition(test_ini1); + SyntaxHighlighting hl; + SyntaxHighlighting_Init(&hl, def); + + char str[4096]; + generate_random_string(str, 1024, tokens, sizeof(tokens) / sizeof(tokens[0])); + TEST_CASE(str); + + String test = String_FromCStr(str, strlen(str)); + Stack *open_blocks = SyntaxHighlighting_HighlightString(&hl, &test, NULL); + + TEST_CHECK(open_blocks != NULL); + TEST_CHECK(!Stack_IsEmpty(open_blocks)); + + Stack_Destroy(open_blocks); + String_Deinit(&test); + SyntaxHighlighting_Deinit(&hl); + SyntaxDefinition_Destroy(def); + } + +} + +TEST_LIST = { + { "SyntaxHighlighting: Simple", test_highlight_string_simple }, + { "SyntaxHighlighting: Basics", test_basics }, + { "SyntaxHighlighting: Open blocks", test_open_blocks }, + { "SyntaxHighlighting: Random Tests", test_stress }, + { NULL, NULL } +}; \ No newline at end of file diff --git a/tests/test_table.c b/tests/test_table.c index 4cc41c1..fd8f75c 100644 --- a/tests/test_table.c +++ b/tests/test_table.c @@ -115,11 +115,24 @@ void test_edge_case(void) { Table_Destroy(table); } +void test_ptr_table(void) { + Table *table = Table_CreatePtr(); + + Table_Set(table, (void*)1, strdup("Foobar"), free); + Table_Set(table, (void*)2, strdup("Blub"), free); + + TEST_CHECK(strcmp(Table_Get(table, (void*)1), "Foobar") == 0); + TEST_CHECK(strcmp(Table_Get(table, (void*)2), "Blub") == 0); + + Table_Destroy(table); +} + TEST_LIST = { { "Table: Creation", test_creation }, { "Table: Set and Get", test_set_get }, { "Table: Existence and Owndership", test_has_hasownership }, { "Table: Rehashing", test_rehashing }, { "Table: Edge Case", test_edge_case }, + { "Table: Pointer Keys", test_ptr_table }, { NULL, NULL } }; \ No newline at end of file