/*
 [The "BSD license"]
 Copyright (c) 2011 Terence Parr
 All rights reserved.

 Grammar conversion to ANTLR v3 and C#:
 Copyright (c) 2011 Sam Harwell, Pixel Mine, Inc.
 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 ActionEvaluator;

options
{
	language=CSharp3;
	tokenVocab=Action;
	ASTLabelType=StringTemplateAST;
}

@header
{
using System.Linq;
using Antlr.Runtime.JavaExtensions;

using StringWriter = System.IO.StringWriter;
}

@namespace {Antlr3.ST.Language}
@ctorModifier{private}

public
action returns [int numCharsWritten=0]
	:	expr {$numCharsWritten = chunk.WriteAttribute(self,$expr.value,writer);}
	;

expr returns [object value]
	:	^(PLUS a=expr b=expr {$value = chunk.Add($a.value,$b.value);})
	|	templateApplication {$value = $templateApplication.value;}
	|	attribute {$value = $attribute.value;}
	|	templateInclude {$value = $templateInclude.value;}
	|	function {$value = $function.value;}
	|	list {$value = $list.value;}
	|	^(VALUE e=expr)
		// convert to string (force early eval)
		{
			StringWriter buf = new StringWriter();
			IStringTemplateWriter sw = self.Group.GetStringTemplateWriter(buf);
			int n = chunk.WriteAttribute(self,$e.value,sw);
			if ( n > 0 )
			{
				$value = buf.ToString();
			}
		}
	;

/** create a new list of expressions as a new multi-value attribute */
list returns [object value]
@init
{
	List<object> elements = new List<object>();
}
	:	^(	LIST
			(	expr
				{
					if ( $expr.value!=null )
					{
						elements.Add($expr.value);
					}
				}
			|	NOTHING
				{
					List<object> nullSingleton = new List<object>( new object[] {null} );
					elements.Add(nullSingleton.GetEnumerator()); // add a blank
				}
			)+
		)
		{$value = new Cat(elements);}
	;

templateInclude returns [object value]
@init
{
	StringTemplateAST args = null;
	string name = null;
}
	:	^(	INCLUDE
			//{value = chunk.GetTemplateInclude(self, name.getText(), #args);}
			(	id=ID a1=.
				{name=$id.text; args=$a1;}

			|	^( VALUE n=expr a2=. )
				{if ($n.value!=null) {name=$n.value.ToString();} args=$a2;}

			)
		)
		{
			if ( name!=null )
			{
				$value = chunk.GetTemplateInclude(self, name, args);
			}
		}
	;

/** Apply template(s) to an attribute; can be applied to another apply
 *  result.
 */
templateApplication returns [object value]
@init
{
	var templatesToApply = new List<StringTemplate>();
	List<object> attributes = new List<object>();
}
	:	^(	APPLY a=expr
			(template[templatesToApply])+
			{$value = chunk.ApplyListOfAlternatingTemplates(self,$a.value,templatesToApply);}
		)
	|	^(	MULTI_APPLY (a=expr {attributes.Add($a.value);} )+ COLON
			anon=ANONYMOUS_TEMPLATE
			{
				StringTemplate anonymous = $anon.StringTemplate;
				templatesToApply.Add(anonymous);
				$value = chunk.ApplyTemplateToListOfAttributes(self,
															  attributes,
															  $anon.StringTemplate);
			}
		)
	;

function returns [object value]
	:	^(	FUNCTION
			(	'first'		a=singleFunctionArg	{$value=chunk.First($a.value);}
			|	'rest'		a=singleFunctionArg	{$value=chunk.Rest($a.value);}
			|	'last'		a=singleFunctionArg	{$value=chunk.Last($a.value);}
			|	'length'	a=singleFunctionArg	{$value=chunk.Length($a.value);}
			|	'strip'		a=singleFunctionArg	{$value=chunk.Strip($a.value);}
			|	'trunc'		a=singleFunctionArg	{$value=chunk.Trunc($a.value);}
			)
		)
	;

