From 2d84f9f0f8bbf225d6261d834e7dbd1d7e7e2489 Mon Sep 17 00:00:00 2001 From: defname Date: Wed, 22 Oct 2025 17:41:06 +0200 Subject: [PATCH 01/24] add Stack_Copy() and Stack_IsEmpty() --- src/common/stack.c | 23 +++++++++++++++++++++++ src/common/stack.h | 4 ++++ 2 files changed, 27 insertions(+) diff --git a/src/common/stack.c b/src/common/stack.c index 63f7741..fb0fb41 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,23 @@ void Stack_Deinit(Stack *stack) { stack->capacity = 0; } +Stack *Stack_Copy(const Stack *stack) { + if (!stack) { + return NULL; + } + Stack *copy = malloc(sizeof(Stack)); + if (!copy) { + logFatal("Failed to allocate memory for stack copy."); + } + copy->items = malloc(stack->capacity * sizeof(void *)); + if (!copy->items) { + logFatal("Failed to allocate memory for stack copy items."); + } + memcpy(copy->items, stack->items, stack->capacity * sizeof(void *)); + copy->size = stack->size; + copy->capacity = stack->capacity; +} + void Stack_Push(Stack *stack, void *item) { if (!stack) { return; @@ -86,6 +105,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..5436193 100644 --- a/src/common/stack.h +++ b/src/common/stack.h @@ -32,12 +32,16 @@ typedef struct _Stack { void Stack_Init(Stack *stack); void Stack_Deinit(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); From dcdab600d4841382c852e312e020cc240b80555f Mon Sep 17 00:00:00 2001 From: defname Date: Wed, 22 Oct 2025 18:33:18 +0200 Subject: [PATCH 02/24] fix: return copy in Stack_Copy() --- src/common/stack.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/common/stack.c b/src/common/stack.c index fb0fb41..60cb423 100644 --- a/src/common/stack.c +++ b/src/common/stack.c @@ -66,6 +66,8 @@ Stack *Stack_Copy(const Stack *stack) { memcpy(copy->items, stack->items, stack->capacity * sizeof(void *)); copy->size = stack->size; copy->capacity = stack->capacity; + + return copy; } void Stack_Push(Stack *stack, void *item) { From 7f1725ffef44259fb2dd0aa569c5a9251faf17df Mon Sep 17 00:00:00 2001 From: defname Date: Wed, 22 Oct 2025 18:44:01 +0200 Subject: [PATCH 03/24] implement syntax highlighting --- src/syntax/highlighting.c | 195 ++++++++++++++++++++++++++++++++++++++ src/syntax/highlighting.h | 108 +++++++++++++++++++++ 2 files changed, 303 insertions(+) create mode 100644 src/syntax/highlighting.c create mode 100644 src/syntax/highlighting.h diff --git a/src/syntax/highlighting.c b/src/syntax/highlighting.c new file mode 100644 index 0000000..2fbb93c --- /dev/null +++ b/src/syntax/highlighting.c @@ -0,0 +1,195 @@ +#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_Create(); +} + +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 match; + if (regexec(&child->start, str, 1, &match, 0) == 0) { + first_match = 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 *hl, 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(hl->strings, text); + if (shs) { + // clear it if so + SyntaxHighlightingString_Clear(shs); + } + else { + // otherwise create and store + shs = SyntaxHighlightingString_Create(text); + Table_Set(hl->strings, text, shs, SyntaxHighlightingString_Destroy); + } + + // create a working copy of the stack + Stack *open_blocks = Stack_Copy(open_blocks_at_begin); + + // 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 match; + const SyntaxBlockDef *next_block = find_first_child(text->bytes + offset, current_block, &match); + if (next_block) { + // 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 + match.rm_so; + tag.block = next_block; + SyntaxHighlightingString_AddTag(shs, tag); + + // increase the offset to the end of the match + offset += match.rm_eo; + + // push the next_block to the stack to continue with it in the next iteration + Stack_Push(open_blocks, next_block); + continue; + } + // no child block found + // so check if the last block ends + if (find_end_of_block(text->bytes + offset, current_block, &match)) { + // 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 + 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 += 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..3ebf126 --- /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 occures + 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); + +/** + * @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. + * + * @returns + * A new created `Stack` containing all open blocks at the end of `text`. + */ +Stack *SyntaxHighlighting_HighlightString(SyntaxHighlighting *hl, const String *text, const Stack *open_blocks); + +#endif \ No newline at end of file From 39d1a5f4cfe23b61f936a1a7c7571c082f5292b1 Mon Sep 17 00:00:00 2001 From: defname Date: Wed, 22 Oct 2025 19:30:56 +0200 Subject: [PATCH 04/24] add Table_CreateCustom() and Table_CreatePtr() --- src/common/table.c | 71 ++++++++++++++++++++++++++++++++++++---------- src/common/table.h | 44 +++++++++++++++++++++++----- tests/test_table.c | 13 +++++++++ 3 files changed, 106 insertions(+), 22 deletions(-) diff --git a/src/common/table.c b/src/common/table.c index 9b78e30..dd7b792 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; } @@ -131,7 +144,7 @@ static void increase_capacity(Table *table) { for (size_t i=0; icapacity; i++) { TableSlot *slot = &table->slots[i]; if (slot->state == TABLE_SLOT_USED) { - free(slot->key); + 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,7 +243,7 @@ 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); + slot->key = table->key_copy_func(key); if (slot->key == NULL) { logFatal("No memory for string copy in Table_Set()."); } @@ -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/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 From a83d37ca4cd02604446482de7ba0470d086d1732 Mon Sep 17 00:00:00 2001 From: defname Date: Wed, 22 Oct 2025 19:34:17 +0200 Subject: [PATCH 05/24] fix minor typing bugs --- src/syntax/highlighting.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/syntax/highlighting.c b/src/syntax/highlighting.c index 2fbb93c..ee0edde 100644 --- a/src/syntax/highlighting.c +++ b/src/syntax/highlighting.c @@ -88,7 +88,7 @@ void SyntaxHighlightingString_Clear(SyntaxHighlightingString *shs) { void SyntaxHighlighting_Init(SyntaxHighlighting *hl, const SyntaxDefinition *def) { hl->def = def; - hl->strings = Table_Create(); + hl->strings = Table_CreatePtr(); } void SyntaxHighlighting_Deinit(SyntaxHighlighting *hl) { @@ -138,7 +138,7 @@ Stack *SyntaxHighlighting_HighlightString(SyntaxHighlighting *hl, const String * else { // otherwise create and store shs = SyntaxHighlightingString_Create(text); - Table_Set(hl->strings, text, shs, SyntaxHighlightingString_Destroy); + Table_Set(hl->strings, text, shs, (void(*)(void*))SyntaxHighlightingString_Destroy); } // create a working copy of the stack @@ -165,7 +165,7 @@ Stack *SyntaxHighlighting_HighlightString(SyntaxHighlighting *hl, const String * offset += match.rm_eo; // push the next_block to the stack to continue with it in the next iteration - Stack_Push(open_blocks, next_block); + Stack_Push(open_blocks, (void*)next_block); continue; } // no child block found From 48a1223afa318b2b06f7f4ce3345c5ad3e32108c Mon Sep 17 00:00:00 2001 From: defname Date: Wed, 22 Oct 2025 19:42:44 +0200 Subject: [PATCH 06/24] add Stack_Create(), Stack_Destroy() --- src/common/stack.c | 17 +++++++++++++++++ src/common/stack.h | 3 +++ 2 files changed, 20 insertions(+) diff --git a/src/common/stack.c b/src/common/stack.c index 60cb423..cee2c33 100644 --- a/src/common/stack.c +++ b/src/common/stack.c @@ -51,6 +51,23 @@ 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 *stack) { if (!stack) { return NULL; diff --git a/src/common/stack.h b/src/common/stack.h index 5436193..1e0b09c 100644 --- a/src/common/stack.h +++ b/src/common/stack.h @@ -32,6 +32,9 @@ 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); From db6c5ae0c9c9d24ee53d584c62cc3476a6d83336 Mon Sep 17 00:00:00 2001 From: defname Date: Wed, 22 Oct 2025 20:48:30 +0200 Subject: [PATCH 07/24] fix but in find_first_child to actually return the child with the first occurence --- src/syntax/highlighting.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/syntax/highlighting.c b/src/syntax/highlighting.c index ee0edde..51bab9f 100644 --- a/src/syntax/highlighting.c +++ b/src/syntax/highlighting.c @@ -107,9 +107,11 @@ static const SyntaxBlockDef *find_first_child(const char *str, const SyntaxBlock const SyntaxBlockDef *child = current->children[i]; regmatch_t match; if (regexec(&child->start, str, 1, &match, 0) == 0) { - first_match = match; - first_block = child; - had_match = true; + if (!had_match || match.rm_so < first_match.rm_so) { + first_match = match; + first_block = child; + had_match = true; + } } } if (had_match) { From 29b94039ab5ff2ee92b32696bfcbd9f3f960be09 Mon Sep 17 00:00:00 2001 From: defname Date: Wed, 22 Oct 2025 21:04:23 +0200 Subject: [PATCH 08/24] automatically add patterns to end only_start blocks and to start/end root --- src/syntax/definition.c | 10 ++++++++++ src/syntax/definition.h | 3 +++ 2 files changed, 13 insertions(+) diff --git a/src/syntax/definition.c b/src/syntax/definition.c index cf83044..796dbbe 100644 --- a/src/syntax/definition.c +++ b/src/syntax/definition.c @@ -75,6 +75,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, @@ -112,6 +119,9 @@ SyntaxBlockDef *SyntaxBlockDef_FromTable(const char *name, const Table *table, S } 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..2351280 100644 --- a/src/syntax/definition.h +++ b/src/syntax/definition.h @@ -42,6 +42,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 From 72376ce1d378ba112e1d9b8bb14ff01680a051d2 Mon Sep 17 00:00:00 2001 From: defname Date: Wed, 22 Oct 2025 21:06:39 +0200 Subject: [PATCH 09/24] fix memory leak produced in last commit --- src/syntax/definition.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/syntax/definition.c b/src/syntax/definition.c index 796dbbe..212c070 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); } @@ -113,8 +112,9 @@ SyntaxBlockDef *SyntaxBlockDef_FromTable(const char *name, const Table *table, S SYNTAXDEFINITION_REGEX_ERROR_END, String_Format("Error in end regex \"%s\" in block \"%s\": %s", start_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; From 1370b1c019d4f6565157409ab072a7d99a9d871c Mon Sep 17 00:00:00 2001 From: defname Date: Wed, 22 Oct 2025 21:24:26 +0200 Subject: [PATCH 10/24] fix tests --- tests/test_syntax_definition.c | 11 +++++++++++ 1 file changed, 11 insertions(+) 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 Date: Wed, 22 Oct 2025 21:51:53 +0200 Subject: [PATCH 11/24] fix bug in SyntaxHighllighting_HighlightString(): check if child or block end comes first --- src/syntax/highlighting.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/syntax/highlighting.c b/src/syntax/highlighting.c index 51bab9f..e490da5 100644 --- a/src/syntax/highlighting.c +++ b/src/syntax/highlighting.c @@ -153,38 +153,41 @@ Stack *SyntaxHighlighting_HighlightString(SyntaxHighlighting *hl, const String * const SyntaxBlockDef *current_block = (SyntaxBlockDef*)Stack_Peek(open_blocks); // find the first child block - regmatch_t match; - const SyntaxBlockDef *next_block = find_first_child(text->bytes + offset, current_block, &match); - if (next_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 + match.rm_so; - tag.block = next_block; + 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 += match.rm_eo; + offset += child_match.rm_eo; - // push the next_block to the stack to continue with it in the next iteration - Stack_Push(open_blocks, (void*)next_block); + // 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 (find_end_of_block(text->bytes + offset, current_block, &match)) { + 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 + match.rm_eo; // the current block goes behind the match of its end + 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 += match.rm_eo; + offset += end_match.rm_eo; continue; } // neither the beginning of a new block nor the end of the current block found From 165751661b17c35181498462242254a5bd72f47e Mon Sep 17 00:00:00 2001 From: defname Date: Wed, 22 Oct 2025 21:52:12 +0200 Subject: [PATCH 12/24] add basic tests --- tests/test_syntax_highlighting.c | 242 +++++++++++++++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 tests/test_syntax_highlighting.c diff --git a/tests/test_syntax_highlighting.c b/tests/test_syntax_highlighting.c new file mode 100644 index 0000000..8377381 --- /dev/null +++ b/tests/test_syntax_highlighting.c @@ -0,0 +1,242 @@ +#include "acutest.h" +#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 + +[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 'ba//r' foo", // string with comment inside (should be ignored) + 2, + {4, 11}, + {"string", "root"}, + 1, + {"root"} + }, + { + test_ini1, + "foo keyword foo", // keyword + 2, + {4, 11}, + {"keyword", "root"}, + 1, + {"root"} + }, + { + test_ini1, + "foo 'keyword' foo", // keyword inside string + 2, + {4, 13}, + {"string", "root"}, + 1, + {"root"} + }, + { + test_ini1, + "foo (bar) foo", // keyword inside string + 2, + {4, 9}, + {"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"} + } + + }; + size_t count = sizeof(cases) / sizeof(cases[0]); + + for (size_t i=0; i Date: Wed, 22 Oct 2025 23:16:06 +0200 Subject: [PATCH 13/24] use root block it open_blocks parameter is NULL in SyntaxHighlighting_HighlightString() --- src/syntax/highlighting.c | 9 ++++++++- src/syntax/highlighting.h | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/syntax/highlighting.c b/src/syntax/highlighting.c index e490da5..f229178 100644 --- a/src/syntax/highlighting.c +++ b/src/syntax/highlighting.c @@ -144,7 +144,14 @@ Stack *SyntaxHighlighting_HighlightString(SyntaxHighlighting *hl, const String * } // create a working copy of the stack - Stack *open_blocks = Stack_Copy(open_blocks_at_begin); + 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, hl->def->root); + } // iterate over the string size_t offset = 0; diff --git a/src/syntax/highlighting.h b/src/syntax/highlighting.h index 3ebf126..d4bd43d 100644 --- a/src/syntax/highlighting.h +++ b/src/syntax/highlighting.h @@ -98,7 +98,7 @@ void SyntaxHighlighting_Deinit(SyntaxHighlighting *sh); * * @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. + * @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`. From ab4071a654bfdf4f678a3fc64b4a167900466d3e Mon Sep 17 00:00:00 2001 From: defname Date: Wed, 22 Oct 2025 23:17:19 +0200 Subject: [PATCH 14/24] add random tests --- tests/test_syntax_highlighting.c | 131 ++++++++++++++++++++++++++++--- 1 file changed, 120 insertions(+), 11 deletions(-) diff --git a/tests/test_syntax_highlighting.c b/tests/test_syntax_highlighting.c index 8377381..cb8eb2e 100644 --- a/tests/test_syntax_highlighting.c +++ b/tests/test_syntax_highlighting.c @@ -1,4 +1,5 @@ #include "acutest.h" +#include #include "syntax/highlighting.h" #include "syntax/definition.h" #include "common/iniparser.h" @@ -135,7 +136,7 @@ allowed_blocks=string, comment, keyword, brackets [block:brackets] start=\( end=\) -allowed_blocks=string, keyword +allowed_blocks=string, keyword, brackets [block:keyword] start=keyword @@ -171,36 +172,36 @@ void test_basics(void) { }, { test_ini1, - "foo 'ba//r' foo", // string with comment inside (should be ignored) + "foo keyword foo", // keyword 2, {4, 11}, - {"string", "root"}, + {"keyword", "root"}, 1, {"root"} }, { test_ini1, - "foo keyword foo", // keyword + "foo '//not a comment' foo", // string with comment inside (should be ignored) 2, - {4, 11}, - {"keyword", "root"}, + {4, 21}, + {"string", "root"}, 1, {"root"} }, { test_ini1, - "foo 'keyword' foo", // keyword inside string + "foo 'not a keyword' foo", // keyword inside string 2, - {4, 13}, + {4, 19}, {"string", "root"}, 1, {"root"} }, { test_ini1, - "foo (bar) foo", // keyword inside string + "foo (no brackets) foo", 2, - {4, 9}, + {4, 17}, {"brackets", "root"}, 1, {"root"} @@ -222,7 +223,25 @@ void test_basics(void) { {"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]); @@ -231,12 +250,102 @@ void test_basics(void) { TEST_CASE(cases[i].str); assert_highlight_tags(cases[i]); } +} + +void test_open_blocks(void) { + TagTestCase cases[] = { + { + test_ini1, + "(keyword", + 3, + {0, 1, 8}, + {"brackets", "keyword", "brackets"}, + 2, + {"brackets", "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 From cc90894074902a852b468e4859b6fc1fea98ad50 Mon Sep 17 00:00:00 2001 From: defname Date: Thu, 23 Oct 2025 06:41:25 +0200 Subject: [PATCH 15/24] handle src->capacity == 0 in Stack_Copy() --- src/common/stack.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/common/stack.c b/src/common/stack.c index cee2c33..2e8eb0f 100644 --- a/src/common/stack.c +++ b/src/common/stack.c @@ -68,21 +68,30 @@ void Stack_Destroy(Stack *stack) { free(stack); } -Stack *Stack_Copy(const Stack *stack) { - if (!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."); } - copy->items = malloc(stack->capacity * sizeof(void *)); + + 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."); } - memcpy(copy->items, stack->items, stack->capacity * sizeof(void *)); - copy->size = stack->size; - copy->capacity = stack->capacity; + // 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; } From 6f330ce05a2458918ae64ae142ae1cd34336adfd Mon Sep 17 00:00:00 2001 From: defname Date: Thu, 23 Oct 2025 06:45:01 +0200 Subject: [PATCH 16/24] add NULL pointer guard before using the key_free_func() function pointer in the Table module --- src/common/table.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/table.c b/src/common/table.c index dd7b792..e71bf30 100644 --- a/src/common/table.c +++ b/src/common/table.c @@ -143,7 +143,7 @@ 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) { + if (slot->state == TABLE_SLOT_USED && table->key_free_func) { table->key_free_func(slot->key); } } From ba7657323298402f809bab881834f928637a3f49 Mon Sep 17 00:00:00 2001 From: defname Date: Thu, 23 Oct 2025 06:46:25 +0200 Subject: [PATCH 17/24] fix error message for end_regex compilation error in SyntaxBlockDef_FromTable() --- src/syntax/definition.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/syntax/definition.c b/src/syntax/definition.c index 212c070..f8e23a4 100644 --- a/src/syntax/definition.c +++ b/src/syntax/definition.c @@ -110,7 +110,7 @@ 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) ); regfree(&block->start); free(block->name); From cbf841bb3d5ecac753bee8746b0ab8bf88360c6f Mon Sep 17 00:00:00 2001 From: defname Date: Thu, 23 Oct 2025 06:47:49 +0200 Subject: [PATCH 18/24] correct inlcudes --- src/syntax/highlighting.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/syntax/highlighting.h b/src/syntax/highlighting.h index d4bd43d..c176b4c 100644 --- a/src/syntax/highlighting.h +++ b/src/syntax/highlighting.h @@ -27,7 +27,7 @@ #ifndef SYNTAX_HIGHLIGHTING_H #define SYNTAX_HIGHLIGHTING_H -#include +#include #include "definition.h" #include "common/stack.h" #include "common/table.h" From 4dece63e4250fb27dee2ae2b08cb50a9f0ad03b8 Mon Sep 17 00:00:00 2001 From: defname Date: Thu, 23 Oct 2025 06:51:02 +0200 Subject: [PATCH 19/24] fix typo --- src/syntax/highlighting.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/syntax/highlighting.h b/src/syntax/highlighting.h index c176b4c..dfd29f9 100644 --- a/src/syntax/highlighting.h +++ b/src/syntax/highlighting.h @@ -43,7 +43,7 @@ */ typedef struct _SyntaxHighlightingTag { const String *text; //< pointer to the string this tag is for - size_t byte_offset; //< byte offset where the tag occures + size_t byte_offset; //< byte offset where the tag occurs const SyntaxBlockDef *block; //< pointer to the block this tag is for } SyntaxHighlightingTag; From a94c78cb7455bb656487be175e04767e670b6191 Mon Sep 17 00:00:00 2001 From: defname Date: Thu, 23 Oct 2025 06:53:13 +0200 Subject: [PATCH 20/24] add missing includes --- tests/test_syntax_highlighting.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_syntax_highlighting.c b/tests/test_syntax_highlighting.c index cb8eb2e..c73406e 100644 --- a/tests/test_syntax_highlighting.c +++ b/tests/test_syntax_highlighting.c @@ -1,5 +1,8 @@ #include "acutest.h" #include +#include +#include +#include #include "syntax/highlighting.h" #include "syntax/definition.h" #include "common/iniparser.h" From 8352c27ab4250e57bd774d6d1b15bacabdad4a9a Mon Sep 17 00:00:00 2001 From: defname Date: Thu, 23 Oct 2025 08:47:35 +0200 Subject: [PATCH 21/24] change examples in documentation --- src/syntax/definition.h | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/syntax/definition.h b/src/syntax/definition.h index 2351280..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). From 70e3a4a82ff51ad804bae043b12b7c13551ff04b Mon Sep 17 00:00:00 2001 From: defname Date: Thu, 23 Oct 2025 08:50:32 +0200 Subject: [PATCH 22/24] change "key == NULL" to "!key" for non pointer keys --- src/common/table.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/table.c b/src/common/table.c index e71bf30..8608c54 100644 --- a/src/common/table.c +++ b/src/common/table.c @@ -244,7 +244,7 @@ void Table_Set(Table *table, const void *key, void *value, void (*destructor)(vo if (slot->state == TABLE_SLOT_EMPTY) { slot->state = TABLE_SLOT_USED; slot->key = table->key_copy_func(key); - if (slot->key == NULL) { + if (!slot->key) { logFatal("No memory for string copy in Table_Set()."); } table->used++; From 316bf22992b5a5e8a3e09cff8bbde5cf23835e6f Mon Sep 17 00:00:00 2001 From: defname Date: Thu, 23 Oct 2025 08:54:19 +0200 Subject: [PATCH 23/24] do not shadow parameter match in find_first_block() --- src/syntax/highlighting.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/syntax/highlighting.c b/src/syntax/highlighting.c index f229178..a06fa70 100644 --- a/src/syntax/highlighting.c +++ b/src/syntax/highlighting.c @@ -105,10 +105,10 @@ static const SyntaxBlockDef *find_first_child(const char *str, const SyntaxBlock const SyntaxBlockDef *first_block; for (size_t i=0; ichildren_count; i++) { const SyntaxBlockDef *child = current->children[i]; - regmatch_t match; - if (regexec(&child->start, str, 1, &match, 0) == 0) { - if (!had_match || match.rm_so < first_match.rm_so) { - first_match = match; + 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; } From 68b8fed213b2f97e939a52179b3cb1ad3890b7d9 Mon Sep 17 00:00:00 2001 From: defname Date: Thu, 23 Oct 2025 08:56:22 +0200 Subject: [PATCH 24/24] change first parameter name in SyntaxHighlighting_HighlightString() for consitency --- src/syntax/highlighting.c | 8 ++++---- src/syntax/highlighting.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/syntax/highlighting.c b/src/syntax/highlighting.c index a06fa70..6d75c63 100644 --- a/src/syntax/highlighting.c +++ b/src/syntax/highlighting.c @@ -130,9 +130,9 @@ static bool find_end_of_block(const char *str, const SyntaxBlockDef *current, re return false; } -Stack *SyntaxHighlighting_HighlightString(SyntaxHighlighting *hl, const String *text, const Stack *open_blocks_at_begin) { +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(hl->strings, text); + SyntaxHighlightingString *shs = Table_Get(sh->strings, text); if (shs) { // clear it if so SyntaxHighlightingString_Clear(shs); @@ -140,7 +140,7 @@ Stack *SyntaxHighlighting_HighlightString(SyntaxHighlighting *hl, const String * else { // otherwise create and store shs = SyntaxHighlightingString_Create(text); - Table_Set(hl->strings, text, shs, (void(*)(void*))SyntaxHighlightingString_Destroy); + Table_Set(sh->strings, text, shs, (void(*)(void*))SyntaxHighlightingString_Destroy); } // create a working copy of the stack @@ -150,7 +150,7 @@ Stack *SyntaxHighlighting_HighlightString(SyntaxHighlighting *hl, const String * } else { open_blocks = Stack_Create(); - Stack_Push(open_blocks, hl->def->root); + Stack_Push(open_blocks, sh->def->root); } // iterate over the string diff --git a/src/syntax/highlighting.h b/src/syntax/highlighting.h index dfd29f9..5c1739e 100644 --- a/src/syntax/highlighting.h +++ b/src/syntax/highlighting.h @@ -72,7 +72,7 @@ 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 { @@ -103,6 +103,6 @@ void SyntaxHighlighting_Deinit(SyntaxHighlighting *sh); * @returns * A new created `Stack` containing all open blocks at the end of `text`. */ -Stack *SyntaxHighlighting_HighlightString(SyntaxHighlighting *hl, const String *text, const Stack *open_blocks); +Stack *SyntaxHighlighting_HighlightString(SyntaxHighlighting *sh, const String *text, const Stack *open_blocks); #endif \ No newline at end of file