将令牌映射到枚举值的更好方法?

时间:2011-09-09 20:10:15

标签: java enums antlr antlr3

我正在尝试让我的解析器规则根据我的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 个答案:

答案 0 :(得分:1)

我对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())。

答案 1 :(得分:1)

另一种选择是重写标记的内部文本,使它们与您的枚举值匹配。在您的解析器中,您可以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; }
 ...
 ;

答案 2 :(得分:1)

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;

这将意味着更多的令牌,但确实减少了打字。

我也尝试将其与Bart的建议结合起来,但似乎在lexing阶段没有设置state.type(导致NullPointerException)。词法分析器确实为片段分配了类型ID,似乎没有任何方法可以从词法分析器规则中访问它们。

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;
...