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();
  • двете места, които създадоха нов екземпляр на dedent-token, сега използват createDedent(), където се създава dedent token със същия номер на ред като 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 (както е описано в интерфейса на Token). - 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