aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/ricochet/parser/dom_tree.hh35
-rw-r--r--include/ricochet/parser/html_lexer.hh2
-rw-r--r--include/ricochet/parser/tree_builder.hh44
-rw-r--r--src/core/browser.cc35
-rw-r--r--src/parser/tree_builder.cc90
5 files changed, 189 insertions, 17 deletions
diff --git a/include/ricochet/parser/dom_tree.hh b/include/ricochet/parser/dom_tree.hh
new file mode 100644
index 0000000..b1e4812
--- /dev/null
+++ b/include/ricochet/parser/dom_tree.hh
@@ -0,0 +1,35 @@
+/*
+ * Ricochet - A minimalist CLI web browser for fast, distraction-free navigation.
+ * Copyright (C) 2026 Richard Qin
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+namespace ricochet::parser {
+
+/**
+ * @brief DOM Node. Pure value semantics, no pointers.
+ */
+struct DomNode {
+ std::string tag_name{}; // If empty, it's a pure text node
+ std::string text_content{}; // Only meaningful when tag_name is empty
+ std::vector<DomNode> children{}; // Recursive child nodes list
+};
+
+} // namespace ricochet::parser \ No newline at end of file
diff --git a/include/ricochet/parser/html_lexer.hh b/include/ricochet/parser/html_lexer.hh
index f97bcfa..2cfdda9 100644
--- a/include/ricochet/parser/html_lexer.hh
+++ b/include/ricochet/parser/html_lexer.hh
@@ -25,7 +25,7 @@
namespace ricochet::parser {
- struct TextToken {
+struct TextToken {
std::string content{};
};
diff --git a/include/ricochet/parser/tree_builder.hh b/include/ricochet/parser/tree_builder.hh
new file mode 100644
index 0000000..4adbc4d
--- /dev/null
+++ b/include/ricochet/parser/tree_builder.hh
@@ -0,0 +1,44 @@
+/*
+ * Ricochet - A minimalist CLI web browser for fast, distraction-free navigation.
+ * Copyright (C) 2026 Richard Qin
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "ricochet/parser/dom_tree.hh"
+#include "ricochet/parser/html_lexer.hh"
+
+#include <vector>
+
+namespace ricochet::parser {
+
+class TreeBuilder {
+public:
+ TreeBuilder() = default;
+ ~TreeBuilder() = default;
+
+ TreeBuilder(const TreeBuilder&) = delete;
+ TreeBuilder& operator=(const TreeBuilder&) = delete;
+ TreeBuilder(TreeBuilder&&) = delete;
+ TreeBuilder& operator=(TreeBuilder&&) = delete;
+
+ /**
+ * @brief Assemble a one-dimensional token sequence into a multi-dimensional DOM tree
+ */
+ DomNode build(const std::vector<HtmlToken>& tokens) const;
+};
+
+} // namespace ricochet::parser \ No newline at end of file
diff --git a/src/core/browser.cc b/src/core/browser.cc
index 1e9d044..7acc915 100644
--- a/src/core/browser.cc
+++ b/src/core/browser.cc
@@ -19,20 +19,23 @@
#include "ricochet/core/browser.hh"
#include "ricochet/net/http_client.hh"
#include "ricochet/parser/html_lexer.hh"
+#include "ricochet/parser/tree_builder.hh"
#include <iostream>
namespace {
-struct TokenPrinter {
- void operator()(const ricochet::parser::TextToken& t) const {
- std::cout << "[TEXT] " << t.content << "\n";
- }
- void operator()(const ricochet::parser::TagOpenToken& t) const {
- std::cout << "[OPEN] <" << t.name << ">\n";
- }
- void operator()(const ricochet::parser::TagCloseToken& t) const {
- std::cout << "[CLOSE] </" << t.name << ">\n";
+void print_dom_tree(const ricochet::parser::DomNode& node, std::size_t depth = 0) // NOLINT(misc-no-recursion)
+{
+ const std::string indent(depth * 2, ' ');
+
+ if (node.tag_name.empty()) {
+ std::cout << indent << "\"" << node.text_content << "\"\n";
+ } else {
+ std::cout << indent << "<" << node.tag_name << ">\n";
+ for (const auto& child : node.children) {
+ print_dom_tree(child, depth + 1);
+ }
}
-};
+}
} // namespace
namespace ricochet::core {
@@ -49,15 +52,15 @@ int Browser::run( std::string_view initial_url )
}
const auto& response = response_result.value();
- std::cout << "[=== HTTP Status: " << response.status_code << " ===]\n";
- std::cout << "\n[=== Tokenizing HTML ===]\n";
const parser::HtmlLexer lexer;
- auto tokens = lexer.tokenize(response.body);
+ const auto tokens = lexer.tokenize(response.body);
- for (const auto& token : tokens) {
- std::visit(TokenPrinter{}, token);
- }
+ const parser::TreeBuilder builder;
+ const auto dom_root = builder.build(tokens);
+
+ std::cout << "\n[=== Abstract Syntax Tree (DOM) ===]\n";
+ print_dom_tree(dom_root);
return 0;
}
diff --git a/src/parser/tree_builder.cc b/src/parser/tree_builder.cc
new file mode 100644
index 0000000..51a8a51
--- /dev/null
+++ b/src/parser/tree_builder.cc
@@ -0,0 +1,90 @@
+/*
+ * Ricochet - A minimalist CLI web browser for fast, distraction-free navigation.
+ * Copyright (C) 2026 Richard Qin
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "ricochet/parser/tree_builder.hh"
+#include "ricochet/parser/dom_tree.hh"
+#include <utility>
+#include <vector>
+
+namespace ricochet::parser {
+
+namespace {
+struct TokenVisitor {
+ std::vector<DomNode>* stack;
+
+ void operator()(const TextToken& token) const {
+ stack->back().children.emplace_back(
+ DomNode{
+ .tag_name = "",
+ .text_content = token.content,
+ .children = {}
+ }
+ );
+ }
+
+ void operator()(const TagOpenToken& token) const {
+ DomNode new_node{
+ .tag_name = token.name,
+ .text_content = "",
+ .children = {}
+ };
+
+ if (token.name == "meta" || token.name == "link" || token.name == "img" ||
+ token.name == "br" || token.name == "hr" || token.name == "input" ||
+ token.name == "base" || token.name == "col") {
+
+ stack->back().children.push_back(std::move(new_node));
+ } else {
+ stack->push_back(std::move(new_node));
+ }
+ }
+
+ void operator()(const TagCloseToken& /*token*/) const {
+ if (stack->size() > 1) {
+ DomNode completed_node = std::move(stack->back());
+ stack->pop_back();
+ stack->back().children.push_back(std::move(completed_node));
+ }
+ }
+};
+} // namespace
+
+DomNode TreeBuilder::build(const std::vector<HtmlToken>& tokens) const // NOLINT(readability-convert-member-functions-to-static)
+{
+ std::vector<DomNode> stack;
+ stack.emplace_back(DomNode{
+ .tag_name = "Document",
+ .text_content = "",
+ .children = {}
+ });
+
+ TokenVisitor visitor{&stack};
+ for (const auto& token : tokens) {
+ std::visit(visitor, token);
+ }
+
+ while (stack.size() > 1) {
+ DomNode completed_node = std::move(stack.back());
+ stack.pop_back();
+ stack.back().children.push_back(std::move(completed_node));
+ }
+
+ return std::move(stack.front());
+}
+
+} // namespace ricochet::parser \ No newline at end of file