singleFunctionArg returns [object value]
	:	^( SINGLEVALUEARG expr {$value = $expr.value;} )
	;

template[List<StringTemplate> templatesToApply]
@init
{
	IDictionary<string, object> argumentContext = null;
}
	:	^(	TEMPLATE
			(	ID args=. // don't eval argList now; must re-eval each iteration
				{
					string templateName = $ID.text;
					StringTemplateGroup group = self.Group;
					StringTemplate embedded = group.GetEmbeddedInstanceOf(self, templateName);
					if ( embedded!=null )
					{
						embedded.ArgumentsAST = $args;
						$templatesToApply.Add(embedded);
					}
				}

			|	anon=ANONYMOUS_TEMPLATE
				{
					StringTemplate anonymous = $anon.StringTemplate;
					// to properly see overridden templates, always set
					// anonymous' group to be self's group
					anonymous.Group = self.Group;
					$templatesToApply.Add(anonymous);
				}

			|	^(	VALUE n=expr args=.
					{
						StringTemplate embedded = null;
						if ( $n.value!=null )
						{
							string templateName = $n.value.ToString();
							StringTemplateGroup group = self.Group;
							embedded = group.GetEmbeddedInstanceOf(self, templateName);
							if ( embedded!=null )
							{
								embedded.ArgumentsAST = $args;
								$templatesToApply.Add(embedded);
							}
						}
					}
				)
			)
		)
	;

public
ifCondition returns [bool value]
	:	a=ifAtom {$value = chunk.TestAttributeTrue($a.value);}
	|	^(NOT a=ifAtom) {$value = !chunk.TestAttributeTrue($a.value);}
	;

ifAtom returns [object value]
	:	expr {$value = $expr.value;}
	;

attribute returns [object value]
@init
{
	object propName = null;
}
	:	^(	DOT obj=expr
			(	prop=ID {propName = $prop.text;}
				// don't force early eval here in case it's a map
				// we need the right type on the key.
				// E.g., <aMap.keys:{k|<k>:<aMap.(k)>}>
				// If aMap has Integer keys, can't convert k to string then lookup.
			|	^(VALUE e=expr) {if ($e.value!=null) {propName=$e.value;}}
			)
		)
		{$value = chunk.GetObjectProperty(self,$obj.value,propName);}

	|	ID
		{
		$value=self.GetAttribute($ID.text);
		}

	|	INT {$value=int.Parse($INT.text);}

	|	STRING
		{
		$value=$STRING.text;
		}

	|	ANONYMOUS_TEMPLATE
		{
			$value = $ANONYMOUS_TEMPLATE.text;
			if ($ANONYMOUS_TEMPLATE.text != null)
			{
				StringTemplate valueST =new StringTemplate(self.Group, $ANONYMOUS_TEMPLATE.text);
				valueST.EnclosingInstance = self;
				valueST.Name = "<anonymous template argument>";
				$value = valueST;
			}
		}
	;

/** self is assumed to be the enclosing context as foo(x=y) must find y in
 *  the template that encloses the ref to foo(x=y).  We must pass in
 *  the embedded template (the one invoked) so we can check formal args
 *  in rawSetArgumentAttribute.
 */
public
argList[StringTemplate embedded, Dictionary<string, object> initialContext]
	returns [Dictionary<string, object> argumentContext]
@init
{
	$argumentContext = $initialContext;
	if ( $argumentContext==null )
	{
		$argumentContext=new System.Collections.Generic.Dictionary<string, object>();
	}
}
	:	^( ARGS (argumentAssignment[$embedded,$argumentContext])* )
	|	singleTemplateArg[$embedded,$argumentContext]
	;

