/** * A grammar file for chess notations in PGN format. * * Author: Martin Rademacher mano@radebatz.net * Version: 0.7.5 * * This version understands SAN and long notation. * * PGN file formats are divided into import and export formats. While import * formats have a somewhat weaker grammar definition, export files are supposed * to follow certain rules. * At the moment this grammar supports a mixture of both. There are also different * 'flavours' regarding moves; see the castle pattern or pawn promotion. * So far I've seen "e8=Q" (PGN standard), "e8Q" or even e8(Q). This will be * incorporated in one of the next versions. * * This grammar is based on the PGN standard description found * on the PGN standard Revision 1994.03.12 as found at: * http://www.very-best.de/pgn-spec.htm * or * http://www.saremba.de/chessgml/standards/pgn/pgn-complete.htm * * * Changes from v0.7.0: * - support for long move notation * - 'Resigns, Draw added as game terminator (IGNORE_CASE) * - stripping of additional characters from tags * - removed the Singleton code; that belonged to some classes I created, but it's not * really neccesary * * Changes from v0.6.5: * - full support * - the grammar accepts now e8=Q and e8Q * - I came across move related comments in '{' and '}' that will be tolerated now. * (see MoveAnnotation()) * - simple '(' and ')' handler are implemented (I think it's up to the application * to do something useful with them...) * - the move number can have more that one now. * - and will be trated as token now (thanks to the enhanced promotion rules) * * * I would be happy about response including praise, donations or even * bug reports ;) * * TODO: * - add more syntax 'flavours' * */ /* options { DEBUG_TOKEN_MANAGER = true; } */ PARSER_BEGIN(PGNParser) public class PGNParser { public static void main(String args[]) throws ParseException, Exception { try { PGNParser parser = new PGNParser(new java.io.FileInputStream(args[0])); while(-1 != parser.parse()); } catch (ParseException pe) { System.out.println("Exiting..."); throw pe; } catch (Exception e) { throw e; } } /** * Some little helper. */ private static final String imageOf(Token t) { return (null != t ? t.image : ""); //return (null != t ? t.image : null); } private static final String trimImage(String s) { return s.trim(); } private static final String trimImage(String s, int a, int z) { return s.substring(a, s.length()-z).trim(); } } PARSER_END(PGNParser) /** * skip some stuff */ SKIP : { "\r" | "\n" } /* PGN token and literals */ TOKEN : { < LBRACKET: "[" > : IN_PGN_TAG | < LPAREN: "(" > | < RPAREN: ")" > | < RBRACE: "}" > : DEFAULT | < LBRACE: "{" > : IN_MOVE_COMMENT | < DOT: "." > | < MOVE_NUMBER: ( ()+) > | < BLACK_MOVE: ( ) > | < FILE_NAME: ["a" - "h"] > | < RANK_NAME: ["1" - "8"] > | < SQUARE: ( ) > | < CASTLE_KINGSIDE: ("O-O" | "0-0" | "o-o") > | < CASTLE_QUEENSIDE: ("O-O-O" | "0-0-0" | "o-o-o") > | < PIECE_IDENT: ["N","B","R","Q","K"] > | < PROMOTE: "=" > | < CAPTURE: "x" > | < DASH: "-" > | < CHECK: "+" > | < END_OF_TOKEN: (" " | )+ > | < EOL: ("\n" | "\r\n") > | < CHECKMATE: ("#" | "++") > | < SUFFIX_ANNOTATION: ("!!" | "!" | "!?" | "?!" | "?" | "??") > | < NAG: ("$" ()? ()?) > | < #INTEGER: ()+ > | < #DIGIT: ["0"-"9"] > | < #LETTER: ["a"-"z","A"-"Z"] > } TOKEN [IGNORE_CASE] : { < GAME_TERMINATOR: ("1-0" | "0-1" | "1/2-1/2" | "*" | "Resigns" | "Draw") > } /* Handle PGN tags */ TOKEN : { < RBRACKET: "]" > : DEFAULT | < SPACE: " "> | < SYMBOL: (( | ) ( | | "_" | "+" | "#" | "=" | ":" | "-")*) > | < STRING: "\"" ( (~["\"","\\","\n","\r"]) | ("\\" ( ["n","t","b","r","f","\\","'","\""] | ["0"-"7"] ( ["0"-"7"] )? | ["0"-"3"] ["0"-"7"] ["0"-"7"] ) ) )* "\"" > } /* Comments; * Per definition the escape char '%' is only defined as a special token, * when in the first column of the line - I suppose it'll do no damage though * since it it not defined anywhere else... */ MORE : { < START_OF_COMMENT: [";", "%"] > : IN_COMMENT } MORE : { < ~[] > } TOKEN : { < COMMENT: > : DEFAULT } MORE : { < ~["}"] > } TOKEN : { < MOVE_COMMENT: > : DEFAULT } /* ******************************************************************************* * Productions start here! ******************************************************************************* */ /** * PGN tag. * Doesn't allow white space around the brackets. * Tags omitting the symbol tag are regarded as global comments. */ void PGNTag() : { Token t = null; Token v = null; } { ( ((t = ()+) | ()+)? v = ()? ) { System.out.println("Tag: " + imageOf(t) + " = '" + trimImage(imageOf(v), 1, 1) + "'"); } } /** * Move number. */ void MoveNumber() : { Token t; } { ( t = ) { System.out.println("#" + t.image); } } /** * A move. */ void Move() : { Token ss = null; Token pp = null; Token ii = null; Token cc = null; Token aa = null; Token ff = null; Token rr = null; } { // pawns; long notation LOOKAHEAD(2) (ff = ((aa = ) | (aa = ))? ss = ((pp = )? ii = )?) { System.out.println("pawn: " + imageOf(ff) + imageOf(aa) + imageOf(ss) + imageOf(pp) + imageOf(ii)); } // pawns; SAN | ((ff = (aa = )?)? ss = ((pp = )? ii = )?) { System.out.println("pawn: " + imageOf(ff) + imageOf(aa) + imageOf(ss) + imageOf(pp) + imageOf(ii)); } // pieces; long notation | LOOKAHEAD(3) (ii = ff = ((aa = ) | (aa = ))? ss = ) { System.out.println("piece: " + imageOf(ii) + imageOf(ff) + imageOf(aa) + imageOf(rr) + imageOf(ss)); } // pieces; SAN | (ii = (ff = )? (rr = )? (aa = )? ss = ) { System.out.println("piece: " + imageOf(ii) + imageOf(ff) + imageOf(aa) + imageOf(rr) + imageOf(ss)); } // castle | ((cc = | cc = )) { System.out.println("casteling: " + imageOf(cc)); } // ... | (cc = ) { System.out.println("black move: " + imageOf(cc)); } } /** * Move text is a complete move notation. */ void MoveText() : { Token cc = null; Token ss = null; } { (Move() (cc = | cc = )? (ss = )?) { System.out.println("movetext: " + imageOf(cc) + imageOf(ss)); } } /** * Move text is a complete move notation. */ void GameTerminator() : { Token gg = null; } { (gg = ) { System.out.println("game terminator: " + imageOf(gg)); } } /** * NAG. * A numeric annotation glyph; Range: 0 - 255. */ void NAG() : { Token gg = null; } { gg = { System.out.println("NAG: " + imageOf(gg)); } } /** * Tag comment. */ void TagComment() : { Token cc = null; } { cc = { System.out.println("COMMENT: " + trimImage(imageOf(cc))); } } /** * Move comment. */ void MoveComment() : { Token cc = null; } { ( cc = ) { System.out.println("MOVE_COMMENT: " + trimImage(imageOf(cc), 0, 1)); } } /** * Recursive annotation variation. */ void RecAnnotation() : { Token pp = null; } { (pp = ) { System.out.println("Start of recursive annotation: " + imageOf(pp)); } | (pp = ) { System.out.println("End of recursive annotation: " + imageOf(pp)); } } /** * Main production. */ int parse() : { } { PGNTag() { return 1; } | MoveNumber() { return 1; } | MoveText() { return 1; } | TagComment() { return 1; } | MoveComment() { return 1; } | GameTerminator() { return 1; } | RecAnnotation() { return 1; } | NAG() { return 1; } | ( | ) { return 0; } | { return -1; } }