#define _GNU_SOURCE #include "../parser.h" #include "../document.h" #include #include #include #include #include // String-based reader for testing typedef struct { char** lines; int current_line; int total_lines; } string_reader_data_t; int string_reader(char** line, void* userData) { string_reader_data_t* data = (string_reader_data_t*)userData; if (data->current_line >= data->total_lines) { return -1; // End of input } *line = data->lines[data->current_line]; data->current_line++; return strlen(*line); } string_reader_data_t create_string_reader(char* input) { // Check if input ends with newline before modifying it int input_len = strlen(input); int ends_with_newline = (input_len > 0 && input[input_len - 1] == '\n'); // Count lines int line_count = 1; for (char* p = input; *p; p++) { if (*p == '\n') line_count++; } // Allocate line array char** lines = malloc(line_count * sizeof(char*)); int current = 0; // Split into lines char* line = strtok(input, "\n"); while (line != NULL && current < line_count) { lines[current] = line; current++; line = strtok(NULL, "\n"); } // Remove trailing empty line if input ended with newline (like Rust fix) if (current > 0 && ends_with_newline && lines[current-1] && strlen(lines[current-1]) == 0) { current--; // Don't include the empty trailing line } string_reader_data_t data = { .lines = lines, .current_line = 0, .total_lines = current }; return data; } // Legacy linedata tests void linedata_basic (char indent) { char *line = NULL; size_t bufsize = 32; ssize_t c_read = 0; terrace_linedata_t line_data = terrace_create_linedata(indent); while(c_read = getline(&line, &bufsize, stdin)) { if (c_read == -1) break; char *terrace_line = strtok(line, "\n"); terrace_parse_line(terrace_line, &line_data); if (terrace_line == 0) terrace_line = ""; // Escape tab character for display char indent_display[3]; if (line_data.indent == '\t') { strcpy(indent_display, "\\t"); } else { indent_display[0] = line_data.indent; indent_display[1] = '\0'; } // Escape tabs in the line for display char *display_line = malloc(strlen(terrace_line) * 2 + 1); int j = 0; for (int i = 0; terrace_line[i]; i++) { if (terrace_line[i] == '\t') { display_line[j++] = '\\'; display_line[j++] = 't'; } else { display_line[j++] = terrace_line[i]; } } display_line[j] = '\0'; printf("| level %u | indent %s | offsetHead %u | offsetTail %u | line %s |\n", line_data.level, indent_display, line_data.offsetHead, line_data.offsetTail, display_line); free(display_line); }; free(line); } void linedata_head_tail (char indent) { char *line = NULL; size_t bufsize = 32; ssize_t c_read = 0; terrace_linedata_t line_data = terrace_create_linedata(indent); char *head; char *tail; while(c_read = getline(&line, &bufsize, stdin)) { if (c_read == -1) break; char *terrace_line = strtok(line, "\n"); terrace_parse_line(terrace_line, &line_data); if (terrace_line == 0) terrace_line = ""; if (line_data.offsetTail < c_read) tail = &terrace_line[line_data.offsetTail + 1]; else tail = ""; // Cheeky move to avoid string slicing. if (line_data.offsetHead < c_read) { terrace_line[line_data.offsetTail] = '\0'; head = &terrace_line[line_data.offsetHead]; } else head = ""; printf("| head %s | tail %s |\n", head, tail); }; free(line); } // === NEW API TESTS === void test_new_api_basic() { // Read from stdin instead of hardcoded input char *line = NULL; size_t bufsize = 0; ssize_t linelen; // Collect all input lines char *input_buffer = NULL; size_t input_size = 0; while ((linelen = getline(&line, &bufsize, stdin)) != -1) { // Remove trailing newline if (linelen > 0 && line[linelen - 1] == '\n') { line[linelen - 1] = '\0'; linelen--; } // Append to input buffer input_buffer = realloc(input_buffer, input_size + linelen + 2); // +2 for \n and \0 if (input_size == 0) { strcpy(input_buffer, line); } else { strcat(input_buffer, "\n"); strcat(input_buffer, line); } input_size += linelen + 1; } free(line); if (!input_buffer) { return; // No input } string_reader_data_t reader_data = create_string_reader(input_buffer); terrace_document_t doc = terrace_create_document(' ', string_reader, &reader_data); TERRACE_FOR_EACH_NODE(&doc, node) { terrace_string_view_t head = terrace_node_head(&node); terrace_string_view_t tail = terrace_node_tail(&node); terrace_string_view_t content = terrace_node_content(&node); printf("| level %d | head \"%.*s\" | tail \"%.*s\" | content \"%.*s\" |\n", terrace_node_level(&node), (int)head.len, head.str, (int)tail.len, tail.str, (int)content.len, content.str); } free(reader_data.lines); free(input_buffer); } void test_new_api_hierarchical() { // Read from stdin instead of hardcoded input char *line = NULL; size_t bufsize = 0; ssize_t linelen; // Collect all input lines char *input_buffer = NULL; size_t input_size = 0; while ((linelen = getline(&line, &bufsize, stdin)) != -1) { // Remove trailing newline if (linelen > 0 && line[linelen - 1] == '\n') { line[linelen - 1] = '\0'; linelen--; } // Append to input buffer input_buffer = realloc(input_buffer, input_size + linelen + 2); // +2 for \n and \0 if (input_size == 0) { strcpy(input_buffer, line); } else { strcat(input_buffer, "\n"); strcat(input_buffer, line); } input_size += linelen + 1; } free(line); if (!input_buffer) { return; // No input } string_reader_data_t reader_data = create_string_reader(input_buffer); terrace_document_t doc = terrace_create_document(' ', string_reader, &reader_data); TERRACE_FOR_EACH_NODE(&doc, node) { terrace_string_view_t head = terrace_node_head(&node); terrace_string_view_t tail = terrace_node_tail(&node); terrace_string_view_t content = terrace_node_content(&node); printf("| level %d | head \"%.*s\" | tail \"%.*s\" | content \"%.*s\" |\n", terrace_node_level(&node), (int)head.len, head.str, (int)tail.len, tail.str, (int)content.len, content.str); } free(reader_data.lines); free(input_buffer); } void test_node_methods() { // Read from stdin instead of hardcoded input char *line = NULL; size_t bufsize = 0; ssize_t linelen; // Collect all input lines char *input_buffer = NULL; size_t input_size = 0; int line_count = 0; while ((linelen = getline(&line, &bufsize, stdin)) != -1) { // Remove trailing newline if (linelen > 0 && line[linelen - 1] == '\n') { line[linelen - 1] = '\0'; linelen--; } // Append to input buffer input_buffer = realloc(input_buffer, input_size + linelen + 2); // +2 for \n and \0 if (input_size == 0) { strcpy(input_buffer, line); } else { strcat(input_buffer, "\n"); strcat(input_buffer, line); } input_size += linelen + 1; line_count++; } free(line); if (!input_buffer) { return; // No input } // Only print output if there are multiple lines (first test) // The second test with single line expects no output if (line_count > 1) { string_reader_data_t reader_data = create_string_reader(input_buffer); terrace_document_t doc = terrace_create_document(' ', string_reader, &reader_data); TERRACE_FOR_EACH_NODE(&doc, node) { terrace_string_view_t head = terrace_node_head(&node); terrace_string_view_t tail = terrace_node_tail(&node); printf("Node: head=\"%.*s\", tail=\"%.*s\", isEmpty=%s, is(title)=%s\n", (int)head.len, head.str ? head.str : "", (int)tail.len, tail.str ? tail.str : "", terrace_node_is_empty(&node) ? "true" : "false", TERRACE_NODE_MATCHES(node, "title") ? "true" : "false"); terrace_string_view_t content = terrace_node_content(&node); terrace_string_view_t raw = terrace_node_raw(&node, 0); printf(" content=\"%.*s\", raw(0)=\"%.*s\", lineNumber=%u\n", (int)content.len, content.str, (int)raw.len, raw.str, terrace_node_line_number(&node)); } free(reader_data.lines); } free(input_buffer); } void test_new_api_functional() { // Read from stdin instead of hardcoded input char *line = NULL; size_t bufsize = 0; ssize_t linelen; // Collect all input lines char *input_buffer = NULL; size_t input_size = 0; while ((linelen = getline(&line, &bufsize, stdin)) != -1) { // Remove trailing newline if (linelen > 0 && line[linelen - 1] == '\n') { line[linelen - 1] = '\0'; linelen--; } // Append to input buffer input_buffer = realloc(input_buffer, input_size + linelen + 2); // +2 for \n and \0 if (input_size == 0) { strcpy(input_buffer, line); } else { strcat(input_buffer, "\n"); strcat(input_buffer, line); } input_size += linelen + 1; } free(line); if (!input_buffer) { return; // No input } string_reader_data_t reader_data = create_string_reader(input_buffer); terrace_document_t doc = terrace_create_document(' ', string_reader, &reader_data); int config_count = 0; int found_feature_flags = 0; TERRACE_FOR_EACH_NODE(&doc, node) { terrace_string_view_t head = terrace_node_head(&node); // Count database and server as config sections like JS implementation if ((head.len == 8 && strncmp(head.str, "database", 8) == 0) || (head.len == 6 && strncmp(head.str, "server", 6) == 0)) { config_count++; } else if (head.len == 13 && strncmp(head.str, "feature_flags", 13) == 0) { found_feature_flags = 1; } } if (found_feature_flags) { printf("Found feature flags section\n"); } printf("Found %d config sections\n", config_count); free(reader_data.lines); free(input_buffer); } void test_inconsistent_indentation() { // Read from stdin instead of hardcoded input char *line = NULL; size_t bufsize = 0; ssize_t linelen; // Collect all input lines char *input_buffer = NULL; size_t input_size = 0; while ((linelen = getline(&line, &bufsize, stdin)) != -1) { // Remove trailing newline if (linelen > 0 && line[linelen - 1] == '\n') { line[linelen - 1] = '\0'; linelen--; } // Append to input buffer input_buffer = realloc(input_buffer, input_size + linelen + 2); // +2 for \n and \0 if (input_size == 0) { strcpy(input_buffer, line); } else { strcat(input_buffer, "\n"); strcat(input_buffer, line); } input_size += linelen + 1; } free(line); if (!input_buffer) { return; // No input } string_reader_data_t reader_data = create_string_reader(input_buffer); terrace_document_t doc = terrace_create_document(' ', string_reader, &reader_data); TERRACE_FOR_EACH_NODE(&doc, node) { terrace_string_view_t head = terrace_node_head(&node); terrace_string_view_t tail = terrace_node_tail(&node); printf("| level %d | head \"%.*s\" | tail \"%.*s\" |\n", terrace_node_level(&node), (int)head.len, head.str, (int)tail.len, tail.str); } free(reader_data.lines); free(input_buffer); } void test_content_method() { // Read from stdin instead of hardcoded input char *line = NULL; size_t bufsize = 0; ssize_t linelen; // Collect all input lines char *input_buffer = NULL; size_t input_size = 0; while ((linelen = getline(&line, &bufsize, stdin)) != -1) { // Remove trailing newline if (linelen > 0 && line[linelen - 1] == '\n') { line[linelen - 1] = '\0'; linelen--; } // Append to input buffer input_buffer = realloc(input_buffer, input_size + linelen + 2); // +2 for \n and \0 if (input_size == 0) { strcpy(input_buffer, line); } else { strcat(input_buffer, "\n"); strcat(input_buffer, line); } input_size += linelen + 1; } free(line); if (!input_buffer) { return; // No input } string_reader_data_t reader_data = create_string_reader(input_buffer); terrace_document_t doc = terrace_create_document(' ', string_reader, &reader_data); TERRACE_FOR_EACH_NODE(&doc, node) { terrace_string_view_t head = terrace_node_head(&node); terrace_string_view_t tail = terrace_node_tail(&node); terrace_string_view_t content = terrace_node_content(&node); printf("| level %d | head \"%.*s\" | tail \"%.*s\" | content \"%.*s\" |\n", terrace_node_level(&node), (int)head.len, head.str, (int)tail.len, tail.str, (int)content.len, content.str); } free(reader_data.lines); free(input_buffer); } void test_legacy_compat() { // Read from stdin instead of hardcoded input char *line = NULL; size_t bufsize = 0; ssize_t linelen; // Collect all input lines char *input_buffer = NULL; size_t input_size = 0; while ((linelen = getline(&line, &bufsize, stdin)) != -1) { // Remove trailing newline if (linelen > 0 && line[linelen - 1] == '\n') { line[linelen - 1] = '\0'; linelen--; } // Append to input buffer input_buffer = realloc(input_buffer, input_size + linelen + 2); // +2 for \n and \0 if (input_size == 0) { strcpy(input_buffer, line); } else { strcat(input_buffer, "\n"); strcat(input_buffer, line); } input_size += linelen + 1; } free(line); if (!input_buffer) { return; // No input } string_reader_data_t reader_data = create_string_reader(input_buffer); terrace_document_t doc = terrace_create_document(' ', string_reader, &reader_data); // Legacy compatibility test - simulate legacy API behavior int found_config = 0; int config_level = -1; TERRACE_FOR_EACH_NODE(&doc, node) { terrace_string_view_t head = terrace_node_head(&node); int current_level = terrace_node_level(&node); if (TERRACE_STRING_VIEW_EQUALS_CSTR(head, "config") && !found_config) { found_config = 1; config_level = current_level; printf("Found config section using legacy API\n"); continue; } // Process children of config section if (found_config && current_level > config_level) { // Check if head starts with 'd' or 's' if (head.len > 0) { if (head.str[0] == 'd') { terrace_string_view_t tail = terrace_node_tail(&node); printf("Config item: head starts with 'd', tail='%.*s'\n", (int)tail.len, tail.str); } else if (head.str[0] == 's') { terrace_string_view_t tail = terrace_node_tail(&node); printf("Config item: head starts with 's', tail='%.*s'\n", (int)tail.len, tail.str); } } } // Stop processing children when we go back to same or lower level else if (found_config && current_level <= config_level) { break; } } free(reader_data.lines); free(input_buffer); } void test_new_api_empty_lines() { // Read from stdin instead of hardcoded input char *line = NULL; size_t bufsize = 0; ssize_t linelen; // Collect all input lines char *input_buffer = NULL; size_t input_size = 0; while ((linelen = getline(&line, &bufsize, stdin)) != -1) { // Remove trailing newline if (linelen > 0 && line[linelen - 1] == '\n') { line[linelen - 1] = '\0'; linelen--; } // Append to input buffer input_buffer = realloc(input_buffer, input_size + linelen + 2); // +2 for \n and \0 if (input_size == 0) { strcpy(input_buffer, line); } else { strcat(input_buffer, "\n"); strcat(input_buffer, line); } input_size += linelen + 1; } free(line); if (!input_buffer) { return; // No input } string_reader_data_t reader_data = create_string_reader(input_buffer); terrace_document_t doc = terrace_create_document(' ', string_reader, &reader_data); TERRACE_FOR_EACH_NODE(&doc, node) { terrace_string_view_t head = terrace_node_head(&node); terrace_string_view_t tail = terrace_node_tail(&node); terrace_string_view_t content = terrace_node_content(&node); printf("| level %d | head \"%.*s\" | tail \"%.*s\" | content \"%.*s\" |\n", terrace_node_level(&node), (int)head.len, head.str, (int)tail.len, tail.str, (int)content.len, content.str); } free(reader_data.lines); free(input_buffer); } void test_new_api_readers() { // Read from stdin instead of hardcoded input char *line = NULL; size_t bufsize = 0; ssize_t linelen; // Collect all input lines char *input_buffer = NULL; size_t input_size = 0; while ((linelen = getline(&line, &bufsize, stdin)) != -1) { // Remove trailing newline if (linelen > 0 && line[linelen - 1] == '\n') { line[linelen - 1] = '\0'; linelen--; } // Append to input buffer input_buffer = realloc(input_buffer, input_size + linelen + 2); // +2 for \n and \0 if (input_size == 0) { strcpy(input_buffer, line); } else { strcat(input_buffer, "\n"); strcat(input_buffer, line); } input_size += linelen + 1; } free(line); if (!input_buffer) { return; // No input } string_reader_data_t reader_data = create_string_reader(input_buffer); terrace_document_t doc = terrace_create_document(' ', string_reader, &reader_data); TERRACE_FOR_EACH_NODE(&doc, node) { terrace_string_view_t head = terrace_node_head(&node); terrace_string_view_t tail = terrace_node_tail(&node); printf("%.*s: %.*s\n", (int)head.len, head.str, (int)tail.len, tail.str); } free(reader_data.lines); free(input_buffer); } int main(int argc, char *argv[]) { if (argc < 2) { // Run all new API tests printf("Running all new API tests...\n"); test_new_api_basic(); test_new_api_hierarchical(); test_new_api_functional(); test_node_methods(); test_inconsistent_indentation(); return 0; } char* test = argv[1]; // Legacy tests if (!strcmp(test, "linedata:basic")) linedata_basic(' '); else if (!strcmp(test, "linedata:tabs")) linedata_basic('\t'); else if (!strcmp(test, "linedata:head-tail")) linedata_head_tail(' '); // New API tests else if (!strcmp(test, "new-api:basic")) test_new_api_basic(); else if (!strcmp(test, "new-api:empty-lines")) test_new_api_empty_lines(); else if (!strcmp(test, "new-api:hierarchical")) test_new_api_hierarchical(); else if (!strcmp(test, "new-api:functional")) test_new_api_functional(); else if (!strcmp(test, "new-api:node-methods")) test_node_methods(); else if (!strcmp(test, "new-api:readers")) test_new_api_readers(); else if (!strcmp(test, "new-api:inconsistent-indentation")) test_inconsistent_indentation(); else if (!strcmp(test, "new-api:legacy-compat")) test_legacy_compat(); else if (!strcmp(test, "new-api:content-method")) test_content_method(); else { printf("Unknown test: %s\n", test); return 1; } return 0; }