From 2881897c23ecf72222acdbb629b0d87c88caf08c Mon Sep 17 00:00:00 2001 From: Hayden Hargreaves Date: Tue, 28 Oct 2025 16:12:16 -0700 Subject: [PATCH] (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. --- input.md | 18 +++++++ lib/parser.cpp | 118 +++++++++++++++++++++++++++++++++++++++++- lib/parser.h | 4 +- lib/structureNode.cpp | 10 ++-- 4 files changed, 144 insertions(+), 6 deletions(-) diff --git a/input.md b/input.md index f66ff5f..a4cf29c 100644 --- a/input.md +++ b/input.md @@ -20,3 +20,21 @@ this is too far` # ***This is both!*** ###### 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 diff --git a/lib/parser.cpp b/lib/parser.cpp index 1d5a7e5..4e8b052 100644 --- a/lib/parser.cpp +++ b/lib/parser.cpp @@ -72,11 +72,30 @@ std::unique_ptr Parser::ParseBlock() { // std::unique_ptr block = std::make_unique(ch); // Consume(); - if (Peek() == '#') { + char c = Peek(); + char c_next = Peek(1); + + // 1. Parse heading + if (c == '#') { 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(); } @@ -120,6 +139,44 @@ std::unique_ptr Parser::ParseHeading() { return node; } +std::unique_ptr Parser::ParseList(bool ordered) { + auto node = std::make_unique(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> Parser::ParseInline() { vector> nodes; string str; @@ -217,6 +274,63 @@ vector> Parser::ParseInlineHeading() { return nodes; } +vector> Parser::ParseInlineListContent() { + vector> 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 Parser::ParseItalic() { string str; Consume(1); diff --git a/lib/parser.h b/lib/parser.h index 02ccb35..1305c02 100644 --- a/lib/parser.h +++ b/lib/parser.h @@ -121,10 +121,12 @@ private: std::unique_ptr ParseParagraph(); std::unique_ptr ParseHeading(); + std::unique_ptr ParseList(bool ordered); vector> ParseInline(); - // The only difference is the exit condition + // The only differences are the exit condition vector> ParseInlineHeading(); + vector> ParseInlineListContent(); void PushTextNode(vector> &nodes, string &str); diff --git a/lib/structureNode.cpp b/lib/structureNode.cpp index 55ac4d1..ca4b00f 100644 --- a/lib/structureNode.cpp +++ b/lib/structureNode.cpp @@ -55,11 +55,15 @@ string ParagraphNode::ToHtml() const { return ss.str(); } -// TODO: Implement string ListNode::ToHtml() const { std::stringstream ss; - ss << (this->ordered ? "
    NOT YET IMPLEMENTED
" - : "
    NOT YET IMPLEMENTED
"); + ss << (this->ordered ? "
    " : "
      ") << "\n"; + + for (const auto &child : this->GetChilren()) { + ss << "
    • " << child->ToHtml() << "
    • " << "\n"; + } + + ss << (this->ordered ? "
" : "") << "\n"; return ss.str(); }