singleTemplateArg[StringTemplate embedded, IDictionary<string, object> argumentContext]
	:	^( SINGLEVALUEARG e=expr )
		{
			if ( $e.value!=null )
			{
				string soleArgName = null;
				// find the sole defined formal argument for embedded
				bool error = false;
				var formalArgs = $embedded.FormalArguments;
				if ( formalArgs!=null )
				{
					var argNames = formalArgs.Select( fa => fa.name ).ToArray();
					if ( argNames.Length==1 )
					{
						soleArgName = (string)argNames.ToArray()[0];
						//System.out.println("sole formal arg of "+$embedded.Name+" is "+soleArgName);
					}
					else
					{
						error=true;
					}
				}
				else
				{
					error=true;
				}
				if ( error )
				{
					self.Error("template "+$embedded.Name+
							   " must have exactly one formal arg in template context "+
							   self.GetEnclosingInstanceStackString());
				}
				else
				{
					self.RawSetArgumentAttribute($embedded,$argumentContext,soleArgName,$e.value);
				}
			}
		}
	;

argumentAssignment[StringTemplate embedded, IDictionary<string, object> argumentContext]
	:	^( ASSIGN arg=ID expr )
		{
			if ( $expr.value != null )
			{
				self.RawSetArgumentAttribute($embedded,$argumentContext,$arg.text,$expr.value);
			}
		}
	|	DOTDOTDOT {$embedded.SetPassThroughAttributes(true);}
	;

public
actionCompiled[System.Reflection.Emit.ILGenerator gen]
	:	exprCompiled[$gen]
		{
#if COMPILE_EXPRESSIONS
			EmitWriteAttribute($gen);
#endif
		}
	;

public
actionFunctional returns [System.Func<ASTExpr,StringTemplate,IStringTemplateWriter,int> func]
	:	exprFunctional
		{
#if COMPILE_EXPRESSIONS
			$func = (chunk,self,writer) => chunk.WriteAttribute(self,$exprFunctional.func(chunk,self,writer),writer);
#endif
		}
	;

public
ifConditionCompiled[System.Reflection.Emit.ILGenerator gen]
	:	ifAtomCompiled[$gen]
	|	^(NOT ifAtomCompiled[$gen])
		{
#if COMPILE_EXPRESSIONS
			EmitNot($gen);
#endif
		}
	;

public
ifConditionFunctional returns [System.Func<ASTExpr,StringTemplate,IStringTemplateWriter,bool> func]
	:	ifAtomFunctional
		{
#if COMPILE_EXPRESSIONS
			$func = $ifAtomFunctional.func;
#endif
		}
	|	^(NOT ifAtomFunctional)
		{
#if COMPILE_EXPRESSIONS
			$func = (chunk,self,writer) => !($ifAtomFunctional.func(chunk,self,writer));
#endif
		}
	;

ifAtomCompiled[System.Reflection.Emit.ILGenerator gen]
	:	exprCompiled[$gen]
		{
#if COMPILE_EXPRESSIONS
			EmitTest($gen);
#endif
		}
	;

ifAtomFunctional returns [System.Func<ASTExpr,StringTemplate,IStringTemplateWriter,bool> func]
	:	exprFunctional
		{
#if COMPILE_EXPRESSIONS
			$func = (chunk,self,writer) => chunk.TestAttributeTrue($exprFunctional.func(chunk,self,writer));
#endif
		}
	;

exprCompiled[System.Reflection.Emit.ILGenerator gen]
	:	^(PLUS exprCompiled[$gen] exprCompiled[$gen])
		{
#if COMPILE_EXPRESSIONS
			EmitAdd($gen);
#endif
		}
	|	templateApplicationCompiled[$gen]
	|	attributeCompiled[$gen]
	|	templateIncludeCompiled[$gen]
	|	functionCompiled[$gen]
	|	listCompiled[$gen]
	|	^(VALUE exprCompiled[$gen])
		{
#if COMPILE_EXPRESSIONS
			EmitWriteToString($gen);
#endif
		}
	;

