diff --git a/data/SGF.g b/data/SGF.g new file mode 100644 index 0000000..abce28f --- /dev/null +++ b/data/SGF.g @@ -0,0 +1,184 @@ +grammar SGF; + +@header {package net.woodyfolsom.msproj.sgf;} + +@lexer::header {package net.woodyfolsom.msproj.sgf;} + +collection returns [SGFNodeCollection sgfNodeCollection] +@init { +$sgfNodeCollection = new SGFNodeCollection(); +} + : (gameTree{$sgfNodeCollection.add($gameTree.sgfGameTree);})+ + ; + +gameTree returns [SGFGameTree sgfGameTree] +@init { +$sgfGameTree = new SGFGameTree(); +} + : LPAREN sequence{$sgfGameTree.setNodeSequence($sequence.nodeSequence);} (gameTree{$sgfGameTree.addSubTree(sgfGameTree);})* RPAREN + ; + +sequence returns [List nodeSequence] +@init { +$nodeSequence = new ArrayList(); +} + : (node{$nodeSequence.add($node.sgfNode);})+; + +node returns [SGFNode sgfNode] +@init { +$sgfNode = new SGFNode(); +} + : SEMICOLON (property{$sgfNode.addProperty($property.sgfProperty);})*; + +property returns [SGFProperty sgfProperty] +@init { +$sgfProperty = new SGFProperty(); +} + : (numIdent{$sgfProperty.setIdentifier($numIdent.sgfIdent);}) ((LBRACKET numValue RBRACKET){$sgfProperty.addValue($numValue.sgfValue);})+ + + //an empty coord is a pass move + | (playerIdent{$sgfProperty.setIdentifier($playerIdent.sgfPlayer);}) ((LBRACKET RBRACKET){sgfProperty.addValue(SGFValue.EMPTY);})+ + | (playerIdent{$sgfProperty.setIdentifier($playerIdent.sgfPlayer);}) ((LBRACKET coordValue RBRACKET){sgfProperty.addValue(new SGFValue($coordValue.sgfCoord));})+ + + // 2 rules here because the disambiguating semantic pred. for 'player' would throw NPE + | (strIdent{$sgfProperty.setIdentifier($strIdent.sgfIdent);}) ((LBRACKET RBRACKET){$sgfProperty.addValue(SGFValue.EMPTY);}) + | (strIdent{$sgfProperty.setIdentifier($strIdent.sgfIdent);}) ((LBRACKET strValue RBRACKET){$sgfProperty.addValue(new SGFValue($strValue.text));})+ + + | (result{$sgfProperty.setIdentifier(SGFIdentifier.RESULT);}) ((LBRACKET resValue RBRACKET){$sgfProperty.addValue(new SGFValue(new SGFResult($resValue.text)));})+ + | (komi{$sgfProperty.setIdentifier(SGFIdentifier.KOMI);}) ((LBRACKET realValue RBRACKET){$sgfProperty.addValue(new SGFValue(Double.parseDouble($realValue.text)));})+ + | (coordIdent{$sgfProperty.setIdentifier($coordIdent.sgfIdent);})((LBRACKET coordValue RBRACKET){$sgfProperty.addValue(new SGFValue(new SGFCoord($coordValue.text)));})+ + ; + +//playerIdent is a disambiguating rule +playerIdent returns [SGFIdentifier sgfPlayer] + : {(input.LT(1).getText().equals("W"))}? strIdent {$sgfPlayer = $strIdent.sgfIdent;} + | {(input.LT(1).getText().equals("B"))}? strIdent {$sgfPlayer = $strIdent.sgfIdent;} + ; + +strIdent returns [SGFIdentifier sgfIdent] + : charEnc{$sgfIdent = SGFIdentifier.CHARSET;} + | source + | blackCountry + | whiteCountry + | event + | playerBlack + | playerWhite + | blackRank + | whiteRank + | result + | rules + | place + | application + | copyright + | username + | date + | 'B'{$sgfIdent = SGFIdentifier.MOVE_BLACK;} + | 'W'{$sgfIdent = SGFIdentifier.MOVE_WHITE;} + //SGF grammar proper allows extensions, but this reader does not + ; //if this is matched from rule playerIdent, the value is thrown away - this rule alone + //lack the context to disambiguate the single-character player movement IDs. + +coordIdent returns [SGFIdentifier sgfIdent] + : addBlack{$sgfIdent = $addBlack.sgfIdent;} + | addWhite{$sgfIdent = $addWhite.sgfIdent;} + ; + +numIdent returns [SGFIdentifier sgfIdent] + : fileFormat{$sgfIdent = SGFIdentifier.FILE_FORMAT;} + | game{$sgfIdent = SGFIdentifier.GAME;} + | size{$sgfIdent = SGFIdentifier.SIZE;} + | time{$sgfIdent = SGFIdentifier.TIME;}; + +numValue returns [SGFValue sgfValue] + : (DIGIT+){$sgfValue = new SGFValue(Integer.parseInt($numValue.text));}; + +realIdent + : komi; + +resValue //'R' is resign + : playerIdent PLUS ('R' | realValue); + +realValue + : DIGIT+ PERIOD DIGIT+; + +coordValue returns [SGFCoord sgfCoord] + : LCLETTER LCLETTER {$sgfCoord = new SGFCoord($text);} + ; + +fileFormat + : 'FF'; +game : 'GM'; +size : 'SZ'; +charEnc : 'CA'; +source : 'SO'; +blackCountry + : 'BC'; +whiteCountry + : 'WC'; +event : 'EV'; +playerBlack + : 'PB'; +playerWhite + : 'PW'; +blackRank + : 'BR'; +whiteRank + : 'WR'; +komi : 'KM'; +result : 'RE'; +rules : 'RU'; +place : 'PC'; +application + : 'AP'; +time : 'TM'; +date : 'DT'; + +addBlack returns [SGFIdentifier sgfIdent] + : 'AB'{$sgfIdent = SGFIdentifier.ADD_BLACK;} + ; + +addWhite returns [SGFIdentifier sgfIdent] + : 'AW'{$sgfIdent = SGFIdentifier.ADD_WHITE;} + ; + +copyright + : 'CP'; +username: 'US'; + +strValue : (UCLETTER | LCLETTER | MINUS | DIGIT | SPACE | PERIOD | COMMA | PLUS | SLASH | COLON)+; + +LPAREN : '(' ; + +SEMICOLON : ';' ; + +UCLETTER : 'A'..'Z'; + +LCLETTER : 'a'..'z'; + +DIGIT : '0'..'9'; + +LBRACKET + : '['; + +RBRACKET + : ']'; + +RPAREN : ')'; + +COLON : ':'; + +MINUS : '-'; + +SPACE : ' '; + +PERIOD : '.'; + +COMMA : ','; + +PLUS : '+'; + +SLASH : '/'; + +CR : '\r'{$channel=HIDDEN;}; + +NEWLINE : '\n'{$channel=HIDDEN;};