Python 3.x ANTLR4:获取上下文的结束位置

Python 3.x ANTLR4:获取上下文的结束位置,python-3.x,antlr,antlr4,Python 3.x,Antlr,Antlr4,我试图在ANTLR4中获取上下文的开始和结束位置(行和列)。我正在使用Python3语法。我写了一个监听器,它打印开始和结束行: class MyListener extends Python3BaseListener { @Override public void enterFuncdef(@NotNull Python3Parser.FuncdefContext ctx) { Token start = ctx.getStart(); Syst

我试图在ANTLR4中获取上下文的开始和结束位置(行和列)。我正在使用Python3语法。我写了一个监听器,它打印开始和结束行:

class MyListener extends Python3BaseListener {
    @Override
    public void enterFuncdef(@NotNull Python3Parser.FuncdefContext ctx) {
        Token start = ctx.getStart();
        System.out.print("start: ");
        System.out.print(start.getText());
        System.out.print(": ");
        System.out.println(start.getLine());

        Token stop = ctx.getStop();
        System.out.print("stop: ");
        System.out.print(stop.getText());
        System.out.print(": ");
        System.out.println(stop.getLine());
    }
}
我的测试输入:

def factorial(n):
    if n == 1:
        return 1
    else:
        return n * factorial(n-1)

def iterative_factorial(n):
    result = 1
    for i in range(2,n+1):
        result *= i
    return result
我的听众打印

start: def: 1
stop: DEDENT: 0
start: def: 7
stop: DEDENT: 0
但我想

start: def: 1
stop: DEDENT: 5
start: def: 7
stop: DEDENT: 11

怎么了?

funcdef
规则匹配的最后一个标记(计算它调用的后代规则)是
DEDENT
标记。
DEDENT
标记不是由lexer规则生成的,而是由重写的
nextToken()
方法中的操作生成的。创建
DEDENT
标记的代码不分配任何位置信息,可能是因为它们实际上不是输入的一部分。有两种方法可以解决这个问题

  • 创建标记时,将位置信息分配给标记。此信息可能是一个零宽度标记,位于
    DEDENT
    标记前面的最后一个真输入标记的最后一个字符之后

  • 编写自己的
    getStop()
    方法实现,该方法忽略所有
    DEDENT
    标记,以确保只返回具有正确位置信息的标记


  • 以下是实现280Z28第一个提案的方法:

    grammar Python3;
    
    ...
    
    @lexer::members {
    
      ...
    
      // The most recently produced token.
      private Token lastToken = null;
    
      ...
    
      @Override
      public Token nextToken() {
    
        // Check if the end-of-file is ahead and there are still some DEDENTS expected.
        if (_input.LA(1) == EOF && !this.indents.isEmpty()) {
    
          // First emit an extra line break that serves as the end of the statement.
          this.emit(new CommonToken(Python3Parser.NEWLINE, "\n"));
    
          // Now emit as much DEDENT tokens as needed.
          while (!indents.isEmpty()) {
            this.emit(createDedent());
            indents.pop();
          }
        }
    
        Token next = super.nextToken();
    
        if (next.getChannel() == Token.DEFAULT_CHANNEL) {
          // Keep track of the last token on the default channel.
          this.lastToken = next;
        }
    
        return tokens.isEmpty() ? next : tokens.poll();
      }
    
      private Token createDedent() {
        CommonToken dedent = new CommonToken(Python3Parser.DEDENT, "DEDENT");
        dedent.setLine(this.lastToken.getLine());
        return dedent;
      }
    
      ...
    }
    
    ...
    
    NEWLINE
     : ( '\r'? '\n' | '\r' ) SPACES?
       {
         ...
    
         if (opened > 0 || next == '\r' || next == '\n' || next == '#') {
           ...
         }
         else {
           ...
    
           if (indent == previous) {
             ...
           }
           else if (indent > previous) {
             ...
           }
           else {
             // Possibly emit more than 1 DEDENT token.
             while(!indents.isEmpty() && indents.peek() > indent) {
               this.emit(createDedent());
               indents.pop();
             }
           }
         }
       }
     ;
    
    ...
    
    这些变化是:

    • lexer中有一个
      lastToken:Token
      ,它跟踪在
      nextToken()
      方法中设置的最近发出的令牌
    • 创建dedent令牌新实例的两个地方现在使用
      createDedent()
      创建dedent令牌,其中使用与
      lastToken
      相同的行号创建dedent令牌
    查看此提交中的确切更改:

    完整的语法可在此处找到:

    其中打印:

    start: def: 1 stop: DEDENT: 5 start: def: 7 stop: DEDENT: 11 开始:def:1 停止:DEDENT:5 开始:def:7 停止:DEDENT:11
    谢谢您的意见。

    谢谢。我想实现1,但我在lexer的什么位置或如何获得该职位?非常感谢。我没想到,事情会那么棘手。我在createDedent中添加了列信息:
    dedent.setCharPositionInLine(this.lastToken.getCharPositionInLine()+this.lastToken.getText().length()+1)你认为这在任何情况下都有效吗?我在添加列信息的方法中发现了两个错误(在上面的评论中)。首先,省略
    +1
    ,因为getCharPositionInLine是基于零的。其次,发出的标记包含源代码中不存在的文本。因此,我根据他的开始和停止索引计算最后一个令牌的长度。为了能够将常规和自定义发出的令牌分开,我将发出令牌的开始和停止索引设置为
    -1
    (在令牌接口中)。