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<SGFNode> nodeSequence]
@init {
$nodeSequence = new ArrayList<SGFNode>();
}
	:	(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<SGFCoord>($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<String>($strValue.text));})+
	
	| (result{$sgfProperty.setIdentifier(SGFIdentifier.RESULT);}) ((LBRACKET strValue RBRACKET){$sgfProperty.addValue(new SGFValue<SGFResult>(new SGFResult($strValue.text)));})+
	
	| (komi{$sgfProperty.setIdentifier(SGFIdentifier.KOMI);}) ((LBRACKET realValue RBRACKET){$sgfProperty.addValue(new SGFValue<Double>(Double.parseDouble($realValue.text)));})+
	| (coordIdent{$sgfProperty.setIdentifier($coordIdent.sgfIdent);})((LBRACKET coordValue RBRACKET){$sgfProperty.addValue(new SGFValue<SGFCoord>(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;}
	| {(input.LT(1).getText().equals("White"))}? strIdent {$sgfPlayer = $strIdent.sgfIdent;}
	| {(input.LT(1).getText().equals("Black"))}? strIdent {$sgfPlayer = $strIdent.sgfIdent;}
	;

strIdent returns [SGFIdentifier sgfIdent]
	:	charEnc{$sgfIdent = SGFIdentifier.CHARSET;}
	|       source{$sgfIdent = SGFIdentifier.SOURCE;}
	|       blackCountry{$sgfIdent = SGFIdentifier.COUNTRY_BLACK;}
	|       whiteCountry{$sgfIdent = SGFIdentifier.COUNTRY_WHITE;}
	|	event{$sgfIdent = SGFIdentifier.EVENT;}
	|	playerBlack{$sgfIdent = SGFIdentifier.PLAYER_BLACK;}
	|	playerWhite{$sgfIdent = SGFIdentifier.PLAYER_WHITE;}
	|	blackRank{$sgfIdent = SGFIdentifier.RANK_BLACK;}
	|	whiteRank{$sgfIdent = SGFIdentifier.RANK_WHITE;}
	|	rules{$sgfIdent = SGFIdentifier.RULES;}
	|	place{$sgfIdent = SGFIdentifier.PLACE;}
        | 	application{$sgfIdent = SGFIdentifier.APPLICATION;}
        |	copyright
        |	username
        |	date{$sgfIdent = SGFIdentifier.DATE;}
        |	'Black'{$sgfIdent = SGFIdentifier.MOVE_BLACK;}
        |	'White'{$sgfIdent = SGFIdentifier.MOVE_WHITE;}
        |	'B'{$sgfIdent = SGFIdentifier.MOVE_BLACK;}
        |	'W'{$sgfIdent = SGFIdentifier.MOVE_WHITE;}
        |	view{$sgfIdent = SGFIdentifier.VIEW;} //this is not really a strIdent, but is a placeholder since I don't use this info
        |	comment{$sgfIdent = SGFIdentifier.COMMENT;}
        //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<Integer> sgfValue]
	: 	(STRVALUE){$sgfValue = new SGFValue<Integer>(Integer.parseInt($numValue.text));};
		
realIdent
	:	komi;
	
realValue
	:	STRVALUE;
	
coordValue returns [SGFCoord sgfCoord]	
	: STRVALUE {$sgfCoord = new SGFCoord($coordValue.text);}
	;

fileFormat 
	:	'FF';
	
game 	:	'GM' | 'GaMe';

size 	:	'SZ' | 'SiZe';

view 	:	'VW' | 'VieW';

charEnc	:	'CA';

source :	'SO';

blackCountry 
	:	'BC';
	
whiteCountry 
	:	'WC';
	
event	:	'EV' | 'EVent';

playerBlack
	:	'PB' | 'PlayerBlack';
playerWhite
	:	'PW' | 'PlayerWhite';
blackRank
	:	'BR';

whiteRank 
	: 	'WR';
	
komi 	:	'KM' | 'KoMi';

result 	:	'RE' 
	| 	'REsult'
	;

rules	:	'RU';

place 	:	'PC' | 'PlaCe';

application
	:	'AP';
	
time	:	'TM';

date 	:	'DT' | 'DaTe';

addBlack returns [SGFIdentifier sgfIdent]
	:	'AB'{$sgfIdent = SGFIdentifier.ADD_BLACK;}
	;
	
addWhite returns [SGFIdentifier sgfIdent]
	:	'AW'{$sgfIdent = SGFIdentifier.ADD_WHITE;}
	;

copyright
	:	'CP';

username:	'US';

comment :	'C' | 'Comment';

strValue 
	:	(STRVALUE)+;
	
SPACE 	:	' ';

fragment DIGIT 	:	'0'..'9';

COLON 	:	':';

MINUS 	:	'-';

fragment PERIOD 	:	'.';

COMMA 	:	',';

PLUS 	:	'+';

SLASH 	:	'/';


LPAREN 	:	'(' ;

RPAREN 	:	')';

STRVALUE : (UCLETTER | LCLETTER | MINUS | DIGIT | SPACE | PERIOD | COMMA | PLUS | SLASH | COLON | LPAREN | RPAREN )+;


SEMICOLON : 	';' ;

fragment UCLETTER :	'A'..'Z';

fragment LCLETTER :	'a'..'z';

LBRACKET 
	:	'[';
	
RBRACKET 
	:	']';

//CR 	:	'\r'{$channel=HIDDEN;};

//NEWLINE :	'\n'{$channel=HIDDEN;};
