Terrace/packages/c/document.h
Joshua Bemenderfer 9d9757e868 Updates.
2025-09-08 16:24:38 -04:00

399 lines
13 KiB
C

#ifndef TERRACE_DOCUMENT_H
#define TERRACE_DOCUMENT_H
#include "parser.h"
#include <string.h>
#include <stddef.h>
// 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