/*
 * [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.
 */

tree grammar CodeGenerator;

options {
	language=CSharp3;
	tokenVocab=TemplateParser;
	ASTLabelType=CommonTree;
}

@header{
using Antlr4.StringTemplate.Misc;
}

@namespace{Antlr4.StringTemplate.Compiler}

@ctorModifier{private}

templateAndEOF : template[null,null] EOF; // hush warning; ignore

public
template[string name, List<FormalArgument> args] returns [CompiledTemplate impl]
scope {
    CompilationState state; // automatically get a new state pointer per invocation
}
@init {
 	$template::state = new CompilationState(errMgr, name, input.TokenStream);
	$impl = $template::state.impl;
 	if ($template.Count == 1) outermostImpl = $impl;
	$impl.DefineFormalArguments($args); // make sure args are defined prior to compilation
	if ( name!=null && name.StartsWith(TemplateCompiler.SubtemplatePrefix) ) {
	    $impl.AddArgument(new FormalArgument("i"));
	    $impl.AddArgument(new FormalArgument("i0"));
    }
	$impl.Template = _template; // always forget the entire template; char indexes are relative to it
}
	:	chunk
		{ // finish off the CompiledTemplate result
        if ( $template::state.stringtable!=null ) $impl.strings = $template::state.stringtable.ToArray();
        $impl.codeSize = $template::state.ip;
		}
	;

chunk
	:	element*
	;

element
	:	^(INDENT compoundElement[$INDENT])
	|	compoundElement[null]
	|	^(INDENT {Indent($INDENT);} singleElement {Dedent();})
	|	INDENT {Indent($INDENT); Dedent();}
	|	singleElement
	;

singleElement
	:	exprElement
	|	TEXT
		{
		if ($TEXT.text.Length > 0)
			emit1($TEXT,Bytecode.INSTR_WRITE_STR, $TEXT.text);
		}
	|	NEWLINE {emit($NEWLINE, Bytecode.INSTR_NEWLINE);}
	;

compoundElement[CommonTree indent]
	:	ifstat[$indent]
	|	region[$indent]
	;

exprElement
@init { Bytecode op = Bytecode.INSTR_WRITE; }
	:	^( EXPR expr (exprOptions {op=Bytecode.INSTR_WRITE_OPT;})? )
		{emit($EXPR, op);}
	;

region[CommonTree indent] returns [string name]
@init {if ($indent != null) Indent($indent);}
@after {if ($indent != null) Dedent();}
	:	^(	REGION ID
			{$name = TemplateGroup.GetMangledRegionName(outermostTemplateName, $ID.text);}
			template[$name,null]
			{
			CompiledTemplate sub = $template.impl;
	        sub.IsRegion = true;
	        sub.RegionDefType = Template.RegionType.Embedded;
			sub.TemplateDefStartToken = $ID.Token;
			outermostImpl.AddImplicitlyDefinedTemplate(sub);
			emit2($start, Bytecode.INSTR_NEW, $region.name, 0);
			emit($start, Bytecode.INSTR_WRITE);
			}
		 )
	;

subtemplate returns [string name, int nargs]
@init {
    $name = TemplateCompiler.GetNewSubtemplateName();
	List<FormalArgument> args = new List<FormalArgument>();
}
	:	^(	SUBTEMPLATE
			(^(ARGS (ID {args.Add(new FormalArgument($ID.text));})+))*
			{$nargs = args.Count;}
			template[$name,args]
			{
			CompiledTemplate sub = $template.impl;
			sub.IsAnonSubtemplate = true;
			sub.TemplateDefStartToken = $SUBTEMPLATE.Token;
			sub.Ast = $SUBTEMPLATE;
			sub.Ast.SetUnknownTokenBoundaries();
			sub.Tokens = input.TokenStream;
			outermostImpl.AddImplicitlyDefinedTemplate(sub);
			}
		 )
	;

