Лучший способ сопоставить токены со значениями перечисления? - PullRequest
2 голосов
/ 10 сентября 2011

Я пытаюсь, чтобы мое правило синтаксического анализатора выбрало значение enum на основе моего DIR токена.Есть ли способ, которым я могу сделать это без создания отдельных, полноценных токенов для каждого направления?Или вообще более чистый подход?

DIR : (NORTH|SOUTH) (EAST|WEST)?
 | EAST
 | WEST;

fragment NORTH: N '.'? | N O R T H;
fragment SOUTH: S '.'? | S O U T H;
fragment EAST : E '.'? | E A S T;
fragment WEST : W '.'? | W E S T;

(для каждой буквы есть фрагменты токенов для облегчения учета регистра)

Перечисление public enum Direction { NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST }

Прямо сейчасЕдинственное решение, которое я вижу, - это преобразовать DIR в правило синтаксического анализатора и разделить токены на отдельные направления:

NORTH: N '.'? | N O R T H;
SOUTH: S '.'? | S O U T H;

dir returns [Direction dir]
 : NORTH { dir = Direction.NORTH; }
 | SOUTH { dir = Direction.SOUTH; }

Это не ужасно для этого сценария, но у меня естьнекоторые другие перечисления, у которых будет намного больше опций, поэтому я ищу способы упростить это.

Ответы [ 3 ]

1 голос
/ 10 сентября 2011

Другой вариант - переписать внутренний текст токенов, чтобы они соответствовали вашим значениям перечисления. В вашем парсере вы можете затем сделать Direction.valueOf(String), чтобы разобрать его в реальное перечисление.

Примерно так:

...

parse
  :  (
       DIR {System.out.println("enum=" + Direction.valueOf($DIR.text));}
     )* 
     EOF
  ;

DIR
  :  ( NORTH {setText("NORTH");}      | SOUTH {setText("SOUTH");}      ) 
     ( EAST  {setText($text+"EAST");} | WEST  {setText($text+"WEST");} )?
  |  EAST {setText("EAST");}
  |  WEST {setText("WEST");}     
  ;

...

Следующий тест:

import org.antlr.runtime.*;

public class Main {
  public static void main(String[] args) throws Exception {
    String src = "N EaSt S. w NE N.w. Southe SWeSt";
    CompassLexer lexer = new CompassLexer(new ANTLRStringStream(src));
    CompassParser parser = new CompassParser(new CommonTokenStream(lexer));
    parser.parse();
  }
}

произведено:

java -cp antlr-3.3.jar org.antlr.Tool Compass.g 
javac -cp antlr-3.3.jar *.java
java -cp .:antlr-3.3.jar Main

enum=NORTH
enum=EAST
enum=SOUTH
enum=WEST
enum=NORTHEAST
enum=NORTHWEST
enum=SOUTHEAST
enum=SOUTHWEST

Возможно, это немного неуклюже. Но если вы собираетесь создавать токены из (многих) разных токенов (например, с юго-западом или северо-востоком), это может сократить вашу грамматику, а не что-то вроде:

dir returns [Direction dir]
 : NORTH { dir = Direction.NORTH; }
 | SOUTH { dir = Direction.SOUTH; }
 ...
 ;
1 голос
/ 10 сентября 2011

Расширяя идею в комментарии Confusion , я нашел способ получить имена токенов.Поэтому, если я сделаю токен для каждого направления, я смогу сделать что-то вроде:

dir returns [Direction dir]
 : (d=NORTH | d=SOUTH | d=EAST | d=WEST | d=NORTHEAST | d=NORTHWEST | d=SOUTHEAST | d=SOUTHWEST )
   { dir = Direction.valueOf(getTokenNames()[$d.getType()]); }

NORTH: N '.'? | N O R T H;
SOUTH: S '.'? | S O U T H;
EAST:  E '.'? | E A S T;
WEST:  W '.'? | W E S T;
NORTHEAST : N E | N '.' E '.' | N O R T H E A S T;
NORTHWEST : N W | N '.' W '.' | N O R T H W E S T;
SOUTHEAST : S E | S '.' E '.' | S O U T H E A S T;
SOUTHWEST : S W | S '.' W '.' | S O U T H W E S T;

Это будет означать намного больше токенов, но на самом деле сократит набор текста.

Iтакже попытался объединить это с предложением Барта, но похоже, что state.type не установлен во время фазы лексинга (это приводит к NullPointerException).Лексер действительно присваивает идентификаторы типов фрагментам, но, кажется, нет никакого способа получить к ним доступ из правила лексера.

main_rule[CustomObject object]: d=DIR ...
           { object.setDirection(Direction.valueof($d.text)); };

DIR
 : (NORTH | SOUTH | EAST| WEST | NORTHEAST | NORTHWEST | SOUTHEAST | SOUTHWEST)
   { setText(getTokenNames()[state.type]);

fragment NORTH: N '.'? | N O R T H;
...
1 голос
/ 10 сентября 2011

Я не очень знаком с ANTLR, но при быстром сканировании документов он, похоже, работает почти как yacc / racc, и, кажется, позволяет определять произвольные методы в блоке @member, поэтому я бы хотел ожидать, что вы можете использовать что-то вроде:

dir returns [Direction dir]
: DIR { $result = directionStringToEnum($DIR.text); }

, где вы должны определить отдельный

public Direction directionStringToEnum(String dir) {
   Direction.valueOf(dir.toUpperCase());
}

в блоке @member. Возможно, вам удастся обобщить это для обработки произвольных перечислений (но это возможно любым уродливым способом, требующим Class.forName ()).

...