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