我试图使用生成的BaseVisitor
类的继承实例从我用于简单编译器的语法的解析树构建AST。
考虑我的语法的一个子集,其中Stat
是一个轻量级语言的语句:
...
program: stat;
stat: SkipStat # Skip
| type Ident AssignEq assignRHS # Declare
| assignLHS AssignEq assignRHS # Assign
...
我的理解(根据这篇文章)是让访问者呼叫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
只允许使用确切已知的类进行强制转换,并且不允许任何派生类,所以这种方法不起作用。相反,我已经开始创建自己的访问者,与生成的访问者完全分离(即不是子访问者)。
我认为有一个更好的解决方案,使用生成的类,我错过了。
已经找到了一个不一样的帖子的答案,它甚至值得构建一个AST吗?如果我对如何使用antlr4的想法是不准确的,请让我知道并指出我可以从一个好的来源开始。谢谢。
编辑:根据最终antlr 4参考的第7章,我相信我可以通过使用持有BaseNode*
的堆栈并适当地铸造弹出节点来实现我想要的。这似乎不是最好的解决方案。理想情况下,我希望实现类似于java实现方法的东西,其中我们将预期的返回类型传递给访问者类。
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));
}
我仍然觉得这个解决方案不是最好的——它感觉非常粗糙,因为参数的顺序必须反向弹出,而且很容易在创建AST节点后忘记将其推入堆栈。
我现在将使用这个实现,但是,如果在c++中使用antlr 4的人更喜欢一个更好的方法,请务必告诉我。
要解析算术表达式,我更喜欢使用带有"exit"重载的侦听器。方法:
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
}