我正在使用ANTLR4为我的语法创建一个解析树,我想要做的是修改树中的某些节点。这将包括删除某些节点并插入新节点。这背后的目的是优化我正在编写的语言。我还没有找到解决这个问题的方法。什么是最好的方法呢?
答案 0 :(得分:4)
虽然目前没有真正的支持或树重写工具,但很有可能。它甚至没那么痛苦。
ParseTreeListener
或您的MyBaseListener
可以与ParseTreeWalker
一起使用来浏览您的解析树。
从此处,您可以删除ParserRuleContext.removeLastChild()
的节点,但是在执行此操作时,您必须注意ParseTreeWalker.walk
:
public void walk(ParseTreeListener listener, ParseTree t) {
if ( t instanceof ErrorNode) {
listener.visitErrorNode((ErrorNode)t);
return;
}
else if ( t instanceof TerminalNode) {
listener.visitTerminal((TerminalNode)t);
return;
}
RuleNode r = (RuleNode)t;
enterRule(listener, r);
int n = r.getChildCount();
for (int i = 0; i<n; i++) {
walk(listener, r.getChild(i));
}
exitRule(listener, r);
}
你 必须 如果walker访问过这些节点的父节点,则用某些内容替换已移除的节点,我通常选择空ParseRuleContext
个对象(这是因为上述方法中缓存的n
值。这可以防止ParseTreeWalker
抛出NPE。
添加节点时,请确保将ParseRuleContext
上的可变父级设置为新父级。此外,由于上述方法中的缓存n
,一个好的策略是检测更改需要 之前 ,如果您想要进行更改进入walk
,所以ParseTreeWalker
将在同一个传球中越过它们(否则你可能需要多次传球......)
您的伪代码应如下所示:
public void enterRewriteTarget(@NotNull MyParser.RewriteTargetContext ctx){
if(shouldRewrite(ctx)){
ArrayList<ParseTree> nodesReplaced = replaceNodes(ctx);
addChildTo(ctx, createNewParentFor(nodesReplaced));
}
}
我已经使用这种方法编写了一个将同步内部语言编译成异步javascript的转换器。这真是太痛苦了。
答案 1 :(得分:4)
另一种方法是编写一个ParseTreeVisitor
,将树转换回字符串。 (在某些情况下,这可能是微不足道的,因为您只调用TerminalNode.getText()
并在aggregateResult(..)
中连接。)
然后,您将修改添加到此访问者,以便生成的字符串表示包含您尝试实现的修改。
然后解析字符串,得到一个包含所需修改的解析树。
这在某些方面肯定是hackish,因为你解析了两次字符串。另一方面,该解决方案不依赖于antlr实现细节。
答案 2 :(得分:0)
我需要类似的东西来进行简单的转换。我最终使用了一个 ParseTreeWalker
和一个自定义的 ...BaseListener
,我在其中覆盖了 enter...
方法。在此方法中,ParserRuleContext.children
可用且可以操作。
class MyListener extends ...BaseListener {
@Override
public void enter...(...Context ctx) {
super.enter...(ctx);
ctx.children.add(...);
}
}
new ParseTreeWalker().walk(new MyListener(), parseTree);