exprFunctional returns [System.Func<ASTExpr,StringTemplate,IStringTemplateWriter,object> func]
	:	^(PLUS a=exprFunctional b=exprFunctional)
		{
#if COMPILE_EXPRESSIONS
			$func = (chunk,self,writer) => chunk.Add($a.func(chunk,self,writer),$b.func(chunk,self,writer));
#endif
		}
	|	templateApplicationFunctional
		{
#if COMPILE_EXPRESSIONS
			$func = $templateApplicationFunctional.func;
#endif
		}
	|	attributeFunctional
		{
#if COMPILE_EXPRESSIONS
			$func = $attributeFunctional.func;
#endif
		}
	|	templateIncludeFunctional
		{
#if COMPILE_EXPRESSIONS
			$func = $templateIncludeFunctional.func;
#endif
		}
	|	functionFunctional
		{
#if COMPILE_EXPRESSIONS
			$func = $functionFunctional.func;
#endif
		}
	|	listFunctional
		{
#if COMPILE_EXPRESSIONS
			$func = $listFunctional.func;
#endif
		}
	|	^(VALUE a=exprFunctional)
		{
#if COMPILE_EXPRESSIONS
			$func = (chunk,self,writer) =>
				{
					var value = $a.func(chunk,self,writer);
					StringWriter buf = new StringWriter();
					IStringTemplateWriter sw = self.Group.GetStringTemplateWriter( buf );
					int n = chunk.WriteAttribute( self, value, sw );
					if ( n > 0 )
						return buf.ToString();
					return value;
				};
#endif
		}
	;

templateApplicationCompiled[System.Reflection.Emit.ILGenerator gen]
@init
{
#if COMPILE_EXPRESSIONS
	System.Reflection.Emit.LocalBuilder templates = null;
	System.Reflection.Emit.LocalBuilder attributes = null;
#endif
}
	:	^(	APPLY exprCompiled[$gen]
			{
#if COMPILE_EXPRESSIONS
				templates = EmitCreateList<StringTemplate>($gen);
#endif
			}
			(	templateCompiled[$gen]
				{
#if COMPILE_EXPRESSIONS
					EmitAddValueToList($gen, templates);
#endif
				}
			)+
			{
#if COMPILE_EXPRESSIONS
				EmitApplyAlternatingTemplates( $gen, templates );
#endif
			}
		)
	|	^(	MULTI_APPLY
			{
#if COMPILE_EXPRESSIONS
				attributes = EmitCreateList<object>($gen);
#endif
			}
			(	exprCompiled[$gen]
				{
#if COMPILE_EXPRESSIONS
					EmitAddValueToList($gen, attributes);
#endif
				}
			)+
			COLON
			ANONYMOUS_TEMPLATE
			{
#if COMPILE_EXPRESSIONS
				EmitApplyAnonymousTemplate( $gen, $ANONYMOUS_TEMPLATE.StringTemplate, attributes );
#endif
			}
		)
	;

templateApplicationFunctional returns [System.Func<ASTExpr,StringTemplate,IStringTemplateWriter,object> func]
@init
{
	var  templateApplicators = new List<System.Func<ASTExpr,StringTemplate,IStringTemplateWriter,StringTemplate>>();
#if COMPILE_EXPRESSIONS
	List<System.Func<ASTExpr,StringTemplate,IStringTemplateWriter,object>> attributes = new List<System.Func<ASTExpr,StringTemplate,IStringTemplateWriter,object>>();
#endif
}
	:	^(	APPLY a=exprFunctional
			(templateFunctional[templateApplicators])+
			{
#if COMPILE_EXPRESSIONS
				$func = (chunk,self,writer) =>
					{
						var templatesToApply =	( from applicator in templateApplicators
												  let st = applicator(chunk,self,writer)
												  where st != null
												  select st )
												.ToList();
						return chunk.ApplyListOfAlternatingTemplates( self, $a.func(chunk,self,writer), templatesToApply );
					};
#endif
			}
		)
	|	^(	MULTI_APPLY
			(	a=exprFunctional
				{
#if COMPILE_EXPRESSIONS
					attributes.Add($a.func);
#endif
				}
			)+
			COLON ANONYMOUS_TEMPLATE
			{
#if COMPILE_EXPRESSIONS
				StringTemplate anonymous = $ANONYMOUS_TEMPLATE.StringTemplate;
				$func = (chunk,self,writer) =>
					{
						var attr =	from f in attributes
									select f(chunk,self,writer);
						return chunk.ApplyTemplateToListOfAttributes( self, attr.ToList(), anonymous );
					};
#endif
			}
		)
	;

