/*
 * [The "BSD license"]
 * 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.
 */

grammar Group;

options {
	language=CSharp3;
}

tokens {
	COMMA=',';
	TRUE='true';
	FALSE='false';
	LBRACK='[';
	RBRACK=']';
}

@lexer::namespace{Antlr4.StringTemplate.Compiler}
@parser::namespace{Antlr4.StringTemplate.Compiler}

@lexer::header{
using Antlr4.StringTemplate.Misc;
}

@parser::header{
using Antlr4.StringTemplate.Misc;
using StringBuilder = System.Text.StringBuilder;
}

public
group[TemplateGroup group, string prefix]
@init {
GroupLexer lexer = (GroupLexer)input.TokenSource;
this._group = lexer.group = $group;
}
	:	oldStyleHeader?
		delimiters?
		(	'import' STRING {_group.ImportTemplates($STRING);}
		|	'import' // common error: name not in string
			{
			MismatchedTokenException e = new MismatchedTokenException(STRING, input);
			ReportError(e);
			}
			ID ('.' ID)* // might be a.b.c.d
		)*
		def[prefix]*
		EOF
	;

oldStyleHeader // ignore but lets us use this parser in AW for both v3 and v4
    :   'group' ID ( ':' ID )?
	    ( 'implements' ID (',' ID)* )?
	    ';'
	;

groupName returns [string name]
@init {StringBuilder buf = new StringBuilder();}
	:	a=ID {buf.Append($a.text);} ('.' a=ID {buf.Append($a.text);})*
	;

delimiters
	:	{string.Equals("delimiters", input.LT(1).Text)}? => ID open=STRING ',' close=STRING {_group.SetDelimiters($open, $close);}
	;

/** Match template and dictionary defs outside of (...)+ loop in group.
 *  The key is catching while still in the loop; must keep prediction of
 *  elements separate from "stay in loop" prediction.
 */
def[string prefix] : templateDef[prefix] | dictDef ;
	catch[RecognitionException] {
		// pretend we already saw an error here
		state.lastErrorIndex = input.Index;
		Error("garbled template definition starting at '"+input.LT(1).Text+"'");
	}

public
templateDef[string prefix]
@init {
    string template=null;
    int n=0; // num char to strip from left, right of template def
}
	:	(	'@' enclosing=ID '.' name=ID '(' ')'
		|	name=ID '(' formalArgs ')'
		)
	    '::='
	    {IToken templateToken = input.LT(1);}
	    (	STRING     {template=$STRING.text; n=1;}
	    |	BIGSTRING  {template=$BIGSTRING.text; n=2;}
	    |	BIGSTRING_NO_NL  {template=$BIGSTRING_NO_NL.text; n=2;}
	    |	{
	    	template = string.Empty;
	    	string msg = "missing template at '"+input.LT(1).Text+"'";
            NoViableAltException e = new NoViableAltException(string.Empty, 0, 0, input);
    	    _group.ErrorManager.GroupSyntaxError(ErrorType.SYNTAX_ERROR, SourceName, e, msg);
    	    }
	    )
	    {
		if ($name.index >= 0)
		{
			// if ID missing
			template = Utility.Strip(template, n);
			string templateName = $name.text;
			if (prefix.Length > 0) templateName = prefix + $name.text;
			string enclosingTemplateName = $enclosing.text;
			if (enclosingTemplateName != null && enclosingTemplateName.Length > 0 && prefix.Length > 0)
				enclosingTemplateName = prefix + enclosingTemplateName;

			_group.DefineTemplateOrRegion(templateName, enclosingTemplateName, templateToken,
	    								 template, $name, $formalArgs.args);
		}
	    }
	|   alias=ID '::=' target=ID  {_group.DefineTemplateAlias($alias, $target);}
	;

formalArgs returns[List<FormalArgument> args = new List<FormalArgument>()]
scope {
bool hasOptionalParameter;
}
	:	formalArg[$args] (',' formalArg[$args])*
	|
	;

formalArg[List<FormalArgument> args]
	:	ID
		(	'=' a=(STRING|ANONYMOUS_TEMPLATE|'true'|'false') {$formalArgs::hasOptionalParameter = true;}
		|	'=' a='[' ']' {$formalArgs::hasOptionalParameter = true;}
		|	{if ($formalArgs::hasOptionalParameter) _group.ErrorManager.GroupSyntaxError(ErrorType.REQUIRED_PARAMETER_AFTER_OPTIONAL, SourceName, input.LT(1));}
		)
		{$args.Add(new FormalArgument($ID.text, $a));}
    ;

/*
suffix returns [int cardinality=FormalArgument.REQUIRED]
    :   OPTIONAL
    |   STAR
    |   PLUS
	|
    ;
        */

