如何在ANTLR3树解析器@init动作中获取行号

时间:2011-12-01 15:57:01

标签: java antlr antlr3

在ANTLR版本3中,如何在高级树解析器规则的@init操作中获取行号?

例如,在下面的@init操作中,我想将行号和句子文本一起推送。

sentence
    @init { myNodeVisitor.pushScriptContext( new MyScriptContext( $sentence.text )); }
    : assignCommand 
    | actionCommand;
    finally {
        m_nodeVisitor.popScriptContext();
    }

我需要在执行与规则中的符号相关联的操作之前推送上下文

的一些事情:

  • 使用$sentence.line - 未定义,即使$sentence.text是。
  • 将释义推送到规则操作中。放置在规则之前,规则中没有令牌可用。放置在规则之后,操作发生在与规则符号关联的操作之后。
  • 在@init操作中使用此表达式,该操作编译但返回值0:getTreeNodeStream().getTreeAdaptor().getToken( $sentence.start ).getLine()编辑: 实际上,如果$ sentence.start是真正的令牌或带引用的虚构,这确实有效 - 请参阅下面的Bart Kiers答案。

似乎我可以很容易地在@init规则中获得匹配的文本和第一个匹配的标记,也应该有一种简单的方法来获取行号。

1 个答案:

答案 0 :(得分:7)

您可以使用以下内容查看树语法的令牌/树流中的第1步:CommonTree ahead = (CommonTree)input.LT(1),您可以将其放在@init部分中。

每个CommonTree(ANTLR中的默认Tree实现)都有getToken()方法,该方法返回与此树关联的Token。并且每个Token都有一个getLine()方法,毫不奇怪,它会返回此标记的行号。

因此,如果您执行以下操作:

sentence
@init {
  CommonTree ahead = (CommonTree)input.LT(1);
  int line = ahead.getToken().getLine();
  System.out.println("line=" + line);
}
  :  assignCommand 
  |  actionCommand
  ;

您应该能够看到正在打印的部分正确的行号。我说一些,因为这不会在所有案例中按计划进行。让我演示一个简单的示例语法:

grammar ASTDemo;

options { 
  output=AST;
}

tokens {
  ROOT;
  ACTION;
}

parse
  :  sentence+ EOF -> ^(ROOT sentence+)
  ;

sentence
  :  assignCommand 
  |  actionCommand
  ;

assignCommand
  :  ID ASSIGN NUMBER -> ^(ASSIGN ID NUMBER)
  ;

actionCommand
  :  action ID -> ^(ACTION action ID)
  ;

action
  :  START
  |  STOP
  ;

ASSIGN : '=';
START  : 'start';
STOP   : 'stop';
ID     : ('a'..'z' | 'A'..'Z')+;
NUMBER : '0'..'9'+;
SPACE  : (' ' | '\t' | '\r' | '\n')+ {skip();};

其树语法如下:

tree grammar ASTDemoWalker;

options {
  output=AST;
  tokenVocab=ASTDemo;
  ASTLabelType=CommonTree;
}


walk
  :  ^(ROOT sentence+)
  ;

sentence
@init {
  CommonTree ahead = (CommonTree)input.LT(1);
  int line = ahead.getToken().getLine();
  System.out.println("line=" + line);
}
  :  assignCommand 
  |  actionCommand
  ;

assignCommand
  :  ^(ASSIGN ID NUMBER)
  ;

actionCommand
  :  ^(ACTION action ID)
  ;

action
  :  START
  |  STOP
  ;

如果您运行以下测试类:

import org.antlr.runtime.*;
import org.antlr.runtime.tree.*;

public class Main {
  public static void main(String[] args) throws Exception {
    String src = "\n\n\nABC = 123\n\nstart ABC";
    ASTDemoLexer lexer = new ASTDemoLexer(new ANTLRStringStream(src));
    ASTDemoParser parser = new ASTDemoParser(new CommonTokenStream(lexer));
    CommonTree root = (CommonTree)parser.parse().getTree();
    ASTDemoWalker walker = new ASTDemoWalker(new CommonTreeNodeStream(root));
    walker.walk();
  }
}

您将看到以下内容:

line=4
line=0

如您所见,"ABC = 123"产生了预期的输出(第4行),但"start ABC"没有产生(第0行)。这是因为action规则的根是ACTION标记,并且该标记永远不会在词法分析器中定义,只能在tokens{...}块中定义。并且因为输入中并不存在,所以默认情况下会将0行附加到输入中。如果你想改变行号,你需要提供一个“引用”标记作为这个所谓的虚构 ACTION标记的参数,它用于将属性复制到自身。

因此,如果您将组合语法中的actionCommand规则更改为:

actionCommand
  :  ref=action ID -> ^(ACTION[$ref.start] action ID)
  ;

行号将符合预期(第6行)。

请注意,每个解析器规则都有startend属性,分别是对第一个和最后一个令牌的引用。如果action是词法分析器规则(例如FOO),那么您可以从中省略.start

actionCommand
  :  ref=FOO ID -> ^(ACTION[$ref] action ID)
  ;

现在,ACTION令牌已复制$ref所指向的所有属性,但令牌类型除外,当然是int ACTION。但这也意味着它复制了text属性,所以在我的例子中,ref=action ID -> ^(ACTION[$ref.start] action ID)创建的AST看起来像:

            [text=START,type=ACTION]
                  /         \
                 /           \
                /             \
   [text=START,type=START]  [text=ABC,type=ID]

当然,这是一个合适的AST,因为节点的类型是唯一的,但由于ACTIONSTART共享相同的.text属性,因此调试令人困惑。

您可以通过提供第二个字符串参数将所有属性复制到除.text.type之外的虚构标记,如下所示:

actionCommand
  :  ref=action ID -> ^(ACTION[$ref.start, "Action"] action ID)
  ;

如果您现在再次运行相同的测试类,您将看到以下内容:

line=4
line=6

如果您检查生成的树,它将如下所示:

[type=ROOT, text='ROOT']
  [type=ASSIGN, text='=']
    [type=ID, text='ABC']
    [type=NUMBER, text='123']
  [type=ACTION, text='Action']
    [type=START, text='start']
    [type=ID, text='ABC']