attributeCompiled[System.Reflection.Emit.ILGenerator gen]
	:	^(	DOT exprCompiled[$gen]
			(	id=ID
				{
#if COMPILE_EXPRESSIONS
					EmitLoadString($gen,$id.text);
#endif
				}
			|	^(VALUE exprCompiled[$gen])
			)
			{
#if COMPILE_EXPRESSIONS
				EmitObjectProperty($gen);
#endif
			}
		)

	|	id=ID
		{
#if COMPILE_EXPRESSIONS
			EmitAttribute($gen,$id.text);
#endif
		}

	|	INT
		{
#if COMPILE_EXPRESSIONS
			EmitLoadIntAsObject($gen,int.Parse($INT.text));
#endif
		}

	|	STRING
		{
#if COMPILE_EXPRESSIONS
			EmitLoadString($gen,$STRING.text);
#endif
		}

	|	ANONYMOUS_TEMPLATE
		{
#if COMPILE_EXPRESSIONS
			EmitAnonymousTemplate($gen,$ANONYMOUS_TEMPLATE.text);
#endif
		}
	;

attributeFunctional returns [System.Func<ASTExpr,StringTemplate,IStringTemplateWriter,object> func]
	:	^(	DOT a=exprFunctional
			(	id=ID
				{
#if COMPILE_EXPRESSIONS
					string text = $id.text;
					$func = (chunk,self,writer) => chunk.GetObjectProperty( self, $a.func(chunk,self,writer), text );
#endif
				}
			|	^(VALUE b=exprFunctional)
				{
#if COMPILE_EXPRESSIONS
					$func = (chunk,self,writer) => chunk.GetObjectProperty( self, $a.func(chunk,self,writer), $b.func(chunk,self,writer) );
#endif
				}
			)
		)

	|	id=ID
		{
#if COMPILE_EXPRESSIONS
			string text = $id.text;
			$func = (chunk,self,writer) => self.GetAttribute( text );
#endif
		}

	|	INT
		{
#if COMPILE_EXPRESSIONS
			int i = int.Parse($INT.text);
			$func = (chunk,self,writer) => i;
#endif
		}

	|	STRING
		{
#if COMPILE_EXPRESSIONS
			string text = $STRING.text;
			$func = (chunk,self,writer) => text;
#endif
		}

	|	ANONYMOUS_TEMPLATE
		{
#if COMPILE_EXPRESSIONS
			string text = $ANONYMOUS_TEMPLATE.text;
			if ( text != null )
			{
				$func = (chunk,self,writer) =>
					new StringTemplate( self.Group, text )
					{
						EnclosingInstance = self,
						Name = "<anonymous template argument>"
					};
			}
			else
			{
				$func = (chunk,self,writer) => null;
			}
#endif
		}
	;

templateIncludeCompiled[System.Reflection.Emit.ILGenerator gen]
	:	^(	INCLUDE
			(	ID args=.
				{
#if COMPILE_EXPRESSIONS
					EmitLoadString( $gen, $ID.text );
#endif
				}
			|	^( VALUE exprCompiled[$gen] args=. )
			)
		)
		{
#if COMPILE_EXPRESSIONS
			EmitTemplateInclude( $gen, $args );
#endif
		}
	;

