尽管目前尚无真正的支持或工具来重写树,但仍有可能这样做。甚至没有那么痛苦。
该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);
}
Run Code Online (Sandbox Code Playgroud)
你必须如果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));
}
}
Run Code Online (Sandbox Code Playgroud)
我已经使用这种方法编写了一个编译器,该编译器将同步内部语言编译为异步javascript。真是痛苦。
另一种方法是编写一个ParseTreeVisitor
将树转换回字符串的方法。(在某些情况下这可能是微不足道的,因为您只是TerminalNode.getText()
在aggregateResult(..)
. 中调用和连接。)
然后将修改添加到此访问者,以便生成的字符串表示包含您尝试实现的修改。
然后解析字符串,你会得到一个带有所需修改的解析树。
这在某些方面肯定是骇人听闻的,因为您对字符串进行了两次解析。另一方面,该解决方案不依赖于 antlr 实现细节。