Files
spider-compiler/src/spider/compiler/text/ParseTree.hpp
T
2026-09-27 01:05:03 -06:00

296 lines
9.2 KiB
C++

#pragma once
#include <spider/compiler/common.hpp>
#include <spider/compiler/text/unicode.hpp>
#include <spider/compiler/text/Token.hpp>
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<isize>(-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<std::string_view> 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<std::string_view>& 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<ParseNode*> findAll(std::string_view name);
vector<const ParseNode*> 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<ParseNode> 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<isize>& 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<isize> 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<ParseNode*> findAll(std::string_view name);
vector<const ParseNode*> 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;
};
}