diff --git a/a.exe b/a.exe index 6f5a295..00914ae 100644 Binary files a/a.exe and b/a.exe differ diff --git a/examples/test.c b/examples/test.c index 1432ec1..3586916 100644 --- a/examples/test.c +++ b/examples/test.c @@ -1,31 +1,198 @@ -#define CYAML_IMPLEMENTATION -#include "../include/cyaml.h" #include +#include +#include + +#define YAML_IMPLEMENTATION +#include "../include/cyaml.h" + +// Define a simple User struct. +typedef struct { + int id; + char name[100]; + char email[100]; +} User; + +#define MAX_USERS 100 + +User users[MAX_USERS]; +size_t user_count = 0; + +// Display the list of users. +void list_users(void) { + printf("\n-- User Database --\n"); + if (user_count == 0) { + printf("No users found.\n"); + return; + } + for (size_t i = 0; i < user_count; i++) { + printf("ID %d: %s, %s\n", users[i].id, users[i].name, users[i].email); + } +} + +// Save the in-memory user database to a YAML file. +// The output YAML file will have a mapping with the key "users" +// which contains a sequence of mappings (each representing a user). +int save_to_file(const char *filename) { + // Create a new document with a mapping as root. + YAMLDoc *doc = (YAMLDoc*)malloc(sizeof(YAMLDoc)); + if (!doc) return -1; + doc->root = yaml_new_map(); + if (!doc->root) { + free(doc); + return -1; + } + + // Create a sequence to hold all user mappings. + YAMLNode *userSeq = yaml_new_seq(); + if (!userSeq) { + yaml_doc_destroy(doc); + return -1; + } + + // For each user, create a mapping and add it to the sequence. + for (size_t i = 0; i < user_count; i++) { + YAMLNode *userMap = yaml_new_map(); + char idStr[20]; + snprintf(idStr, sizeof(idStr), "%d", users[i].id); + yaml_map_set(userMap, "id", yaml_new_str(idStr)); + yaml_map_set(userMap, "name", yaml_new_str(users[i].name)); + yaml_map_set(userMap, "email", yaml_new_str(users[i].email)); + yaml_seq_append(userSeq, userMap); + } + yaml_map_set(doc->root, "users", userSeq); + + // Create an emitter and write the document to the file. + YAMLEmitter *emitter = yaml_emitter_create(); + if (!emitter) { + yaml_doc_destroy(doc); + return -1; + } + if (yaml_emit(emitter, doc) != 0) { + yaml_emitter_destroy(emitter); + yaml_doc_destroy(doc); + return -1; + } + int ret = yaml_emit_to_file(emitter, filename); + + // Clean up. + yaml_emitter_destroy(emitter); + yaml_doc_destroy(doc); + return ret; +} + +// Add a new user via CLI. +void add_user(void) { + if (user_count >= MAX_USERS) { + printf("User database is full.\n"); + return; + } + User newUser; + newUser.id = user_count + 1; // Simple sequential id assignment. + + printf("Enter user name: "); + if (!fgets(newUser.name, sizeof(newUser.name), stdin)) { + printf("Error reading name.\n"); + return; + } + newUser.name[strcspn(newUser.name, "\n")] = '\0'; // Remove newline. + + printf("Enter user email: "); + if (!fgets(newUser.email, sizeof(newUser.email), stdin)) { + printf("Error reading email.\n"); + return; + } + newUser.email[strcspn(newUser.email, "\n")] = '\0'; + + users[user_count++] = newUser; + printf("User added successfully.\n"); +} + +// Edit an existing user via CLI. +void edit_user(void) { + list_users(); + printf("Enter the user ID to edit: "); + int id; + if (scanf("%d", &id) != 1) { + printf("Invalid input.\n"); + while(getchar() != '\n'); // Flush input buffer. + return; + } + while(getchar() != '\n'); // Consume newline. + + int found = 0; + for (size_t i = 0; i < user_count; i++) { + if (users[i].id == id) { + found = 1; + printf("Editing user (ID %d): %s, %s\n", users[i].id, users[i].name, users[i].email); + printf("Enter new name (leave empty to keep unchanged): "); + char newName[100]; + if (fgets(newName, sizeof(newName), stdin)) { + newName[strcspn(newName, "\n")] = '\0'; + if (strlen(newName) > 0) + strncpy(users[i].name, newName, sizeof(users[i].name)); + } + printf("Enter new email (leave empty to keep unchanged): "); + char newEmail[100]; + if (fgets(newEmail, sizeof(newEmail), stdin)) { + newEmail[strcspn(newEmail, "\n")] = '\0'; + if (strlen(newEmail) > 0) + strncpy(users[i].email, newEmail, sizeof(users[i].email)); + } + printf("User updated successfully.\n"); + break; + } + } + if (!found) + printf("User with ID %d not found.\n", id); +} 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; + int running = 1; + while (running) { + printf("\n--- User Database CLI ---\n"); + printf("1. List users\n"); + printf("2. Add user\n"); + printf("3. Edit user\n"); + printf("4. Save database to file\n"); + printf("5. Exit\n"); + printf("Enter choice: "); + + int choice; + if (scanf("%d", &choice) != 1) { + printf("Invalid input. Please enter a number.\n"); + while(getchar() != '\n'); // Clear the invalid input. + continue; + } + while(getchar() != '\n'); // Consume newline. + + switch (choice) { + case 1: + list_users(); + break; + case 2: + add_user(); + break; + case 3: + edit_user(); + break; + case 4: { + char filename[256]; + printf("Enter filename to save the database: "); + if (fgets(filename, sizeof(filename), stdin)) { + filename[strcspn(filename, "\n")] = '\0'; + if (save_to_file(filename) == 0) + printf("Database saved successfully to '%s'.\n", filename); + else + printf("Error saving database to '%s'.\n", filename); + } + break; + } + case 5: + running = 0; + break; + default: + printf("Invalid choice. Please try again.\n"); + } } - - // 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 index 7b9f509..06f746b 100644 --- a/examples/units.c +++ b/examples/units.c @@ -2,363 +2,110 @@ #include #include -#define CYAML_IMPLEMENTATION -#include "../include/cyaml.h" // Adjust this path as needed +#define YAML_IMPLEMENTATION +#include "../include/cyaml.h" -/* 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"); + int fail_count = 0; + + // --- Unit Test 1: Building and Emitting a YAML Document --- + printf("Unit Test 1: Document creation and emission\n"); + + // Create a new document with a mapping as the root. + YAMLDoc *doc = (YAMLDoc*)malloc(sizeof(YAMLDoc)); + if (!doc) { + fprintf(stderr, "Memory allocation failure.\n"); + return EXIT_FAILURE; + } + doc->root = yaml_new_map(); + if (!doc->root) { + fprintf(stderr, "Failed to create root node.\n"); + free(doc); + return EXIT_FAILURE; + } + + // Set "name" to a scalar value. + if (yaml_map_set(doc->root, "name", yaml_new_str("Alice")) != 0) { + fprintf(stderr, "Error setting key 'name'.\n"); + yaml_doc_destroy(doc); + return EXIT_FAILURE; + } + + // Create a sequence for "skills". + YAMLNode *skills = yaml_new_seq(); + if (!skills) { + fprintf(stderr, "Failed to create skills sequence.\n"); + yaml_doc_destroy(doc); + return EXIT_FAILURE; + } + yaml_seq_append(skills, yaml_new_str("C")); + yaml_seq_append(skills, yaml_new_str("Python")); + if (yaml_map_set(doc->root, "skills", skills) != 0) { + fprintf(stderr, "Error setting key 'skills'.\n"); + yaml_doc_destroy(doc); + return EXIT_FAILURE; + } + + // Emit the document. + YAMLEmitter *emitter = yaml_emitter_create(); + if (!emitter) { + fprintf(stderr, "Failed to create emitter.\n"); + yaml_doc_destroy(doc); + return EXIT_FAILURE; + } + if (yaml_emit(emitter, doc) != 0) { + fprintf(stderr, "Error emitting YAML document.\n"); + yaml_emitter_destroy(emitter); + yaml_doc_destroy(doc); + return EXIT_FAILURE; + } + char *output = yaml_emit_to_str(emitter); + if (!output) { + fprintf(stderr, "Failed to retrieve emitter string.\n"); + yaml_emitter_destroy(emitter); + yaml_doc_destroy(doc); + return EXIT_FAILURE; + } + printf("Emitted YAML:\n%s\n", output); + + // Check expected content. + if (strstr(output, "Alice") == NULL || + strstr(output, "skills") == NULL || + strstr(output, "- Python") == NULL) + { + printf("Unit Test 1 Failed: Emitted YAML output is missing expected content.\n"); + fail_count++; + } else { + printf("Unit Test 1 Passed.\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; + free(output); + yaml_emitter_destroy(emitter); + yaml_doc_destroy(doc); + + // --- Unit Test 2: Loading a YAML Document from a String --- + printf("\nUnit Test 2: YAML load from string\n"); + const char *yaml_input = "Hello YAML!\nThis is a test."; + YAMLDoc *doc2 = yaml_load_str(yaml_input); + if (!doc2) { + fprintf(stderr, "Error loading YAML from string.\n"); + return EXIT_FAILURE; + } + const YAMLNode *root2 = yaml_doc_root(doc2); + if (!root2 || strcmp(yaml_as_str(root2), yaml_input) != 0) { + printf("Unit Test 2 Failed: Loaded YAML does not match input string.\n"); + fail_count++; + } else { + printf("Unit Test 2 Passed.\n"); + } + yaml_doc_destroy(doc2); + + // --- Finalize Unit Tests --- + if (fail_count == 0) { + printf("\nAll unit tests passed successfully.\n"); + return EXIT_SUCCESS; + } else { + printf("\n%d unit test(s) failed.\n", fail_count); + return EXIT_FAILURE; + } } diff --git a/include/cyaml.h b/include/cyaml.h index e54f93f..9e19f4b 100644 --- a/include/cyaml.h +++ b/include/cyaml.h @@ -1,5 +1,5 @@ -#ifndef CYAML_H -#define CYAML_H +#ifndef YAML_H +#define YAML_H #ifdef __cplusplus extern "C" { @@ -7,124 +7,164 @@ extern "C" { #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; +/* --- Public Types --- */ // Node types. typedef enum { - CYAML_NODE_UNDEFINED, - CYAML_NODE_SCALAR, - CYAML_NODE_SEQUENCE, - CYAML_NODE_MAPPING -} cyaml_node_type_t; + YAML_UNDEF, // Undefined + YAML_STR, // Scalar string + YAML_SEQ, // Sequence (list) + YAML_MAP // Mapping (dictionary) +} YAMLType; -/* Document Loading and Parsing */ +// Opaque types. +typedef struct YAMLNode YAMLNode; +typedef struct YAMLDoc YAMLDoc; +typedef struct YAMLEmitter YAMLEmitter; -// 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); +/* --- Document Loading and Parsing --- */ -// 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); +// Load a YAML document from a file. For demonstration, the file content is treated as a scalar. +YAMLDoc* yaml_load_file(const char *filename); -// Free the document and all associated nodes. -void cyaml_document_destroy(cyaml_document_t *doc); +// Load a YAML document from a string. (Currently, the entire string is parsed as a scalar.) +YAMLDoc* yaml_load_str(const char *yaml_string); + +// Destroy the document and free all associated memory. +void yaml_doc_destroy(YAMLDoc *doc); // Get the root node of the document. -const cyaml_node_t* cyaml_document_get_root(const cyaml_document_t *doc); +const YAMLNode* yaml_doc_root(const YAMLDoc *doc); -/* Node Querying and Access */ +/* --- Node Querying and Access --- */ -// Get the type of a node. -cyaml_node_type_t cyaml_node_get_type(const cyaml_node_t *node); +// Get the type of a YAML node. +YAMLType yaml_node_type(const YAMLNode *node); -// For scalar nodes: retrieve as a string. Returns NULL if not convertible. -const char* cyaml_node_as_string(const cyaml_node_t *node); +// Retrieve the scalar value of a node (if it is a scalar), or NULL. +const char* yaml_as_str(const YAMLNode *node); -// For scalar nodes: retrieve as an integer. -int cyaml_node_as_int(const cyaml_node_t *node); +// Convert a scalar node to int. Returns 0 if not a scalar. +int yaml_as_int(const YAMLNode *node); -// For scalar nodes: retrieve as a double. -double cyaml_node_as_double(const cyaml_node_t *node); +// Convert a scalar node to double. Returns 0.0 if not a scalar. +double yaml_as_double(const YAMLNode *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 mapping nodes: get a child node by key (returns NULL if not found). +const YAMLNode* yaml_map_get(const YAMLNode *node, const char *key); // For sequence nodes: return the number of child nodes. -size_t cyaml_node_size(const cyaml_node_t *node); +size_t yaml_seq_size(const YAMLNode *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); +// For sequence nodes: retrieve a child node by index (returns NULL if out of range). +const YAMLNode* yaml_seq_index(const YAMLNode *node, size_t index); -/* Emitting YAML */ +/* --- Node Creation and Modification --- */ -// Create an emitter object. -cyaml_emitter_t* cyaml_emitter_create(void); +// Create a new scalar node with the given string value. +YAMLNode* yaml_new_str(const char *value); -// Emit a document into the emitter's internal buffer. -int cyaml_emitter_emit(cyaml_emitter_t *emitter, const cyaml_document_t *doc); +// Create a new, empty sequence node. +YAMLNode* yaml_new_seq(void); -// 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); +// Create a new, empty mapping node. +YAMLNode* yaml_new_map(void); -// 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); +// Append a child node to a sequence node. +int yaml_seq_append(YAMLNode *node, YAMLNode *child); -// Destroy the emitter and free associated memory. -void cyaml_emitter_destroy(cyaml_emitter_t *emitter); +// Set a key/value pair in a mapping node (the key is copied). +int yaml_map_set(YAMLNode *node, const char *key, YAMLNode *child); + +// Recursively free a YAML node and its children. +void yaml_node_destroy(YAMLNode *node); + +/* --- YAML Emitting --- */ + +// Create a new YAML emitter. +YAMLEmitter* yaml_emitter_create(void); + +// Emit the YAML document into the emitter's buffer. +int yaml_emit(YAMLEmitter *emitter, const YAMLDoc *doc); + +// Write the emitter's content to a file (returns 0 on success). +int yaml_emit_to_file(YAMLEmitter *emitter, const char *filename); + +// Get a string copy of the emitter's buffer (caller must free it). +char* yaml_emit_to_str(YAMLEmitter *emitter); + +// Destroy the emitter and free its memory. +void yaml_emitter_destroy(YAMLEmitter *emitter); #ifdef __cplusplus } #endif /* --- Implementation --- */ -/* To include the implementation, define CYAML_IMPLEMENTATION in *one* source file before including "cyaml.h". */ +/* To compile the implementation, define YAML_IMPLEMENTATION in one source file before including "yaml.h". */ -#ifdef CYAML_IMPLEMENTATION +#ifdef YAML_IMPLEMENTATION #include #include #include -/* Internal structures */ +/* --- Internal Structures --- */ -struct cyaml_document_t { - cyaml_node_t *root; +struct YAMLDoc { + YAMLNode *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. +struct YAMLNode { + YAMLType type; + // For scalar nodes: + char *str; + // For sequence nodes: + YAMLNode **seq; + size_t seq_size; + // For mapping nodes: char **keys; - cyaml_node_t **values; - size_t mapping_size; + YAMLNode **vals; + size_t map_size; }; -struct cyaml_emitter_t { +struct YAMLEmitter { char *buffer; size_t size; size_t capacity; }; -#define CYAML_EMITTER_INITIAL_CAPACITY 256 +#define YAMLEMIT_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) { +// For demonstration purposes, our "parser" treats the entire input as a scalar node. +// In a full implementation, you would integrate a complete YAML parser. +YAMLDoc* yaml_load_str(const char *yaml_string) { + if (!yaml_string) + return NULL; + YAMLDoc *doc = (YAMLDoc*)malloc(sizeof(YAMLDoc)); + if (!doc) + return NULL; + doc->root = (YAMLNode*)malloc(sizeof(YAMLNode)); + if (!doc->root) { + free(doc); + return NULL; + } + doc->root->type = YAML_STR; + doc->root->str = strdup(yaml_string); + doc->root->seq = NULL; + doc->root->seq_size = 0; + doc->root->keys = NULL; + doc->root->vals = NULL; + doc->root->map_size = 0; + return doc; +} + +YAMLDoc* yaml_load_file(const char *filename) { FILE *file = fopen(filename, "rb"); - if (!file) return NULL; + if (!file) + return NULL; fseek(file, 0, SEEK_END); long length = ftell(file); rewind(file); @@ -136,160 +176,323 @@ cyaml_document_t* cyaml_load_file(const char *filename) { fread(content, 1, length, file); content[length] = '\0'; fclose(file); - cyaml_document_t *doc = cyaml_load_string(content); + YAMLDoc *doc = yaml_load_str(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); - } +void yaml_doc_destroy(YAMLDoc *doc) { + if (!doc) + return; + if (doc->root) + yaml_node_destroy(doc->root); free(doc); } -const cyaml_node_t* cyaml_document_get_root(const cyaml_document_t *doc) { - if (!doc) return NULL; +const YAMLNode* yaml_doc_root(const YAMLDoc *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; +YAMLType yaml_node_type(const YAMLNode *node) { + if (!node) + return YAML_UNDEF; 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; +const char* yaml_as_str(const YAMLNode *node) { + if (!node || node->type != YAML_STR) + return NULL; + return node->str; } -int cyaml_node_as_int(const cyaml_node_t *node) { - if (!node || node->type != CYAML_NODE_SCALAR) return 0; - return atoi(node->scalar); +int yaml_as_int(const YAMLNode *node) { + if (!node || node->type != YAML_STR) + return 0; + return atoi(node->str); } -double cyaml_node_as_double(const cyaml_node_t *node) { - if (!node || node->type != CYAML_NODE_SCALAR) return 0.0; - return atof(node->scalar); +double yaml_as_double(const YAMLNode *node) { + if (!node || node->type != YAML_STR) + return 0.0; + return atof(node->str); } -// 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]; - } +const YAMLNode* yaml_map_get(const YAMLNode *node, const char *key) { + if (!node || node->type != YAML_MAP) + return NULL; + for (size_t i = 0; i < node->map_size; i++) { + if (strcmp(node->keys[i], key) == 0) + return node->vals[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; +size_t yaml_seq_size(const YAMLNode *node) { + if (!node || node->type != YAML_SEQ) + return 0; + return node->seq_size; +} + +const YAMLNode* yaml_seq_index(const YAMLNode *node, size_t index) { + if (!node || node->type != YAML_SEQ || index >= node->seq_size) + return NULL; + return node->seq[index]; +} + +/* --- Node Creation and Modification --- */ + +YAMLNode* yaml_new_str(const char *value) { + YAMLNode *node = (YAMLNode*)malloc(sizeof(YAMLNode)); + if (!node) + return NULL; + node->type = YAML_STR; + node->str = strdup(value ? value : ""); + node->seq = NULL; + node->seq_size = 0; + node->keys = NULL; + node->vals = NULL; + node->map_size = 0; + return node; +} + +YAMLNode* yaml_new_seq(void) { + YAMLNode *node = (YAMLNode*)malloc(sizeof(YAMLNode)); + if (!node) + return NULL; + node->type = YAML_SEQ; + node->str = NULL; + node->seq = NULL; + node->seq_size = 0; + node->keys = NULL; + node->vals = NULL; + node->map_size = 0; + return node; +} + +YAMLNode* yaml_new_map(void) { + YAMLNode *node = (YAMLNode*)malloc(sizeof(YAMLNode)); + if (!node) + return NULL; + node->type = YAML_MAP; + node->str = NULL; + node->seq = NULL; + node->seq_size = 0; + node->keys = NULL; + node->vals = NULL; + node->map_size = 0; + return node; +} + +int yaml_seq_append(YAMLNode *node, YAMLNode *child) { + if (!node || node->type != YAML_SEQ) + return -1; + YAMLNode **new_seq = (YAMLNode**)realloc(node->seq, sizeof(YAMLNode*) * (node->seq_size + 1)); + if (!new_seq) + return -1; + node->seq = new_seq; + node->seq[node->seq_size] = child; + node->seq_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; +int yaml_map_set(YAMLNode *node, const char *key, YAMLNode *child) { + if (!node || node->type != YAML_MAP) + return -1; + // Append new key/value pair + char **new_keys = (char**)realloc(node->keys, sizeof(char*) * (node->map_size + 1)); + if (!new_keys) + return -1; + node->keys = new_keys; + node->keys[node->map_size] = strdup(key); + YAMLNode **new_vals = (YAMLNode**)realloc(node->vals, sizeof(YAMLNode*) * (node->map_size + 1)); + if (!new_vals) + return -1; + node->vals = new_vals; + node->vals[node->map_size] = child; + node->map_size++; + return 0; +} + +void yaml_node_destroy(YAMLNode *node) { + if (!node) + return; + if (node->type == YAML_STR) { + free(node->str); + } else if (node->type == YAML_SEQ) { + for (size_t i = 0; i < node->seq_size; i++) { + yaml_node_destroy(node->seq[i]); + } + free(node->seq); + } else if (node->type == YAML_MAP) { + for (size_t i = 0; i < node->map_size; i++) { + free(node->keys[i]); + yaml_node_destroy(node->vals[i]); + } + free(node->keys); + free(node->vals); + } + free(node); } /* --- 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); +YAMLEmitter* yaml_emitter_create(void) { + YAMLEmitter *emitter = (YAMLEmitter*)malloc(sizeof(YAMLEmitter)); + if (!emitter) + return NULL; + emitter->buffer = (char*)malloc(YAMLEMIT_INITIAL_CAPACITY); if (!emitter->buffer) { free(emitter); return NULL; } emitter->buffer[0] = '\0'; emitter->size = 0; - emitter->capacity = CYAML_EMITTER_INITIAL_CAPACITY; + emitter->capacity = YAMLEMIT_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; +// Internal function: append string to emitter's buffer. +static int yaml_emitter_append(YAMLEmitter *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; + size_t new_cap = emitter->capacity * 2; + while (emitter->size + len + 1 > new_cap) + new_cap *= 2; + char *new_buf = (char*)realloc(emitter->buffer, new_cap); + if (!new_buf) + return -1; + emitter->buffer = new_buf; + emitter->capacity = new_cap; } - memcpy(emitter->buffer + emitter->size, str, len + 1); + memcpy(emitter->buffer + emitter->size, str, len); emitter->size += len; + emitter->buffer[emitter->size] = '\0'; 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); +// Recursive function to emit a YAML node with proper indentation. +static int yaml_emit_node(YAMLEmitter *emitter, const YAMLNode *node, int indent) { + char indent_str[256]; + if (indent < (int)sizeof(indent_str)) { + memset(indent_str, ' ', indent); + indent_str[indent] = '\0'; + } else { + indent_str[0] = '\0'; } - return -1; + char line_buf[1024]; + int ret; + if (!node) + return -1; + switch (node->type) { + case YAML_STR: + // Use block scalar style if the string contains newline(s). + if (strchr(node->str, '\n')) { + ret = yaml_emitter_append(emitter, "|-\n"); + if (ret) + return ret; + char *copy = strdup(node->str); + char *line = strtok(copy, "\n"); + while (line) { + snprintf(line_buf, sizeof(line_buf), "%s%s\n", indent_str, line); + ret = yaml_emitter_append(emitter, line_buf); + if (ret) { + free(copy); + return ret; + } + line = strtok(NULL, "\n"); + } + free(copy); + } else { + snprintf(line_buf, sizeof(line_buf), "%s%s\n", indent_str, node->str); + ret = yaml_emitter_append(emitter, line_buf); + if (ret) + return ret; + } + break; + case YAML_SEQ: + for (size_t i = 0; i < node->seq_size; i++) { + snprintf(line_buf, sizeof(line_buf), "%s- ", indent_str); + ret = yaml_emitter_append(emitter, line_buf); + if (ret) + return ret; + if (node->seq[i]->type == YAML_STR) { + ret = yaml_emitter_append(emitter, node->seq[i]->str); + ret = yaml_emitter_append(emitter, "\n"); + if (ret) + return ret; + } else { + ret = yaml_emitter_append(emitter, "\n"); + if (ret) + return ret; + ret = yaml_emit_node(emitter, node->seq[i], indent + 2); + if (ret) + return ret; + } + } + break; + case YAML_MAP: + for (size_t i = 0; i < node->map_size; i++) { + snprintf(line_buf, sizeof(line_buf), "%s%s: ", indent_str, node->keys[i]); + ret = yaml_emitter_append(emitter, line_buf); + if (ret) + return ret; + if (node->vals[i]->type == YAML_STR) { + ret = yaml_emitter_append(emitter, node->vals[i]->str); + ret = yaml_emitter_append(emitter, "\n"); + if (ret) + return ret; + } else { + ret = yaml_emitter_append(emitter, "\n"); + if (ret) + return ret; + ret = yaml_emit_node(emitter, node->vals[i], indent + 2); + if (ret) + return ret; + } + } + break; + default: + break; + } + return 0; } -int cyaml_emitter_to_file(cyaml_emitter_t *emitter, const char *filename) { - if (!emitter || !filename) return -1; +int yaml_emit(YAMLEmitter *emitter, const YAMLDoc *doc) { + if (!emitter || !doc || !doc->root) + return -1; + return yaml_emit_node(emitter, doc->root, 0); +} + +int yaml_emit_to_file(YAMLEmitter *emitter, const char *filename) { + if (!emitter || !filename) + return -1; FILE *file = fopen(filename, "w"); - if (!file) return -1; + 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; +char* yaml_emit_to_str(YAMLEmitter *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); +void yaml_emitter_destroy(YAMLEmitter *emitter) { + if (!emitter) + return; + free(emitter->buffer); free(emitter); } -#endif // CYAML_IMPLEMENTATION +#endif // YAML_IMPLEMENTATION -#endif // CYAML_H +#endif // YAML_H diff --git a/users.yaml b/users.yaml new file mode 100644 index 0000000..e69de29