#pragma once #include #include #include namespace spider { class ParseTree; /** * @brief DOM style handle over a single node of a parsed token tree. * @details Every node knows its parent, its children and its siblings, so a * parsed program can be walked and inspected much like a document. * Nodes are owned by the ParseTree that produced them and stay * valid while that tree is alive. The tree itself is built by * ParseTree, never by hand. */ class ParseNode { friend class ParseTree; public: /** @brief Index value used for "no node" links, since isize is unsigned. */ static constexpr isize npos = static_cast(-1); private: ParseTree* owner = nullptr; isize parent = npos; isize first_child = npos; isize last_child = npos; isize next_sibling = npos; isize prev_sibling = npos; isize children_count = 0; optional tag = {}; std::u32string own = U""; std::u32string text = U""; public: ParseNode() = default; public: /** * @brief The grammar tag of this node, or nothing if untagged. */ const optional& tagName() const { return tag; } /** * @brief Checks if this node carries the given tag. */ bool hasTag(std::string_view name) const { return tag.has_value() && *tag == name; } /** * @brief The full source text covered by this node and all of its children. */ const std::u32string& fullText() const { return text; } /** * @brief The source text held by this node alone, without its children. * @note For tagged nodes that fold their children, this is the full text. */ const std::u32string& ownText() const { return own; } /** * @brief UTF-8 rendering of text(). */ std::string textUtf8() const { return unicode::toUTF8(text); } /** * @brief UTF-8 rendering of ownText(). */ std::string ownTextUtf8() const { return unicode::toUTF8(own); } /** * @brief Amount of direct children of this node. */ isize childCount() const { return children_count; } /** * @brief True when this node holds no text and no children. */ bool isLeaf() const { return children_count == 0; } /** * @brief Distance from the root of the tree, zero for the root itself. */ isize depth() const; /** * @brief Storage index of this node inside its parent, npos for the root. */ isize indexInParent() const { return parent; } public: // ---------------------------------------------------------------- // // Navigation // // ---------------------------------------------------------------- // ParseNode* parentNode(); const ParseNode* parentNode() const; ParseNode* firstChild(); const ParseNode* firstChild() const; ParseNode* lastChild(); const ParseNode* lastChild() const; ParseNode* nextSibling(); const ParseNode* nextSibling() const; ParseNode* previousSibling(); const ParseNode* previousSibling() const; /** * @brief The index-th direct child of this node, nullptr when out of range. */ ParseNode* childAt(isize index); const ParseNode* childAt(isize index) const; public: // ---------------------------------------------------------------- // // Navigation filtered by tag // // ---------------------------------------------------------------- // /** * @brief First direct child carrying the given tag. */ ParseNode* firstChild(std::string_view name); const ParseNode* firstChild(std::string_view name) const; /** * @brief Last direct child carrying the given tag. */ ParseNode* lastChild(std::string_view name); const ParseNode* lastChild(std::string_view name) const; /** * @brief Next sibling of this node carrying the given tag. */ ParseNode* nextSibling(std::string_view name); const ParseNode* nextSibling(std::string_view name) const; /** * @brief Nearest ancestor carrying the given tag, nullptr when there is none. */ ParseNode* ancestor(std::string_view name); const ParseNode* ancestor(std::string_view name) const; public: // ---------------------------------------------------------------- // // Queries // // ---------------------------------------------------------------- // /** * @brief First node in document order carrying the given tag, this node included. */ ParseNode* find(std::string_view name); const ParseNode* find(std::string_view name) const; /** * @brief Every node in document order carrying the given tag, this node included. */ vector findAll(std::string_view name); vector findAll(std::string_view name) const; /** * @brief Amount of nodes in document order carrying the given tag. */ isize count(std::string_view name) const; /** * @brief True when at least one node carries the given tag. */ bool contains(std::string_view name) const { return find(name) != nullptr; } public: /** * @brief Human readable XML-like rendering of this node and its children. */ std::string toString() const; /** * @brief XML-like rendering of this node alone, indented by the given depth. */ std::string toString(isize depth) const; private: std::string describe(isize depth) const; }; /** * @brief Owner of a parsed token tree, exposing a document like interface. * @details Use parse() to turn source text into an inspectable tree. The tree * is materialized once and then only read, so any number of consumers * can walk the same nodes safely. */ class ParseTree { friend class ParseNode; private: deque nodes; isize root_index = ParseNode::npos; private: ParseNode* resolve(isize index); const ParseNode* resolve(isize index) const; isize addNode(const TokenResult& result); void linkChildren(isize parent, const vector& children); /** * @brief Materializes a parse result, dropping the grammar scaffolding. * @details Only tagged rules become nodes, so a consumer walks syntax and not * combinators: untagged rules are pure plumbing and simply hoist their * children upwards, and empty shells are discarded. Returns the top * level nodes produced by this subtree. */ vector collapse(const TokenResult& result); public: ParseTree() = default; ~ParseTree() = default; // Nodes point back at their owning tree, so trees are never copied or moved. ParseTree(const ParseTree&) = delete; ParseTree& operator=(const ParseTree&) = delete; ParseTree(ParseTree&&) = delete; ParseTree& operator=(ParseTree&&) = delete; public: /** * @brief Parses source text, requiring the grammar to consume all of it. * @returns True on success, in which case the tree holds the parsed nodes. */ bool parse(const Token* token, const std::string& source); /** * @brief Parses from a reader, requiring the grammar to consume all of it. * @returns True on success, in which case the tree holds the parsed nodes. */ bool parse(const Token* token, TextReader& reader); /** * @brief Parses the longest matching prefix of a reader. * @returns True when at least something was matched. */ bool parsePrefix(const Token* token, TextReader& reader); /** * @brief Adopts an already produced parse result. */ void build(const TokenResult& result); public: ParseNode* root() { return resolve(root_index); } const ParseNode* root() const { return resolve(root_index); } bool empty() const { return root_index == ParseNode::npos; } public: ParseNode* find(std::string_view name); const ParseNode* find(std::string_view name) const; vector findAll(std::string_view name); vector findAll(std::string_view name) const; isize count(std::string_view name) const; /** * @brief Human readable XML-like rendering of the whole tree. */ std::string toString() const; }; }