std::any 使用 antlr4 c++ 访问者构造 AST 的继承问题

问题描述 投票:0回答:1

我正在尝试使用生成的

BaseVisitor
类的继承实例从我用于简单编译器的语法解析树构造AST。

考虑我的语法的一个子集,其中

Stat
是一种轻量级语言的陈述:

...
program: stat;

stat: SkipStat                         # Skip
    | type Ident AssignEq assignRHS    # Declare
    | assignLHS AssignEq assignRHS     # Assign

...

我的理解(根据this post)是让访客打电话给

visit(ctx->stat())
,其中
ctx
的类型为
ProgramContext*
。派生的访问者然后正确地调用相应的重写
visitSkip(..)
visitDeclare(..)

我的 AST 有简单的节点类,子集如下所示:

struct BaseNode {};

struct Program : BaseNode {
    Program(std::shared_ptr<Stat> body) : body(std::move(body)) {}
    std::shared_ptr<Stat> body;
};

struct Stat : BaseNode {};

struct Assign : Stat {
    Assign(std::shared_ptr<AssignLHS> lhs, std::shared_ptr<AssignRHS> rhs) :
        lhs(std::move(lhs)),
        rhs(std::move(rhs)) {}

    std::shared_ptr<AssignLHS> lhs;
    std::shared_ptr<AssignRHS> rhs;
};

struct Declare : Stat {
    Declare(std::shared_ptr<Type> type, std::string name, std::shared_ptr<AssignRHS> rhs) :
        type(std::move(type)),
        name(std::move(name)),
        rhs(std::move(rhs)) {}

    std::shared_ptr<Type> type;
    std::string name;
    std::shared_ptr<AssignRHS> rhs;
};

struct Skip : Stat {};

将两点联系在一起,我试图让提到的

visitSkip(..)
visitDeclare(..)
等(都是
std::any
类型)返回
std::shared_ptr<Skip>
std::shared_ptr<Declare>
等,这样
visitProgram(..) 
可以通过调用
visit
的形式接收它们

std::shared_ptr<Stat> stat = std::any_cast<std::shared_ptr<Stat>>visit(ctx->stat());

但是(!),

std::any
只允许使用确切的已知类进行转换,而not 任何派生类,所以这种方法不起作用。相反,我已经开始创建我自己的访问者,完全独立于生成的访问者(即不是孩子)。

我假设使用我缺少的生成类有更好的解决方案。

找到了一个相似帖子的答案,是否值得构建一个 AST?如果我对如何使用 antlr4 的想法不准确,请告诉我并指出我可以从中开始的良好来源。谢谢。

编辑:根据权威的 antlr 4 参考文献的第 7 章,我相信我可以通过使用持有

BaseNode*
的堆栈并适当地转换弹出节点来实现我想要的。这似乎不是最佳解决方案。理想情况下,我想实现类似于 java 实现方法的东西,其中我们将预期的返回类型传递给访问者类。

Edit 2:我现在已经实现了这样的解决方案,下面是

exitAssign(..)
函数的例子:

void Listener::exitAssign(Parser::AssignContext* ctx) {
    const auto rhs = std::static_pointer_cast<AssignRHS>(m_stack.top());
    m_stack.pop();

    const auto lhs = std::static_pointer_cast<AssignLHS>(m_stack.top());
    m_stack.pop();

    m_stack.push(std::make_shared<Assign>(lhs, rhs));
}

我仍然觉得这个解决方案不是最好的 - 它感觉非常 hacky 因为参数的顺序必须反向弹出,而且很容易忘记在创建 AST 节点后压入堆栈。

我现在会使用这个实现,但是如果在 c++ 中使用 antlr 4 的人更喜欢更好的方法,请告诉我。

c++ compiler-construction antlr antlr4
1个回答
0
投票

要解析算术表达式,我更喜欢使用带有“退出”方法重载的侦听器:

class MyListener final : public FormulaBaseListener {
public:
   void exitUnaryOp(FormulaParser::UnaryOpContext *ctx) override;
   void exitLiteral(FormulaParser::LiteralContext *ctx) override;
   void exitCell(FormulaParser::CellContext *ctx) override;
   void exitBinaryOp(FormulaParser::BinaryOpContext *ctx) override;

   std::vector<astToken> getResult();
};

int main(){
     antlr4::ANTLRInputStream input(".........");
     std::unique_ptr<FormulaLexer> up_fl;
     up_fl = std::make_unique<FormulaLexer>(&input);
     FormulaLexer& fl = *up_fl;
     BailErrorListener error_listener; //custom : public antlr4::BaseErrorListener with syntaxError override
     fl.removeErrorListeners();
     fl.addErrorListener(&error_listener);
     antlr4::CommonTokenStream tokens(&fl);
     std::unique_ptr<FormulaParser> up_parser;
     up_parser = std::make_unique<FormulaParser>(&tokens);
     FormulaParser& parser = *up_parser;
     auto error_handler = std::make_shared<antlr4::BailErrorStrategy>();
     parser.setErrorHandler(error_handler);
     parser.removeErrorListeners();
     FormulaParser::MainContext* tree;
     tree = parser.main();
     MyListener listener; //custom final : public FormulaBaseListener with void exit_*_(FormulaParser::_*_Context *ctx) override;
     antlr4::tree::ParseTreeWalker::DEFAULT.walk(&listener, tree);
     asttree_ = listener.getResult(); //get what you want

}
© www.soinside.com 2019 - 2024. All rights reserved.