ANTLR4:获取上下文的结束位置

时间:2014-08-31 15:48:31

标签: python-3.x antlr antlr4

我试图获取ANTLR4中上下文的开始和结束位置(行和列)。我正在使用this 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

出了什么问题?

2 个答案:

答案 0 :(得分:1)

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

  1. 创建时,将位置信息分配给DEDENT标记。此信息可能是跟随DEDENT令牌之前的最后一个真实输入标记的最后一个字符的零宽度标记。

  2. 编写自己的getStop()方法实现,忽略所有DEDENT标记,以确保只返回包含正确位置信息的标记。

答案 1 :(得分:1)

这是完成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();
         }
       }
     }
   }
 ;

...

变化是:

  • 词法分析器中有一个lastToken: Token,用于跟踪在nextToken()方法中设置的最新发出的令牌;
  • 创建dedent-token的新实例的两个地方现在使用createDedent(),其中使用与lastToken相同的行号创建dedent令牌。

查看此提交中的确切更改:https://github.com/bkiers/python3-parser/commit/1a1118f8f540843ebc2d6bb2a76c56f894869b93

完整的语法可以在这里找到:https://github.com/bkiers/python3-parser/blob/master/src/main/antlr4/nl/bigo/pythonparser/Python3.g4

打印:

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

输入。