#ifndef TERRACE_DOCUMENT_H #define TERRACE_DOCUMENT_H #include "parser.h" #include #include // String view structure for safe, zero-allocation string handling typedef struct { const char* str; size_t len; } terrace_string_view_t; // Convenience macros for common patterns #define TERRACE_STRING_VIEW_NULL ((terrace_string_view_t){.str = NULL, .len = 0}) #define TERRACE_STRING_VIEW_FROM_CSTR(cstr) ((terrace_string_view_t){.str = cstr, .len = strlen(cstr)}) #define TERRACE_STRING_VIEW_EQUALS_CSTR(view, cstr) \ ((view).len == strlen(cstr) && strncmp((view).str, cstr, (view).len) == 0) #define TERRACE_STRING_VIEW_IS_EMPTY(view) ((view).len == 0) // Safe string view comparison static inline int terrace_string_view_equals(terrace_string_view_t a, terrace_string_view_t b) { return a.len == b.len && (a.len == 0 || strncmp(a.str, b.str, a.len) == 0); } // Enhanced node structure for easier navigation typedef struct terrace_node_s { // Line content and metadata const char* _raw_line; terrace_linedata_t _line_data; unsigned int line_number; // Parent document reference struct terrace_document_s* document; } terrace_node_t; // Tracks state of a document being parsed. typedef struct terrace_document_s { // == Internal State == // unsigned int _repeat_current_node; terrace_node_t _current_node; terrace_node_t _pushed_back_node; unsigned int _has_pushed_back_node; unsigned int _line_number; // Legacy fields for backward compatibility char* _currentLine; terrace_linedata_t lineData; // == External Information == // // Custom data passed to the readline function void* userData; /** * Line reader function, provided by the user * @param {char**} line First argument is a pointer to line buffer * @param {void*} userData Second argument is `userData` * @returns {int} The number of characters read, or -1 if no characters were read. */ int (*reader)(char** line, void* userData); } terrace_document_t; /** * Initialize a Terrace document with indent parameters and the function needed to read lines. * @param {char} indent The indent character to use. Generally a single space character. * @param {int (*reader)(char** line, void* userData)} A function pointer to a function that reads lines sequentially * @param {void*} userData A user-supplied pointer to any state information needed by their reader function. * @returns {terrace_document_t} An initialized document that can now be used for further parsing. */ terrace_document_t terrace_create_document(const char indent, int (*reader)(char** line, void* userData), void* userData) { terrace_document_t document = { ._repeat_current_node = 0, ._current_node = {0}, ._pushed_back_node = {0}, ._has_pushed_back_node = 0, ._line_number = 0, ._currentLine = NULL, .lineData = terrace_create_linedata(indent), .reader = reader, .userData = userData }; return document; } // === NEW NODE-BASED API === /** * Get a string view of the node's head (first word) * @param {terrace_node_t*} node Pointer to the node * @returns {terrace_string_view_t} String view of the head portion */ terrace_string_view_t terrace_node_head(terrace_node_t* node) { terrace_string_view_t view = { .str = node->_raw_line + node->_line_data.offsetHead, .len = node->_line_data.offsetTail - node->_line_data.offsetHead }; return view; } /** * Get a string view of the node's tail (everything after first word) * @param {terrace_node_t*} node Pointer to the node * @returns {terrace_string_view_t} String view of the tail portion */ terrace_string_view_t terrace_node_tail(terrace_node_t* node) { if (node->_line_data.offsetTail >= strlen(node->_raw_line) || node->_raw_line[node->_line_data.offsetTail] != ' ') { return TERRACE_STRING_VIEW_NULL; } const char* tail_start = node->_raw_line + node->_line_data.offsetTail + 1; terrace_string_view_t view = { .str = tail_start, .len = strlen(tail_start) }; return view; } /** * Get a string view of the node's content (line without indentation) * @param {terrace_node_t*} node Pointer to the node * @returns {terrace_string_view_t} String view of the content */ terrace_string_view_t terrace_node_content(terrace_node_t* node) { const char* content_start = node->_raw_line + node->_line_data.offsetHead; terrace_string_view_t view = { .str = content_start, .len = strlen(content_start) }; return view; } /** * Get a string view of the node's raw content with custom offset * @param {terrace_node_t*} node Pointer to the node * @param {int} offset Offset from start of line (0 = include all indentation) * @returns {terrace_string_view_t} String view from the specified offset */ terrace_string_view_t terrace_node_raw(terrace_node_t* node, int offset) { if (offset < 0) offset = node->_line_data.offsetHead; // Default to content const char* start = node->_raw_line + offset; terrace_string_view_t view = { .str = start, .len = strlen(start) }; return view; } /** * Get the indentation level of a node * @param {terrace_node_t*} node Pointer to the node * @returns {unsigned int} Indentation level */ unsigned int terrace_node_level(terrace_node_t* node) { return node->_line_data.level; } /** * Get the line number of a node * @param {terrace_node_t*} node Pointer to the node * @returns {unsigned int} Line number */ unsigned int terrace_node_line_number(terrace_node_t* node) { return node->line_number; } /** * Check if the node's head matches a given string * @param {terrace_node_t*} node Pointer to the node * @param {const char*} match_str String to match against * @returns {int} 1 if matches, 0 if not */ int terrace_node_is(terrace_node_t* node, const char* match_str) { terrace_string_view_t head = terrace_node_head(node); return TERRACE_STRING_VIEW_EQUALS_CSTR(head, match_str); } /** * Check if the node is empty (blank line) * @param {terrace_node_t*} node Pointer to the node * @returns {int} 1 if empty, 0 if not */ int terrace_node_is_empty(terrace_node_t* node) { terrace_string_view_t content = terrace_node_content(node); for (size_t i = 0; i < content.len; i++) { if (content.str[i] != ' ' && content.str[i] != '\t') { return 0; } } return 1; } // === ENHANCED DOCUMENT ITERATION === /** * Get the next node from the document * @param {terrace_document_t*} doc Pointer to the document * @param {terrace_node_t*} node Pointer to store the next node * @returns {int} 1 if node was retrieved, 0 if end of document */ int terrace_next_node(terrace_document_t* doc, terrace_node_t* node) { // Check for pushed back node first if (doc->_has_pushed_back_node) { *node = doc->_pushed_back_node; doc->_has_pushed_back_node = 0; return 1; } // Read next line int chars_read = doc->reader(&doc->_currentLine, doc->userData); if (chars_read == -1) return 0; // Parse the line terrace_parse_line(doc->_currentLine, &doc->lineData); // Populate node node->_raw_line = doc->_currentLine; node->_line_data = doc->lineData; node->line_number = doc->_line_number++; node->document = doc; return 1; } /** * Push back a node to be returned by the next call to terrace_next_node * @param {terrace_document_t*} doc Pointer to the document * @param {terrace_node_t*} node Pointer to the node to push back */ void terrace_push_back_node(terrace_document_t* doc, terrace_node_t* node) { doc->_pushed_back_node = *node; doc->_has_pushed_back_node = 1; } // === ENHANCED MACROS FOR ITERATION === /** * Iterate through all nodes in a document * Usage: TERRACE_FOR_EACH_NODE(doc, node) { ... } */ #define TERRACE_FOR_EACH_NODE(doc, node) \ terrace_node_t node; \ while (terrace_next_node(doc, &node)) /** * Iterate through child nodes of a given parent level (supports arbitrary nesting) * Usage: TERRACE_FOR_CHILD_NODES(doc, parent_level, node) { ... } */ #define TERRACE_FOR_CHILD_NODES(doc, parent_level, node) \ terrace_node_t node; \ while (terrace_next_node(doc, &node) && terrace_node_level(&node) > parent_level) /** * Check if a node matches a string (shorthand for terrace_node_is) */ #define TERRACE_NODE_MATCHES(node, str) terrace_node_is(&node, str) // === LEGACY API FOR BACKWARD COMPATIBILITY === /** * Returns the number of indent characters of the current line * @param {terrace_document_t*} doc A pointer to the Terrace document being parsed * @returns {unsigned int} The indent level of the current line */ unsigned int terrace_level(terrace_document_t* doc) { return doc->lineData.level; } /** * Get a string with the current line contents (legacy API) * @param {terrace_document_t*} doc A pointer to the Terrace document being parsed * @param {int} startOffset How many indent characters to skip before outputting the line contents. * @returns {char*} The line contents starting from `startOffset` */ char* terrace_line(terrace_document_t* doc, int startOffset) { if (startOffset == -1) startOffset = doc->lineData.level; return doc->_currentLine + startOffset; } // === NEW STRING VIEW API === /** * Get string view of current line head (legacy compatibility) * @param {terrace_document_t*} doc Pointer to document * @returns {terrace_string_view_t} String view of head */ terrace_string_view_t terrace_head_view(terrace_document_t* doc) { terrace_string_view_t view = { .str = doc->_currentLine + doc->lineData.offsetHead, .len = doc->lineData.offsetTail - doc->lineData.offsetHead }; return view; } /** * Get string view of current line tail (legacy compatibility) * @param {terrace_document_t*} doc Pointer to document * @returns {terrace_string_view_t} String view of tail */ terrace_string_view_t terrace_tail_view(terrace_document_t* doc) { if (doc->lineData.offsetTail >= strlen(doc->_currentLine) || doc->_currentLine[doc->lineData.offsetTail] != ' ') { return TERRACE_STRING_VIEW_NULL; } const char* tail_start = doc->_currentLine + doc->lineData.offsetTail + 1; terrace_string_view_t view = { .str = tail_start, .len = strlen(tail_start) }; return view; } /** * Get string view of current line content (legacy compatibility) * @param {terrace_document_t*} doc Pointer to document * @param {int} offset Offset from start of line * @returns {terrace_string_view_t} String view from offset */ terrace_string_view_t terrace_line_view(terrace_document_t* doc, int offset) { if (offset == -1) offset = doc->lineData.level; const char* start = doc->_currentLine + offset; terrace_string_view_t view = { .str = start, .len = strlen(start) }; return view; } /** * Enhanced match function using string views * @param {terrace_document_t*} doc Pointer to document * @param {const char*} match_str String to match * @returns {int} 1 if matches, 0 if not */ int terrace_match_view(terrace_document_t* doc, const char* match_str) { terrace_string_view_t head = terrace_head_view(doc); return TERRACE_STRING_VIEW_EQUALS_CSTR(head, match_str); } // Enhanced legacy macro #define TERRACE_MATCH(doc, str) terrace_match_view(doc, str) /** * Get the *length* of the first "word" of a line (legacy API) * @param {terrace_document_t*} doc A pointer to the current document state struct. * @returns {int} The length of the `head` portion (first word) of a line */ unsigned int terrace_head_length(terrace_document_t* doc) { return doc->lineData.offsetTail - doc->lineData.offsetHead; } /** * Get a char pointer to everything following the first "word" of a line (legacy API) * @param {terrace_document_t*} doc A pointer to the current document state struct. * @returns {char*} The remainder of the line following the `head` portion, with no leading space. */ char* terrace_tail(terrace_document_t* doc) { return doc->_currentLine + doc->lineData.offsetTail + 1; } /** * Quickly check if the current line head matches a specified value (legacy API) * @param {terrace_document_t*} doc A pointer to the current document state struct. * @param {const char*} matchHead A string to check against the line `head` for equality. * @returns {char} A byte set to 0 if the head does not match, or 1 if it does match. */ char terrace_match(terrace_document_t* doc, const char* matchHead) { return terrace_match_view(doc, matchHead); } /** * Advances the current position in the terrace document (legacy API) * @param {terrace_document_t*} doc A pointer to the current document state struct. * @param {int} levelScope If set above -1, will return `0` when it encounters a line at or below `levelScope` * @returns {char} Returns `1` after parsing a line, or `0` if the document has ended */ char terrace_next(terrace_document_t* doc, int levelScope) { // Legacy implementation using new node-based system terrace_node_t node; if (!terrace_next_node(doc, &node)) return 0; // Update legacy fields for backward compatibility doc->lineData = node._line_data; doc->_currentLine = (char*)node._raw_line; // Check level scope if (levelScope != -1 && (int)terrace_node_level(&node) <= levelScope) { terrace_push_back_node(doc, &node); return 0; } return 1; } #endif