ifstat[CommonTree indent]
@init {
	/* Tracks address of branch operand (in code block).  It's how
	 * we backpatch forward references when generating code for IFs.
	 */
	int prevBranchOperand = -1;
	/* Branch instruction operands that are forward refs to end of IF.
	 * We need to update them once we see the endif.
	 */
	List<int> endRefs = new List<int>();
	if ($indent != null) Indent($indent);
}
@after {
	if ($indent != null) Dedent();
}
	:	^(	i='if' conditional
			{
	        prevBranchOperand = address()+1;
	        emit1($i,Bytecode.INSTR_BRF, -1); // write placeholder as branch target
			}
			chunk
			(	^(eif='elseif'
				{
				endRefs.Add(address()+1);
				emit1($eif,Bytecode.INSTR_BR, -1); // br end
				// update previous branch instruction
				write(prevBranchOperand, (short)address());
				prevBranchOperand = -1;
				}
				ec=conditional
				{
		       	prevBranchOperand = address()+1;
		       	// write placeholder as branch target
		       	emit1($ec.start, Bytecode.INSTR_BRF, -1);
				}
				chunk
				)
			)*
			(	^(	el='else'
					{
					endRefs.Add(address()+1);
					emit1($el, Bytecode.INSTR_BR, -1); // br end
					// update previous branch instruction
					write(prevBranchOperand, (short)address());
					prevBranchOperand = -1;
					}
					chunk
				 )
			)?
		 )
		{
		if ( prevBranchOperand>=0 ) {
			write(prevBranchOperand, (short)address());
		}
        foreach (int opnd in endRefs) write(opnd, (short)address());
		}
	;

conditional
	:	^(OR conditional conditional)		{emit($OR, Bytecode.INSTR_OR);}
	|	^(AND conditional conditional)		{emit($AND, Bytecode.INSTR_AND);}
	|	^(BANG conditional)					{emit($BANG, Bytecode.INSTR_NOT);}
	|	expr // not all expr are valid, but reuse code gen (parser restricts syntax)
	;

exprOptions : {emit($start, Bytecode.INSTR_OPTIONS);} ^(OPTIONS option*) ;

option : ^('=' ID expr) {setOption($ID);} ;

expr
@init {int nt = 0, ne = 0;}
	:	^(ZIP ^(ELEMENTS (expr {ne++;})+) mapTemplateRef[ne])
		{emit1($ZIP, Bytecode.INSTR_ZIP_MAP, ne);}
	|	^(MAP expr (mapTemplateRef[1] {nt++;})+)
		{
		if ( nt>1 ) emit1($MAP, nt>1?Bytecode.INSTR_ROT_MAP:Bytecode.INSTR_MAP, nt);
		else emit($MAP, Bytecode.INSTR_MAP);
		}
	|	prop
	|	includeExpr
	;

prop:	^(PROP expr ID)						{emit1($PROP, Bytecode.INSTR_LOAD_PROP, $ID.text);}
	|	^(PROP_IND expr expr)				{emit($PROP_IND, Bytecode.INSTR_LOAD_PROP_IND);}
	;
	
mapTemplateRef[int num_exprs]
	:	^(	INCLUDE ID
			{for (int i=1; i<=$num_exprs; i++) emit($INCLUDE,Bytecode.INSTR_NULL);}
			args
		)
		{
		if ( $args.passThru ) emit1($start, Bytecode.INSTR_PASSTHRU, $ID.text);
		if ( $args.namedArgs ) emit1($INCLUDE, Bytecode.INSTR_NEW_BOX_ARGS, $ID.text);
		else emit2($INCLUDE, Bytecode.INSTR_NEW, $ID.text, $args.n+$num_exprs);
		}
	|	subtemplate
		{
		if ($subtemplate.nargs != $num_exprs) {
            errMgr.CompiletimeError(ErrorType.ANON_ARGUMENT_MISMATCH, templateToken, $subtemplate.start.Token, $subtemplate.nargs, $num_exprs);
		}
		for (int i=1; i<=$num_exprs; i++) emit($subtemplate.start,Bytecode.INSTR_NULL);
        emit2($subtemplate.start, Bytecode.INSTR_NEW,
	              $subtemplate.name,
	              $num_exprs);
		}

	|	^(	INCLUDE_IND expr
			{
			emit($INCLUDE_IND,Bytecode.INSTR_TOSTR);
			for (int i=1; i<=$num_exprs; i++) emit($INCLUDE_IND,Bytecode.INSTR_NULL);
			}
			args
			{emit1($INCLUDE_IND, Bytecode.INSTR_NEW_IND, $args.n+$num_exprs);}
		 )
	;

