(FEAT): List nodes seem to be working.

However, the DRY principals are being screwed around with like they
don't exist. Some better architecture needs to be implemented. But that
will take place after block code nodes and anchor tags are implemented.

I will remain on this branch for those other implementations, for now.
This commit is contained in:
Hayden Hargreaves 2025-10-28 16:12:16 -07:00
parent 0ef0500fe8
commit 2881897c23
4 changed files with 144 additions and 6 deletions

View File

@ -20,3 +20,21 @@ this is too far`
# ***This is both!*** # ***This is both!***
###### This is neither ###### This is neither
- Hello world
- This is a list
* this is also a list
* this is still a list
1. This list is ordered
2. This is **number two**
- hello
world
- hello
world number two

View File

@ -72,11 +72,30 @@ std::unique_ptr<Node> Parser::ParseBlock() {
// std::unique_ptr<Node> block = std::make_unique<TextNode>(ch); // std::unique_ptr<Node> block = std::make_unique<TextNode>(ch);
// Consume(); // Consume();
if (Peek() == '#') { char c = Peek();
char c_next = Peek(1);
// 1. Parse heading
if (c == '#') {
return ParseHeading(); return ParseHeading();
} }
// this is the default case // 2. Parser unordered list
if (c == '*' || c == '-' || c == '+') {
// Next character must be space or tab
if (c_next == ' ' || c_next == '\t') {
return ParseList(false);
}
}
// 3. Parse ordered list
// TODO: This only checks a single digit, should check for 'n' digits
if (std::isdigit(c) && c_next == '.') {
// TODO: Do we need to check for white space?
return ParseList(true);
}
// 4. Parser paragraph
return ParseParagraph(); return ParseParagraph();
} }
@ -120,6 +139,44 @@ std::unique_ptr<Node> Parser::ParseHeading() {
return node; return node;
} }
std::unique_ptr<Node> Parser::ParseList(bool ordered) {
auto node = std::make_unique<ListNode>(ordered);
// Consume the required white space and list char ('* ' or '1.')
while (true) {
Consume(ordered ? 2 : 1);
ConsumeWhiteSpace();
// Parse until either '\n\n' (exit) or the next list element is found ('* '
// or '1.') If '\n\n', then create a node and exit
auto children = ParseInlineListContent();
for (auto &child : children) {
node->AddChild(std::move(child));
}
char c = Peek();
char c_next = Peek(1);
// 2. Parser unordered list
if (c == '*' || c == '-' || c == '+') {
if (c_next == ' ' || c_next == '\t') {
continue;
}
}
// 3. Parse ordered list
// TODO: This only checks a single digit, should check for 'n' digits
if (std::isdigit(c) && c_next == '.') {
continue;
}
break;
}
return node;
};
vector<std::unique_ptr<Node>> Parser::ParseInline() { vector<std::unique_ptr<Node>> Parser::ParseInline() {
vector<std::unique_ptr<Node>> nodes; vector<std::unique_ptr<Node>> nodes;
string str; string str;
@ -217,6 +274,63 @@ vector<std::unique_ptr<Node>> Parser::ParseInlineHeading() {
return nodes; return nodes;
} }
vector<std::unique_ptr<Node>> Parser::ParseInlineListContent() {
vector<std::unique_ptr<Node>> nodes;
string str;
while (!IsEOF()) {
char c = Peek();
char c_next = Peek(1);
// If this char and next char are both newlines: then we have an empty line,
// we should stop.
if (c == '\n' && Peek(1) == '\n')
break;
// Check if a list block has been found
if ((c == '*' || c == '-' || c == '+') && (c_next == ' ' || c_next == '\t'))
break;
if (std::isdigit(c) && c_next == '.')
break;
if (c == '*' && Peek(1) == '*' && Peek(2) == '*') {
PushTextNode(nodes, str);
auto node = ParseBoldItalic();
if (!node->IsEmpty())
nodes.push_back(std::move(node));
continue;
} else if (c == '*' && Peek(1) == '*') {
PushTextNode(nodes, str);
auto node = ParseBold();
if (!node->IsEmpty())
nodes.push_back(std::move(node));
continue;
} else if (c == '*') {
PushTextNode(nodes, str);
auto node = ParseItalic();
if (!node->IsEmpty())
nodes.push_back(std::move(node));
continue;
}
if (c == '`') {
PushTextNode(nodes, str);
auto node = ParseCode();
if (!node->IsEmpty())
nodes.push_back(std::move(node));
continue;
}
// If a newline, use a space instead
str += (c == '\n' ? ' ' : c);
Consume();
}
// Push the last node, if the string is not empty
PushTextNode(nodes, str);
return nodes;
}
std::unique_ptr<Node> Parser::ParseItalic() { std::unique_ptr<Node> Parser::ParseItalic() {
string str; string str;
Consume(1); Consume(1);

View File

@ -121,10 +121,12 @@ private:
std::unique_ptr<Node> ParseParagraph(); std::unique_ptr<Node> ParseParagraph();
std::unique_ptr<Node> ParseHeading(); std::unique_ptr<Node> ParseHeading();
std::unique_ptr<Node> ParseList(bool ordered);
vector<std::unique_ptr<Node>> ParseInline(); vector<std::unique_ptr<Node>> ParseInline();
// The only difference is the exit condition // The only differences are the exit condition
vector<std::unique_ptr<Node>> ParseInlineHeading(); vector<std::unique_ptr<Node>> ParseInlineHeading();
vector<std::unique_ptr<Node>> ParseInlineListContent();
void PushTextNode(vector<std::unique_ptr<Node>> &nodes, string &str); void PushTextNode(vector<std::unique_ptr<Node>> &nodes, string &str);

View File

@ -55,11 +55,15 @@ string ParagraphNode::ToHtml() const {
return ss.str(); return ss.str();
} }
// TODO: Implement
string ListNode::ToHtml() const { string ListNode::ToHtml() const {
std::stringstream ss; std::stringstream ss;
ss << (this->ordered ? "<ol>NOT YET IMPLEMENTED</ol>"
: "<ul>NOT YET IMPLEMENTED</ul>");
ss << (this->ordered ? "<ol>" : "<ul>") << "\n";
for (const auto &child : this->GetChilren()) {
ss << "<li>" << child->ToHtml() << "</li>" << "\n";
}
ss << (this->ordered ? "</ol>" : "</ul>") << "\n";
return ss.str(); return ss.str();
} }