开发者

Better way to map tokens to enum values?

I'm trying to have my parser rule select an enum value based on my DIR token. Is there a way I can do this without creating separate, full-fledged tokens for each direction? Or generally a cleaner approach?开发者_高级运维

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;

(there are token fragments for each letter to facilitate case-insensitivity)

The enum is public enum Direction { NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST }

Right now the only solution I see is to convert DIR to a parser rule and make the directions separate tokens:

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

This isn't terrible for this scenario, but I've got some other enums that will have lots more options so I'm looking for any ways to simplify this.


I'm not very familiar with ANTLR, but from a fast scan of the docs it seems to work pretty much like yacc/racc and it seems to allow arbitrary methods to be defined in an @member block, so I would expect you can use something like:

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

where you have to define a separate

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

in the @member block. You may be able to generalize that to handle arbitrary enums (but probable in any ugly way, requiring Class.forName()).


Another option is to rewrite the inner text of the tokens so they match your enum values. In your parser, you could then do Direction.valueOf(String) to parse it into a real enum.

Something like this:

...

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");}     
  ;

...

The following test:

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();
  }
}

produced:

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

It's a bit clunky, perhaps. But if you're going to construct tokens from (many) different tokens (like with South-West or North-East), it may shorten your grammar opposed to something like:

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


Expanding on the idea in Confusion's comment, I did track down a way to get the token names. So if I make a token for each direction I should be able to do something like:

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;

This will mean a lot more tokens, but really cuts down on the typing.

I also tried to combine it this with Bart's suggestion, but it appears that state.type isn't set during the lexing phase (it results in NullPointerException). The lexer does assign type IDs to fragments, there just doesn't seem to be any way to access them from a lexer rule.

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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