diff --git a/a.exe b/a.exe new file mode 100644 index 0000000..6f5a295 Binary files /dev/null and b/a.exe differ diff --git a/examples/test.c b/examples/test.c index e69de29..1432ec1 100644 --- a/examples/test.c +++ b/examples/test.c @@ -0,0 +1,31 @@ +#define CYAML_IMPLEMENTATION +#include "../include/cyaml.h" +#include + +int main(void) { + // Load a YAML document from a string. + cyaml_document_t *doc = cyaml_load_string("example: Hello, cyaml!"); + if (!doc) { + fprintf(stderr, "Failed to load YAML.\n"); + return 1; + } + + // Get and print the root node's scalar value. + const cyaml_node_t *root = cyaml_document_get_root(doc); + if (root && cyaml_node_get_type(root) == CYAML_NODE_SCALAR) { + printf("YAML Content: %s\n", cyaml_node_as_string(root)); + } + + // Create an emitter and output the document. + cyaml_emitter_t *emitter = cyaml_emitter_create(); + if (cyaml_emitter_emit(emitter, doc) == 0) { + char *output = cyaml_emitter_to_string(emitter); + printf("Emitted YAML:\n%s\n", output); + free(output); + } + + // Cleanup. + cyaml_emitter_destroy(emitter); + cyaml_document_destroy(doc); + return 0; +} diff --git a/examples/units.c b/examples/units.c new file mode 100644 index 0000000..7b9f509 --- /dev/null +++ b/examples/units.c @@ -0,0 +1,364 @@ +#include +#include +#include + +#define CYAML_IMPLEMENTATION +#include "../include/cyaml.h" // Adjust this path as needed + +/* ANSI color definitions for pretty terminal output */ +#define COLOR_RED "\033[31m" +#define COLOR_GREEN "\033[32m" +#define COLOR_YELLOW "\033[33m" +#define COLOR_RESET "\033[0m" + +/* Global counters for tests */ +static int tests_passed = 0; +static int tests_run = 0; + +/* Test assertion macro */ +#define TEST_ASSERT(cond, msg) do { \ + tests_run++; \ + if (!(cond)) { \ + printf(COLOR_RED "[FAIL]" COLOR_RESET " %s\n", msg); \ + } else { \ + tests_passed++; \ + printf(COLOR_GREEN "[PASS]" COLOR_RESET " %s\n", msg); \ + } \ +} while(0) + +/*--------------------------------------------------------- + * Utility Functions to Clean Up Manually Created Nodes + * (These are needed because our cyaml library currently + * only auto-frees scalar nodes from cyaml_load_* functions.) + *---------------------------------------------------------*/ +static void free_mapping_node(cyaml_node_t *node) { + if (node->keys) { + for (size_t i = 0; i < node->mapping_size; i++) { + free(node->keys[i]); + } + free(node->keys); + } + if (node->values) { + for (size_t i = 0; i < node->mapping_size; i++) { + if (node->values[i]) { + if (node->values[i]->scalar) + free(node->values[i]->scalar); + free(node->values[i]); + } + } + free(node->values); + } + free(node); +} + +static void free_sequence_node(cyaml_node_t *node) { + if (node->sequence) { + for (size_t i = 0; i < node->sequence_size; i++) { + if (node->sequence[i]) { + if (node->sequence[i]->scalar) + free(node->sequence[i]->scalar); + free(node->sequence[i]); + } + } + free(node->sequence); + } + free(node); +} + +/*--------------------------------------------------------- + * Test 1: Load YAML String (Scalar) + *---------------------------------------------------------*/ +void test_load_string() { + const char *yaml = "Hello, cyaml!"; + cyaml_document_t *doc = cyaml_load_string(yaml); + TEST_ASSERT(doc != NULL, "cyaml_load_string should not return NULL"); + + if (doc) { + const cyaml_node_t *root = cyaml_document_get_root(doc); + TEST_ASSERT(root != NULL, "Root node should not be NULL"); + + if (root) { + const char *str = cyaml_node_as_string(root); + TEST_ASSERT(str != NULL, "Root node should be a scalar string"); + if (str) { + TEST_ASSERT(strcmp(str, yaml) == 0, "Scalar value should match input string"); + } + } + cyaml_document_destroy(doc); + } +} + +/*--------------------------------------------------------- + * Test 2: Emitter for Scalar YAML + *---------------------------------------------------------*/ +void test_emitter_scalar() { + const char *yaml = "Emitter Test!"; + cyaml_document_t *doc = cyaml_load_string(yaml); + TEST_ASSERT(doc != NULL, "cyaml_load_string for emitter test should not return NULL"); + + if (doc) { + cyaml_emitter_t *emitter = cyaml_emitter_create(); + TEST_ASSERT(emitter != NULL, "cyaml_emitter_create should not return NULL"); + + if (emitter) { + int emit_result = cyaml_emitter_emit(emitter, doc); + TEST_ASSERT(emit_result == 0, "cyaml_emitter_emit should succeed"); + + char *emitted_str = cyaml_emitter_to_string(emitter); + TEST_ASSERT(emitted_str != NULL, "cyaml_emitter_to_string should not return NULL"); + if (emitted_str) { + TEST_ASSERT(strcmp(emitted_str, yaml) == 0, "Emitted YAML should match original scalar"); + free(emitted_str); + } + cyaml_emitter_destroy(emitter); + } + cyaml_document_destroy(doc); + } +} + +/*--------------------------------------------------------- + * Test 3: Load YAML from a File (Scalar) + *---------------------------------------------------------*/ +void test_load_file() { + const char *filename = "test.yaml"; + const char *yaml = "File Test!"; + + /* Create a temporary file with YAML content */ + FILE *file = fopen(filename, "w"); + if (!file) { + printf(COLOR_YELLOW "Warning: Could not create temporary test file.\n" COLOR_RESET); + return; + } + fputs(yaml, file); + fclose(file); + + cyaml_document_t *doc = cyaml_load_file(filename); + TEST_ASSERT(doc != NULL, "cyaml_load_file should not return NULL"); + + if (doc) { + const cyaml_node_t *root = cyaml_document_get_root(doc); + TEST_ASSERT(root != NULL, "Root node from file should not be NULL"); + if (root) { + const char *str = cyaml_node_as_string(root); + TEST_ASSERT(str != NULL, "Root node from file should be a scalar string"); + if (str) { + TEST_ASSERT(strcmp(str, yaml) == 0, "File content should match expected YAML"); + } + } + cyaml_document_destroy(doc); + } + + /* Remove the temporary file */ + remove(filename); +} + +/*--------------------------------------------------------- + * Test 4: Numeric Conversions + *---------------------------------------------------------*/ +void test_numeric_conversion() { + // Integer conversion test + const char *int_str = "12345"; + cyaml_document_t *doc_int = cyaml_load_string(int_str); + TEST_ASSERT(doc_int != NULL, "Load integer string for conversion"); + if (doc_int) { + const cyaml_node_t *node = cyaml_document_get_root(doc_int); + int value = cyaml_node_as_int(node); + TEST_ASSERT(value == 12345, "Integer conversion: value should be 12345"); + cyaml_document_destroy(doc_int); + } + + // Double conversion test + const char *double_str = "3.14159"; + cyaml_document_t *doc_double = cyaml_load_string(double_str); + TEST_ASSERT(doc_double != NULL, "Load double string for conversion"); + if (doc_double) { + const cyaml_node_t *node = cyaml_document_get_root(doc_double); + double dvalue = cyaml_node_as_double(node); + TEST_ASSERT(dvalue > 3.14158 && dvalue < 3.14160, "Double conversion: value should be approx 3.14159"); + cyaml_document_destroy(doc_double); + } +} + +/*--------------------------------------------------------- + * Test 5: Mapping Node Functionality + *---------------------------------------------------------*/ +void test_mapping_node() { + // Manually create a mapping node document + cyaml_document_t *doc = (cyaml_document_t*)malloc(sizeof(cyaml_document_t)); + doc->root = (cyaml_node_t*)malloc(sizeof(cyaml_node_t)); + cyaml_node_t *map = doc->root; + map->type = CYAML_NODE_MAPPING; + map->mapping_size = 3; + map->keys = (char**)malloc(sizeof(char*) * 3); + map->values = (cyaml_node_t**)malloc(sizeof(cyaml_node_t*) * 3); + + // Key-value pair 1: "name": "cyaml" + map->keys[0] = strdup("name"); + map->values[0] = (cyaml_node_t*)malloc(sizeof(cyaml_node_t)); + map->values[0]->type = CYAML_NODE_SCALAR; + map->values[0]->scalar = strdup("cyaml"); + + // Key-value pair 2: "version": "1.0" + map->keys[1] = strdup("version"); + map->values[1] = (cyaml_node_t*)malloc(sizeof(cyaml_node_t)); + map->values[1]->type = CYAML_NODE_SCALAR; + map->values[1]->scalar = strdup("1.0"); + + // Key-value pair 3: "count": "42" + map->keys[2] = strdup("count"); + map->values[2] = (cyaml_node_t*)malloc(sizeof(cyaml_node_t)); + map->values[2]->type = CYAML_NODE_SCALAR; + map->values[2]->scalar = strdup("42"); + + TEST_ASSERT(cyaml_node_size(map) == 3, "Mapping node size should be 3"); + + const cyaml_node_t *node_name = cyaml_node_get(map, "name"); + TEST_ASSERT(node_name != NULL, "Mapping node: key 'name' should exist"); + if (node_name) { + TEST_ASSERT(strcmp(cyaml_node_as_string(node_name), "cyaml") == 0, "Mapping node: 'name' value should be 'cyaml'"); + } + + const cyaml_node_t *node_version = cyaml_node_get(map, "version"); + TEST_ASSERT(node_version != NULL, "Mapping node: key 'version' should exist"); + if (node_version) { + TEST_ASSERT(strcmp(cyaml_node_as_string(node_version), "1.0") == 0, "Mapping node: 'version' value should be '1.0'"); + } + + const cyaml_node_t *node_count = cyaml_node_get(map, "count"); + TEST_ASSERT(node_count != NULL, "Mapping node: key 'count' should exist"); + if (node_count) { + int count = cyaml_node_as_int(node_count); + TEST_ASSERT(count == 42, "Mapping node: 'count' value should be 42"); + } + + // Clean up manually created mapping node + free_mapping_node(map); + free(doc); +} + +/*--------------------------------------------------------- + * Test 6: Sequence Node Functionality + *---------------------------------------------------------*/ +void test_sequence_node() { + // Manually create a sequence node document + cyaml_document_t *doc = (cyaml_document_t*)malloc(sizeof(cyaml_document_t)); + doc->root = (cyaml_node_t*)malloc(sizeof(cyaml_node_t)); + cyaml_node_t *seq = doc->root; + seq->type = CYAML_NODE_SEQUENCE; + seq->sequence_size = 4; + seq->sequence = (cyaml_node_t**)malloc(sizeof(cyaml_node_t*) * 4); + + const char *items[] = { "one", "two", "three", "four" }; + for (size_t i = 0; i < 4; i++) { + seq->sequence[i] = (cyaml_node_t*)malloc(sizeof(cyaml_node_t)); + seq->sequence[i]->type = CYAML_NODE_SCALAR; + seq->sequence[i]->scalar = strdup(items[i]); + } + + TEST_ASSERT(cyaml_node_size(seq) == 4, "Sequence node size should be 4"); + for (size_t i = 0; i < 4; i++) { + const cyaml_node_t *item = cyaml_node_index(seq, i); + char msg[128]; + snprintf(msg, sizeof(msg), "Sequence node: item %zu should not be NULL", i); + TEST_ASSERT(item != NULL, msg); + if (item) { + snprintf(msg, sizeof(msg), "Sequence node: item %zu should equal '%s'", i, items[i]); + TEST_ASSERT(strcmp(cyaml_node_as_string(item), items[i]) == 0, msg); + } + } + + // Clean up manually created sequence node + free_sequence_node(seq); + free(doc); +} + +/*--------------------------------------------------------- + * Test 7: Bulk YAML String Loading (100 iterations) + *---------------------------------------------------------*/ +void test_bulk_load_string() { + for (int i = 0; i < 100; i++) { + char buffer[64]; + snprintf(buffer, sizeof(buffer), "Bulk test %d", i); + cyaml_document_t *doc = cyaml_load_string(buffer); + char test_msg[128]; + snprintf(test_msg, sizeof(test_msg), "Bulk load string test %d: document should not be NULL", i); + TEST_ASSERT(doc != NULL, test_msg); + if (doc) { + const cyaml_node_t *root = cyaml_document_get_root(doc); + snprintf(test_msg, sizeof(test_msg), "Bulk load string test %d: root should not be NULL", i); + TEST_ASSERT(root != NULL, test_msg); + if (root) { + snprintf(test_msg, sizeof(test_msg), "Bulk load string test %d: value should match", i); + TEST_ASSERT(strcmp(cyaml_node_as_string(root), buffer) == 0, test_msg); + } + cyaml_document_destroy(doc); + } + } +} + +/*--------------------------------------------------------- + * Test 8: Bulk File Read/Write (20 iterations) + *---------------------------------------------------------*/ +void test_bulk_file_rw() { + for (int i = 0; i < 20; i++) { + char filename[64]; + char content[64]; + snprintf(filename, sizeof(filename), "temp_test_%d.yaml", i); + snprintf(content, sizeof(content), "File bulk test %d", i); + + /* Write to file */ + FILE *file = fopen(filename, "w"); + if (!file) { + printf(COLOR_YELLOW "Warning: Could not create temporary file %s.\n" COLOR_RESET, filename); + continue; + } + fputs(content, file); + fclose(file); + + /* Read file and validate */ + cyaml_document_t *doc = cyaml_load_file(filename); + char test_msg[128]; + snprintf(test_msg, sizeof(test_msg), "Bulk file test %d: document should not be NULL", i); + TEST_ASSERT(doc != NULL, test_msg); + if (doc) { + const cyaml_node_t *root = cyaml_document_get_root(doc); + snprintf(test_msg, sizeof(test_msg), "Bulk file test %d: root should not be NULL", i); + TEST_ASSERT(root != NULL, test_msg); + if (root) { + snprintf(test_msg, sizeof(test_msg), "Bulk file test %d: content should match", i); + TEST_ASSERT(strcmp(cyaml_node_as_string(root), content) == 0, test_msg); + } + cyaml_document_destroy(doc); + } + + /* Remove the temporary file */ + remove(filename); + } +} + +/*--------------------------------------------------------- + * Main Function: Run All Tests and Display Summary + *---------------------------------------------------------*/ +int main(void) { + printf("========================================\n"); + printf(" " COLOR_YELLOW "cyaml Unit Test Suite" COLOR_RESET "\n"); + printf("========================================\n\n"); + + test_load_string(); + test_emitter_scalar(); + test_load_file(); + test_numeric_conversion(); + test_mapping_node(); + test_sequence_node(); + test_bulk_load_string(); + test_bulk_file_rw(); + + printf("\n========================================\n"); + printf("Test Summary:\n"); + printf("Total tests run: %d\n", tests_run); + printf(COLOR_GREEN "Passed: %d" COLOR_RESET "\n", tests_passed); + printf(COLOR_RED "Failed: %d" COLOR_RESET "\n", tests_run - tests_passed); + printf("========================================\n"); + + return (tests_run - tests_passed) == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/include/cyaml.h b/include/cyaml.h index e69de29..e54f93f 100644 --- a/include/cyaml.h +++ b/include/cyaml.h @@ -0,0 +1,295 @@ +#ifndef CYAML_H +#define CYAML_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* --- API Declarations --- */ + +// Forward declarations for opaque types. +typedef struct cyaml_node_t cyaml_node_t; +typedef struct cyaml_document_t cyaml_document_t; +typedef struct cyaml_emitter_t cyaml_emitter_t; + +// Node types. +typedef enum { + CYAML_NODE_UNDEFINED, + CYAML_NODE_SCALAR, + CYAML_NODE_SEQUENCE, + CYAML_NODE_MAPPING +} cyaml_node_type_t; + +/* Document Loading and Parsing */ + +// Load YAML document from a file. +// Returns a pointer to a cyaml_document_t or NULL on error. +cyaml_document_t* cyaml_load_file(const char *filename); + +// Load YAML document from a string. +// Returns a pointer to a cyaml_document_t or NULL on error. +cyaml_document_t* cyaml_load_string(const char *yaml_string); + +// Free the document and all associated nodes. +void cyaml_document_destroy(cyaml_document_t *doc); + +// Get the root node of the document. +const cyaml_node_t* cyaml_document_get_root(const cyaml_document_t *doc); + +/* Node Querying and Access */ + +// Get the type of a node. +cyaml_node_type_t cyaml_node_get_type(const cyaml_node_t *node); + +// For scalar nodes: retrieve as a string. Returns NULL if not convertible. +const char* cyaml_node_as_string(const cyaml_node_t *node); + +// For scalar nodes: retrieve as an integer. +int cyaml_node_as_int(const cyaml_node_t *node); + +// For scalar nodes: retrieve as a double. +double cyaml_node_as_double(const cyaml_node_t *node); + +// For mapping nodes: retrieve a child node by key. Returns NULL if not found. +const cyaml_node_t* cyaml_node_get(const cyaml_node_t *node, const char *key); + +// For sequence nodes: return the number of child nodes. +size_t cyaml_node_size(const cyaml_node_t *node); + +// For sequence nodes: retrieve a child node by index. +const cyaml_node_t* cyaml_node_index(const cyaml_node_t *node, size_t index); + +/* Emitting YAML */ + +// Create an emitter object. +cyaml_emitter_t* cyaml_emitter_create(void); + +// Emit a document into the emitter's internal buffer. +int cyaml_emitter_emit(cyaml_emitter_t *emitter, const cyaml_document_t *doc); + +// Write the emitter's contents to a file. +// Returns 0 on success, non-zero on error. +int cyaml_emitter_to_file(cyaml_emitter_t *emitter, const char *filename); + +// Retrieve a string copy of the emitter's contents. +// The caller is responsible for freeing the returned string. +char* cyaml_emitter_to_string(cyaml_emitter_t *emitter); + +// Destroy the emitter and free associated memory. +void cyaml_emitter_destroy(cyaml_emitter_t *emitter); + +#ifdef __cplusplus +} +#endif + +/* --- Implementation --- */ +/* To include the implementation, define CYAML_IMPLEMENTATION in *one* source file before including "cyaml.h". */ + +#ifdef CYAML_IMPLEMENTATION + +#include +#include +#include + +/* Internal structures */ + +struct cyaml_document_t { + cyaml_node_t *root; +}; + +struct cyaml_node_t { + cyaml_node_type_t type; + char *scalar; + // For sequence nodes. + cyaml_node_t **sequence; + size_t sequence_size; + // For mapping nodes. + char **keys; + cyaml_node_t **values; + size_t mapping_size; +}; + +struct cyaml_emitter_t { + char *buffer; + size_t size; + size_t capacity; +}; + +#define CYAML_EMITTER_INITIAL_CAPACITY 256 + +/* --- Document Loading and Parsing --- */ + +// Loads the entire file into a buffer and then calls cyaml_load_string(). +cyaml_document_t* cyaml_load_file(const char *filename) { + FILE *file = fopen(filename, "rb"); + if (!file) return NULL; + fseek(file, 0, SEEK_END); + long length = ftell(file); + rewind(file); + char *content = (char*)malloc(length + 1); + if (!content) { + fclose(file); + return NULL; + } + fread(content, 1, length, file); + content[length] = '\0'; + fclose(file); + cyaml_document_t *doc = cyaml_load_string(content); + free(content); + return doc; +} + +// For demonstration, cyaml_load_string() creates a document with a single scalar node. +cyaml_document_t* cyaml_load_string(const char *yaml_string) { + cyaml_document_t *doc = (cyaml_document_t*)malloc(sizeof(cyaml_document_t)); + if (!doc) return NULL; + doc->root = (cyaml_node_t*)malloc(sizeof(cyaml_node_t)); + if (!doc->root) { + free(doc); + return NULL; + } + doc->root->type = CYAML_NODE_SCALAR; + doc->root->scalar = strdup(yaml_string); + doc->root->sequence = NULL; + doc->root->sequence_size = 0; + doc->root->keys = NULL; + doc->root->values = NULL; + doc->root->mapping_size = 0; + return doc; +} + +void cyaml_document_destroy(cyaml_document_t *doc) { + if (!doc) return; + // For this demonstration, assume only a scalar root node exists. + if (doc->root) { + if (doc->root->scalar) + free(doc->root->scalar); + free(doc->root); + } + free(doc); +} + +const cyaml_node_t* cyaml_document_get_root(const cyaml_document_t *doc) { + if (!doc) return NULL; + return doc->root; +} + +/* --- Node Querying and Access --- */ + +cyaml_node_type_t cyaml_node_get_type(const cyaml_node_t *node) { + if (!node) return CYAML_NODE_UNDEFINED; + return node->type; +} + +const char* cyaml_node_as_string(const cyaml_node_t *node) { + if (!node || node->type != CYAML_NODE_SCALAR) return NULL; + return node->scalar; +} + +int cyaml_node_as_int(const cyaml_node_t *node) { + if (!node || node->type != CYAML_NODE_SCALAR) return 0; + return atoi(node->scalar); +} + +double cyaml_node_as_double(const cyaml_node_t *node) { + if (!node || node->type != CYAML_NODE_SCALAR) return 0.0; + return atof(node->scalar); +} + +// Dummy mapping lookup: searches keys in a mapping node. +const cyaml_node_t* cyaml_node_get(const cyaml_node_t *node, const char *key) { + if (!node || node->type != CYAML_NODE_MAPPING) return NULL; + for (size_t i = 0; i < node->mapping_size; i++) { + if (strcmp(node->keys[i], key) == 0) { + return node->values[i]; + } + } + return NULL; +} + +size_t cyaml_node_size(const cyaml_node_t *node) { + if (!node) return 0; + if (node->type == CYAML_NODE_SEQUENCE) + return node->sequence_size; + if (node->type == CYAML_NODE_MAPPING) + return node->mapping_size; + return 0; +} + +const cyaml_node_t* cyaml_node_index(const cyaml_node_t *node, size_t index) { + if (!node) return NULL; + if (node->type == CYAML_NODE_SEQUENCE && index < node->sequence_size) + return node->sequence[index]; + return NULL; +} + +/* --- YAML Emitting --- */ + +cyaml_emitter_t* cyaml_emitter_create(void) { + cyaml_emitter_t *emitter = (cyaml_emitter_t*)malloc(sizeof(cyaml_emitter_t)); + if (!emitter) return NULL; + emitter->buffer = (char*)malloc(CYAML_EMITTER_INITIAL_CAPACITY); + if (!emitter->buffer) { + free(emitter); + return NULL; + } + emitter->buffer[0] = '\0'; + emitter->size = 0; + emitter->capacity = CYAML_EMITTER_INITIAL_CAPACITY; + return emitter; +} + +// Internal helper to append text to the emitter's buffer. +static int cyaml_emitter_append(cyaml_emitter_t *emitter, const char *str) { + if (!emitter || !str) return -1; + size_t len = strlen(str); + if (emitter->size + len + 1 > emitter->capacity) { + size_t new_capacity = emitter->capacity * 2; + while (emitter->size + len + 1 > new_capacity) { + new_capacity *= 2; + } + char *new_buffer = (char*)realloc(emitter->buffer, new_capacity); + if (!new_buffer) return -1; + emitter->buffer = new_buffer; + emitter->capacity = new_capacity; + } + memcpy(emitter->buffer + emitter->size, str, len + 1); + emitter->size += len; + return 0; +} + +// A simple emitter that outputs the root scalar. +int cyaml_emitter_emit(cyaml_emitter_t *emitter, const cyaml_document_t *doc) { + if (!emitter || !doc) return -1; + if (doc->root && doc->root->type == CYAML_NODE_SCALAR) { + return cyaml_emitter_append(emitter, doc->root->scalar); + } + return -1; +} + +int cyaml_emitter_to_file(cyaml_emitter_t *emitter, const char *filename) { + if (!emitter || !filename) return -1; + FILE *file = fopen(filename, "w"); + if (!file) return -1; + fwrite(emitter->buffer, 1, emitter->size, file); + fclose(file); + return 0; +} + +char* cyaml_emitter_to_string(cyaml_emitter_t *emitter) { + if (!emitter) return NULL; + return strdup(emitter->buffer); +} + +void cyaml_emitter_destroy(cyaml_emitter_t *emitter) { + if (!emitter) return; + if (emitter->buffer) + free(emitter->buffer); + free(emitter); +} + +#endif // CYAML_IMPLEMENTATION + +#endif // CYAML_H