includeExpr
	:	^(EXEC_FUNC ID expr?)		{func($ID);}
	|	^(INCLUDE ID args)
		{
		if ( $args.passThru ) emit1($start, Bytecode.INSTR_PASSTHRU, $ID.text);
		if ( $args.namedArgs ) emit1($INCLUDE, Bytecode.INSTR_NEW_BOX_ARGS, $ID.text);
		else emit2($INCLUDE, Bytecode.INSTR_NEW, $ID.text, $args.n);
		}
	|	^(INCLUDE_SUPER ID args)
		{
		if ( $args.passThru ) emit1($start, Bytecode.INSTR_PASSTHRU, $ID.text);
		if ( $args.namedArgs ) emit1($INCLUDE_SUPER, Bytecode.INSTR_SUPER_NEW_BOX_ARGS, $ID.text);
		else emit2($INCLUDE_SUPER, Bytecode.INSTR_SUPER_NEW, $ID.text, $args.n);
		}
	|	^(INCLUDE_REGION ID)		{
									CompiledTemplate impl =
										TemplateCompiler.DefineBlankRegion(outermostImpl, $ID.Token);
									emit2($INCLUDE_REGION,Bytecode.INSTR_NEW,impl.Name,0);
									}
	|	^(INCLUDE_SUPER_REGION ID)	{
									string mangled = TemplateGroup.GetMangledRegionName(outermostImpl.Name, $ID.Text);
									emit2($INCLUDE_SUPER_REGION,Bytecode.INSTR_SUPER_NEW,mangled,0);
									}
	|	primary
	;

primary
	:	ID				{refAttr($ID);}
	|	STRING			{emit1($STRING,Bytecode.INSTR_LOAD_STR, Utility.Strip($STRING.text,1));}	
	|	TRUE			{emit($TRUE, Bytecode.INSTR_TRUE);}
	|	FALSE			{emit($FALSE, Bytecode.INSTR_FALSE);}
	|	subtemplate		// push a subtemplate but ignore args since we can't pass any to it here
		                {emit2($start,Bytecode.INSTR_NEW, $subtemplate.name, 0);}
	|	list
	|	^(	INCLUDE_IND	
			expr 		{emit($INCLUDE_IND, Bytecode.INSTR_TOSTR);}
			args		{emit1($INCLUDE_IND, Bytecode.INSTR_NEW_IND, $args.n);}
		 )
	|	^(TO_STR expr)	{emit($TO_STR, Bytecode.INSTR_TOSTR);}
	;

arg : expr ;

args returns [int n=0, bool namedArgs=false, bool passThru]
	:	( arg {$n++;} )+
	|	{emit($args.start, Bytecode.INSTR_ARGS); $namedArgs=true;}
		(	^(eq='=' ID expr)
			{$n++; emit1($eq, Bytecode.INSTR_STORE_ARG, defineString($ID.text));}
		)+
		('...' {$passThru=true;})?
	|	'...' {$passThru=true; emit($args.start, Bytecode.INSTR_ARGS); $namedArgs=true;}
	|
 	;

list:	{emit($start, Bytecode.INSTR_LIST);}
		^(LIST (listElement {emit($listElement.start, Bytecode.INSTR_ADD);})* ) 
	;

listElement : expr | NULL {emit($NULL,Bytecode.INSTR_NULL);} ;
