399 lines
13 KiB
C
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
|