ANTLR4: получить конечную позицию контекста

Я пытаюсь получить начальную и конечную позицию (строку и столбец) контекста в 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

Что случилось?


person maiermic    schedule 31.08.2014    source источник


Ответы (2)


Вот способ выполнить первое предложение 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();
  • два места, которые создали новый экземпляр токена-отступника, теперь используют createDedent(), где токен-отступник создается с тем же номером строки, что и lastToken.

Просмотрите точные изменения в этой фиксации: 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

за ваш вклад.

person Bart Kiers    schedule 31.08.2014
comment
Большое спасибо. Я не ожидал, что это так сложно. Я добавил информацию о столбцах в createDedent: dedent.setCharPositionInLine(this.lastToken.getCharPositionInLine() + this.lastToken.getText().length() + 1); Как вы думаете, это будет работать во всех случаях? - person maiermic; 01.09.2014
comment
Я понял 2 ошибки в своем подходе к добавлению информации о столбцах (в комментарии выше). Во-первых, опустите + 1, потому что getCharPositionInLine отсчитывается от нуля. Во-вторых, выпущенные токены содержат текст, которого нет в исходном коде. Поэтому я рассчитываю длину последнего токена на основе его стартового и стопового индекса. Чтобы иметь возможность разделить обычные и пользовательские выпущенные токены, я установил начальный и конечный индексы выпущенных токенов на -1 (как описано в интерфейсе токена). - person maiermic; 01.09.2014

Последний токен, соответствующий правилу funcdef (с учетом вызываемых им правил-потомков), является токеном DEDENT. Маркер DEDENT создается не правилом лексера, а действием переопределенного метода nextToken(). Код, который создает токены DEDENT, не присваивает никакой информации о позиции, предположительно, поскольку они фактически не являются частью ввода. Есть два способа решить эту проблему.

  1. Назначайте информацию о позиции токенам DEDENT при их создании. Эта информация, скорее всего, будет токеном нулевой ширины, следующим за последним символом последнего истинного входного токена, предшествующего токену DEDENT.

  2. Напишите собственную реализацию метода getStop(), которая игнорирует все токены DEDENT, чтобы гарантировать возврат только токенов с правильной информацией о позиции.

person Sam Harwell    schedule 31.08.2014
comment
Спасибо. Я хотел бы реализовать 1., но где или как я могу получить позицию в лексере? - person maiermic; 31.08.2014