From cf2f7bfe72f896eb1615eded9bcc9c510f6cb604 Mon Sep 17 00:00:00 2001 From: Bodo Date: Mon, 20 Oct 2025 22:13:05 +0200 Subject: [PATCH 01/17] add TableIterator (#79) * add TableIterator * change include that provides ssize_t * add additional tests * make current slot pointer const * include stdbool.h for bool --- src/common/tableiterator.c | 24 +++++++++++ src/common/tableiterator.h | 17 ++++++++ tests/test_tableiterator.c | 88 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+) create mode 100644 src/common/tableiterator.c create mode 100644 src/common/tableiterator.h create mode 100644 tests/test_tableiterator.c diff --git a/src/common/tableiterator.c b/src/common/tableiterator.c new file mode 100644 index 0000000..538d6d3 --- /dev/null +++ b/src/common/tableiterator.c @@ -0,0 +1,24 @@ +#include "tableiterator.h" + +TableIterator TableIterator_Begin(const Table *table) { + return (TableIterator){ + .table = table, + .index = -1, + .current = NULL + }; +} + +bool TableIterator_Next(TableIterator *it) { + if (!it || !it->table) { + return false; + } + for (size_t i = (size_t)(it->index + 1); i < it->table->capacity; i++) { + TableSlot *slot = &it->table->slots[i]; + if (slot->state == TABLE_SLOT_USED) { + it->index = i; + it->current = slot; + return true; + } + } + return false; +} diff --git a/src/common/tableiterator.h b/src/common/tableiterator.h new file mode 100644 index 0000000..bf7f06c --- /dev/null +++ b/src/common/tableiterator.h @@ -0,0 +1,17 @@ +#ifndef TABLEITERATOR_H +#define TABLEITERATOR_H + +#include +#include +#include "table.h" + +typedef struct _TableIterator { + const Table *table; + ssize_t index; + const TableSlot *current; +} TableIterator; + +TableIterator TableIterator_Begin(const Table *table); +bool TableIterator_Next(TableIterator *it); + +#endif \ No newline at end of file diff --git a/tests/test_tableiterator.c b/tests/test_tableiterator.c new file mode 100644 index 0000000..ae1aca0 --- /dev/null +++ b/tests/test_tableiterator.c @@ -0,0 +1,88 @@ +#include "acutest.h" +#include "common/tableiterator.h" +#include "common/table.h" + +void fill_table(const char **key, const char **values, size_t count) { + Table *table = Table_Create(); + + for (size_t i=0; istate == TABLE_SLOT_USED); + slots++; + } + TEST_CHECK(slots == count); + Table_Destroy(table); +} + +void test_standard(void) { + const char *keys[] = { + "key0", + "key1", + "key2", + "key3" + }; + + const char *values[] = { + "value0", + "value1", + "value2", + "value3" + }; + size_t count = sizeof(keys) / sizeof(keys[0]); + + fill_table(keys, values, count); + fill_table(keys, values, 0); + fill_table(keys, values, 1); +} + +void test_iterating_twice(void) { + Table *table = Table_Create(); + Table_Set(table, "key", "value", NULL); + Table_Set(table, "key2", "value2", NULL); + Table_Set(table, "key3", "value3", NULL); + + TableIterator it = TableIterator_Begin(table); + size_t count = 0; + while (TableIterator_Next(&it)) { + count++; + } + TEST_CHECK(count == 3); + + // run second time without reinitializing + count = 0; + while (TableIterator_Next(&it)) { + count++; + } + TEST_CHECK(count == 0); + + // run again with reinitializing + it = TableIterator_Begin(table); + count = 0; + while (TableIterator_Next(&it)) { + count++; + } + TEST_CHECK(count == 3); + + Table_Destroy(table); +} + +void test_edgecases(void) { + TableIterator it = TableIterator_Begin(NULL); + TEST_CHECK(!TableIterator_Next(&it)); + + TEST_CHECK(!TableIterator_Next(NULL)); +} + + +TEST_LIST = { + { "TableIterator: Standard", test_standard }, + { "TableIterator: Iterating twice", test_iterating_twice }, + { "TableIterator: Edgecases", test_edgecases }, + { NULL, NULL } +}; + From 7cb19977f3769161ce9cdce20642dc78eda18f02 Mon Sep 17 00:00:00 2001 From: defname Date: Tue, 21 Oct 2025 07:02:06 +0200 Subject: [PATCH 02/17] WIP: implement definition loader --- src/syntax/definition.c | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/syntax/definition.c b/src/syntax/definition.c index 2435765..0892fec 100644 --- a/src/syntax/definition.c +++ b/src/syntax/definition.c @@ -1,8 +1,9 @@ #include "definition.h" #include "common/logging.h" #include "common/typedtable.h" +#include "common/tableiterator.h" -SyntaxBlockDef *SyntaxBlockDef_Create() { +static SyntaxBlockDef *SyntaxBlockDef_Create() { SyntaxBlockDef *block = malloc(sizeof(SyntaxBlockDef)); if (!block) { logFatal("Cannot allocate memory for SyntaxBlockDef."); @@ -14,10 +15,13 @@ SyntaxBlockDef *SyntaxBlockDef_Create() { return block; } -void SyntaxBlockDef_Destroy(SyntaxBlockDef *block) { +static void SyntaxBlockDef_Destroy(SyntaxBlockDef *block) { free(block->children); free(block); } +static SyntaxBlockDef *SyntaxBlockDef_FromTable(const char *name, const Table *table) { + +} SyntaxDefinition *SyntaxDefinition_FromTable(const Table *table) { SyntaxDefinition *def = malloc(sizeof(SyntaxDefinition)); @@ -30,6 +34,18 @@ SyntaxDefinition *SyntaxDefinition_FromTable(const Table *table) { } def->name = TypedTable_GetString(meta, "name"); - // Need an Iterator for the table - + TableIterator it = TableIterator_Begin(table); + while (!TableIterator_IsEnd(it)) { + const char *key = TableIterator_Key(&it); + if (strncmp(key, "block:") == 0) { + const char *block_name = key + 6; + SyntaxBlockDef *block = SyntaxBlockDef_FromTable(block_name, TableIterator_Value(&it)); + if (!block) { + SyntaxDefinition_Destroy(def); + return NULL; + } + } + + TableIterator_Next(&it); + } \ No newline at end of file From 9c175a0dcc2e363537a0c6607666f0461a89a629 Mon Sep 17 00:00:00 2001 From: defname Date: Tue, 21 Oct 2025 14:38:20 +0200 Subject: [PATCH 03/17] add Table_GetUsage() --- src/common/table.c | 7 +++++++ src/common/table.h | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/src/common/table.c b/src/common/table.c index 3dd739e..61f4696 100644 --- a/src/common/table.c +++ b/src/common/table.c @@ -274,3 +274,10 @@ bool Table_HasOwnership(const Table *table, const char *key) { TableSlot *slot = find_slot(table, key); return (slot->state == TABLE_SLOT_USED && slot->destructor != NULL); } + +size_t Table_GetUsage(const Table *table) { + if (!table) { + return 0; + } + return table->used; +} \ No newline at end of file diff --git a/src/common/table.h b/src/common/table.h index a4c916a..1ec71ee 100644 --- a/src/common/table.h +++ b/src/common/table.h @@ -97,5 +97,9 @@ bool Table_Has(const Table *table, const char *key); */ bool Table_HasOwnership(const Table *table, const char *key); +/** + * @brief Return the current number of non-free table slots. + */ +size_t Table_GetUsage(const Table *table); #endif \ No newline at end of file From bec5990f85d669e7ee9029d7081c8fb1caaad120 Mon Sep 17 00:00:00 2001 From: defname Date: Tue, 21 Oct 2025 15:20:45 +0200 Subject: [PATCH 04/17] allow ':' in INI file key names --- src/common/iniparser.c | 2 +- src/common/iniparser.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/iniparser.c b/src/common/iniparser.c index 29c40e2..ada7601 100644 --- a/src/common/iniparser.c +++ b/src/common/iniparser.c @@ -142,7 +142,7 @@ static bool is_digit(char ch) { } static bool is_key_char(char ch) { - return is_letter(ch) || is_digit(ch) || ch == '.' || ch == '-' || ch == '_'; + return is_letter(ch) || is_digit(ch) || ch == '.' || ch == '-' || ch == '_' || ch == ':'; } static bool is_at_end(const IniParser *parser) { diff --git a/src/common/iniparser.h b/src/common/iniparser.h index b5fd734..3049065 100644 --- a/src/common/iniparser.h +++ b/src/common/iniparser.h @@ -57,7 +57,7 @@ ::= ' ' | '\t' ::= [a-zA-Z] - ::= | | '.' | '-' | '_' + ::= | | '.' | '-' | '_' | ':' ::= * *****************************************/ From acba4b473c31d942ec9ff47929b0eec026773a71 Mon Sep 17 00:00:00 2001 From: defname Date: Tue, 21 Oct 2025 16:00:09 +0200 Subject: [PATCH 05/17] fix location of tests --- CMakeLists.txt | 2 ++ tests/CMakeLists.txt | 7 +------ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8bcba1a..57e2577 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,6 +53,8 @@ target_link_libraries(${PROJECT_NAME} PRIVATE ${PROJECT_OBJECTS}) add_compile_definitions(_XOPEN_SOURCE=700) +# CTest-Unterstützung aktivieren +enable_testing() # --- Add tools --- add_subdirectory(tools) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9a75117..214670b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,8 +1,3 @@ - -# --- Test-Konfiguration --- -# CTest-Unterstützung aktivieren -enable_testing() - # Finde alle Testdateien file(GLOB TEST_SOURCES "test_*.c") @@ -26,6 +21,6 @@ foreach(TEST_SOURCE_FILE ${TEST_SOURCES}) target_link_libraries(${TEST_NAME} PRIVATE ${TEST_LINK_OBJECTS}) # Den Test zu CTest hinzufügen, damit er mit `ctest` ausgeführt werden kann - add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME}) + add_test(NAME ${TEST_NAME} COMMAND $) endforeach() \ No newline at end of file From 5eee148866640fc05d85ba5bc5613e6da1d37868 Mon Sep 17 00:00:00 2001 From: defname Date: Tue, 21 Oct 2025 16:25:07 +0200 Subject: [PATCH 06/17] fix typo in Doxygen comment --- src/common/string.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/string.h b/src/common/string.h index b9dfd65..88eede7 100644 --- a/src/common/string.h +++ b/src/common/string.h @@ -178,7 +178,7 @@ void String_Set(String *str, String src); * * Its for situation where dst is already initialized and your want to fill it * with the content of another string without moving memory around. - * dst will be deinitialized! + * src will be deinitialized! */ void String_Take(String *dst, String *src); From 7f8bd73084bf051e29de0c6a0fd9f6f5ab2ea4a5 Mon Sep 17 00:00:00 2001 From: defname Date: Tue, 21 Oct 2025 16:28:45 +0200 Subject: [PATCH 07/17] add NULL guard to String_Take() --- src/common/string.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/common/string.c b/src/common/string.c index a3220ac..941ebe2 100644 --- a/src/common/string.c +++ b/src/common/string.c @@ -316,6 +316,9 @@ void String_Set(String *str, String src) { } void String_Take(String *dst, String *src) { + if (!dst || ! src) { + return; + } String_Deinit(dst); *dst = String_TakeCStr(src->bytes); From 6314f6698490ea3818cd0b1c57e21d6d9ba8a4c9 Mon Sep 17 00:00:00 2001 From: Bodo Date: Tue, 21 Oct 2025 16:50:51 +0200 Subject: [PATCH 08/17] Add early-exit guards to table lookup functions (#80) * Fix bug in reading operations on an empty table * add check for empty table also to Table_Delete() --- src/common/table.c | 12 ++++++++++++ tests/test_table.c | 8 +++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/common/table.c b/src/common/table.c index 3dd739e..737a5f5 100644 --- a/src/common/table.c +++ b/src/common/table.c @@ -220,6 +220,9 @@ void *Table_Get(const Table *table, const char *key) { if (!table) { logFatal("Invalid table in Table_Get()."); } + if (table->used == 0) { + return NULL; + } if (!key) { logWarn("Table_Get() was called with key == NULL."); return NULL; @@ -236,6 +239,9 @@ void Table_Delete(Table *table, const char *key) { if (!table) { logFatal("Invalid table in Table_Delete()."); } + if (table->used == 0) { + return; + } if (!key) { logWarn("Table_Delete() is called with key == NULL."); return; @@ -255,6 +261,9 @@ bool Table_Has(const Table *table, const char *key) { if (!table) { logFatal("Invalid table in Table_Has()."); } + if (table->used == 0) { + return false; + } if (!key) { logWarn("Table_Has() was called with key == NULL."); return false; @@ -267,6 +276,9 @@ bool Table_HasOwnership(const Table *table, const char *key) { if (!table) { logFatal("Invalid table in Table_HasOwnership()."); } + if (table->used == 0) { + return false; + } if (!key) { logWarn("Table_HasOwnership() was called with key == NULL."); return false; diff --git a/tests/test_table.c b/tests/test_table.c index b967aa0..4cc41c1 100644 --- a/tests/test_table.c +++ b/tests/test_table.c @@ -105,7 +105,13 @@ void test_edge_case(void) { } Table_Get(table, "key0"); - + Table_Destroy(table); + + // checks on empty table + table = Table_Create(); + TEST_CHECK(!Table_Get(table, "key")); + TEST_CHECK(!Table_Has(table, "key")); + TEST_CHECK(!Table_HasOwnership(table, "key")); Table_Destroy(table); } From 90c1acd3c2b6f4cbd04fbf8316524f9fa8ec0286 Mon Sep 17 00:00:00 2001 From: defname Date: Tue, 21 Oct 2025 19:47:02 +0200 Subject: [PATCH 09/17] add functiond to create SyntaxDefinition_FromTable() with tests. More tests needed --- src/syntax/definition.c | 300 ++++++++++++++++++++++++++++++--- src/syntax/definition.h | 33 +++- tests/test_syntax_definition.c | 159 +++++++++++++++++ 3 files changed, 469 insertions(+), 23 deletions(-) create mode 100644 tests/test_syntax_definition.c diff --git a/src/syntax/definition.c b/src/syntax/definition.c index 0892fec..5138647 100644 --- a/src/syntax/definition.c +++ b/src/syntax/definition.c @@ -1,51 +1,311 @@ #include "definition.h" +#include +#include +#include #include "common/logging.h" #include "common/typedtable.h" #include "common/tableiterator.h" -static SyntaxBlockDef *SyntaxBlockDef_Create() { + +#define NO_ERROR ((SyntaxDefinitionError){ .code = SYNTAXDEFINITION_NO_ERROR, .message = String_Empty() }) +#define ERROR(err_code, ...) ((SyntaxDefinitionError){ .code = err_code, .message = String_Format(__VA_ARGS__) }) + +static void set_error(SyntaxDefinitionError *error, SyntaxDefinitionErrorCode code, String message) { + error->code = code; + logError("Error in SyntaxDefinition: %s", String_AsCStr(&message)); + String_Take(&error->message, &message); +} + + +void SyntaxDefinitionError_Deinit(SyntaxDefinitionError *error) { + String_Deinit(&error->message); +} + + +SyntaxBlockDef *SyntaxBlockDef_Create() { SyntaxBlockDef *block = malloc(sizeof(SyntaxBlockDef)); if (!block) { logFatal("Cannot allocate memory for SyntaxBlockDef."); } - block->name[0] = '\0'; + block->name = NULL; block->only_start = false; block->children = NULL; block->children_count = 0; return block; } -static void SyntaxBlockDef_Destroy(SyntaxBlockDef *block) { - free(block->children); + +void SyntaxBlockDef_Destroy(SyntaxBlockDef *block) { + if (block->children) { + free(block->children); + } + if (block->name) { + free(block->name); + } + regfree(&block->start); + if (!block->only_start) { + regfree(&block->end); + } free(block); } -static SyntaxBlockDef *SyntaxBlockDef_FromTable(const char *name, const Table *table) { -} -SyntaxDefinition *SyntaxDefinition_FromTable(const Table *table) { - SyntaxDefinition *def = malloc(sizeof(SyntaxDefinition)); - if (!def) { - logFatal("Cannot allocate memory for SyntaxDefinition."); +SyntaxBlockDef *SyntaxBlockDef_FromTable(const char *name, const Table *table, SyntaxDefinitionError *error) { + *error = NO_ERROR; + const char *start_regex = TypedTable_GetString(table, "start"); + const char *end_regex = TypedTable_GetString(table, "end"); + + SyntaxBlockDef *block = SyntaxBlockDef_Create(); + block->name = strdup(name); + block->only_start = true; + block->color = (uint8_t)TypedTable_GetNumber(table, "color"); + + if (!start_regex) { + set_error(error, + SYNTAXDEFINITION_BLOCK_NO_START_REGEX, + String_Format("Block \"%s\" has no start regex defined.", start_regex, name) + ); + free(block->name); + free(block); + return NULL; } + int ret = regcomp(&block->start, start_regex, REG_EXTENDED); + if (ret != 0) { + char errbuf[256]; + regerror(ret, &block->start, errbuf, sizeof(errbuf)); + set_error(error, + SYNTAXDEFINITION_REGEX_ERROR_START, + String_Format("Error in start regex \"%s\" in block \"%s\": %s", start_regex, name, errbuf) + ); + regfree(&block->start); + free(block->name); + free(block); + return NULL; + } + if (end_regex) { + ret = regcomp(&block->end, end_regex, REG_EXTENDED); + if (ret != 0) { + char errbuf[256]; + regerror(ret, &block->start, errbuf, sizeof(errbuf)); + set_error(error, + SYNTAXDEFINITION_REGEX_ERROR_END, + String_Format("Error in start regex \"%s\" in block \"%s\": %s", start_regex, name, errbuf) + ); + block->only_start = true; + SyntaxBlockDef_Destroy(block); + return NULL; + } + block->only_start = false; + } + + return block; +} + + +// helper struct +typedef struct _table_block_mapping { + const Table *table; + SyntaxBlockDef *block; +} table_block_mapping; + + +static SyntaxDefinitionError init_definition(SyntaxDefinition *def, const Table *table) { + def->name = NULL; + def->blocks = NULL; + def->blocks_count = 0; + def->root = NULL; + + // Get meta table Table *meta = TypedTable_GetTable(table, "meta"); if (!meta) { - return NULL; + return ERROR(SYNTAXDEFINITION_NO_META, "No meta section found."); + } + def->name = strdup(TypedTable_GetString(meta, "name")); + + // initialize block list + def->blocks = malloc(sizeof(SyntaxBlockDef*) * (Table_GetUsage(table) - 1)); + if (!def->blocks) { + logFatal("Cannot allocate memory for SyntaxDefinition blocks"); } - def->name = TypedTable_GetString(meta, "name"); + return NO_ERROR; +} + + +static SyntaxDefinitionError build_blocks(SyntaxDefinition *def, const Table *table, Table *blocks) { + // get all block tables and build all block definitions (without children) TableIterator it = TableIterator_Begin(table); - while (!TableIterator_IsEnd(it)) { - const char *key = TableIterator_Key(&it); - if (strncmp(key, "block:") == 0) { + while (TableIterator_Next(&it)) { + const char *key = it.current->key; + + if (strncmp(key, "block:", 6) == 0) { const char *block_name = key + 6; - SyntaxBlockDef *block = SyntaxBlockDef_FromTable(block_name, TableIterator_Value(&it)); + const TypedValue *value = (TypedValue*)it.current->value; + if (value->type != VALUE_TYPE_TABLE) { + // block is no block, but a value or something + return ERROR(SYNTAXDEFINITION_BLOCK_NOT_A_SECTION, "Block \"block:%s\" is not a section.", block_name); + } + if (strlen(block_name) == 0) { + // block name is empty + return ERROR(SYNTAXDEFINITION_BLOCK_NAME_EMPTY, "Empty block name found."); + } + const Table *block_table = value->data.table_value; + + // Create the block (without linking children) + SyntaxDefinitionError error; + SyntaxBlockDef *block = SyntaxBlockDef_FromTable(block_name, block_table, &error); + if (!block) { - SyntaxDefinition_Destroy(def); - return NULL; + // block definition was invalid + return error; + } + SyntaxDefinitionError_Deinit(&error); + + // set the block in the temporary table + table_block_mapping *mapping = malloc(sizeof(table_block_mapping)); + if (!mapping) { + logFatal("Cannot allocate memory for table_block_mapping."); + } + mapping->table = block_table; + mapping->block = block; + Table_Set(blocks, block_name, mapping, free); + + // add the block to the list of all blocks (important for freeing also unreferenced blocks later) + def->blocks[def->blocks_count++] = block; + + if (strncmp(block_name, "root", 4) == 0) { + def->root = block; + } + } + } + + if (!def->root) { + return ERROR(SYNTAXDEFINITION_NO_ROOT_BLOCK, "No root block defined."); + } + + return NO_ERROR; +} + + +SyntaxDefinitionError link_children(SyntaxDefinition *def, Table *blocks) { + for (size_t i=0; iblocks_count; i++) { + SyntaxBlockDef *block = def->blocks[i]; + + // get the mapping + table_block_mapping *mapping = Table_Get(blocks, block->name); + if (!mapping) { + logFatal("Some serious design flaw detected."); + } + const Table *block_table = mapping->table; + + const char *children_str = TypedTable_GetString(block_table, "children"); + if (!children_str) { + continue; + } + + String s = String_FromCStr(children_str, strlen(children_str)); + ssize_t children_count = 0; + String delimiter = String_FromCStr(",", 1); + StringView *children = String_Split(&s, &delimiter, &children_count); + if (children_count < 0) { + logFatal("Potential rrror in String_Split()."); + } + String_Deinit(&delimiter); + if (children_count == 0) { + continue; + } + + // malloc memory for the children of SyntaxBlockDef + block->children = malloc(sizeof(SyntaxBlockDef*) * children_count); + if (!block->children) { + logFatal("Cannot allocate memory for children of SyntaxBlockDef."); + } + + for (size_t child_idx=0; child_idx<(size_t)children_count; child_idx++) { + String child_name = String_FromView(children[child_idx]); + String_Trim(&child_name); + table_block_mapping *child_mapping = Table_Get(blocks, child_name.bytes); + String_Deinit(&child_name); + if (!child_mapping) { + // no block with the given child name + // something like + // allowed_blocks = block1, non_existing_block + String_Deinit(&s); + free(children); + return ERROR( + SYNTAXDEFINITION_BLOCK_DOES_NOT_EXIST, + "Block \"%s\" defines non-existing block \"%s\" as it's child.", block->name, child_name.bytes + ); } + block->children[child_idx] = child_mapping->block; } + block->children_count = children_count; + String_Deinit(&s); + free(children); + } + return NO_ERROR; +} + + +SyntaxDefinition *SyntaxDefinition_FromTable(const Table *table, SyntaxDefinitionError *error) { + if (!table) { + return NULL; + } + + SyntaxDefinition *def = malloc(sizeof(SyntaxDefinition)); + if (!def) { + logFatal("Cannot allocate memory for SyntaxDefinition."); + } + + // Initialize SyntaxDefinition instance + *error = init_definition(def, table); + if (error->code != SYNTAXDEFINITION_NO_ERROR) { + SyntaxDefinition_Destroy(def); + return NULL; + } + SyntaxDefinitionError_Deinit(error); + + // temporary table mapping all block names to it's SyntaxBlockDef + Table *blocks = Table_Create(); + *error = build_blocks(def, table, blocks); + if (error->code != SYNTAXDEFINITION_NO_ERROR) { + SyntaxDefinition_Destroy(def); + Table_Destroy(blocks); + return NULL; + } + SyntaxDefinitionError_Deinit(error); + + // construct the children lists + *error = link_children(def, blocks); + if (error->code != SYNTAXDEFINITION_NO_ERROR) { + SyntaxDefinition_Destroy(def); + Table_Destroy(blocks); + return NULL; + } + SyntaxDefinitionError_Deinit(error); + + // cleanup + Table_Destroy(blocks); + + return def; +} + +void SyntaxDefinition_Destroy(SyntaxDefinition *def) { + if (!def) { + return; + } + if (def->name) { + free(def->name); + } + if (def->blocks) { + for (size_t i=0; iblocks_count; i++) { + SyntaxBlockDef_Destroy(def->blocks[i]); + } + free(def->blocks); + } + free(def); +} - TableIterator_Next(&it); -} \ No newline at end of file +#undef NO_ERROR +#undef ERROR \ No newline at end of file diff --git a/src/syntax/definition.h b/src/syntax/definition.h index d8c086b..b140a34 100644 --- a/src/syntax/definition.h +++ b/src/syntax/definition.h @@ -6,11 +6,36 @@ #include "common/table.h" #include "common/string.h" + +typedef enum { + SYNTAXDEFINITION_NO_ERROR, + + SYNTAXDEFINITION_BLOCK_NO_START_REGEX, + SYNTAXDEFINITION_REGEX_ERROR_START, + SYNTAXDEFINITION_REGEX_ERROR_END, + SYNTAXDEFINITION_NO_META, + SYNTAXDEFINITION_BLOCK_NOT_A_SECTION, + SYNTAXDEFINITION_BLOCK_NAME_EMPTY, + SYNTAXDEFINITION_NO_ROOT_BLOCK, + SYNTAXDEFINITION_BLOCK_DOES_NOT_EXIST +} SyntaxDefinitionErrorCode; + +/** + * @brief Holds error information. + */ +typedef struct _SyntaxDefinitionError { + SyntaxDefinitionErrorCode code; + String message; +} SyntaxDefinitionError; + +void SyntaxDefinitionError_Deinit(SyntaxDefinitionError *error); + + /** * @brief Holds the definition of a syntax block. */ typedef struct _SyntaxBlockDef { - char name[12]; //< name of the block definition. for easier debugging + char *name; //< name of the block definition. for easier debugging regex_t start; //< compiled regex to determine the begin of the block regex_t end; //< compiled regex to determine the end of the block (optional) @@ -24,6 +49,7 @@ typedef struct _SyntaxBlockDef { SyntaxBlockDef *SyntaxBlockDef_Create(); void SyntaxBlockDef_Destroy(SyntaxBlockDef *block); +SyntaxBlockDef *SyntaxBlockDef_FromTable(const char *name, const Table *table, SyntaxDefinitionError *error); /** @@ -33,8 +59,10 @@ typedef struct _SyntaxDefinition { char *name; //< name of the filetype (e.g. INI) SyntaxBlockDef *root; //< root block definition SyntaxBlockDef **blocks; //< list of all blocks + size_t blocks_count; //< number of blocks } SyntaxDefinition; + /** * @brief Constructs a SyntaxDefinition from a typed Table. * @@ -43,8 +71,7 @@ typedef struct _SyntaxDefinition { * @returns * A SyntaxDefinition or NULL if the definition in table is not valid. */ -SyntaxDefinition *SyntaxDefinition_FromTable(const Table *table); +SyntaxDefinition *SyntaxDefinition_FromTable(const Table *table, SyntaxDefinitionError *error); void SyntaxDefinition_Destroy(SyntaxDefinition *def); - #endif \ No newline at end of file diff --git a/tests/test_syntax_definition.c b/tests/test_syntax_definition.c new file mode 100644 index 0000000..5252795 --- /dev/null +++ b/tests/test_syntax_definition.c @@ -0,0 +1,159 @@ +#include "acutest.h" +#include "syntax/definition.h" +#include "common/iniparser.h" + + + +void test_block_simple(void) { + Table *block_table = Table_Create(); + TypedTable_SetStringCopy(block_table, "start", ".*"); + TypedTable_SetNumber(block_table, "color", 10); + SyntaxDefinitionError error; + SyntaxBlockDef *block = SyntaxBlockDef_FromTable("test", block_table, &error); + TEST_CHECK(block != NULL); + TEST_CHECK(block->name != NULL); + TEST_CHECK(strcmp(block->name, "test") == 0); + TEST_CHECK(block->start.re_nsub == 0); + TEST_CHECK(block->color == 10); + SyntaxBlockDef_Destroy(block); + Table_Destroy(block_table); + SyntaxDefinitionError_Deinit(&error); +} + +void test_block_with_end(void) { + Table *block_table = Table_Create(); + TypedTable_SetStringCopy(block_table, "start", ".*"); + TypedTable_SetStringCopy(block_table, "end", ".*"); + TypedTable_SetNumber(block_table, "color", 10); + SyntaxDefinitionError error; + SyntaxBlockDef *block = SyntaxBlockDef_FromTable("test", block_table, &error); + TEST_CHECK(block != NULL); + TEST_CHECK(block->name != NULL); + TEST_CHECK(strcmp(block->name, "test") == 0); + TEST_CHECK(block->start.re_nsub == 0); + TEST_CHECK(block->color == 10); + SyntaxBlockDef_Destroy(block); + Table_Destroy(block_table); + SyntaxDefinitionError_Deinit(&error); +} + +void test_block_errors(void) { + Table *block_table = Table_Create(); + + // no start regex + SyntaxDefinitionError error; + SyntaxBlockDef *block = SyntaxBlockDef_FromTable("test", block_table, &error); + TEST_CHECK(block == NULL); + TEST_CHECK(error.code == SYNTAXDEFINITION_BLOCK_NO_START_REGEX); + SyntaxDefinitionError_Deinit(&error); + + // error in start regex + TypedTable_SetStringCopy(block_table, "start", "(\\("); + block = SyntaxBlockDef_FromTable("test", block_table, &error); + TEST_CHECK(block == NULL); + TEST_CHECK(error.code == SYNTAXDEFINITION_REGEX_ERROR_START); + SyntaxDefinitionError_Deinit(&error); + + // error in end regex + TypedTable_SetStringCopy(block_table, "start", ".*"); + TypedTable_SetStringCopy(block_table, "end", "(\\("); + block = SyntaxBlockDef_FromTable("test", block_table, &error); + TEST_CHECK(block == NULL); + TEST_CHECK(error.code == SYNTAXDEFINITION_REGEX_ERROR_END); + SyntaxDefinitionError_Deinit(&error); + + Table_Destroy(block_table); +} + +Table *table_from_ini(const char *ini_str) { + IniParser parser; + IniParser_Init(&parser); + IniParser_SetText(&parser, ini_str); + Table *table = IniParser_Parse(&parser); + TEST_ASSERT(table != NULL); + IniParser_Deinit(&parser); + return table; +} + +void test_minimal_definition(void) { + const char *ini = + "[meta]\n" + "name = MINI\n" + "[block:root]\n" + "start=\".*\"\n"; + Table *table = table_from_ini(ini); + SyntaxDefinitionError error; + SyntaxDefinition *def = SyntaxDefinition_FromTable(table, &error); + TEST_CHECK(def != NULL); + TEST_CHECK(def->name != NULL); + TEST_CHECK(strcmp(def->name, "MINI") == 0); + SyntaxDefinitionError_Deinit(&error); + SyntaxDefinition_Destroy(def); + Table_Destroy(table); +} + +SyntaxDefinitionErrorCode test_error(const char *ini) { + Table *table = table_from_ini(ini); + SyntaxDefinitionError error; + SyntaxDefinition *def = SyntaxDefinition_FromTable(table, &error); + TEST_ASSERT(def == NULL); + Table_Destroy(table); + SyntaxDefinitionErrorCode code = error.code; + SyntaxDefinitionError_Deinit(&error); + return code; +} + +typedef struct { + const char *ini; + SyntaxDefinitionErrorCode error_code; +} error_test_case; + +void test_errors(void) { + error_test_case cases[] = { + { + "[met]\n" // missing meta section + "name = MINI\n" + "[block:root]\n" + "start=\".*\"\n", + SYNTAXDEFINITION_NO_META + }, + { + "[meta]\n" + "name = MINI\n", + SYNTAXDEFINITION_NO_ROOT_BLOCK + }, + { + "[meta]\n" + "name = MINI\n" + "[block:root]\n" + "start=\".*\"\n" + "[block:]\n" + "start=\".*\"\n", + SYNTAXDEFINITION_BLOCK_NAME_EMPTY + }, + + { + "[meta]\n" + "name = MINI\n" + "[block:root]\n" + "start=\".*)(\"\n", + SYNTAXDEFINITION_REGEX_ERROR_START + }, + }; + + int cases_num = sizeof(cases) / sizeof(cases[0]); + + for (int i=0; i Date: Tue, 21 Oct 2025 20:02:29 +0200 Subject: [PATCH 10/17] additional tests (still not enough) --- src/syntax/definition.c | 2 +- src/syntax/definition.h | 1 - tests/test_syntax_definition.c | 27 ++++++++++++++++++++++++++- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/syntax/definition.c b/src/syntax/definition.c index 5138647..9e599a0 100644 --- a/src/syntax/definition.c +++ b/src/syntax/definition.c @@ -198,7 +198,7 @@ SyntaxDefinitionError link_children(SyntaxDefinition *def, Table *blocks) { } const Table *block_table = mapping->table; - const char *children_str = TypedTable_GetString(block_table, "children"); + const char *children_str = TypedTable_GetString(block_table, "allowed_blocks"); if (!children_str) { continue; } diff --git a/src/syntax/definition.h b/src/syntax/definition.h index b140a34..aa1cacb 100644 --- a/src/syntax/definition.h +++ b/src/syntax/definition.h @@ -9,7 +9,6 @@ typedef enum { SYNTAXDEFINITION_NO_ERROR, - SYNTAXDEFINITION_BLOCK_NO_START_REGEX, SYNTAXDEFINITION_REGEX_ERROR_START, SYNTAXDEFINITION_REGEX_ERROR_END, diff --git a/tests/test_syntax_definition.c b/tests/test_syntax_definition.c index 5252795..a3f4d7e 100644 --- a/tests/test_syntax_definition.c +++ b/tests/test_syntax_definition.c @@ -111,7 +111,7 @@ typedef struct { void test_errors(void) { error_test_case cases[] = { { - "[met]\n" // missing meta section + "[met]\n" "name = MINI\n" "[block:root]\n" "start=\".*\"\n", @@ -139,6 +139,30 @@ void test_errors(void) { "start=\".*)(\"\n", SYNTAXDEFINITION_REGEX_ERROR_START }, + { + "block:root = foo\n" + "[meta]\n" + "name = MINI\n", + SYNTAXDEFINITION_BLOCK_NOT_A_SECTION + }, + { + "[meta]\n" + "name = MINI\n" + "[block:root]\n" + "start=.*\n" + "allowed_blocks=foo", + SYNTAXDEFINITION_BLOCK_DOES_NOT_EXIST + }, + { + "[meta]\n" + "name = MINI\n" + "[block:root]\n" + "start=.*\n" + "allowed_blocks=foo, bar\n" + "[block:foo]\n" + "start=.*\n", + SYNTAXDEFINITION_BLOCK_DOES_NOT_EXIST + }, }; int cases_num = sizeof(cases) / sizeof(cases[0]); @@ -146,6 +170,7 @@ void test_errors(void) { for (int i=0; i Date: Tue, 21 Oct 2025 20:11:17 +0200 Subject: [PATCH 11/17] fix: free correct regex in regex_end branch in SyntaxBlockDef_FromTable() --- src/syntax/definition.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/syntax/definition.c b/src/syntax/definition.c index 9e599a0..5b44d28 100644 --- a/src/syntax/definition.c +++ b/src/syntax/definition.c @@ -86,10 +86,10 @@ SyntaxBlockDef *SyntaxBlockDef_FromTable(const char *name, const Table *table, S ret = regcomp(&block->end, end_regex, REG_EXTENDED); if (ret != 0) { char errbuf[256]; - regerror(ret, &block->start, errbuf, sizeof(errbuf)); + regerror(ret, &block->end, errbuf, sizeof(errbuf)); set_error(error, SYNTAXDEFINITION_REGEX_ERROR_END, - String_Format("Error in start regex \"%s\" in block \"%s\": %s", start_regex, name, errbuf) + String_Format("Error in end regex \"%s\" in block \"%s\": %s", start_regex, name, errbuf) ); block->only_start = true; SyntaxBlockDef_Destroy(block); From eb6f0b792fd4090777df8943fc7dfca54cc91465 Mon Sep 17 00:00:00 2001 From: defname Date: Tue, 21 Oct 2025 20:24:36 +0200 Subject: [PATCH 12/17] fix: wrong numbe of arguments in String_Format() call in SymtaxBlockDef_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 5b44d28..c63b7ea 100644 --- a/src/syntax/definition.c +++ b/src/syntax/definition.c @@ -63,7 +63,7 @@ SyntaxBlockDef *SyntaxBlockDef_FromTable(const char *name, const Table *table, S if (!start_regex) { set_error(error, SYNTAXDEFINITION_BLOCK_NO_START_REGEX, - String_Format("Block \"%s\" has no start regex defined.", start_regex, name) + String_Format("Block \"%s\" has no start regex defined.", name) ); free(block->name); free(block); From 4f24ddd56a237d05f1f3662a3c500989272f87eb Mon Sep 17 00:00:00 2001 From: defname Date: Tue, 21 Oct 2025 20:26:58 +0200 Subject: [PATCH 13/17] add explanation --- src/syntax/definition.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/syntax/definition.c b/src/syntax/definition.c index c63b7ea..82d361f 100644 --- a/src/syntax/definition.c +++ b/src/syntax/definition.c @@ -77,7 +77,6 @@ SyntaxBlockDef *SyntaxBlockDef_FromTable(const char *name, const Table *table, S SYNTAXDEFINITION_REGEX_ERROR_START, String_Format("Error in start regex \"%s\" in block \"%s\": %s", start_regex, name, errbuf) ); - regfree(&block->start); free(block->name); free(block); return NULL; @@ -91,8 +90,8 @@ 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; - SyntaxBlockDef_Destroy(block); + 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 return NULL; } block->only_start = false; From 12b8a9888c36bc389a4e5f3398170b068de4a605 Mon Sep 17 00:00:00 2001 From: defname Date: Tue, 21 Oct 2025 20:28:34 +0200 Subject: [PATCH 14/17] fix correct recognition of root block --- 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 82d361f..702693b 100644 --- a/src/syntax/definition.c +++ b/src/syntax/definition.c @@ -172,7 +172,7 @@ static SyntaxDefinitionError build_blocks(SyntaxDefinition *def, const Table *ta // add the block to the list of all blocks (important for freeing also unreferenced blocks later) def->blocks[def->blocks_count++] = block; - if (strncmp(block_name, "root", 4) == 0) { + if (strcmp(block_name, "root") == 0) { def->root = block; } } From 3dcbaa2322dad0858133ff170f69eb5729894877 Mon Sep 17 00:00:00 2001 From: defname Date: Tue, 21 Oct 2025 20:29:43 +0200 Subject: [PATCH 15/17] fix memory lealk --- src/syntax/definition.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/syntax/definition.c b/src/syntax/definition.c index 702693b..23859e6 100644 --- a/src/syntax/definition.c +++ b/src/syntax/definition.c @@ -211,6 +211,8 @@ SyntaxDefinitionError link_children(SyntaxDefinition *def, Table *blocks) { } String_Deinit(&delimiter); if (children_count == 0) { + String_Deinit(&s); + free(children); continue; } From 5af64c26a6f1c14dba5f05b5fec2ca90410f368a Mon Sep 17 00:00:00 2001 From: defname Date: Tue, 21 Oct 2025 20:34:17 +0200 Subject: [PATCH 16/17] add NULL guard for strdup in init_definition() --- src/syntax/definition.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/syntax/definition.c b/src/syntax/definition.c index 23859e6..5d2feae 100644 --- a/src/syntax/definition.c +++ b/src/syntax/definition.c @@ -119,7 +119,8 @@ static SyntaxDefinitionError init_definition(SyntaxDefinition *def, const Table if (!meta) { return ERROR(SYNTAXDEFINITION_NO_META, "No meta section found."); } - def->name = strdup(TypedTable_GetString(meta, "name")); + const char name = TypedTable_GetString(meta, "name"); + def->name = strdup(name ? name : ""); // initialize block list def->blocks = malloc(sizeof(SyntaxBlockDef*) * (Table_GetUsage(table) - 1)); @@ -226,18 +227,21 @@ SyntaxDefinitionError link_children(SyntaxDefinition *def, Table *blocks) { String child_name = String_FromView(children[child_idx]); String_Trim(&child_name); table_block_mapping *child_mapping = Table_Get(blocks, child_name.bytes); - String_Deinit(&child_name); + if (!child_mapping) { // no block with the given child name // something like // allowed_blocks = block1, non_existing_block - String_Deinit(&s); - free(children); - return ERROR( + SyntaxDefinitionError error = ERROR( SYNTAXDEFINITION_BLOCK_DOES_NOT_EXIST, "Block \"%s\" defines non-existing block \"%s\" as it's child.", block->name, child_name.bytes ); + String_Deinit(&child_name); + String_Deinit(&s); + free(children); + return error; } + String_Deinit(&child_name); block->children[child_idx] = child_mapping->block; } block->children_count = children_count; From e43de22c843964e38e3ca2f827680c89b5cfa3d5 Mon Sep 17 00:00:00 2001 From: defname Date: Tue, 21 Oct 2025 20:35:30 +0200 Subject: [PATCH 17/17] fix last fix --- src/syntax/definition.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/syntax/definition.c b/src/syntax/definition.c index 5d2feae..04fa511 100644 --- a/src/syntax/definition.c +++ b/src/syntax/definition.c @@ -119,8 +119,8 @@ static SyntaxDefinitionError init_definition(SyntaxDefinition *def, const Table if (!meta) { return ERROR(SYNTAXDEFINITION_NO_META, "No meta section found."); } - const char name = TypedTable_GetString(meta, "name"); - def->name = strdup(name ? name : ""); + const char *name = TypedTable_GetString(meta, "name"); + def->name = strdup(name ? name : ""); // initialize block list def->blocks = malloc(sizeof(SyntaxBlockDef*) * (Table_GetUsage(table) - 1));