我有一个antlr语法,其中嵌入式操作用于自下而上收集数据并构建聚合数据结构。下面给出了一个简短的版本,其中只打印聚合数据结构(即在这个简短的示例代码中没有为它们创建类)。
grammar Sample;
top returns [ArrayList l]
@init { $l = new ArrayList<String>(); }
: (mid { $l.add($mid.s); } )* ;
mid returns [String s]
: i1=identifier 'hello' i2=identifier
{ $s = $i1.s + " bye " + $i2.s; }
;
identifier returns [String s]
: ID { $s = $ID.getText(); } ;
ID : [a-z]+ ;
WS : [ \t\r\n]+ -> skip ;
其对应的主程序是:
public class Main {
public static void main( String[] args) throws Exception
{
SampleLexer lexer = new SampleLexer( new ANTLRFileStream(args[0]));
CommonTokenStream tokens = new CommonTokenStream( lexer );
SampleParser parser = new SampleParser( tokens );
ArrayList<String> top = parser.top().l;
System.out.println(top);
}
}
样本测试是:
aaa hello bbb
xyz hello pqr
由于antlr的目标之一是保持语法文件可重用且与操作无关,我试图从该文件中删除操作并将其移动到树步行器。我用以下代码对它进行了第一次尝试:
public class Main {
public static void main( String[] args) throws Exception
{
SampleLexer lexer = new SampleLexer( new ANTLRFileStream(args[0]));
CommonTokenStream tokens = new CommonTokenStream( lexer );
SampleParser parser = new SampleParser( tokens );
ParseTree tree = parser.top();
ParseTreeWalker walker = new ParseTreeWalker();
walker.walk( new Walker(), tree );
}
}
public class Walker extends SampleBaseListener {
public void exitTop(SampleParser.TopContext ctx ) {
System.out.println( "Exit Top : " + ctx.mid() );
}
public String exitMid(SampleParser.MidContext ctx ) {
return ctx.identifier() + " bye "; // ignoring the 2nd instance here
}
public String exitIdentifier(SampleParser.IdentifierContext ctx ) {
return ctx.ID().getText() ;
}
}
但显然这是错误的,因为至少,Walker方法的返回类型应该是无效的,因此它们没有办法在上游返回聚合值。其次,我没有看到如何访问&#34; i1&#34;和&#34; i2&#34;从沃克代码,所以我无法区分&#34;标识符&#34;的两个实例。在那条规则中。
有关如何为此目的将操作与语法分开的任何建议吗?
我应该在这里使用访问者而不是听众,因为访问者有能力返回值吗?如果我使用访问者,我该如何解决区分&#34; i1&#34;和&#34; i2&#34; (如上所述)?
访问者是否仅在规则的退出处执行其操作(与侦听器不同,对于入口和出口都存在)?例如,如果我必须在规则&#34; top&#34;的条目下初始化列表,我该如何使用仅在规则结束时执行的访问者?我是否需要为此目的使用enterTop监听器?
编辑:在初始发布后,我修改了规则&#34; top&#34;创建并返回一个列表,并将该列表传递回主程序进行打印。这是为了说明为什么我需要一个代码的初始化机制。
答案 0 :(得分:1)
根据您的尝试,我认为您可以从使用ANTLR的BaseVisitor类而不是BaseListener类中受益。
假设你的语法是这样的(我将其概括,我将解释下面的变化):
grammar Sample;
top : mid* ;
mid : i1=identifier 'hello' i2=identifier ;
identifier : ID ;
ID : [a-z]+ ;
WS : [ \t\r\n]+ -> skip ;
然后你的沃克看起来像这样:
public class Walker extends SampleBaseVisitor<Object> {
public ArrayList<String> visitTop(SampleParser.TopContext ctx) {
ArrayList<String> arrayList = new ArrayList<>();
for (SampleParser.MidContext midCtx : ctx.mid()) {
arrayList.add(visitMid(midCtx));
}
return arrayList;
}
public String visitMid(SampleParser.MidContext ctx) {
return visitIdentifier(ctx.i1) + " bye " + visitIdentifier(ctx.i2);
}
public String visitIdentifier(SampleParser.IdentifierContext ctx) {
return ctx.getText();
}
}
这使您可以访问并获得所需规则的结果。
您可以通过访问者方法标记i1
和i2
。请注意,您并不真正需要identifier
规则,因为它只包含一个令牌,您可以直接在visitMid
中访问令牌的文本,但实际上这是个人偏好。
您还应注意SampleBaseVisitor
是一个泛型类,其中generic参数确定访问方法的返回类型。对于您的示例,我设置了通用参数Object
,但您甚至可以创建自己的类,其中包含您要保留的信息并将其用于通用参数。
以下是一些更有用的methods BaseVisitor
继承,可能会帮助您解决问题。
最后,您的主要方法最终会看起来像这样:
public static void main( String[] args) throws IOException {
FileInputStream fileInputStream = new FileInputStream(args[0]);
SampleLexer lexer = new SampleLexer(CharStreams.fromStream(fileInputStream));
CommonTokenStream tokens = new CommonTokenStream(lexer);
SampleParser parser = new SampleParser(tokens);
for (String string : new Walker().visitTop(parser.top())) {
System.out.println(string);
}
}
作为旁注,ANTLR4中的ANTLRFileStream
类为deprecated。
建议改为使用CharStreams
。
答案 1 :(得分:0)
正如Terence Parr在“权威参考”中所指出的,访问者和监听者之间的一个主要区别是访问者可以返回值。这很方便。但是Listener也有一席之地! What I do for listener is exemplified in this answer。当然,有一些更简单的方法可以解析数字列表,但我做了一个答案,展示了一个完整的工作示例,说明如何将监听器的返回值聚合到以后可以使用的公共数据结构中
public class ValuesListener : ValuesBaseListener
{
public List<double> doubles = new List<double>(); // <<=== SEE HERE
public override void ExitNumber(ValuesParser.NumberContext context)
{
doubles.Add(Convert.ToDouble(context.GetChild(0).GetText()));
}
}
仔细观察Listener类,我包含一个公共数据集合 - 在这种情况下为List<double>
- 用于收集在侦听器事件中解析或计算的值。您可以使用您喜欢的任何数据结构:另一个自定义类,一个列表,一个队列,一个堆栈(伟大的用于计算和表达式评估),无论您喜欢什么。
因此,虽然访问者可以说更灵活,但是监听器也是一个强有力的竞争者,具体取决于您希望如何汇总结果。