В ANTLR, могу ли я смотреть вперед для конкретных токенов, фактически не сопоставляя их?

В принципе, мне нужно посмотреть, существует ли определенный токен, но без его соответствия (т.е. так, чтобы еще одно правило синтаксического анализа все равно соответствовало ему).

Точные детали проблемы - это предложение "END-ALL". Язык имеет такие конструкции, как "IF" (закрыт "END-IF" ), "FOR" (закрыт "END-FOR" ) и т.д.

Но можно глобально закрыть все такие открытые петли с помощью "END-ALL" (тем самым устраняя необходимость в действительных предложениях "END-IF" или "END-FOR" ).

В любом случае я могу правильно реализовать это?

Ответы

Ответ 1

Это можно сделать, создав булевский флаг внутри операторов iffor -), которые отслеживают, нужно ли потреблять ENDALL или если достаточно взглянуть на будущее. Этот логический флаг передается правилу синтаксического анализатора, которое соответствует концу кодового блока if.

Небольшая демонстрация:

grammar T;

options {
  output=AST;
}

tokens {
  BLOCK;
  ASSIGN;
}

@parser::members {
  private boolean flag = true;
}

parse
  :  block EOF -> block
  ;

block
  :  stat* -> ^(BLOCK stat*)
  ;

stat
  :  PRINT expression -> ^(PRINT expression)
  |  assignment
  |  ifStat
  ;

assignment
  :  ID '=' expression -> ^(ASSIGN ID expression)
  ;

ifStat
@init{
  boolean consumeEndAll = false;
  if(flag) {
    consumeEndAll = true;
    flag = false;
  }
}
@after {
  if(consumeEndAll) {
    flag = true;
  }
}
  :  IF expression DO block end[consumeEndAll] -> ^(IF expression block)
  ;

expression
  :  NUMBER
  |  TRUE
  |  FALSE
  |  ID
  ;

end [boolean consumeEndAll]
  :                                       END
  |                                       EOF
  |  {consumeEndAll}?=>                   ENDALL
  |  {input.LT(1).getType() == ENDALL}?=> { /* consume no token */ }
  ;

PRINT  : 'print';  
ENDALL : 'endall';
END    : 'end';
IF     : 'if';
DO     : 'do';
TRUE   : 'true';
FALSE  : 'false';
NUMBER : '0'..'9'+ ('.' '0'..'9'+)?;
ID     : ('a'..'z' | 'A'..'Z')+;
SPACE  : (' ' | '\t' | '\r' | '\n') {skip();};

Предикаты внутри правила end ({ ... }?=>) приводят к тому, что правило либо потребляет ENDALL, либо только смотрит вперед на наличие такого токена, но не потребляет его.

Подробнее о предикатах: Что такое "семантический предикат" в ANTLR?

Парсер, сгенерированный грамматикой выше, даст идентичный АСТ для обоих сценариев 1 и 2:

Script 1

if 1 do
  print a
  if 2 do
    print b
    print c
    if 3 do
    end
  end
end
print d

Script 2

if 1 do
  print a
  if 2 do
    print b
    print c
    if 3 do
endall
print d

а именно, следующий AST:

enter image description here

(изображение, сгенерированное с помощью graphviz-dev.appspot.com)

Вы можете протестировать все это со следующим классом Java:

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

public class Main {
  public static void main(String[] args) throws Exception {
    String source =
        "if 1 do        \n" +
        "  print a      \n" +
        "  if 2 do      \n" +
        "    print b    \n" +
        "    print c    \n" +
        "    if 3 do    \n" +
        "endall         \n" +
        "print d          ";
    System.out.println(source + "\n------------------\n");
    TLexer lexer = new TLexer(new ANTLRStringStream(source));
    TParser parser = new TParser(new CommonTokenStream(lexer));
    CommonTree tree = (CommonTree)parser.parse().getTree();
    DOTTreeGenerator gen = new DOTTreeGenerator();
    StringTemplate st = gen.toDOT(tree);
    System.out.println(st);
  }
}