I am writing a parser. To build up the tree, I need to find the correct type for my node based on text from my language input file. Right now, I have a map from a string (the node name) to a factory function which returns the correct type. Is there any way I can make this more simple/idiomatic when calling make shared?
Anything else I can do more idiomatically or with a better architecture is appreciated too.
abstract_node.hpp
#pragma once
#include <memory>
#include <vector>
#include <utility>
namespace ql::parser {
class AbstractNode : protected std::enable_shared_from_this<AbstractNode> {
public:
typedef std::vector<std::shared_ptr<AbstractNode>> ChildrenRef;
typedef std::weak_ptr<AbstractNode> ParentRef;
protected:
ChildrenRef m_Children;
ParentRef m_Parent;
public:
explicit AbstractNode(ParentRef parent) : m_Parent(std::move(parent)) {}
void addChild(std::shared_ptr<AbstractNode> const& node);
};
}
abstract_node_with_descriptor.hpp
#pragma once
#include <parser/node/parse_node.hpp>
namespace ql::parser {
class ParseWithDescriptorNode : public ParseNode {
protected:
std::string_view m_InnerBody;
public:
ParseWithDescriptorNode(std::string&& body, std::string_view const& innerBody, std::vector<std::string>&& tokens, ParentRef const& parent)
: ParseNode(std::move(body), std::move(tokens), parent), m_InnerBody(innerBody) {
}
};
}
parser.hpp
#pragma once
#include <boost/program_options/variables_map.hpp>
#include <parser/node/master_node.hpp>
#include <parser/node/structure/parse_with_descriptor_node.hpp>
namespace po = boost::program_options;
namespace ql::parser {
class Parser {
private:
using NodeFactory = std::function<std::shared_ptr<ParseWithDescriptorNode>(std::string&&, std::string_view const&, std::vector<std::string>&&,
AbstractNode::ParentRef)>;
std::map<std::string, NodeFactory> m_NamesToNodes;
template<typename TNode>
void registerNode(std::string_view nodeName) {
// TODO use forwarding?
m_NamesToNodes.emplace(nodeName, [](auto&& block, auto const& body, auto&& tokens, auto parent) {
auto node = std::make_shared<TNode>(std::forward<decltype(block)>(block), body, std::forward<decltype(tokens)>(tokens), parent);
node->parse();
return node;
});
}
std::shared_ptr<AbstractNode> getNode(std::string const& nodeName,
std::string&& blockWithInfo, std::string_view const& innerBlock, std::vector<std::string>&& tokens,
AbstractNode::ParentRef parent);
void recurseNodes(std::string_view code, std::weak_ptr<AbstractNode> const& parent, int depth = 0);
public:
Parser();
std::shared_ptr<MasterNode> parse(po::variables_map& options);
std::shared_ptr<MasterNode> getNodes(std::string code);
};
}
parser.cpp
#include "parser.hpp"
#include <utility>
#include <iomanip>
#include <iostream>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/trim_all.hpp>
#include <boost/range/algorithm_ext/erase.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/program_options/variables_map.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <util/read.hpp>
#include <util/terminal_color.hpp>
#include <parser/node/structure/package_node.hpp>
#include <parser/node/structure/def_func_node.hpp>
#include <parser/node/structure/impl_func_node.hpp>
namespace ql::parser {
Parser::Parser() {
registerNode<PackageNode>("pckg");
registerNode<DefineFunctionNode>("def");
registerNode<ImplementFunctionNode>("impl");
registerNode<ParseWithDescriptorNode>("default");
}
std::shared_ptr<MasterNode> Parser::parse(po::variables_map& options) {
auto sources = options["input"].as<std::vector<std::string>>();
std::string sourceFileName = sources[0];
std::cout << sourceFileName << std::endl;
auto src = util::readAllText(sourceFileName);
auto node = getNodes(src.value());
return node;
}
std::shared_ptr<AbstractNode> Parser::getNode(std::string const& nodeName,
std::string&& blockWithInfo, std::string_view const& innerBlock, std::vector<std::string>&& tokens,
AbstractNode::ParentRef parent) {
// Check if we have a generator function that can make this requested node, or else use default
auto it = m_NamesToNodes.find(nodeName);
NodeFactory& nodeFactoryFunc = it == m_NamesToNodes.end() ? m_NamesToNodes["default"] : it->second;
// Give ownership of copied code slice to this node. View of inner block still references original memory since we move it instead of copying
auto node = nodeFactoryFunc(std::move(blockWithInfo), innerBlock, std::move(tokens), std::move(parent));
return node;
}
std::shared_ptr<MasterNode> Parser::getNodes(std::string code) {
auto parent = std::make_shared<MasterNode>();
boost::remove_erase_if(code, boost::is_any_of("\n\r"));
recurseNodes(code, parent);
return parent;
}
void Parser::recurseNodes(std::string_view code, std::weak_ptr<AbstractNode> const& parent, int depth) {
auto level = 0;
auto blockInfoStart = 0ul, blockStart = 0ul;
for (auto i = 0ul; i < code.size(); i++) {
char c = code[i];
if (c == '{') {
if (level++ == 0) {
blockStart = i + 1ul;
}
} else if (c == '}') {
if (--level == 0) {
auto blockInfoSize = i - blockInfoStart + 1ul;
std::string blockWithInfo(code.substr(blockInfoStart, blockInfoSize));
// Split by tabs and spaces into tokens, which we use to find what type of node to create
std::vector<std::string> tokens;
auto deliminator = boost::is_any_of("\t ");
boost::split(tokens, blockWithInfo, deliminator, boost::token_compress_on);
// Remove first and last blank tokens if they exist
boost::trim_all_if(tokens, [](auto const& token) { return token.empty(); });
std::string const& nodeName = tokens[0ul]; // TODO do more checks as opposed to just taking first
std::cout << KGRN << std::setw(7) << nodeName << RST << " → " << blockWithInfo << FBLU("#") << std::endl;
// Find inner block
auto blockContentStart = blockStart, blockContentSize = i - blockContentStart;
std::string_view blockContents = std::string_view(blockWithInfo).substr(blockContentStart - blockInfoStart, blockContentSize);
std::cout << blockContents << FRED("$") << std::endl;
auto child = getNode(nodeName, std::move(blockWithInfo), blockContents, std::move(tokens), parent);
// Add children to parent node, parent node is owning via a shared pointer
parent.lock()->addChild(child);
// Recurse on the inner contents of the block so that each node added is for one block only
recurseNodes(code.substr(blockContentStart, blockContentSize), child, depth + 1);
blockInfoStart = i + 1;
}
}
}
}
}