我正在使用Antlr4的C ++访客api来遍历一个解析树。但是,我正在努力让它正常运作。即,我不确定如何使用visitChildren(ParseTree *tree)
电话。
我已经为我定义的每个规则提供了上下文。我可以使用上下文遍历树:context->accept[RuleContext]([RuleContext]* rule)
然而,当我使用它时,我不断多次访问同一个节点。
例如:
program:
: nameRule
dateRule
( statements )*
EOF
;
nameRule
: NAME IDENTIFIER ;
dateRule
: DATE IDENTIFIER ;
statements:
: statementX
| statementY
| statementZ
;
statementX:
: // do something here
statementY:
: // do something here
statementZ:
: // do something here
IDENTIFIER
,DATE
和NAME
是终端。
我通过以下方式构建Antlr解析结构:
void Parser::parse() {
ifstream file(FLAGS_c, ifstream::binary);
// Convert the file into ANTLR's format.
ANTLRInputStream stream = ANTLRInputStream(file);
// Give the input to the lexer.
MyLexer lexer = new MyLexer(&stream);
// Generate the tokens.
CommonTokenStream tokens(lexer);
file.close();
tokens.fill();
// Create the translation that will parse the input.
MyParser parser = new MyParser(&tokens);
parser->setBuildParseTree(true);
MyParser::ProgramContext *tree = parser->program();
auto *visitor = new MyVisitor();
visitor->visitProgram(tree);
}
因此,当我尝试遍历它时,它看起来与此类似,类MyVisitor
扩展MyParserVisitor
。 MyVisitor
是我用来遍历生成的树的访问者类。
Any MyVisitor::visitProgram(ParserVisitor::ProgramContext *context) {
this->visitNameRule(context->nameRule());
this->visitDateRule(context->dateRule());
if (!this->statements.empty()) {
for (auto &it : this->statements) {
this->visitStatements(it);
}
}
return Any(context);
}
// Omitting name and date rules.
Any MyVisitor::visitStatements(ParserVisitor::StatementContext *context) {
this->visitStatementX(context->statementX());
this->visitStatementY(context->statementY());
this->visitStatementZ(context->statementZ());
return Any(context);
}
在这种情况下,每次访问语句时都会访问语句X
,Y
和Z
。即使它们不在输入程序中。
这是使用它的正确方法吗?如果不是,那么我假设visitChildren(ParseTree *tree)
是在每个访问者函数中使用的正确api。但我不明白如何从ParseTree
访问*Context
数据结构。
答案 0 :(得分:1)
这个问题与C ++访问者没有直接关系,而是ANTLR4中的一般访问者问题。你正在做的是以你不打算做的方式快捷访问游客。不要手动明确访问某些子树,而是调用超级实现让它为您做,并将结果收集到单独的visitStatementXXX
函数中。看看这个implementation of a (very simple) expression evaluator,用于单元测试(用C ++编写)。这是部分副本以证明原则:
class EvalParseVisitor : public MySQLParserBaseVisitor {
public:
std::vector<EvalValue> results; // One entry for each select item.
bool asBool(EvalValue in) {
if (!in.isNullType() && in.number != 0)
return true;
return false;
};
virtual Any visitSelectItem(MySQLParser::SelectItemContext *context) override {
Any result = visitChildren(context);
results.push_back(result.as<EvalValue>());
return result;
}
virtual Any visitExprNot(MySQLParser::ExprNotContext *context) override {
EvalValue value = visit(context->expr());
switch (value.type) {
case EvalValue::Null:
return EvalValue::fromNotNull();
case EvalValue::NotNull:
return EvalValue::fromNull();
default:
return EvalValue::fromBool(!asBool(value));
}
}
virtual Any visitExprAnd(MySQLParser::ExprAndContext *context) override {
EvalValue left = visit(context->expr(0));
EvalValue right = visit(context->expr(1));
if (left.isNullType() || right.isNullType())
return EvalValue::fromNull();
return EvalValue::fromBool(asBool(left) && asBool(right));
return visitChildren(context);
}
...
基本部分是对visit()
的调用,str.split
反过来迭代给定上下文树的子节点,并仅触发实际存在的元素的访问者函数。