/*
 * [The "BSD licence"]
 * Copyright (c) 2011 Terence Parr
 * All rights reserved.
 *
 * Conversion to C#:
 * Copyright (c) 2011 Sam Harwell, Tunnel Vision Laboratories, LLC
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/** Build an AST from a single StringTemplate template */
parser grammar TemplateParser;

options {
	language=CSharp3;
	tokenVocab=TemplateLexer;
	TokenLabelType=CommonToken;
	output=AST;
	ASTLabelType=CommonTree;
}

tokens {
	EXPR; OPTIONS; PROP; PROP_IND; INCLUDE; INCLUDE_IND; EXEC_FUNC; INCLUDE_SUPER;
	INCLUDE_SUPER_REGION; INCLUDE_REGION; TO_STR; LIST; MAP; ZIP; SUBTEMPLATE; ARGS;
	ELEMENTS; REGION; NULL;
	}

@namespace{Antlr4.StringTemplate.Compiler}

@header{
using Antlr4.StringTemplate.Extensions;
using Antlr4.StringTemplate.Misc;
// The variable 'name' is declared but never used
#pragma warning disable 168
}

@rulecatch {
catch (RecognitionException re) { re.PreserveStackTrace(); throw; }
}

@ctorModifier{private}

public
templateAndEOF : template EOF!;

template : element* ;

element
	:	{input.LT(1).CharPositionInLine == 0}? INDENT!? COMMENT! NEWLINE!
	|	INDENT^ singleElement
	|	singleElement
	|	compoundElement
	;

singleElement
	:	exprTag
	|	TEXT
	|	NEWLINE
	|	COMMENT!
	;
	
compoundElement
	:	ifstat
	|	region
	;

exprTag
	:	LDELIM<type=EXPR;text="EXPR">^ expr exprOptions? RDELIM!
	;

region
@init {bool indented = false; CommonTree tree = null;}
@after {if (!indented) $tree = tree;}
	:	INDENT^? regionbody
		// kill \n for <@end> on line by itself if multi-line embedded region
		({$region.start.Line != input.LT(1).Line}? => NEWLINE!)?
		{indented = $regionbody.indented; tree = $regionbody.tree;}
	;

regionbody returns[bool indented]
	:	LDELIM<REGION>^ '@'! ID RDELIM! {$indented = (input.LA(1) != NEWLINE);}
		template
		INDENT!? LDELIM! '@end'! RDELIM!
	;

subtemplate
	:	'{'<type=SUBTEMPLATE;text="SUBTEMPLATE">^ subtemplateargs? template INDENT!? '}'!
		// ignore final INDENT before } as it's not part of outer indent
	;

subtemplateargs
	:	subtemplate_args_trunc* subtemplate_args_last
	;

subtemplate_args_trunc
	:	ID ','<ARGS>^
	;

subtemplate_args_last
	:	ID '|'<ARGS>^
	;

ifstat
@init {bool indented = false; CommonTree tree = null;}
@after {if (!indented) $tree = tree;}
	:	INDENT^? ifstatbody
		// kill \n for <endif> on line by itself if multi-line IF
		({$ifstat.start.Line!=input.LT(1).Line}?=> NEWLINE!)?
		{indented = $ifstatbody.indented; tree = $ifstatbody.tree;}
	;

ifstatbody returns[bool indented]
	:	LDELIM! 'if'^ '('! conditional ')'! RDELIM! {$indented = (input.LA(1) != NEWLINE);}
			template
			elseifstat*
			elsestat?
			INDENT!? LDELIM! 'endif'!
		RDELIM!
	;

elseifstat
	:	INDENT!? LDELIM! 'elseif'^ '('! conditional ')'! RDELIM! template
	;

elsestat
	:	INDENT!? LDELIM! 'else'^ RDELIM! template
	;

conditional
scope {
	bool inside;
}
	: andConditional ( '||'^ andConditional )*
	;

