我继承了一个antlr4解析器(在java中),它使用listener方法,但是有一些walker需要特殊的case。
背景资料:
当您在antlr(v4)中编写解析器语法时,它会为您生成一些java类,每个类对应一个解析器非终端。这些课程包括: PowerShellParser.ForEachObjectStatementContext
对于定义 ForEach Statement
. 一个合理大小的语法可以得到几十个(通常接近100个类)。它们是java类层次结构的一部分,如下所示:
ParseTree
|
+ - ErrorNode // used when there are errors in the program being compiled
|
+ - TerminalNode // tokens, e.g. identifiers, '+', '=', whitespace, constants
|
* - RuleNode // non-terminals, subclassed further
|
+ - ProgramContext // for the Program rule/non-terminal
|
+ - ForEachStatementContext // for the ForEach Statement rule
|
+ - IfThenElseStatementContent // if (expr) then-clause else-clause?
|
+ - BlockStatementContext // { statements* }
|
+ - AssignmentStatementContext // var = expression
|
+ - AddExpressionContext // expression + expression
|
and literally many, many more.
antlr还生成了一个“listener”模式,以便为这些不同的代码位定义语义。此模式由上面的类树和两个函数组成,其中的默认实现可以更改。这个 walker
功能和 listener
功能。普通walker函数执行以下操作,调用侦听器的“enter(t)”函数,递归地遍历树中的子对象,并调用侦听器的exit(t)函数。
原始的antlr代码https://github.com/antlr/antlr4/blob/master/runtime/java/src/org/antlr/v4/runtime/tree/parsetreewalker.java 大致如下:
public class Walker {
public void walk(ParseTree t) {
if (t instanceof ErrorNode) {
listener.visitErrorNode((ErrorNode)t);
} else if (t instanceof TerminalNode) {
listener.visitTerminalNode((TerminalNode)t);
} else if (t instanceof RuleNode) {
RuleNode r = (RuleNode)t;
ParserRuleContext ctx =
(ParserRuleContext)r.getRuleContext();
listener.enterEveryRule(ctx);
listener.enterRule(r); // Here we call the listener
int n = r.getChildCount();
for (Integer i = 0; i < n; ++i) {
ParseTree child = r.getChild(i);
walk(child); // Here we recursively call ourselves
}
listener.exitRule(r); // Here we call the listener again
}
}
现在,在侦听器中,您可以编写如下代码:
public class listener {
public void enterRule(PowerShellParser.ProgramContext ctx) {
// Here we are starting a program and write whatever semantics we need.
}
public void exitRule(PowerShellParser.ProgramContext ctx) {
// Here we are done with a program and write whatever semantics we need.
}
对于您关心的所有具有语义的类,依此类推。您可以省略其中一些函数,默认情况下不执行任何操作。
这对于“表达式”很好,因为如果您将代码放在exit函数中,您将得到一种从下到上计算表达式的好方法,基本上是免费的。
但是,这样的遍历对于if语句或循环等不起作用,因为您不想盲目地遍历每个节点。
因此,编写我继承的代码的人修改了walker代码,如下所示。它可以工作,但是它有一层if语句,当我添加更多类型的语句时,我必须不断扩展。我希望对walker的递归调用能够像侦听器代码一样工作,调用与树中非终端类的“type”匹配的版本,而不必在if语句中列出它们。在我的书中,检查物体的类型是一种“气味”。你只要用正确的签名写代码,它就会被调用。但这种情况在递归调用上并不像walker调用侦听器并传递对象那样发生。我想知道如何修复它。
所以。因此,我最初的问题是:
但是,当walker递归地调用子类时,它不会选择特殊的case版本,作者必须插入代码来检查类型并调用正确的类型。哪些地方做错了,需要使用此代码中的ifs:
package walker;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.tree.*;
public class Walker {
public void walk(ParseTree t) {
// why do I need this if statement, why isn't the right function called?
if (t instanceof ErrorNode) {
walk((ErrorNode)t);
} else if (t instanceof TerminalNode) {
walk((TerminalNode)t);
} else if (t instanceof RuleNode) {
walk((RuleNode)t);
} else {
notImplemented("walk ParseTree for " + t);
}
}
public void walk(ErrorNode t) {
listener.visitErrorNode(t);
}
public void walk(TerminalNode t) {
listener.visitTerminal(t);
}
public void walk(RuleNode r) {
// same issue, only worse
if (r instanceof PowerShellParser.ForEachObjectStatementContext) {
walk((PowerShellParser.ForEachObjectStatementContext)r);
return;
} else if (r instanceof PowerShellParser.ForEachStatementContext) {
walk((PowerShellParser.ForEachStatementContext)r);
return;
} else if (r instanceof PowerShellParser.IfStatementContext) {
walk((PowerShellParser.IfStatementContext)r);
return;
} else if (r instanceof PowerShellParser.DoWhileStatementContext) {
walk((PowerShellParser.DoWhileStatementContext)r);
return;
} else if (r instanceof PowerShellParser.HardCase1Context) {
walk((PowerShellParser.HardCase1Context)r);
return;
} else if (r instanceof PowerShellParser.HardCase2Context) {
walk((PowerShellParser.HardCase2Context)r);
return;
} else if (r instanceof PowerShellParser.HiddenStringMethodExpressionContext) {
walk((PowerShellParser.HiddenStringMethodExpressionContext)r);
return;
} else if (r instanceof PowerShellParser.InvokeCommandCommandContext) {
walk((PowerShellParser.InvokeCommandCommandContext)r);
return;
} else if (r instanceof PowerShellParser.ExpressionListContext) {
walk((PowerShellParser.ExpressionListContext)r);
return;
} else if (r instanceof PowerShellParser.JoinWithExpressionListContext) {
walk((PowerShellParser.JoinWithExpressionListContext)r);
return;
}
// this is all I want this function to do, provide a default traversal
// when I don't have a specialized version
ParserRuleContext ctx = (ParserRuleContext)r.getRuleContext();
listener.enterEveryRule(ctx);
enterRule(r);
int n = r.getChildCount();
for (Integer i = 0; i < n; ++i) {
ParseTree child = r.getChild(i); // I presume the issue is here
walk(child);
}
exitRule(r);
listener.exitEveryRule(ctx);
}
public void walk(PowerShellParser.ForEachObjectStatementContext r) {
// I want recursive calls of this object to call this directly
// and not go through the two ifs above.
// E.g. this version walks its children multiple times
// The if version only walks some of its children
// The hard cases actually do something like "eval"
}
public void walk(PowerShellParser.ForEachStatementContext r) {
}
public void walk(PowerShellParser.IfStatementContext r) {
}
public void walk(PowerShellParser.DoWhileStatementContext r) {
}
public void walk(PowerShellParser.JoinWithExpressionListContext r) {
}
public void walk(PowerShellParser.QuotedProgramContext r) {
}
public void walk(PowerShellParser.HardCase1Context r) {
}
public void walk(PowerShellParser.HardCase2Context r) {
}
}
1条答案
按热度按时间byqmnocz1#
在研究了
listener
更仔细地说,我发现即使它也不能完全满足我的要求。它为每个enterRule(X ctx)
成为enterX(X ctx)
x类中的代码将其重定向。因此,您不能轻松地定义一个函数,以java中我想要的方式进行分派,至少在您想要分派的类不能修改的情况下(在本例中,我不能修改)。