From 4b0900e3ae227d6cd950ccf82a177c39b81ed943 Mon Sep 17 00:00:00 2001 From: Hayden Hargreaves Date: Fri, 17 Oct 2025 13:50:29 -0700 Subject: [PATCH 1/3] (FEAT): Added support for inline code blocks. --- lib/inlineNode.cpp | 2 ++ lib/inlineNode.h | 23 +++++++++++++++++++++++ lib/node.h | 9 +++++++++ lib/parser.cpp | 44 ++++++++++++++++++++++++++++++++++++++++---- lib/parser.h | 1 + lib/structureNode.h | 10 ++++++++++ src/main.cpp | 2 +- 7 files changed, 86 insertions(+), 5 deletions(-) diff --git a/lib/inlineNode.cpp b/lib/inlineNode.cpp index 22b4788..625de65 100644 --- a/lib/inlineNode.cpp +++ b/lib/inlineNode.cpp @@ -21,3 +21,5 @@ string BoldNode::ToHtml() const { string BoldItalicNode::ToHtml() const { return "" + this->content + ""; } + +string CodeNode::ToHtml() const { return "" + this->content + ""; } diff --git a/lib/inlineNode.h b/lib/inlineNode.h index 7655afd..af825a7 100644 --- a/lib/inlineNode.h +++ b/lib/inlineNode.h @@ -2,6 +2,7 @@ #define INLINENODE_H #include "node.h" +#include #include /** @@ -37,6 +38,15 @@ public: */ void AddChild(std::unique_ptr child); + /** + * @brief Is the node empty. + * + * This is the same as checking if the nodes content is empty. + * + * @author Hayden Hargreaves (hhargreaves2006@gmail.com) + */ + bool IsEmpty() const { return this->content.empty(); }; + protected: std::string content; }; @@ -94,4 +104,17 @@ public: std::string ToHtml() const; }; +/** + * @desc An inline code block node. + * + * This node returns it's content wrapped with tags. + * + * @author Hayden Hargreaves (hhargreaves2006@gmail.com) + */ +class CodeNode : public InlineNode { +public: + CodeNode(std::string content) : InlineNode(content) {}; + std::string ToHtml() const; +}; + #endif diff --git a/lib/node.h b/lib/node.h index 390b004..20b04d5 100644 --- a/lib/node.h +++ b/lib/node.h @@ -67,6 +67,15 @@ public: virtual const std::vector> &GetChilren() const { return this->children; } + + /** + * @brief Is the node empty. + * + * This is done differently for inline nodes and structure nodes. + * + * @author Hayden Hargreaves (hhargreaves2006@gmail.com) + */ + virtual bool IsEmpty() const = 0; }; #endif diff --git a/lib/parser.cpp b/lib/parser.cpp index 86ab32d..7984ef2 100644 --- a/lib/parser.cpp +++ b/lib/parser.cpp @@ -89,7 +89,7 @@ std::unique_ptr Parser::ParseParagraph() { node->AddChild(std::move(text_node)); } - if (node->GetChilren().size() < 1) + if (node->IsEmpty()) return nullptr; return node; @@ -143,15 +143,29 @@ vector> Parser::ParseInline() { if (c == '*' && Peek(1) == '*' && Peek(2) == '*') { PushTextNode(nodes, str); - nodes.push_back(std::move(ParseBoldItalic())); + auto node = ParseBoldItalic(); + if (!node->IsEmpty()) + nodes.push_back(std::move(node)); continue; } else if (c == '*' && Peek(1) == '*') { PushTextNode(nodes, str); - nodes.push_back(std::move(ParseBold())); + auto node = ParseBold(); + if (!node->IsEmpty()) + nodes.push_back(std::move(node)); continue; } else if (c == '*') { PushTextNode(nodes, str); - nodes.push_back(std::move(ParseItalic())); + 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; } @@ -231,6 +245,28 @@ std::unique_ptr Parser::ParseBoldItalic() { return std::make_unique(str); } +std::unique_ptr Parser::ParseCode() { + string str; + Consume(1); + + while (!IsEOF()) { + char c = Peek(); + + if (c == '\n' && Peek(1) == '\n') + break; + + if (c == '`') { + Consume(1); + break; + } + + str += c; + Consume(); + } + + return std::make_unique(str); +} + void Parser::PushTextNode(vector> &nodes, string &str) { if (!str.empty()) nodes.push_back(std::move(std::make_unique(str))); diff --git a/lib/parser.h b/lib/parser.h index cd24c74..63766af 100644 --- a/lib/parser.h +++ b/lib/parser.h @@ -126,6 +126,7 @@ private: std::unique_ptr ParseItalic(); std::unique_ptr ParseBold(); std::unique_ptr ParseBoldItalic(); + std::unique_ptr ParseCode(); char Peek(size_t offset = 0); void Consume(size_t count = 1); diff --git a/lib/structureNode.h b/lib/structureNode.h index c45feea..7e07bf8 100644 --- a/lib/structureNode.h +++ b/lib/structureNode.h @@ -2,6 +2,7 @@ #define STRUCTURENODE_H #include "node.h" +#include #include /** @@ -23,6 +24,15 @@ public: * @author Hayden Hargreaves (hhargreaves2006@gmail.com) */ virtual std::string ToHtml() const = 0; + + /** + * @brief Is the node empty. + * + * This is the same as checking if the node has no children. + * + * @author Hayden Hargreaves (hhargreaves2006@gmail.com) + */ + bool IsEmpty() const { return this->children.size() == 0; }; }; /** diff --git a/src/main.cpp b/src/main.cpp index cf4be4c..13be374 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -79,7 +79,7 @@ void test_input(int argc, char **argv) { } int main(int argc, char **argv) { - Parser p("syntax.md"); + Parser p("input.md"); p.ParseDocument(); p.WriteOutput(); From d2f0b5451dab7efe41ea2e930f1fcfd710f5040e Mon Sep 17 00:00:00 2001 From: Hayden Hargreaves Date: Mon, 20 Oct 2025 12:00:37 -0700 Subject: [PATCH 2/3] (DOC): Updated to the input test file. --- input.md | 115 ++++--------------------------------------------------- 1 file changed, 8 insertions(+), 107 deletions(-) diff --git a/input.md b/input.md index c031c06..6013472 100644 --- a/input.md +++ b/input.md @@ -1,115 +1,16 @@ -# MarkdownToHtmlCompiler - -### Project Overview - -The goal is to create a program that reads a file containing text formatted in a simple version of -Markdown and converts it into a valid HTML file. The program will need to identify and translate -specific syntax (e.g., ` Heading` to `\Heading\`, `*text*` to `\text\`). +hello `world` -### Implementation Requirements (Generated by Gemini) +This `is also a code block` -Class Hierarchy: Design a class hierarchy to represent the components of your Markdown document. An -abstract base class, Element, can define common behavior. Derived classes would then represent specific -types of elements, such as Heading, Paragraph, BoldText, and ListItem. This is a perfect example of -inheritance and polymorphism. +hi `mom +hello` -Object Composition: A Document class can be composed of multiple Element objects, representing the -entire file. A Parser class would be composed of helper methods to break down the input string and -build the Document object. This shows how you can build a complex system from smaller, self-contained -objects. +hi `mom -File I/O and Exceptions: You will need to use ifstream to read the Markdown file and ofstream to write -the generated HTML file. Your code should use exceptions to gracefully handle potential errors, such -as a file not being found. - -Operator Overloading: Overload the << stream insertion operator for your Element and Document classes. -This would allow you to easily print the generated HTML to the console or write it to a file, making -your code cleaner and more readable. - -UML Diagram: The complexity of the class relationships makes a UML diagram an essential part of the -project. It will help you plan your design and will be a key component of your submission. - -Recursive Descent Parser: This is the primary algorithm you'll use. It's a top-down parsing technique -where a set of recursive functions "descend" through the grammar of your simple Markdown language. For -example, a parse_document() function would call parse_line(), which in turn might call parse_bold_text() -or parse_italic_text(). This method is intuitive and easy to implement for a simple grammar. - -Stack: A stack is essential for handling nested elements. For instance, if you allow bold text inside -italic text (_This is *bold and italic* text_), you can push the _ token onto the stack and then push -the * token. When you encounter the closing *, you check if the top of the stack matches. This ensures -that all tags are correctly opened and closed. Your presentation can visually demonstrate this process -with a stack diagram. - -Hash Map or Map: A hash map (std::unordered_map) or a map (std::map) can be used to efficiently store -and retrieve the HTML equivalent for each Markdown tag. For example, you could map `#` to `\`or `*` -to `\`. This provides O(1) average-case lookup time. +this is too far` -### Contribution Policy +*this is **words*** -###### Branching -When working on this project, please use a feature branch (i.e. `feature/parser`) with a descriptive name. -`feature/a` is not a descriptive name. These branches should be branched off the most recent `main` branch, -we will not make use of a `dev` or `staging` branch since the project is small in scale as well as time. -**However, if the project becomes larger or out-of-control, a dev/staging branch will be implemented.** - -###### Commits - -When working, it is best practice to commit code as much as possible, without being over zealous. For -example, when a feature or bug is complete, its time to commit. But when you have to make a new function, -that does not mean its time. Each team member should use their best judgment. - -Commit messages a little bit more important, when working in a team, it is important to provide strong, -clear and concise commit messages. In this project, the team will use a simple formula: - -**(SUBJECT) Title: textual description** - -i.e. (FIX) Rendering completed: explain what changed in short. - -###### Pushing - -When working in a feature branch, pushing and pulling has no restrictions. Feel free to do as much -(or as little) as possible. However, you **CANNOT** push directly to `main`, the VCS will not allow you -to do so, but do not make that mistake. When you are ready to merge a feature, you will create a PR -and once it has been reviewed and approved it will be automatically merged in. - -###### Pull Requests (PR) - -Once a feature is complete, you will create a pull request. Before a request can be merged into `main`, -one approval is required (which cannot be the author). This practice is to promote team work and encourage -code reviews. Each team member is expected to check in frequently and review as often as they are able to, -however, there is no defined time requirement. Personal communication is totally acceptable as a means to -request approval, since I am unsure if this platform will notify members. - -###### Issues - -If a bug, issue, or otherwise concern is noticed the first thing the team member should do is create an -issue. An issue should be descriptive and contain everything another team member needs to understand the -issue and its context. This way, a new team member can tackle the issue without contextual gaps. - -If a member would like to work on the issue themself, the `assignee` field is where this should be defined. -If a member would like help from another member, they should assign the other team member to the issue, and -leave a comment in the issue itself describing what help is needed. - -**Labels** are important for understanding what type of issues/bugs exist in the application. When a bug is -created, make sure the proper labels are applied. These labels will be abstract, such as: `bug`, `fix` or `feature` -and they will also be specific, such as: `parser`, `i/o` or `processer`. A combination of both styles of labels -allows other team members to understand what is going on. If a member feels an issue is missing, they are free -to create new ones, but there is a such thing as **too many labels** a few per issue is totally fine. They are -not meant to replace the description. - -**Priority** is the final important factor to consider. In this project, priority will be defined using labels -as well. The policy defined above will apply here to priority labels as well. However, these labels are -**mutually exclusive**. - -###### Projects (Sprints) - -The use of the `projects` tab in the VCS will allow the team to remain organized as create notes and action -items that should be completed before one another. These resemble `sprints` from the `AGILE` development life cycle. -A new "project" should be created when a large piece of functionality needs to be created. Issues can **and should** -be attached to the projects they are related too. This will continue to encourage teamwork and organization. - -Projects should have defined criteria, such as input and outputs, expectations and a semi-defined timeline. -Once a description and is defined, tasks can be added and moved around as needed. The team will use **Kanban** -project types, as they are simple and easy to understand for new team members. +## **Hello world** From f0ab2d900633af895a303a95b6a05a65dd3781ae Mon Sep 17 00:00:00 2001 From: Hayden Hargreaves Date: Mon, 20 Oct 2025 12:12:51 -0700 Subject: [PATCH 3/3] (FIX): Cleaned up a little bit, removed some notes. --- input.md | 4 +++- lib/node.h | 1 - lib/parser.cpp | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/input.md b/input.md index 6013472..72e47cf 100644 --- a/input.md +++ b/input.md @@ -13,4 +13,6 @@ this is too far` *this is **words*** -## **Hello world** +## **Hello world** + +### hello world diff --git a/lib/node.h b/lib/node.h index 20b04d5..6009d1c 100644 --- a/lib/node.h +++ b/lib/node.h @@ -18,7 +18,6 @@ /// try to avoid using raw pointers, and only use references when needed. /// Reference: https://www.youtube.com/watch?v=AmjoK55h68Y&t=166s -// NOTE ABC class Node { protected: /** diff --git a/lib/parser.cpp b/lib/parser.cpp index 7984ef2..54372e5 100644 --- a/lib/parser.cpp +++ b/lib/parser.cpp @@ -288,7 +288,6 @@ void Parser::Consume(size_t count) { this->position += count; }; bool Parser::IsEOF() { return this->position >= this->content.length(); }; void Parser::ConsumeWhiteSpace() { - // TODO: This can be optimized using an accumulator and then consuming char c = Peek(); while (c == ' ' || c == '\t' || c == '\n') { Consume();