andConditional : notConditional ( '&&'^ notConditional )* ;

notConditional
	:	'!'^ notConditional
	|	memberExpr
	;

exprOptions
	:	';'<OPTIONS>^ option ( ','! option )*;

option
@init {
	string id = input.LT(1).Text;
	string defVal;
	TemplateCompiler.defaultOptionValues.TryGetValue(id, out defVal);
	bool validOption = TemplateCompiler.supportedOptions.ContainsKey(id);
}
	:	ID
		{
		if ( !validOption ) {
            errMgr.CompiletimeError(ErrorType.NO_SUCH_OPTION, templateToken, $ID, $ID.text);
		}
		}
		(	'=' exprNoComma 					-> {validOption}? ^('=' ID exprNoComma)
												->
		|	{
			if ( defVal==null ) {
				errMgr.CompiletimeError(ErrorType.NO_DEFAULT_VALUE, templateToken, $ID);
			}
			}
												-> {validOption&&defVal!=null}?
												   ^(EQUALS["="] ID STRING[$ID,'"'+defVal+'"'])
												->
		)
	;

exprNoComma
	:	memberExpr
		(':'<MAP>^ mapTemplateRef)?
	;

expr : mapExpr ;

// more complicated than necessary to avoid backtracking, which ruins
// error handling
mapExpr
	:	memberExpr
		((c=','<ELEMENTS>^ memberExpr (c=','! memberExpr)*) ':'<ZIP>^ mapTemplateRef)?
		(':'<MAP>^ mapTemplateRef ({$c==null}? => ','! mapTemplateRef)*)*
	;

/**
expr:template(args)  apply template to expr
expr:{arg | ...}     apply subtemplate to expr
expr:(e)(args)       convert e to a string template name and apply to expr
*/
mapTemplateRef
	:	ID '('<INCLUDE>^ args ')'!
	|	subtemplate
	|	'('<INCLUDE_IND>^ mapExpr ')'! '('! argExprList? ')'!
	;

memberExpr
	:	includeExpr
		(	'.'<type=PROP;text="PROP">^ ID
		|	'.'<type=PROP_IND;text="PROP_IND">^ '('! mapExpr ')'!
		)*
	;

includeExpr
options {k=2;} // prevent full LL(*), which fails, falling back on k=1; need k=2
	:	{TemplateCompiler.funcs.ContainsKey(input.LT(1).Text)}? // predefined function
		ID '('<EXEC_FUNC>^ expr? ')'!
	|	'super'! '.'! ID '('<INCLUDE_SUPER>^ args ')'!
	|	ID '('<INCLUDE>^ args ')'!
	|	'@'! 'super'! '.'! ID '('<INCLUDE_SUPER_REGION>^ ')'!
	|	'@'! ID '('<INCLUDE_REGION>^ ')'!
	|	primary
	;

primary
	:	ID
	|	STRING
	|	TRUE
	|	FALSE
	|	subtemplate
	|	list
	|	{$conditional.Count > 0}? => '('! conditional ')'!
	|	lp='(' expr ')'
		(	'(' argExprList? ')'				-> ^(INCLUDE_IND[$lp] expr argExprList?)
		|										-> ^(TO_STR[$lp] expr)
		)
	;

args
	:	argExprList
	|	namedArg (','! namedArg)* (','! '...')?
	|	'...'
	|
	;

argExprList : arg (','! arg)*;

arg : exprNoComma ;

namedArg : ID '='^ arg;

list
@init {
bool empty = true;
bool finished = false;
}
	:	'['<LIST>^
			(	{!finished}? =>
				(	exprNoComma
					(	','!
					|	']'! {{finished=true;}}
					) {{empty=false;}}
				|	','<NULL> {{empty=false;}}
				|	{!empty}? => ']'<NULL> {{finished=true;}}
				|	']'! {{finished=true;}}
				)
			)+
	;