templateIncludeFunctional returns [System.Func<ASTExpr,StringTemplate,IStringTemplateWriter,object> func]
	:	^(	INCLUDE
			(	ID args=.
			|	^( VALUE exprFunctional args=. )
			)
		)
		{
#if COMPILE_EXPRESSIONS
			if ( $ID != null )
			{
				string name = $ID.text;
				if ( name != null )
				{
					$func = (chunk,self,writer) =>
						{
							return chunk.GetTemplateInclude( self, name, $args );
						};
				}
				else
				{
					$func = (chunk,self,writer) => null;
				}
			}
			else
			{
				$func = (chunk,self,writer) =>
					{
						var value = $exprFunctional.func(chunk,self,writer);
						if ( value == null )
							return null;
							
						string name = value.ToString();
						if ( name != null )
						{
							return chunk.GetTemplateInclude( self, name, $args );
						}
						return null;
					};
			}
#endif
		}
	;

functionCompiled[System.Reflection.Emit.ILGenerator gen]
	:	^(	FUNCTION
			(	'first'		singleFunctionArgCompiled[$gen]
				{
#if COMPILE_EXPRESSIONS
					EmitFunctionFirst($gen);
#endif
				}
			|	'rest'		singleFunctionArgCompiled[$gen]
				{
#if COMPILE_EXPRESSIONS
					EmitFunctionRest($gen);
#endif
				}
			|	'last'		singleFunctionArgCompiled[$gen]
				{
#if COMPILE_EXPRESSIONS
					EmitFunctionLast($gen);
#endif
				}
			|	'length'	singleFunctionArgCompiled[$gen]
				{
#if COMPILE_EXPRESSIONS
					EmitFunctionLength($gen);
#endif
				}
			|	'strip'		singleFunctionArgCompiled[$gen]
				{
#if COMPILE_EXPRESSIONS
					EmitFunctionStrip($gen);
#endif
				}
			|	'trunc'		singleFunctionArgCompiled[$gen]
				{
#if COMPILE_EXPRESSIONS
					EmitFunctionTrunc($gen);
#endif
				}
			)
		)
	;

functionFunctional returns [System.Func<ASTExpr,StringTemplate,IStringTemplateWriter,object> func]
	:	^(	FUNCTION
			(	'first'		a=singleFunctionArgFunctional
				{
#if COMPILE_EXPRESSIONS
					$func = (chunk,self,writer) => chunk.First( $a.func(chunk,self,writer) );
#endif
				}
			|	'rest'		a=singleFunctionArgFunctional
				{
#if COMPILE_EXPRESSIONS
					$func = (chunk,self,writer) => chunk.Rest( $a.func(chunk,self,writer) );
#endif
				}
			|	'last'		a=singleFunctionArgFunctional
				{
#if COMPILE_EXPRESSIONS
					$func = (chunk,self,writer) => chunk.Last( $a.func(chunk,self,writer) );
#endif
				}
			|	'length'	a=singleFunctionArgFunctional
				{
#if COMPILE_EXPRESSIONS
					$func = (chunk,self,writer) => chunk.Length( $a.func(chunk,self,writer) );
#endif
				}
			|	'strip'		a=singleFunctionArgFunctional
				{
#if COMPILE_EXPRESSIONS
					$func = (chunk,self,writer) => chunk.Strip( $a.func(chunk,self,writer) );
#endif
				}
			|	'trunc'		a=singleFunctionArgFunctional
				{
#if COMPILE_EXPRESSIONS
					$func = (chunk,self,writer) => chunk.Trunc( $a.func(chunk,self,writer) );
#endif
				}
			)
		)
	;

listCompiled[System.Reflection.Emit.ILGenerator gen]
@init
{
#if COMPILE_EXPRESSIONS
	var elements = EmitCreateList<object>($gen);
#endif
}
	:	^(	LIST
			(	exprCompiled[$gen]
				{
#if COMPILE_EXPRESSIONS
					EmitAddValueToList($gen, elements);
#endif
				}
			|	NOTHING
				{
#if COMPILE_EXPRESSIONS
					EmitAddNothingToList($gen, elements);
#endif
				}
			)+
		)
		{
#if COMPILE_EXPRESSIONS
			EmitCatList($gen,elements);
#endif
		}
	;

