diff --git a/input.md b/input.md index f66ff5f..cd608b8 100644 --- a/input.md +++ b/input.md @@ -20,3 +20,28 @@ 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 + +``` +int x = 5; +int y = 10; + +console.log(x + y); // '15' +``` diff --git a/lib/inlineNode.cpp b/lib/inlineNode.cpp index 625de65..e0cced9 100644 --- a/lib/inlineNode.cpp +++ b/lib/inlineNode.cpp @@ -23,3 +23,5 @@ string BoldItalicNode::ToHtml() const { } string CodeNode::ToHtml() const { return "" + this->content + ""; } + +string RawTextNode::ToHtml() const { return this->content; }; diff --git a/lib/inlineNode.h b/lib/inlineNode.h index af825a7..9866b6e 100644 --- a/lib/inlineNode.h +++ b/lib/inlineNode.h @@ -117,4 +117,17 @@ public: std::string ToHtml() const; }; +/** + * @desc A raw text node. + * + * This node returns only it content, with no formatting at all. + * + * @author Hayden Hargreaves (hhargreaves2006@gmail.com) + */ +class RawTextNode : public InlineNode { +public: + RawTextNode(std::string content) : InlineNode(content) {}; + std::string ToHtml() const; +}; + #endif diff --git a/lib/parser.cpp b/lib/parser.cpp index 1d5a7e5..f01c505 100644 --- a/lib/parser.cpp +++ b/lib/parser.cpp @@ -4,6 +4,7 @@ #include "structureNode.h" #include #include +#include #include #include @@ -72,11 +73,35 @@ 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. Parse code block + if (c == '`' && c_next == '`' && Peek(2) == '`') { + return ParseCodeBlock(); + } + + // 5. Parser paragraph return ParseParagraph(); } @@ -120,6 +145,75 @@ 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; +}; + +std::unique_ptr Parser::ParseCodeBlock() { + auto node = std::make_unique(); + string str; + + // Remove the first three characters, the '```' + Consume(3); + + // Parse text into a single text node until '```' is found, include everything + // else + while (!IsEOF()) { + char c = Peek(); + if (c == '`' && Peek(1) == '`' && Peek(2) == '`') { + Consume(3); + break; + } + + // Swap any '\n' with BR tags, so it will visually break + if (c == '\n') + str += "\n
\n"; + else + str += c; + + Consume(); + } + + auto text_node = std::make_unique(str); + node->AddChild(std::move(text_node)); + + return node; +} + vector> Parser::ParseInline() { vector> nodes; string str; @@ -217,6 +311,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..12ef8b3 100644 --- a/lib/parser.h +++ b/lib/parser.h @@ -121,10 +121,13 @@ private: std::unique_ptr ParseParagraph(); std::unique_ptr ParseHeading(); + std::unique_ptr ParseList(bool ordered); + std::unique_ptr ParseCodeBlock(); 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..9516c3b 100644 --- a/lib/structureNode.cpp +++ b/lib/structureNode.cpp @@ -55,11 +55,28 @@ 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(); +} + +string CodeBlockNode::ToHtml() const { + std::stringstream ss; + + ss << "\n"; + + for (const auto &child : this->GetChilren()) { + ss << child->ToHtml() << "\n"; + } + + ss << "\n"; return ss.str(); } diff --git a/lib/structureNode.h b/lib/structureNode.h index 7e07bf8..186268e 100644 --- a/lib/structureNode.h +++ b/lib/structureNode.h @@ -111,4 +111,19 @@ public: std::string ToHtml() const; }; +/** + * @desc A code block container node. + * + * This node is used to wrap a code block node. When three '`' are used a + * code block should be created. This node's children are expected to be simple + * text nodes - containing no formatting at all. Since code blocks are not + * parsed any deeper then their parents. + * + * @author Hayden Hargreaves (hhargreaves2006@gmail.com) + */ +class CodeBlockNode : public StructureNode { +public: + std::string ToHtml() const; +}; + #endif