dictDef
	:	ID '::=' dict
        {
        if ( _group.RawGetDictionary($ID.text)!=null ) {
			_group.ErrorManager.CompiletimeError(ErrorType.MAP_REDEFINITION, null, $ID);
        }
        else if ( _group.RawGetTemplate($ID.text)!=null ) {
			_group.ErrorManager.CompiletimeError(ErrorType.TEMPLATE_REDEFINITION_AS_MAP, null, $ID);
        }
        else {
            _group.DefineDictionary($ID.text, $dict.mapping);
        }
        }
	;

dict returns [IDictionary<string,object> mapping = new Dictionary<string, object>()]
	:   '[' dictPairs[mapping] ']'
	;

dictPairs[IDictionary<string,object> mapping]
	:	keyValuePair[mapping]
		(',' keyValuePair[mapping])* (',' defaultValuePair[mapping])?
	|	defaultValuePair[mapping]
	|	/* no dictionary entries */
	;
 	catch[RecognitionException] {
		Error("missing dictionary entry at '"+input.LT(1).Text+"'");
	}

defaultValuePair[IDictionary<string,object> mapping]
	:	'default' ':' keyValue {mapping[TemplateGroup.DefaultKey] = $keyValue.value;}
	;

keyValuePair[IDictionary<string,object> mapping]
	:	STRING ':' keyValue {mapping[Utility.ReplaceEscapes(Utility.Strip($STRING.text, 1))] = $keyValue.value;}
	;

keyValue returns [object value]
	:	a=BIGSTRING				{$value = _group.CreateSingleton($a);}
	|	a=BIGSTRING_NO_NL		{$value = _group.CreateSingleton($a);}
	|	a=ANONYMOUS_TEMPLATE	{$value = _group.CreateSingleton($a);}
	|	a=STRING				{$value = Utility.ReplaceEscapes(Utility.Strip($a.text, 1));}
	|	TRUE					{$value = true;}
	|	FALSE					{$value = false;}
	|	'[' ']'					{$value = new object[0];}
	|	{input.LT(1).Text.Equals("key")}?=> ID
								{$value = TemplateGroup.DictionaryKey;}
	;
	catch[RecognitionException] {
		Error("missing value for key at '"+input.LT(1).Text+"'");
	}

ID	:	('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'-'|'_')*
	;

STRING
	:	'"'
		(	'\\' '"'
		|	'\\' ~'"'
		|	{
			string msg = "\\n in string";
    		NoViableAltException e = new NoViableAltException(string.Empty, 0, 0, input);
			group.ErrorManager.GroupLexerError(ErrorType.SYNTAX_ERROR, SourceName, e, msg);
			}
			'\n'
		|	~('\\'|'"'|'\n')
		)*
		'"'
		{$text = $text.Replace("\\\"","\"");}
	;

/** Match <<...>> but also allow <<..<x>>> so we can have tag on end.
 *  Escapes: >\> means >> inside of <<...>>.
 *  Escapes: \>> means >> inside of <<...>> unless at end like <<...\>>>>.
 *  In that case, use <%..>>%> instead.
 */
BIGSTRING
	:	'<<'
		(	options {greedy=false;}
		:	'\\' '>'  // \> escape
		|	'\\' ~'>' // allow this but don't collapse in action
		|	~'\\'
		)*
		'>>'
		{
		string txt = Text;
		txt = Utility.ReplaceEscapedRightAngle(txt); // replace \> with > unless <\\>
		Text = txt;
		}
	;

BIGSTRING_NO_NL // same as BIGSTRING but means ignore newlines later
	:	'<%' .* '%>'
		// %\> is the escape to avoid end of string
		{
		Text = Text.Replace("\%\\>","\%>");
		}
	;

ANONYMOUS_TEMPLATE
    :	'{'
    	{
		IToken templateToken = new CommonToken(input, ANONYMOUS_TEMPLATE, 0, CharIndex, CharIndex);
		TemplateLexer lexer = new TemplateLexer(group.ErrorManager, input, templateToken, group.DelimiterStartChar, group.DelimiterStopChar);
		lexer.subtemplateDepth = 1;
		IToken t = lexer.NextToken();
		while ( lexer.subtemplateDepth>=1 || t.Type!=TemplateLexer.RCURLY )
		{
			if (t.Type == TemplateLexer.EOF_TYPE)
			{
				MismatchedTokenException e = new MismatchedTokenException('}', input);
				string msg = "missing final '}' in {...} anonymous template";
    			group.ErrorManager.GroupLexerError(ErrorType.SYNTAX_ERROR, SourceName, e, msg);
				break;
			}
			t = lexer.NextToken();
		}
		}
    	// don't match '}' here; our little {...} scanner loop matches it
    	// to terminate.
    ;

COMMENT
    :   '/*' .* '*/' {Skip();}
    ;

LINE_COMMENT
    :	'//' ~('\n'|'\r')* '\r'? '\n' {Skip();}
    ;

WS
	:	(' '|'\r'|'\t'|'\n') {Skip();}
	;
