From 9d2b28026498a44fe719ed8f9738593010c6685e Mon Sep 17 00:00:00 2001 From: defname Date: Mon, 20 Oct 2025 21:19:45 +0200 Subject: [PATCH 1/4] add SyntaxBlockDef and SyntaxDefinition declarations --- src/syntax/definition.c | 35 +++++++++++++++++++++++++++++ src/syntax/definition.h | 50 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 src/syntax/definition.c create mode 100644 src/syntax/definition.h diff --git a/src/syntax/definition.c b/src/syntax/definition.c new file mode 100644 index 0000000..2435765 --- /dev/null +++ b/src/syntax/definition.c @@ -0,0 +1,35 @@ +#include "definition.h" +#include "common/logging.h" +#include "common/typedtable.h" + +SyntaxBlockDef *SyntaxBlockDef_Create() { + SyntaxBlockDef *block = malloc(sizeof(SyntaxBlockDef)); + if (!block) { + logFatal("Cannot allocate memory for SyntaxBlockDef."); + } + block->name[0] = '\0'; + block->only_start = false; + block->children = NULL; + block->children_count = 0; + return block; +} + +void SyntaxBlockDef_Destroy(SyntaxBlockDef *block) { + free(block->children); + free(block); +} + +SyntaxDefinition *SyntaxDefinition_FromTable(const Table *table) { + SyntaxDefinition *def = malloc(sizeof(SyntaxDefinition)); + if (!def) { + logFatal("Cannot allocate memory for SyntaxDefinition."); + } + Table *meta = TypedTable_GetTable(table, "meta"); + if (!meta) { + return NULL; + } + def->name = TypedTable_GetString(meta, "name"); + + // Need an Iterator for the table + +} \ No newline at end of file diff --git a/src/syntax/definition.h b/src/syntax/definition.h new file mode 100644 index 0000000..d8c086b --- /dev/null +++ b/src/syntax/definition.h @@ -0,0 +1,50 @@ +#ifndef SYNTAX_DEFINITION_H +#define SYNTAX_DEFINITION_H + +#include +#include +#include "common/table.h" +#include "common/string.h" + +/** + * @brief Holds the definition of a syntax block. + */ +typedef struct _SyntaxBlockDef { + char name[12]; //< 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) + + bool only_start; //< if true only start is tested and no children are allowed + + struct _SyntaxBlockDef **children; //< list of children definition which are allowed inside the block + size_t children_count; //< number of children + + uint8_t color; //< the color to render the block +} SyntaxBlockDef; + +SyntaxBlockDef *SyntaxBlockDef_Create(); +void SyntaxBlockDef_Destroy(SyntaxBlockDef *block); + + +/** + * @brief Holds a complete syntax definition. + */ +typedef struct _SyntaxDefinition { + char *name; //< name of the filetype (e.g. INI) + SyntaxBlockDef *root; //< root block definition + SyntaxBlockDef **blocks; //< list of all blocks +} SyntaxDefinition; + +/** + * @brief Constructs a SyntaxDefinition from a typed Table. + * + * @param table A typed Table (see common/typedtable.h) holding the syntax definition. + * + * @returns + * A SyntaxDefinition or NULL if the definition in table is not valid. + */ +SyntaxDefinition *SyntaxDefinition_FromTable(const Table *table); +void SyntaxDefinition_Destroy(SyntaxDefinition *def); + + +#endif \ No newline at end of file From cf2f7bfe72f896eb1615eded9bcc9c510f6cb604 Mon Sep 17 00:00:00 2001 From: Bodo Date: Mon, 20 Oct 2025 22:13:05 +0200 Subject: [PATCH 2/4] 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 ebdc8abc7414cb4e079a8da93f56c8b6e26000f5 Mon Sep 17 00:00:00 2001 From: Bodo Date: Tue, 21 Oct 2025 20:37:24 +0200 Subject: [PATCH 3/4] Add table iteration API and improve parsing robustness (#83) * add TableIterator (#79) * add TableIterator * change include that provides ssize_t * add additional tests * make current slot pointer const * include stdbool.h for bool * WIP: implement definition loader * add Table_GetUsage() * allow ':' in INI file key names * fix location of tests * fix typo in Doxygen comment * add NULL guard to String_Take() * 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() * add functiond to create SyntaxDefinition_FromTable() with tests. More tests needed * additional tests (still not enough) * fix: free correct regex in regex_end branch in SyntaxBlockDef_FromTable() * fix: wrong numbe of arguments in String_Format() call in SymtaxBlockDef_FromTable() * add explanation * fix correct recognition of root block * fix memory lealk * add NULL guard for strdup in init_definition() * fix last fix --- CMakeLists.txt | 2 + src/common/iniparser.c | 2 +- src/common/iniparser.h | 2 +- src/common/string.c | 3 + src/common/string.h | 2 +- src/common/table.c | 19 +++ src/common/table.h | 4 + src/common/tableiterator.c | 24 +++ src/common/tableiterator.h | 17 ++ src/syntax/definition.c | 299 ++++++++++++++++++++++++++++++++- src/syntax/definition.h | 32 +++- tests/CMakeLists.txt | 7 +- tests/test_syntax_definition.c | 184 ++++++++++++++++++++ tests/test_table.c | 8 +- tests/test_tableiterator.c | 88 ++++++++++ 15 files changed, 671 insertions(+), 22 deletions(-) create mode 100644 src/common/tableiterator.c create mode 100644 src/common/tableiterator.h create mode 100644 tests/test_syntax_definition.c create mode 100644 tests/test_tableiterator.c 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/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] - ::= | | '.' | '-' | '_' + ::= | | '.' | '-' | '_' | ':' ::= * *****************************************/ 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); 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); diff --git a/src/common/table.c b/src/common/table.c index 3dd739e..9b78e30 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; @@ -274,3 +286,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 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/src/syntax/definition.c b/src/syntax/definition.c index 2435765..04fa511 100644 --- a/src/syntax/definition.c +++ b/src/syntax/definition.c @@ -1,35 +1,316 @@ #include "definition.h" +#include +#include +#include #include "common/logging.h" #include "common/typedtable.h" +#include "common/tableiterator.h" + + +#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; } + void SyntaxBlockDef_Destroy(SyntaxBlockDef *block) { - free(block->children); + if (block->children) { + free(block->children); + } + if (block->name) { + free(block->name); + } + regfree(&block->start); + if (!block->only_start) { + regfree(&block->end); + } free(block); } -SyntaxDefinition *SyntaxDefinition_FromTable(const Table *table) { + +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.", 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) + ); + 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->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) + ); + 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; + } + + 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 ERROR(SYNTAXDEFINITION_NO_META, "No meta section found."); + } + const char *name = TypedTable_GetString(meta, "name"); + def->name = strdup(name ? name : ""); + + // initialize block list + def->blocks = malloc(sizeof(SyntaxBlockDef*) * (Table_GetUsage(table) - 1)); + if (!def->blocks) { + logFatal("Cannot allocate memory for SyntaxDefinition blocks"); + } + + 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_Next(&it)) { + const char *key = it.current->key; + + if (strncmp(key, "block:", 6) == 0) { + const char *block_name = key + 6; + 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) { + // 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 (strcmp(block_name, "root") == 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, "allowed_blocks"); + 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) { + String_Deinit(&s); + free(children); + 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); + + if (!child_mapping) { + // no block with the given child name + // something like + // allowed_blocks = block1, non_existing_block + 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; + 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."); } - Table *meta = TypedTable_GetTable(table, "meta"); - if (!meta) { + + // Initialize SyntaxDefinition instance + *error = init_definition(def, table); + if (error->code != SYNTAXDEFINITION_NO_ERROR) { + SyntaxDefinition_Destroy(def); return NULL; } - def->name = TypedTable_GetString(meta, "name"); + SyntaxDefinitionError_Deinit(error); - // Need an Iterator for the table - -} \ No newline at end of file + // 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); +} + + +#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..aa1cacb 100644 --- a/src/syntax/definition.h +++ b/src/syntax/definition.h @@ -6,11 +6,35 @@ #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 +48,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 +58,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 +70,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/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 diff --git a/tests/test_syntax_definition.c b/tests/test_syntax_definition.c new file mode 100644 index 0000000..a3f4d7e --- /dev/null +++ b/tests/test_syntax_definition.c @@ -0,0 +1,184 @@ +#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" + "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 + }, + { + "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]); + + for (int 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 b7c4d46280ef1d6c68ea019e4095126aadeb651e Mon Sep 17 00:00:00 2001 From: defname Date: Tue, 21 Oct 2025 20:56:22 +0200 Subject: [PATCH 4/4] add additional test --- tests/test_syntax_definition.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/test_syntax_definition.c b/tests/test_syntax_definition.c index a3f4d7e..3be386b 100644 --- a/tests/test_syntax_definition.c +++ b/tests/test_syntax_definition.c @@ -174,11 +174,38 @@ void test_errors(void) { } } + +void test_children(void) { + const char *ini = + "[meta]\n" + "name = Children\n" + "[block:root]\n" + "start=\".\"\n" + "allowed_blocks=block1,block2\n" + "[block:block1]\n" + "start=\".\"\n" + "[block:block2]\n" + "start=\".\"\n"; + Table *table = table_from_ini(ini); + SyntaxDefinitionError error; + SyntaxDefinition *def = SyntaxDefinition_FromTable(table, &error); + TEST_ASSERT(def != NULL); + TEST_CHECK(def->blocks_count == 3); + TEST_CHECK(def->root != NULL); + TEST_CHECK(def->root->children_count == 2); + + Table_Destroy(table); + SyntaxDefinition_Destroy(def); + SyntaxDefinitionError_Deinit(&error); +} + + TEST_LIST = { { "SyntaxDefinition: Block Simple", test_block_simple }, { "SyntaxDefinitions: Block with End", test_block_with_end }, { "SyntaxDefinition: Block Errors", test_block_errors }, { "SyntaxDefinition: Minimal Example", test_minimal_definition }, { "SyntaxDefinition: Errors", test_errors }, + { "SyntaxDefinition: Children", test_children }, { NULL, NULL } };