Updates.
This commit is contained in:
29
packages/c/Makefile
Normal file
29
packages/c/Makefile
Normal file
@@ -0,0 +1,29 @@
|
||||
CC=gcc
|
||||
CFLAGS=-std=c99 -Wall -Wextra -g
|
||||
TARGET=test/test-runner
|
||||
SOURCE=test/test-runner.c
|
||||
|
||||
.PHONY: all test clean
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(SOURCE)
|
||||
$(CC) $(CFLAGS) -o $(TARGET) $(SOURCE)
|
||||
|
||||
test: $(TARGET)
|
||||
./$(TARGET)
|
||||
|
||||
test-basic: $(TARGET)
|
||||
./$(TARGET) new-api:basic
|
||||
|
||||
test-hierarchical: $(TARGET)
|
||||
./$(TARGET) new-api:hierarchical
|
||||
|
||||
test-string-views: $(TARGET)
|
||||
./$(TARGET) new-api:string-views
|
||||
|
||||
test-legacy: $(TARGET)
|
||||
./$(TARGET) new-api:legacy-compat
|
||||
|
||||
clean:
|
||||
rm -f $(TARGET)
|
||||
@@ -16,9 +16,11 @@ Section light
|
||||
|
||||
Markdown
|
||||
Documentation is available for the following languages:
|
||||
- [C](/docs/c/) - 75% Complete
|
||||
- [C](/docs/c/) - 100% Complete
|
||||
- [JavaScript](/docs/javascript/) - 75% Complete
|
||||
- [Python](/docs/python/) - 0% Complete
|
||||
- [Go](/docs/go/) - 50% Complete
|
||||
- [Python](/docs/python/) - 100% Complete
|
||||
- [Rust](/docs/rust/) - 100% Complete
|
||||
|
||||
Heading 2 Getting Started
|
||||
class mt-12 mb-6
|
||||
|
||||
@@ -2,42 +2,78 @@
|
||||
#define TERRACE_DOCUMENT_H
|
||||
|
||||
#include "parser.h"
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// Tracks state of a given while being parsed.
|
||||
// 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 _repeatCurrentLine;
|
||||
// Current line being read
|
||||
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 == //
|
||||
// Embedded line data struct. Holds information about the current parsed line
|
||||
terrace_linedata_t lineData;
|
||||
// Custom data passed to the readline function
|
||||
void* userData;
|
||||
/**
|
||||
* Line reader function, provided by the user
|
||||
* Needed to get the next line inside of `terrace_next(doc)`
|
||||
* @param {char**} line First argument is a pointer to `_currentLine`, above
|
||||
* @param {void*} userData Second argument is `userData`, above
|
||||
* @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 neded to read lines.
|
||||
* 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
|
||||
* from a user-provided source. Receives a pointer to lineData->_currLine, and userData, supplied in the next argument.
|
||||
* @param {void*} userData A user-supplied pointer to any state information needed by their reader function.
|
||||
* Passed to `reader`each time it is called.
|
||||
* @returns {terrace_document_t} An initialized document that can now be used for futher parsing.
|
||||
* @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 = {
|
||||
._repeatCurrentLine = 0,
|
||||
._currentLine = 0,
|
||||
._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
|
||||
@@ -46,17 +82,184 @@ terrace_document_t terrace_create_document(const char indent, int (*reader)(char
|
||||
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
|
||||
*
|
||||
* Given the following document, `terrace_level(doc)` would return 0, 1, 2, and 5 respectively for each line
|
||||
*
|
||||
* ```terrace
|
||||
* block
|
||||
* block
|
||||
* block
|
||||
* block
|
||||
* ```
|
||||
* @param {terrace_document_t*} doc A pointer to the Terrace document being parsed
|
||||
* @returns {unsigned int} The indent level of the current line
|
||||
*/
|
||||
@@ -65,22 +268,9 @@ unsigned int terrace_level(terrace_document_t* doc) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string with the current line contents
|
||||
* If `startOffset` is -1, skips all indent characters by default. Otherwise only skips the amount specified.
|
||||
*
|
||||
* Given the following document
|
||||
*
|
||||
* ```terrace
|
||||
* root
|
||||
* sub-line
|
||||
* ```
|
||||
* `terrace_line(doc, -1)` on the second line returns "sub-line", trimming off the leading indent characters
|
||||
* `terrace_line(doc, 0)` however, returns " sub-line", with all four leading spaces
|
||||
*
|
||||
* `startOffset`s other than `-1` are primarily used for parsing blocks that have literal indented multi-line text
|
||||
*
|
||||
* 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. If set to -1, uses the current indent level.
|
||||
* @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) {
|
||||
@@ -88,23 +278,73 @@ char* terrace_line(terrace_document_t* doc, int startOffset) {
|
||||
return doc->_currentLine + startOffset;
|
||||
}
|
||||
|
||||
// === NEW STRING VIEW API ===
|
||||
|
||||
/**
|
||||
* Get the *length* of the first "word" of a line,
|
||||
* starting from the first non-indent character to the first space or end of the line
|
||||
* Often used for deciding how to parse a block.
|
||||
*
|
||||
* Because C uses NULL-terminated strings, we cannot easily slice a string to return something out of the middle.
|
||||
* Instead, `terrace_head_length()` provides the length of the head portion.
|
||||
* In combination with `doc->lineData.offsetHead`, you can copy the head section into a new string,
|
||||
* or use any number of `strn*` C stdlib functions to work with the head section without copying it.
|
||||
*
|
||||
* Terrace DSLs do not *need* to use head-tail line structure, but support for them is built into the parser
|
||||
*
|
||||
* Given the following line, `terrace_head_length(doc)` returns `5`
|
||||
*
|
||||
* ```terrace
|
||||
* title An Important Document
|
||||
* ```
|
||||
* 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
|
||||
*/
|
||||
@@ -113,16 +353,7 @@ unsigned int terrace_head_length(terrace_document_t* doc) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a char pointer to everything following the first "word" of a line,
|
||||
* starting from the first character after the space at the end of `head`.
|
||||
*
|
||||
* Terrace DSLs do not *need* to use head-tail line structure, but support for them is built into the parser
|
||||
*
|
||||
* Given the following line, `terrace_tail(doc)` returns "An Important Document"
|
||||
*
|
||||
* ```terrace
|
||||
* title An Important Document
|
||||
* ```
|
||||
* 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.
|
||||
*/
|
||||
@@ -131,81 +362,33 @@ char* terrace_tail(terrace_document_t* doc) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Quickly check if the current line head matches a specified value. Useful in many document-parsing situations.
|
||||
*
|
||||
* Given the following line:
|
||||
*
|
||||
* ```terrace
|
||||
* title An Important Document
|
||||
* ```
|
||||
*
|
||||
* `terrace_match(doc, "title")` returns `1`
|
||||
* `terrace_match(doc, "somethingElse") returns `0`
|
||||
*
|
||||
* 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*} matchValue A string to check against the line `head` for equality.
|
||||
* @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) {
|
||||
// Get a pointer to the start of the head portion of the string.
|
||||
char* head = doc->_currentLine + doc->lineData.offsetHead;
|
||||
|
||||
int i = 0;
|
||||
// Loop until we run out of characters in `matchHead`.
|
||||
while (matchHead[i] != '\0') {
|
||||
// Return as unmatched if we run out of `head` characters
|
||||
// or if a character at the same position in both matchHead and head is not identical.
|
||||
if (head[i] == '\0' || matchHead[i] != head[i]) return 0;
|
||||
i++;
|
||||
}
|
||||
|
||||
// If we didn't return inside the while loop, `matchHead` and `head` are equivalent, a successful match.
|
||||
return 1;
|
||||
return terrace_match_view(doc, matchHead);
|
||||
}
|
||||
|
||||
/**
|
||||
* Advances the current position in the terrace document and populates `doc->lineData`
|
||||
* with the parsed information from that line
|
||||
*
|
||||
* Returns `1` after parsing the next line, or `0` upon reaching the end of the document.
|
||||
* If the `levelScope` parameter is not -1, `terrace_next()` will also return `0` when it encounters a line
|
||||
* with a level at or below `levelScope`. This allows you to iterate through subsections of a document.
|
||||
*
|
||||
* If a lower-level line was encountered, the following call to `terrace_next()` will repeat this line again.
|
||||
* This allows a child loop to look forward, determine that the next line will be outside its purview,
|
||||
* and return control to the calling loop transparently without additional logic.
|
||||
*
|
||||
* Intended to be used inside a while loop to parse a section of a Terrace document.
|
||||
*
|
||||
* ```c
|
||||
* while(terrace_next(doc, -1)) {
|
||||
* // Do something with each line.
|
||||
* }
|
||||
* ```
|
||||
* @param {terrace_document_t*} doc A pointer to the current document state struct.
|
||||
* @param {number} levelScope If set above -1, `next()` will return `0` when it encounters a line with a level at or below `levelScope`
|
||||
* @returns {char} Returns `1` after parsing a line, or `0` if the document has ended or a line at or below `levelScope` has been encountered.
|
||||
*/
|
||||
* 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) {
|
||||
// Repeat the current line instead of parsing a new one if the previous call to next()
|
||||
// determined the current line to be out of its scope.
|
||||
if (doc->_repeatCurrentLine) doc->_repeatCurrentLine = 0;
|
||||
// Otherwise parse the line normally.
|
||||
else {
|
||||
// Load the next line from the line reader.
|
||||
int chars_read = doc->reader(&doc->_currentLine, doc->userData);
|
||||
// If there are no more lines, bail out.
|
||||
if (chars_read == -1) return 0;
|
||||
// Legacy implementation using new node-based system
|
||||
terrace_node_t node;
|
||||
if (!terrace_next_node(doc, &node)) return 0;
|
||||
|
||||
// Populate lineData with parsed information from the current line.
|
||||
terrace_parse_line(doc->_currentLine, &doc->lineData);
|
||||
}
|
||||
// Update legacy fields for backward compatibility
|
||||
doc->lineData = node._line_data;
|
||||
doc->_currentLine = (char*)node._raw_line;
|
||||
|
||||
// If we shouldn't be handling this line, make the following call to next() repeat the current line.
|
||||
// Allows a child loop to look forward, determine that the next line will be outside its purview,
|
||||
// and return control to the calling loop transparently without additional logic.
|
||||
if ((int) terrace_level(doc) <= levelScope) {
|
||||
doc->_repeatCurrentLine = 1;
|
||||
// Check level scope
|
||||
if (levelScope != -1 && (int)terrace_node_level(&node) <= levelScope) {
|
||||
terrace_push_back_node(doc, &node);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,69 @@
|
||||
#define _GNU_SOURCE
|
||||
#include "../parser.h"
|
||||
#include "../document.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <assert.h>
|
||||
|
||||
// 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;
|
||||
@@ -17,7 +77,32 @@ void linedata_basic (char indent) {
|
||||
terrace_parse_line(terrace_line, &line_data);
|
||||
if (terrace_line == 0) terrace_line = "";
|
||||
|
||||
printf("| level %u | indent %c | offsetHead %u | offsetTail %u | line %s |\n", line_data.level, line_data.indent, line_data.offsetHead, line_data.offsetTail, 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);
|
||||
@@ -55,12 +140,555 @@ void linedata_head_tail (char indent) {
|
||||
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) return 0;
|
||||
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(' ');
|
||||
if (!strcmp(test, "linedata:tabs")) linedata_basic('\t');
|
||||
if (!strcmp(test, "linedata:head-tail")) linedata_head_tail(' ');
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user