Changes
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 05f3fe2..60f1395 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -47,7 +47,7 @@ target_link_libraries(macrodown PRIVATE macrodown_lib)
# Testing
enable_testing()
-add_executable(macrodown_test tests/test_main.cpp tests/test_macro_engine.cpp tests/test_block_parser.cpp tests/test_integration.cpp)
+add_executable(macrodown_test tests/test_main.cpp tests/test_macro_engine.cpp tests/test_block_parser.cpp tests/test_integration.cpp tests/test_nodes.cpp)
target_link_libraries(macrodown_test PRIVATE macrodown_lib GTest::gtest_main)
target_include_directories(macrodown_test PRIVATE include)
diff --git a/design.md b/design.md
index aa7d541..5a7f723 100644
--- a/design.md
+++ b/design.md
@@ -36,29 +36,37 @@ We strictly follow the CommonMark "Appendix A" strategy:
## 3. Data Structures
### 3.1 AST Nodes
-The final tree consists of these node types:
+The final tree consists of a unified `Node` type using `std::variant` to hold different data types:
```cpp
-enum class NodeType { Text, Macro };
+struct Text {
+ std::string content;
+};
-struct Node {
- virtual ~Node() = default;
- virtual NodeType type() const = 0;
+struct Macro {
+ std::string name;
+ std::vector<std::unique_ptr<Node>> arguments;
+ bool is_special = false;
};
-struct TextNode : public Node {
- std::string content;
- NodeType type() const override { return NodeType::Text; }
+struct Group {
+ std::vector<std::unique_ptr<Node>> children;
};
-struct MacroNode : public Node {
- std::string name;
- std::vector<std::unique_ptr<Node>> arguments; // Arguments are themselves ASTs
- bool is_special = false; // For intrinsics like %def
- NodeType type() const override { return NodeType::Macro; }
+struct Node {
+ using Data = std::variant<Text, Macro, Group>;
+ Data data;
+
+ // Call function on each node in the tree (pre-order traversal).
+ // The callback function takes const Node& as an argument.
+ template<typename Callback>
+ void forEach(Callback f) const;
};
```
+The `forEach` method provides a way to iterate over all nodes in the tree, including children of `Group` nodes and arguments of `Macro` nodes.
+
+
### 3.2 Block Structure (Intermediate)
During Phase 1, we use a structure mirroring CommonMark blocks to maintain state (open/closed blocks, list types).
@@ -116,13 +124,14 @@ struct Block {
### 4.3 Evaluator (`Evaluator`)
* **Environment**: A map `std::map<std::string, MacroDefinition>`.
* **Mechanism**:
- * Traverses the `MacroNode` tree.
- * If it's a `TextNode`, append to output.
- * If it's a `MacroNode`:
+ * Traverses the `Node` tree.
+ * If it's a `Text` node, append to output.
+ * If it's a `Macro` node:
* Look up definition.
* Bind arguments.
* Parse the definition body (if it's a user macro) or execute C++ callback (if intrinsic).
* Recursively evaluate the result.
+ * If it's a `Group` node, recursively evaluate all children.
## 5. The Standard Library
The system will boot with a "Prelude" of defined macros to support Markdown features.
diff --git a/include/nodes.h b/include/nodes.h
index f15444b..c3dbede 100644
--- a/include/nodes.h
+++ b/include/nodes.h
@@ -38,6 +38,30 @@ struct Node
Node(Text t) : data(std::move(t)) {}
Node(Macro m) : data(std::move(m)) {}
Node(Group g) : data(std::move(g)) {}
+
+ // Call function on each node in the tree. Callback function takes
+ // const Node& as argument.
+ template<typename Callback>
+ void forEach(Callback f) const
+ {
+ f(*this);
+ if(std::holds_alternative<Group>(data))
+ {
+ for(const std::unique_ptr<Node>& child:
+ std::get<Group>(data).children)
+ {
+ child->forEach(f);
+ }
+ }
+ else if(std::holds_alternative<Macro>(data))
+ {
+ for(const std::unique_ptr<Node>& arg:
+ std::get<Macro>(data).arguments)
+ {
+ arg->forEach(f);
+ }
+ }
+ }
};
// Inline implementation to avoid circular dependency issues in headers
@@ -48,4 +72,4 @@ inline void Group::addChild(std::unique_ptr<Node> node)
} // namespace macrodown
-#endif // MACRODOWN_NODES_H
\ No newline at end of file
+#endif // MACRODOWN_NODES_H
diff --git a/prompts.md b/prd.md
similarity index 67%
rename from prompts.md
rename to prd.md
index a3a2583..977d71a 100644
--- a/prompts.md
+++ b/prd.md
@@ -50,24 +50,12 @@ Naming style:
Unicode should be handled properly.
-Use Cmake as the build system. You can see an exmaple cmake file at
+Use Cmake as the build system. Exmaple cmake file:
https://github.com/MetroWind/planck-blog/blob/master/CMakeLists.txt.
-----
+The syntax tree should have a function to iterate over all nodes.
-Success Rate: 96.7%
-User Agreement: 100.0% (60 reviewed)
-Code Changes: +1244 -136
-
-Performance
-Wall Time: 38m 35s
-Agent Active: 32m 50s
- » API Time: 10m 31s (32.1%)
- » Tool Time: 22m 18s (67.9%)
-
-Model Usage Reqs Input Tokens Cache Reads Output Tokens
-────────────────────────────────────────────────────────────────────────────
-gemini-2.5-flash-lite 17 28,986 0 9,364
-gemini-3-flash-preview 14 175,114 348,017 1,187
-gemini-3-pro-preview 55 268,873 3,224,960 37,756
-gemini-2.5-flash 4 17,697 0 2,957
+The library should expose an interface that allows the user to render
+a document with 2 steps: The first step will expose the syntax tree
+(the root node), and the second step will render the syntax tree into
+HTML.
diff --git a/tests/test_nodes.cpp b/tests/test_nodes.cpp
new file mode 100644
index 0000000..7745f9f
--- /dev/null
+++ b/tests/test_nodes.cpp
@@ -0,0 +1,39 @@
+#include <gtest/gtest.h>
+#include "nodes.h"
+#include <vector>
+#include <string>
+
+using namespace macrodown;
+
+TEST(NodeTest, ForEachIteration)
+{
+ // Create a tree:
+ // Group
+ // Text("a")
+ // Macro("m")
+ // Group
+ // Text("b")
+
+ Group root;
+ root.addChild(std::make_unique<Node>(Text{"a"}));
+
+ Macro m;
+ m.name = "m";
+ Group arg_group;
+ arg_group.addChild(std::make_unique<Node>(Text{"b"}));
+ m.arguments.push_back(std::make_unique<Node>(std::move(arg_group)));
+
+ root.addChild(std::make_unique<Node>(std::move(m)));
+
+ Node root_node(std::move(root));
+
+ std::vector<std::string> visited_types;
+ root_node.forEach([&](const Node& n) {
+ if (std::holds_alternative<Text>(n.data)) visited_types.push_back("Text");
+ else if (std::holds_alternative<Macro>(n.data)) visited_types.push_back("Macro");
+ else if (std::holds_alternative<Group>(n.data)) visited_types.push_back("Group");
+ });
+
+ std::vector<std::string> expected = {"Group", "Text", "Macro", "Group", "Text"};
+ EXPECT_EQ(visited_types, expected);
+}