Feature: Implemented code blocks and list elements #30

Merged
shultzp1 merged 3 commits from feature/list-nodes into main 2025-10-28 18:19:09 -07:00
4 changed files with 144 additions and 6 deletions
Showing only changes of commit 2881897c23 - Show all commits

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();
} }