listFunctional returns [System.Func<ASTExpr,StringTemplate,IStringTemplateWriter,object> func]
@init
{
	List<System.Func<ASTExpr,StringTemplate,IStringTemplateWriter,object>> elements = new List<System.Func<ASTExpr,StringTemplate,IStringTemplateWriter,object>>();
}
	:	^(	LIST
			(	exprFunctional
				{
#if COMPILE_EXPRESSIONS
					if ( $exprFunctional.func != null )
						elements.Add($exprFunctional.func);
#endif
				}
			|	NOTHING
				{
#if COMPILE_EXPRESSIONS
					elements.Add( (chunk,self,writer) => new List<object>( new object[] { null } ).GetEnumerator() );
#endif
				}
			)+
		)
		{
#if COMPILE_EXPRESSIONS
			$func = (chunk,self,writer) =>
				new Cat(from f in elements
						let value = f(chunk,self,writer)
						where value != null
						select value
						);
#endif
		}
	;

singleFunctionArgCompiled[System.Reflection.Emit.ILGenerator gen]
	:	^( SINGLEVALUEARG exprCompiled[$gen] )
	;

singleFunctionArgFunctional returns [System.Func<ASTExpr,StringTemplate,IStringTemplateWriter,object> func]
	:	^( SINGLEVALUEARG exprFunctional {$func = $exprFunctional.func;} )
	;

templateCompiled[System.Reflection.Emit.ILGenerator gen]
	:	^(	TEMPLATE
			(	ID . // don't eval argList now; must re-eval each iteration
				{
#if COMPILE_EXPRESSIONS
					throw new System.NotImplementedException();
#endif
				}

			|	ANONYMOUS_TEMPLATE
				{
#if COMPILE_EXPRESSIONS
					throw new System.NotImplementedException();
#endif
				}

			|	^(	VALUE exprCompiled[$gen] .
					{
#if COMPILE_EXPRESSIONS
						throw new System.NotImplementedException();
#endif
					}
				)
			)
		)
	;

templateFunctional[List<System.Func<ASTExpr,StringTemplate,IStringTemplateWriter,StringTemplate>> templateApplicators]
@init
{
	IDictionary<string, object> argumentContext = null;
}
	:	^(	TEMPLATE
			(	ID args=. // don't eval argList now; must re-eval each iteration
				{
#if COMPILE_EXPRESSIONS
					System.Func<ASTExpr,StringTemplate,IStringTemplateWriter,StringTemplate> func =
						(chunk,self,writer) =>
						{
							string templateName = $ID.text;
							StringTemplateGroup group = self.Group;
							StringTemplate embedded = group.GetEmbeddedInstanceOf(self, templateName);
							if ( embedded!=null )
							{
								embedded.ArgumentsAST = $args;
							}
							return embedded;
						};
					$templateApplicators.Add( func );
#endif
				}

			|	anon=ANONYMOUS_TEMPLATE
				{
#if COMPILE_EXPRESSIONS
					System.Func<ASTExpr,StringTemplate,IStringTemplateWriter,StringTemplate> func =
						(chunk,self,writer) =>
						{
							StringTemplate anonymous = $anon.StringTemplate;
							// to properly see overridden templates, always set
							// anonymous' group to be self's group
							anonymous.Group = self.Group;
							return anonymous;
						};
					$templateApplicators.Add( func );
#endif
				}

			|	^(	VALUE n=expr args=.
					{
#if COMPILE_EXPRESSIONS
						System.Func<ASTExpr,StringTemplate,IStringTemplateWriter,StringTemplate> func =
							(chunk,self,writer) =>
							{
								StringTemplate embedded = null;
								if ( $n.value!=null )
								{
									string templateName = $n.value.ToString();
									StringTemplateGroup group = self.Group;
									embedded = group.GetEmbeddedInstanceOf(self, templateName);
									if ( embedded!=null )
									{
										embedded.ArgumentsAST = $args;
									}
								}
								return embedded;
							};
						$templateApplicators.Add( func );
#endif
					}
				)
			)
		)
	;
