package railo.transformer.cfml.script;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import railo.commons.lang.StringUtil;
import railo.commons.lang.types.RefBoolean;
import railo.commons.lang.types.RefBooleanImpl;
import railo.runtime.Component;
import railo.runtime.exp.TemplateException;
import railo.runtime.functions.system.CFFunction;
import railo.runtime.type.util.ArrayUtil;
import railo.runtime.type.util.ComponentUtil;
import railo.transformer.bytecode.Body;
import railo.transformer.bytecode.BodyBase;
import railo.transformer.bytecode.BytecodeException;
import railo.transformer.bytecode.FunctionBody;
import railo.transformer.bytecode.Position;
import railo.transformer.bytecode.ScriptBody;
import railo.transformer.bytecode.Statement;
import railo.transformer.bytecode.cast.CastBoolean;
import railo.transformer.bytecode.cast.CastOther;
import railo.transformer.bytecode.cast.CastString;
import railo.transformer.bytecode.expression.ClosureAsExpression;
import railo.transformer.bytecode.expression.ExprBoolean;
import railo.transformer.bytecode.expression.Expression;
import railo.transformer.bytecode.expression.var.Variable;
import railo.transformer.bytecode.literal.LitBoolean;
import railo.transformer.bytecode.literal.LitString;
import railo.transformer.bytecode.statement.Condition;
import railo.transformer.bytecode.statement.Condition.Pair;
import railo.transformer.bytecode.statement.DoWhile;
import railo.transformer.bytecode.statement.ExpressionAsStatement;
import railo.transformer.bytecode.statement.For;
import railo.transformer.bytecode.statement.ForEach;
import railo.transformer.bytecode.statement.Return;
import railo.transformer.bytecode.statement.Switch;
import railo.transformer.bytecode.statement.TryCatchFinally;
import railo.transformer.bytecode.statement.While;
import railo.transformer.bytecode.statement.tag.Attribute;
import railo.transformer.bytecode.statement.tag.Tag;
import railo.transformer.bytecode.statement.tag.TagOther;
import railo.transformer.bytecode.statement.tag.TagParam;
import railo.transformer.bytecode.statement.udf.Closure;
import railo.transformer.bytecode.statement.udf.Function;
import railo.transformer.bytecode.statement.udf.FunctionImpl;
import railo.transformer.cfml.evaluator.EvaluatorException;
import railo.transformer.cfml.evaluator.impl.ProcessingDirectiveException;
import railo.transformer.cfml.expression.AbstrCFMLExprTransformer;
import railo.transformer.cfml.tag.CFMLTransformer;
import railo.transformer.library.function.FunctionLibFunction;
import railo.transformer.library.tag.TagLibException;
import railo.transformer.library.tag.TagLibTag;
import railo.transformer.library.tag.TagLibTagAttr;
import railo.transformer.library.tag.TagLibTagScript;
import railo.transformer.util.CFMLString;


/**	
 * Innerhalb des Tag script kann in CFML eine eigene Scriptsprache verwendet werden, 
 * welche sich an Javascript orientiert. 
 * Da der data.cfml Transformer keine Spezialfaelle zulaesst, 
 * also Tags einfach anhand der eingegeben TLD einliest und transformiert, 
 * aus diesem Grund wird der Inhalt des Tag script einfach als Zeichenkette eingelesen.
 * Erst durch den Evaluator (siehe 3.3), der fuer das Tag script definiert ist, 
 * wird der Inhalt des Tag script uebersetzt.
 * 
 */
public abstract class AbstrCFMLScriptTransformer extends AbstrCFMLExprTransformer {

	private static final String[] IGNORE_LIST_COMPONENT = new String[]{
		"output","synchronized","extends","implements","displayname","style","persistent","accessors"};
	private static final String[] IGNORE_LIST_INTERFACE = new String[]{
		"output","extends","displayname","style","persistent","accessors"};
	private static final String[] IGNORE_LIST_PROPERTY = new String[]{
		"default","fieldtype","name","type","persistent","remotingFetch","column","generator","length",
		"ormtype","params","unSavedValue","dbdefault","formula","generated","insert","optimisticlock",
		"update","notnull","precision","scale","unique","uniquekey","source"
	};
 

	private static EndCondition SEMI_BLOCK=new EndCondition() {
		public boolean isEnd(ExprData data) {
			return data.cfml.isCurrent('{') || data.cfml.isCurrent(';');
		}
	};
	private static EndCondition SEMI=new EndCondition() {
		public boolean isEnd(ExprData data) {
			return data.cfml.isCurrent(';');
		}
	};
	private static EndCondition COMMA_ENDBRACKED=new EndCondition() {
		public boolean isEnd(ExprData data) {
			return data.cfml.isCurrent(',') || data.cfml.isCurrent(')');
		}
	};
	
	
	
	

	private short ATTR_TYPE_NONE=TagLibTagAttr.SCRIPT_SUPPORT_NONE;
	private short ATTR_TYPE_OPTIONAL=TagLibTagAttr.SCRIPT_SUPPORT_OPTIONAL;
	private short ATTR_TYPE_REQUIRED=TagLibTagAttr.SCRIPT_SUPPORT_REQUIRED;
	
	public static class ComponentTemplateException extends TemplateException {
		private static final long serialVersionUID = -8103635220891288231L;
		
		private TemplateException te;

		public ComponentTemplateException(TemplateException te){
			super(te.getPageSource(),te.getLine(),0,te.getMessage());
			this.te=te;
			
		}

		/**
		 * @return the te
		 */
		public TemplateException getTemplateException() {
			return te;
		}
	}

	private static final Expression NULL = LitString.toExprString("NULL"); 
	private static final Attribute ANY = new Attribute(false,"type",LitString.toExprString("any"),"string"); 

	
	/** 
	 * Liest saemtliche Statements des CFScriptString ein. 
	 * <br />
	 * EBNF:<br />
	 * <code>{statement spaces};</code>
	 * @return a statement
	 * @throws TemplateException
	 */
	protected final Body statements(ExprData data) throws TemplateException {
		ScriptBody body=new ScriptBody();
		
		statements(data,body,true);
	return body;
	}
	
	/**
	 * Liest saemtliche Statements des CFScriptString ein. 
	 * <br />
	 * EBNF:<br />
	 * <code>{statement spaces};</code>
	 * @param parent Übergeornetes Element dem das Statement zugewiesen wird.
	 * @param isRoot befindet sich der Parser im root des data.cfml Docs
	 * @throws TemplateException
	 */
	private final void statements(ExprData data,Body body, boolean isRoot) throws TemplateException {
		do {
			if(isRoot && isFinish(data))return;
			statement(data,body);
			comments(data);
		}
		while(data.cfml.isValidIndex() && !data.cfml.isCurrent('}'));
	}
	
	/** 
	 * Liest ein einzelnes Statement ein (if,for,while usw.).
	 * <br />
	 * EBNF:<br />
	 * <code>";" | "if" spaces "(" ifStatement | "function " funcStatement |  "while" spaces "(" whileStatement  |  
			  "do" spaces "{" doStatement  | "for" spaces "(" forStatement | "return" returnStatement | 
			  "break" breakStatement | "continue" continueStatement | "/*" comment | expressionStatement;</code>
	 * @param parent Übergeornetes Element dem das Statement zugewiesen wird.
	 * @throws TemplateException
	 */
	private final void statement(ExprData data,Body parent) throws TemplateException {
		statement(data, parent, data.context);
	}
	private boolean statement(ExprData data,Body parent,short context) throws TemplateException {
		short prior=data.context;
		data.context=context;
		comments(data);
		Statement child=null;
		if(data.cfml.forwardIfCurrent(';')){return true;}
		else if((child=ifStatement(data))!=null) 				parent.addStatement(child);
		else if((child=propertyStatement(data,parent))!=null)	parent.addStatement(child);
		else if((child=paramStatement(data,parent))!=null)	parent.addStatement(child);
		else if((child=funcStatement(data,parent))!=null)		parent.addStatement(child);
		else if((child=whileStatement(data))!=null) 			parent.addStatement(child);
		else if((child=doStatement(data))!=null) 				parent.addStatement(child);
		else if((child=forStatement(data))!=null) 				parent.addStatement(child);
		else if((child=returnStatement(data))!=null) 			parent.addStatement(child);
		else if((child=switchStatement(data))!=null) 			parent.addStatement(child);
		else if((child=tryStatement(data))!=null) 				parent.addStatement(child);
		else if((child=tagStatement(data,parent))!=null)	parent.addStatement(child);
		else if(block(data,parent)){}
		else parent.addStatement(expressionStatement(data,parent));
		data.docComment=null;
		data.context=prior;
		
		return false;
	}
	
	/**
	 * Liest ein if Statement ein.
	 * <br />
	 * EBNF:<br />
	 * <code>spaces condition spaces ")" spaces block {"else if" spaces "(" elseifStatement spaces }
			 [("else"  spaces "(" | "else ") elseStatement spaces];</code>
	 * @return if Statement
	 * @throws TemplateException
	 */
	private final Statement ifStatement(ExprData data) throws TemplateException {
		if(!data.cfml.forwardIfCurrent("if",'(')) return null;
		
		
		Position line = data.cfml.getPosition();
		
		Body body=new BodyBase();
		Condition cont=new Condition(condition(data),body,line,null);
		
		if(!data.cfml.forwardIfCurrent(')')) throw new TemplateException(data.cfml,"if statement must end with a [)]");
		// ex block
		statement(data,body,CTX_IF);
		// else if
		comments(data);
		while(elseifStatement(data,cont)) {
			comments(data);
		}
		// else
		 if(elseStatement(data,cont)) {
			comments(data);
		 }

		cont.setEnd(data.cfml.getPosition());
		return cont;
	}
	
	/**
	 * Liest ein else if Statement ein.
	 * <br />
	 * EBNF:<br />
	 * <code>spaces condition spaces ")" spaces block;</code>
	 * @return else if Statement
	 * @throws TemplateException
	 */
	private  final boolean elseifStatement(ExprData data,Condition cont) throws TemplateException {
		int pos=data.cfml.getPos();
		if(!data.cfml.forwardIfCurrent("else")) return false;
		
		comments(data);
		if(!data.cfml.forwardIfCurrent("if",'(')) {
			data.cfml.setPos(pos);
			return false;
		}
			
		Position line = data.cfml.getPosition();
		Body body=new BodyBase();
		Pair pair = cont.addElseIf(condition(data), body, line,null);

		if(!data.cfml.forwardIfCurrent(')'))
			throw new TemplateException(data.cfml,"else if statement must end with a [)]");
		// ex block
		statement(data,body,CTX_ELSE_IF);
		pair.end=data.cfml.getPosition();
		return true;
	}
	
	/**
	 * Liest ein else Statement ein.
	 * <br />
	 * EBNF:<br />
	 * <code>block;</code>
	 * @return else Statement
	 * @throws TemplateException
	 * 
	 */
	private final boolean elseStatement(ExprData data,Condition cont) throws TemplateException {
		if(!data.cfml.forwardIfCurrent("else",'{') && !data.cfml.forwardIfCurrent("else ") && !data.cfml.forwardIfCurrent("else",'/')) 
			return false;

		// start (
		data.cfml.previous();
		// ex block
		Body body=new BodyBase();
		Pair p = cont.setElse(body, data.cfml.getPosition(),null);
		statement(data,body,CTX_ELSE);
		p.end=data.cfml.getPosition();
		return true;
	}
	

	private final boolean finallyStatement(ExprData data,TryCatchFinally tcf) throws TemplateException {
		if(!data.cfml.forwardIfCurrent("finally",'{') && !data.cfml.forwardIfCurrent("finally ") && !data.cfml.forwardIfCurrent("finally",'/')) 
			return false;

		// start (
		data.cfml.previous();
		// ex block
		Body body=new BodyBase();
		tcf.setFinally(body, data.cfml.getPosition());
		statement(data,body,CTX_FINALLY);
		
		return true;
	}
	
	/**
	 * Liest ein while Statement ein.
	 * <br />
	 * EBNF:<br />
	 * <code>spaces condition spaces ")" spaces block;</code>
	 * @return while Statement
	 * @throws TemplateException
	 */
	private final While whileStatement(ExprData data) throws TemplateException {
		int pos=data.cfml.getPos();
		
		// id
		String id=variableDec(data, false);
		if(id==null) {
			data.cfml.setPos(pos);
			return null;
		}
		if(id.equalsIgnoreCase("while")){
			id=null;
			data.cfml.removeSpace();
			if(!data.cfml.forwardIfCurrent('(')){
				data.cfml.setPos(pos);
				return null;
			}	
		}
		else {
			data.cfml.removeSpace();
			if(!data.cfml.forwardIfCurrent(':')){
				data.cfml.setPos(pos);
				return null;
			}
			data.cfml.removeSpace();
			
			if(!data.cfml.forwardIfCurrent("while",'(')){
				data.cfml.setPos(pos);
				return null;
			}
		}
		
		Position line = data.cfml.getPosition();
		Body body=new BodyBase();
		While whil=new While(condition(data),body,line,null,id);
		
		if(!data.cfml.forwardIfCurrent(')'))
			throw new TemplateException(data.cfml,"while statement must end with a [)]");
		
		statement(data,body,CTX_WHILE);
		whil.setEnd(data.cfml.getPosition());
		return whil;
	}
	
	/**
	 * Liest ein switch Statment ein
	 * @return switch Statement
	 * @throws TemplateException
	 */
	private final Switch switchStatement(ExprData data) throws TemplateException {
		if(!data.cfml.forwardIfCurrent("switch",'('))
			return null;
		
		Position line = data.cfml.getPosition();
		
		comments(data);
		Expression expr = super.expression(data);
		comments(data);
		// end )
		if(!data.cfml.forwardIfCurrent(')'))
			throw new TemplateException(data.cfml,"switch statement must end with a [)]");
		comments(data);

		if(!data.cfml.forwardIfCurrent('{'))
			throw new TemplateException(data.cfml,"switch statement must have a starting  [{]");

		Switch swit=new Switch(expr,line,null);
		
		//	cases
		 //Node child=null;
		 comments(data);
		 while(caseStatement(data,swit)) {
			 comments(data);
		 }
		 // default
		  if(defaultStatement(data,swit)) {
			comments(data);
		  }
		  
		  while(caseStatement(data,swit)) {
				 comments(data);
			 }
		  
		  
		// }
		if(!data.cfml.forwardIfCurrent('}'))
			throw new TemplateException(data.cfml,"invalid construct in switch statement");
		swit.setEnd(data.cfml.getPosition());
		return swit;
	}
	
	/**
	 * Liest ein Case Statement ein
	 * @return case Statement
	 * @throws TemplateException
	 */
	private final boolean caseStatement(ExprData data,Switch swit) throws TemplateException {
		if(!data.cfml.forwardIfCurrentAndNoWordAfter("case"))
			return false;
		
		//int line=data.cfml.getLine();		
		comments(data);
		Expression expr = super.expression(data);
		comments(data);
		
		if(!data.cfml.forwardIfCurrent(':'))
			throw new TemplateException(data.cfml,"case body must start with [:]");
		
		Body body=new BodyBase();
		switchBlock(data,body);
		swit.addCase(expr, body);
		return true;
	}
	
	/**
	 * Liest ein default Statement ein
	 * @return default Statement
	 * @throws TemplateException
	 */
	private final boolean defaultStatement(ExprData data,Switch swit) throws TemplateException {
		if(!data.cfml.forwardIfCurrent("default",':'))
			return false;
		
		//int line=data.cfml.getLine();
		
		Body body=new BodyBase();
		swit.setDefaultCase(body);
		switchBlock(data,body);
		return true;
	}
	
	/**
	 * Liest ein Switch Block ein
	 * @param block
	 * @throws TemplateException
	 */
	private final void switchBlock(ExprData data,Body body) throws TemplateException {
		while(data.cfml.isValidIndex()) {
			comments(data);
			if(data.cfml.isCurrent("case ") || data.cfml.isCurrent("default",':') || data.cfml.isCurrent('}')) 
				return;
			statement(data,body,CTX_SWITCH);
		}
	}
	
	
	/**
	 * Liest ein do Statement ein.
	 * <br />
	 * EBNF:<br />
	 * <code>block spaces "while" spaces "(" spaces condition spaces ")";</code>
	 * @return do Statement
	 * @throws TemplateException
	 */
	private final DoWhile doStatement(ExprData data) throws TemplateException {
		int pos=data.cfml.getPos();
		
		// id
		String id=variableDec(data, false);
		if(id==null) {
			data.cfml.setPos(pos);
			return null;
		}
		if(id.equalsIgnoreCase("do")){
			id=null;
			if(!data.cfml.isCurrent('{') && !data.cfml.isCurrent(' ') && !data.cfml.isCurrent('/')) {
				data.cfml.setPos(pos);
				return null;
			}	
		}
		else {
			data.cfml.removeSpace();
			if(!data.cfml.forwardIfCurrent(':')){
				data.cfml.setPos(pos);
				return null;
			}
			data.cfml.removeSpace();
			
			if(!data.cfml.forwardIfCurrent("do",'{') && !data.cfml.forwardIfCurrent("do ") && !data.cfml.forwardIfCurrent("do",'/')) {
				data.cfml.setPos(pos);
				return null;
			}
			data.cfml.previous();
		}
		
		//if(!data.cfml.forwardIfCurrent("do",'{') && !data.cfml.forwardIfCurrent("do ") && !data.cfml.forwardIfCurrent("do",'/'))
		//	return null;
		
		Position line = data.cfml.getPosition();
		Body body=new BodyBase();
		
		//data.cfml.previous();
		statement(data,body,CTX_DO_WHILE);
		
		
		comments(data);
		if(!data.cfml.forwardIfCurrent("while",'('))
			throw new TemplateException(data.cfml,"do statement must have a while at the end");
		
		DoWhile doWhile=new DoWhile(condition(data),body,line,data.cfml.getPosition(),id);
		
		if(!data.cfml.forwardIfCurrent(')'))
			throw new TemplateException(data.cfml,"do statement must end with a [)]");
		
		
		return doWhile;
	}
	
	/**
	 * Liest ein for Statement ein.
	 * <br />
	 * EBNF:<br />
	 * <code>expression spaces ";" spaces condition spaces ";" spaces expression spaces ")" spaces block;</code>
	 * @return for Statement
	 * @throws TemplateException
	 */
	private final Statement forStatement(ExprData data) throws TemplateException {
		
int pos=data.cfml.getPos();
		
		// id
		String id=variableDec(data, false);
		if(id==null) {
			data.cfml.setPos(pos);
			return null;
		}
		if(id.equalsIgnoreCase("for")){
			id=null;
			data.cfml.removeSpace();
			if(!data.cfml.forwardIfCurrent('(')){
				data.cfml.setPos(pos);
				return null;
			}	
		}
		else {
			data.cfml.removeSpace();
			if(!data.cfml.forwardIfCurrent(':')){
				data.cfml.setPos(pos);
				return null;
			}
			data.cfml.removeSpace();
			
			if(!data.cfml.forwardIfCurrent("for",'(')){
				data.cfml.setPos(pos);
				return null;
			}
		}
		
		
		
		
		//if(!data.cfml.forwardIfCurrent("for",'(')) 
		//	return null;
		
		
		
		Expression left=null;
		Body body=new BodyBase();
		Position line = data.cfml.getPosition();
		comments(data);
		if(!data.cfml.isCurrent(';')) {
			// left
			left=expression(data);
			comments(data);
		}
		// middle for
			if(data.cfml.forwardIfCurrent(';')) {

				Expression cont=null;
				Expression update=null;
				// condition
					comments(data);
					if(!data.cfml.isCurrent(';')) {
						cont=condition(data);
						comments(data);
					}
				// middle
				if(!data.cfml.forwardIfCurrent(';'))
					throw new TemplateException(data.cfml,"invalid syntax in for statement");
				// update
					comments(data);
					if(!data.cfml.isCurrent(')')) {
						update=expression(data);
						comments(data);
					}
				// start )
				if(!data.cfml.forwardIfCurrent(')'))
					throw new TemplateException(data.cfml,"invalid syntax in for statement, for statement must end with a [)]");
				// ex block
				statement(data,body,CTX_FOR);
		
				return new For(left,cont,update,body,line,data.cfml.getPosition(),id);					
			}
		// middle foreach
			else if(data.cfml.forwardIfCurrent("in")) {
				// condition
					comments(data);
					Expression value = expression(data);
					comments(data);
				if(!data.cfml.forwardIfCurrent(')'))
					throw new TemplateException(data.cfml,"invalid syntax in for statement, for statement must end with a [)]");
				
				// ex block
				statement(data,body,CTX_FOR);
				if(!(left instanceof Variable))
					throw new TemplateException(data.cfml,"invalid syntax in for statement, left value is invalid");
				
				if(!(value instanceof Variable))
					throw new TemplateException(data.cfml,"invalid syntax in for statement, right value is invalid");
				return new ForEach((Variable)left,(Variable)value,body,line,data.cfml.getPosition(),id);	
			}
			else 
				throw new TemplateException(data.cfml,"invalid syntax in for statement");
	}
	
	/**
	 * Liest ein function Statement ein.
	 * <br />
	 * EBNF:<br />
	 * <code>identifier spaces "(" spaces identifier spaces {"," spaces identifier spaces} ")" spaces block;</code>
	 * @return function Statement
	 * @throws TemplateException
	 */
	private final Function funcStatement(ExprData data,Body parent) throws TemplateException {
		int pos=data.cfml.getPos();
		
		// access modifier
		String strAccess=variableDec(data, false);
		if(strAccess==null) {
			data.cfml.setPos(pos);
			return null;
		}
		
		String rtnType=null;
		if(strAccess.equalsIgnoreCase("FUNCTION")){
			strAccess=null;
			comments(data);
			// only happens when return type is function
			if(data.cfml.forwardIfCurrent("function ")){
				rtnType="function";
				comments(data);
			}
		}
		else{
			comments(data);
			rtnType=variableDec(data, false);
			if(rtnType==null){
				data.cfml.setPos(pos);
				return null;
			}
			if(rtnType.equalsIgnoreCase("FUNCTION")){
				comments(data);
				// only happens when return type is function
				if(data.cfml.forwardIfCurrent("function ")){
					comments(data);
				}
				else rtnType=null;
			}
			comments(data);
			
			if(rtnType!=null && !data.cfml.forwardIfCurrent("function ") && !rtnType.equalsIgnoreCase("FUNCTION")){
				data.cfml.setPos(pos);
				return null;
			}
			comments(data);
		}

		// check access returntype
		int access=Component.ACCESS_PUBLIC;
		if(strAccess!=null && rtnType!=null){
			access = ComponentUtil.toIntAccess(strAccess,-1);
			if(access==-1)
				throw new TemplateException(data.cfml,"invalid access type ["+strAccess+"], access types are remote, public, package, private");
		}
		if(strAccess!=null && rtnType==null){
			access = ComponentUtil.toIntAccess(strAccess,-1);
			if(access==-1){
				rtnType=strAccess;
				strAccess=null;
				access=Component.ACCESS_PUBLIC;
			}
		}
		
		
		
		Position line = data.cfml.getPosition();
		
		comments(data);
		
		// Name
			String id=identifier(data,false);
			
			
			if(id==null) {
				if(data.cfml.isCurrent('(')) {
					data.cfml.setPos(pos);
					return null;
				}
				throw new TemplateException(data.cfml,"invalid name for a function");
			}
			
			if(!data.isCFC && !data.isInterface){
				FunctionLibFunction flf = getFLF(data,id);
				if(flf!=null && flf.getClazz()!=CFFunction.class)throw new TemplateException(data.cfml,"The name ["+id+"] is already used by a built in Function");
			}
			return closurePart(data, id,access,rtnType,line,false);
	}

	protected  final Function closurePart(ExprData data, String id, int access, String rtnType, Position line,boolean closure) throws TemplateException {		
		
		Body body=new FunctionBody();
		Function func=closure?
				new Closure(data.page,id,access,rtnType,body,line,null)
				:new FunctionImpl(data.page,id,access,rtnType,body,line,null);
		
			comments(data);
			if(!data.cfml.forwardIfCurrent('('))
				throw new TemplateException(data.cfml,"invalid syntax in function head, missing begin [(]");
		
			// arguments
			LitBoolean passByRef;
			Expression displayName;
			Expression hint;
			Map<String,Attribute> meta;
			String _name;
			do	{
				comments(data);
				// finish
				if(data.cfml.isCurrent(')'))break;
				
				// attribute
				
				// name
				//String idName=identifier(data,false,true);
				boolean required=false;
				
				String idName = variableDec(data, false);
				// required
				if("required".equalsIgnoreCase(idName)){
					comments(data);
					String idName2=variableDec(data, false);
					if(idName2!=null){
						idName=idName2;
						required=true;
					}
				}
				
				
				String typeName="any";
				if(idName==null) throw new TemplateException(data.cfml,"invalid argument definition");
				comments(data);
				if(!data.cfml.isCurrent(')') && !data.cfml.isCurrent('=') && !data.cfml.isCurrent(':') && !data.cfml.isCurrent(',')) {
					typeName=idName.toLowerCase();
					idName=identifier(data,false); // MUST was upper case before, is this a problem?
				}
				else if(idName.indexOf('.')!=-1 || idName.indexOf('[')!=-1) {
					throw new TemplateException(data.cfml,"invalid argument name ["+idName+"] definition");
				}
				
				comments(data);
				Expression defaultValue;
				if(data.cfml.isCurrent('=') || data.cfml.isCurrent(':')) {
					data.cfml.next();
					comments(data);
					defaultValue=expression(data);
				}
				else defaultValue=null;
				
				// assign meta data defined in doc comment
				passByRef = LitBoolean.TRUE;
				displayName=LitString.EMPTY;
				hint=LitString.EMPTY;
				meta=null;
				if(data.docComment!=null){
					Map<String, Attribute> params = data.docComment.getParams();
					Attribute[] attrs = params.values().toArray(new Attribute[params.size()]);
					Attribute attr;
					String name;
					
					for(int i=0;i<attrs.length;i++){
						attr=attrs[i];
						name=attr.getName();
						// hint
						if(idName.equalsIgnoreCase(name) || name.equalsIgnoreCase(idName+".hint")) {
							hint=CastString.toExprString(attr.getValue());
							params.remove(name);
						}
						//meta
						if(StringUtil.startsWithIgnoreCase(name, idName+".")) {
							if(name.length()>idName.length()+1){
								if(meta==null) meta=new HashMap<String, Attribute>();
								_name=name.substring(idName.length()+1);
								meta.put(_name, new Attribute(attr.isDynamicType(), _name,attr.getValue(), attr.getType()));
							}
							params.remove(name);
						}
					}
					
				}
				
				// argument attributes
				Attribute[] _attrs = attributes(null,null,data,COMMA_ENDBRACKED,LitString.EMPTY,Boolean.TRUE,null,false);
				Attribute _attr;
				if(!ArrayUtil.isEmpty(_attrs)){
					if(meta==null) meta=new HashMap<String, Attribute>();
					for(int i=0;i<_attrs.length;i++){
						_attr=_attrs[i];
						meta.put(_attr.getName(), _attr);
					}
				}
				
				func.addArgument(
						LitString.toExprString(idName),
						LitString.toExprString(typeName),
						LitBoolean.toExprBoolean(required),
						defaultValue,passByRef,displayName,hint,meta);
				
				comments(data);
			}
			while(data.cfml.forwardIfCurrent(','));

		
		// end )
			comments(data);
			if(!data.cfml.forwardIfCurrent(')'))
				throw new TemplateException(data.cfml,"invalid syntax in function head, missing ending [)]");
		
		//TagLibTag tlt = CFMLTransformer.getTLT(data.cfml,"function");
		
		// doc comment
		if(data.docComment!=null){
			func.setHint(data.docComment.getHint());
			
			
			// params
			/*Map<String, Attribute> params = data.docComment.getParams();
			Iterator<Attribute> it = params.values().iterator();
			Attribute attr;
			String name;
			while(it.hasNext()){
				attr=it.next();
				name=attr.getName();
			}*/
			
			func.setMetaData(data.docComment.getParams());
			data.docComment=null;
		}

		comments(data);
			
		// attributes
		Attribute[] attrs = attributes(null,null,data,SEMI_BLOCK,LitString.EMPTY,Boolean.TRUE,null,false);
		for(int i=0;i<attrs.length;i++){
			func.addAttribute(attrs[i]);
		}
			
		// body
		boolean oldInsideFunction=data.insideFunction;
		data.insideFunction=true;
		try {
		// ex block
		statement(data,body,CTX_FUNCTION);
		}
		finally{
			data.insideFunction=oldInsideFunction;
		}
		func.setEnd(data.cfml.getPosition());
		//eval(tlt,data,func);
		return func;
	}
	

	
	private Statement tagStatement(ExprData data, Body parent) throws TemplateException {
		Statement child;
		
		//TagLibTag[] tags = getScriptTags(data);
		for(int i=0;i<data.scriptTags.length;i++){
			// single
			if(data.scriptTags[i].getScript().getType()==TagLibTagScript.TYPE_SINGLE) { 
				if((child=_singleAttrStatement(parent,data,data.scriptTags[i]))!=null)return child;
			}
			// multiple
			else {//if(tags[i].getScript().getType()==TagLibTagScript.TYPE_MULTIPLE) { 
				if((child=_multiAttrStatement(parent,data,data.scriptTags[i]))!=null)return child;
			}
		}
		
		//if((child=_singleAttrStatement(parent,data,"abort","showerror",ATTR_TYPE_OPTIONAL,true))!=null)		return child;
		//if((child=_multiAttrStatement(parent,data,"admin",CTX_OTHER,false,true))!=null)				return child;
		//else if((child=_multiAttrStatement(parent,data,"application",CTX_OTHER,false,true))!=null)			return child;
		//else if((child=_multiAttrStatement(parent,data,"associate",CTX_OTHER,false,true))!=null)			return child;
		//else if((child=_singleAttrStatement(parent,data,"break",null,ATTR_TYPE_NONE,false))!=null)			return child;
		//else if((child=_multiAttrStatement(parent,data,"cache",CTX_OTHER,true,true))!=null)				return child;
		//else if((child=_multiAttrStatement(parent,data,"content",CTX_OTHER,true,true))!=null)				return child;
		//else if((child=_multiAttrStatement(parent,data,"collection",CTX_OTHER,true,true))!=null)			return child;
		//else if((child=_multiAttrStatement(parent,data,"cookie",CTX_OTHER,false,true))!=null)				return child;
		//else if((child=_multiAttrStatement(parent,data,"component",CTX_CFC,true,false))!=null)				return child;
		//else if((child=_singleAttrStatement(parent,data,"continue",null,ATTR_TYPE_NONE,false))!=null)		return child;
		//else if((child=_multiAttrStatement(parent,data,"dbinfo",CTX_OTHER,false,true))!=null)				return child;
		//else if((child=_multiAttrStatement(parent,data,"execute",CTX_OTHER,true,true))!=null)				return child;
		//else if((child=_singleAttrStatement(parent,data,"exit","method",ATTR_TYPE_OPTIONAL,true))!=null)	return child;
		//else if((child=_multiAttrStatement(parent,data,"feed",CTX_OTHER,false,true))!=null)					return child;
		//else if((child=_multiAttrStatement(parent,data,"file",CTX_OTHER,false,true))!=null)					return child;
		//else if((child=_singleAttrStatement(parent,data,"flush","interval",ATTR_TYPE_OPTIONAL,true))!=null)	return child;
		//else if((child=_multiAttrStatement(parent,data,"ftp",CTX_OTHER,false,true))!=null)					return child;
		//else if((child=_multiAttrStatement(parent,data,"http",CTX_OTHER,true,true))!=null)					return child;
		//else if((child=_multiAttrStatement(parent,data,"httpparam",CTX_OTHER,false,true))!=null)			return child;
		//else if((child=_multiAttrStatement(parent,data,"imap",CTX_OTHER,false,true))!=null)					return child;
		//else if((child=_singleAttrStatement(parent,data,"import","path",ATTR_TYPE_REQUIRED,false))!=null)	return child;
		//else if((child=_multiAttrStatement(parent,data,"index",CTX_OTHER,false,true))!=null)				return child;
		//else if((child=_singleAttrStatement(parent,data,"include","template",ATTR_TYPE_REQUIRED,true))!=null)return child;
		//else if((child=_multiAttrStatement(parent,data,"interface",CTX_INTERFACE,true,false))!=null)		return child;
		//else if((child=_multiAttrStatement(parent,data,"ldap",CTX_OTHER,true,true))!=null)					return child;
		//else if((child=_multiAttrStatement(parent,data,"lock",CTX_LOCK,true,true))!=null)					return child;
		//else if((child=_multiAttrStatement(parent,data,"loop",CTX_LOOP,true,true))!=null)					return child;
		//else if((child=_multiAttrStatement(parent,data,"login",CTX_OTHER,true,true))!=null)					return child;
		//else if((child=_multiAttrStatement(parent,data,"loginuser",CTX_OTHER,false,true))!=null)			return child;
		//else if((child=_singleAttrStatement(parent,data,"logout",null,ATTR_TYPE_NONE,false))!=null)			return child;
		//else if((child=_multiAttrStatement(parent,data,"mail",CTX_OTHER,true,true))!=null)					return child;
		//else if((child=_multiAttrStatement(parent,data,"mailpart",CTX_OTHER,true,true))!=null)				return child;
		//else if((child=_multiAttrStatement(parent,data,"mailparam",CTX_OTHER,false,true))!=null)			return child;
		//else if((child=_multiAttrStatement(parent,data,"module",CTX_OTHER,true,true))!=null)				return child;
		//else if((child=_singleAttrStatement(parent,data,"pageencoding","charset",ATTR_TYPE_OPTIONAL,true))!=null)	return child;
		//else if((child=_multiAttrStatement(parent,data,"param",CTX_OTHER,false,true))!=null)				return child;
		//else if((child=_multiAttrStatement(parent,data,"pdf",CTX_OTHER,true,true))!=null)					return child;
		//else if((child=_multiAttrStatement(parent,data,"pdfparam",CTX_OTHER,false,true))!=null)				return child;
		//else if((child=_multiAttrStatement(parent,data,"procparam",CTX_OTHER,false,true))!=null)			return child;
		//else if((child=_multiAttrStatement(parent,data,"procresult",CTX_OTHER,false,true))!=null)			return child;
		//else if((child=_multiAttrStatement(parent,data,"query",CTX_QUERY,true,true))!=null)					return child;
		//else if((child=_multiAttrStatement(parent,data,"queryparam",CTX_OTHER,false,true))!=null)			return child;
		//else if((child=_singleAttrStatement(parent,data,"rethrow",null,ATTR_TYPE_NONE,false))!=null)		return child;
		//else if((child=_multiAttrStatement(parent,data,"savecontent",CTX_SAVECONTENT,true,true))!=null)		return child;
		//else if((child=_multiAttrStatement(parent,data,"schedule",CTX_OTHER,false,true))!=null)				return child;
		//else if((child=_multiAttrStatement(parent,data,"search",CTX_OTHER,false,true))!=null)				return child;
		//else if((child=_multiAttrStatement(parent,data,"setting",CTX_OTHER,false,true))!=null)				return child;
		//else if((child=_multiAttrStatement(parent,data,"stopwatch",CTX_OTHER,true,true))!=null)				return child;
		//else if((child=_multiAttrStatement(parent,data,"storedproc",CTX_OTHER,true,true))!=null)			return child;
		//else if((child=_multiAttrStatement(parent,data,"thread",CTX_THREAD,true,true))!=null)				return child;
		//else if((child=_multiAttrStatement(parent,data,"trace",CTX_OTHER,true,true))!=null)					return child;
		//else if((child=_singleAttrStatement(parent,data,"throw","message",ATTR_TYPE_OPTIONAL,true))!=null)	return child;
		//else if((child=_multiAttrStatement(parent,data,"transaction",CTX_TRANSACTION,true,true))!=null)		return child;
		//else if((child=_multiAttrStatement(parent,data,"wddx",CTX_OTHER,false,true))!=null)					return child;
		//else if((child=_multiAttrStatement(parent,data,"zip",CTX_ZIP,true,true))!=null)						return child;
		//else if((child=_multiAttrStatement(parent,data,"zipparam",CTX_ZIP,false,true))!=null)				return child;
		
		
		return null;
	}
	
	

	
	
	private final Statement _multiAttrStatement(Body parent, ExprData data,TagLibTag tlt) throws TemplateException  {
		int pos = data.cfml.getPos();
		try {
			return __multiAttrStatement(parent,data,tlt);
		} 
		catch (ProcessingDirectiveException e) {
			throw e;
		}
		catch (TemplateException e) {
			try {
				data.cfml.setPos(pos);
				return expressionStatement(data,parent);
			} catch (TemplateException e1) {
				if(tlt.getScript().getContext()==CTX_CFC) throw new ComponentTemplateException(e);
				throw e;
			}
		}
	}

	private final Tag __multiAttrStatement(Body parent, ExprData data,TagLibTag tlt) throws TemplateException  {
		if(data.ep==null) return null;
		String type=tlt.getName();
		if(data.cfml.forwardIfCurrent(type)) {
			boolean isValid=(data.cfml.isCurrent(' ') || (tlt.getHasBody() && data.cfml.isCurrent('{')));
			if(!isValid){
				data.cfml.setPos(data.cfml.getPos()-type.length());
				return null;
			}
		}
		else return null;
		Position line = data.cfml.getPosition();
		
		TagLibTagScript script = tlt.getScript();
		//TagLibTag tlt = CFMLTransformer.getTLT(data.cfml,type);
		if(script.getContext()==CTX_CFC)data.isCFC=true;
		else if(script.getContext()==CTX_INTERFACE)data.isInterface=true;
		//Tag tag=new TagComponent(line);
		Tag tag=getTag(data,parent,tlt, line,null);
		tag.setTagLibTag(tlt);
		tag.setScriptBase(true);
		
		// add component meta data
		if(data.isCFC) {
			addMetaData(data,tag,IGNORE_LIST_COMPONENT);
		}
		if(data.isInterface) {
			addMetaData(data,tag,IGNORE_LIST_INTERFACE);
		}
		//EvaluatorPool.getPool();
		comments(data);
		
		// attributes
		//attributes(func,data);
		Attribute[] attrs = attributes(tag,tlt,data,SEMI_BLOCK,LitString.EMPTY,script.getRtexpr()?Boolean.TRUE:Boolean.FALSE,null,false);
		
		for(int i=0;i<attrs.length;i++){
			tag.addAttribute(attrs[i]);
		}
		
		comments(data);
	
		// body
		if(tlt.getHasBody()){
			Body body=new BodyBase();
			boolean wasSemiColon=statement(data,body,script.getContext());
			if(!wasSemiColon || !tlt.isBodyFree() || body.hasStatements())
				tag.setBody(body);
			
			
			
		}
		else checkSemiColonLineFeed(data,true,true);
		
		tag.setEnd(data.cfml.getPosition());
		eval(tlt,data,tag);
		return tag;
	}
	
	
	
	private final void addMetaData(ExprData data, Tag tag, String[] ignoreList) {
		if(data.docComment==null) return;
		

		tag.addMetaData(data.docComment.getHintAsAttribute());
		
		Map<String, Attribute> params = data.docComment.getParams();
		Iterator<Attribute> it = params.values().iterator();
		Attribute attr;
		outer:while(it.hasNext()){
			attr = it.next();
			// ignore list
			if(!ArrayUtil.isEmpty(ignoreList)) {
				for(int i=0;i<ignoreList.length;i++){
					if(ignoreList[i].equalsIgnoreCase(attr.getName())) continue outer;
				}
			}
			tag.addMetaData(attr);	
		}
		data.docComment=null;
	}
	
	private final Statement propertyStatement(ExprData data,Body parent) throws TemplateException  {
		int pos = data.cfml.getPos();
		try {
			return _propertyStatement(data, parent);
		} catch (TemplateException e) {
			try {
				data.cfml.setPos(pos);
				return expressionStatement(data,parent);
			} catch (TemplateException e1) {
				throw e;
			}
		}
	}
	
	private final Tag _propertyStatement(ExprData data,Body parent) throws TemplateException  {
		if(data.context!=CTX_CFC || !data.cfml.forwardIfCurrent("property "))
			return null;
		Position line = data.cfml.getPosition();
		
		TagLibTag tlt = CFMLTransformer.getTLT(data.cfml,"property");
		Tag property=new TagOther(line,null);
		addMetaData(data, property,IGNORE_LIST_PROPERTY);
		

		boolean hasName=false,hasType=false;

		// TODO allow the following pattern property "a.b.C" d;
		//Expression t = string(data);
		// print.o("name:"+t.getClass().getName());
		
		int pos = data.cfml.getPos();
		String tmp=variableDec(data, true);
		if(!StringUtil.isEmpty(tmp)) {
			if(tmp.indexOf('.')!=-1) {
				property.addAttribute(new Attribute(false,"type",LitString.toExprString(tmp),"string"));
				hasType=true;
			}
			else {
				data.cfml.setPos(pos);
			}
		}
		else data.cfml.setPos(pos);
		
		
		
		// folgend wird tlt extra nicht uebergeben, sonst findet pruefung statt
		Attribute[] attrs = attributes(property,tlt,data,SEMI,	NULL,Boolean.FALSE,"name",true);
		
		checkSemiColonLineFeed(data,true,true);

		property.setTagLibTag(tlt);
		property.setScriptBase(true);
		
		
		Attribute attr;
		
		// first fill all regular attribute -> name="value"
		for(int i=attrs.length-1;i>=0;i--){
			attr=attrs[i];
			if(!attr.getValue().equals(NULL)){
				if(attr.getName().equalsIgnoreCase("name")){
					hasName=true;
					//attr=new Attribute(attr.isDynamicType(),attr.getName(),CastString.toExprString(attr.getValue()),"string");
				}
				else if(attr.getName().equalsIgnoreCase("type")){
					hasType=true;
					//attr=new Attribute(attr.isDynamicType(),attr.getName(),CastString.toExprString(attr.getValue()),"string");
				}
				property.addAttribute(attr);
			}
		}
		
		// now fill name named attributes -> attr1 attr2
		
		String first=null,second=null;
		for(int i=0;i<attrs.length;i++){
			attr=attrs[i];
			
			if(attr.getValue().equals(NULL)){
				// type
				if(first==null && (!hasName || !hasType)){
					first=attr.getName();
					//type=new Attribute(false,"type",LitString.toExprString(attr.getName()),"string");
					//property.addAttribute(type);
				}
				// name
				else if(second==null && !hasName && !hasType){
					second=attr.getName();
					//name=new Attribute(false,"name",LitString.toExprString(attr.getName()),"string");
					//property.addAttribute(name);
				}
				// attr with no value
				else {
					attr=new Attribute(true,attr.getName(),LitString.EMPTY,"string");
					property.addAttribute(attr);
				}
			}
		}

		
		
		if(first!=null) {
				hasName=true;
			if(second!=null){
				hasType=true;
				property.addAttribute(new Attribute(false,"name",LitString.toExprString(second),"string"));
				property.addAttribute(new Attribute(false,"type",LitString.toExprString(first),"string"));
			}
			else {
				property.addAttribute(new Attribute(false,"name",LitString.toExprString(first),"string"));
			}
		}
		
		if(!hasType)
			property.addAttribute(ANY);
		
		if(!hasName)
			throw new TemplateException(data.cfml,"missing name declaration for property");

		/*Tag property=new TagBase(line);
		property.setTagLibTag(tlt);
		property.addAttribute(new Attribute(false,"name",LitString.toExprString(name),"string"));
		property.addAttribute(new Attribute(false,"type",LitString.toExprString(type),"string"));
		*/
		property.setEnd(data.cfml.getPosition());
		
		return property;
	}
	
	public Statement paramStatement(ExprData data,Body parent) throws TemplateException  {
		int pos = data.cfml.getPos();
		try {
			return _paramStatement(data, parent);
		} catch (TemplateException e) {
			try {
				data.cfml.setPos(pos);
				return expressionStatement(data,parent);
			} catch (TemplateException e1) {
				throw e;
			}
		}
	}
	
	private Tag _paramStatement(ExprData data,Body parent) throws TemplateException  {
		if(!data.cfml.forwardIfCurrent("param "))
			return null;
		Position line = data.cfml.getPosition();
		
		TagLibTag tlt = CFMLTransformer.getTLT(data.cfml,"param");
		TagParam param=new TagParam(line,null);
		
		// type
		boolean hasType=false;
		int pos = data.cfml.getPos();
		String tmp=variableDec(data, true);
		if(!StringUtil.isEmpty(tmp)) {
			if(tmp.indexOf('.')!=-1) {
				param.addAttribute(new Attribute(false,"type",LitString.toExprString(tmp),"string"));
				hasType=true;
			}
			else data.cfml.setPos(pos);
		}
		else data.cfml.setPos(pos);
		
		
		
		// folgend wird tlt extra nicht uebergeben, sonst findet pruefung statt
		Attribute[] attrs = attributes(param,tlt,data,SEMI,	NULL,Boolean.TRUE,"name",true);
		checkSemiColonLineFeed(data,true,true);

		param.setTagLibTag(tlt);
		param.setScriptBase(true);
		
		
		Attribute attr;
		
		// first fill all regular attribute -> name="value"
		boolean hasDynamic=false;
		boolean hasName=false;
		for(int i=attrs.length-1;i>=0;i--){
			attr=attrs[i];
			if(!attr.getValue().equals(NULL)){
				if(attr.getName().equalsIgnoreCase("name")){
					hasName=true;
					param.addAttribute(attr);
				}
				else if(attr.getName().equalsIgnoreCase("type")){
					hasType=true;
					param.addAttribute(attr);
				}
				else if(attr.isDynamicType()){
					hasName=true;
					if(hasDynamic) throw attrNotSupported(data.cfml,tlt,attr.getName());
					hasDynamic=true;
					param.addAttribute(new Attribute(false,"name",LitString.toExprString(attr.getName()),"string"));
					param.addAttribute(new Attribute(false,"default",attr.getValue(),"any"));
				}
				else 
					param.addAttribute(attr);
			}
		}
		
		// now fill name named attributes -> attr1 attr2
		String first=null,second=null;
		for(int i=0;i<attrs.length;i++){
			attr=attrs[i];
			
			if(attr.getValue().equals(NULL)){
				// type
				if(first==null && (!hasName || !hasType)){
					first=attr.getName();
				}
				// name
				else if(second==null && !hasName && !hasType){
					second=attr.getName();
				}
				// attr with no value
				else {
					attr=new Attribute(true,attr.getName(),LitString.EMPTY,"string");
					param.addAttribute(attr);
				}
			}
		}

		
		if(first!=null) {
			if(second!=null){
				hasName=true;
				hasType=true;
				if(hasDynamic) throw attrNotSupported(data.cfml,tlt,first);
				hasDynamic=true;
				param.addAttribute(new Attribute(false,"name",LitString.toExprString(second),"string"));
				param.addAttribute(new Attribute(false,"type",LitString.toExprString(first),"string"));
			}
			else {
				param.addAttribute(new Attribute(false,hasName?"type":"name",LitString.toExprString(first),"string"));
				hasName=true;
			}
		}
		
		//if(!hasType)
		//	param.addAttribute(ANY);
		
		if(!hasName)
			throw new TemplateException(data.cfml,"missing name declaration for param");

		param.setEnd(data.cfml.getPosition());
		return param;
	}


	private TemplateException attrNotSupported(CFMLString cfml, TagLibTag tag, String id) {
		String names=tag.getAttributeNames();
		if(StringUtil.isEmpty(names))
			return new TemplateException(cfml,"Attribute "+id+" is not allowed for tag "+tag.getFullName());
		
		return new TemplateException(cfml,
			"Attribute "+id+" is not allowed for statement "+tag.getName(),
			"valid attribute names are ["+names+"]");
	}



	private final String variableDec(ExprData data,boolean firstCanBeNumber) {
		
		String id=identifier(data, firstCanBeNumber);
		if(id==null) return null;
		
		StringBuffer rtn=new StringBuffer(id);
		data.cfml.removeSpace();
		
		while(data.cfml.forwardIfCurrent('.')){
			data.cfml.removeSpace();
			rtn.append('.');
			id=identifier(data, firstCanBeNumber);
			if(id==null)return null;
			rtn.append(id);
			data.cfml.removeSpace();
		}
		
		while(data.cfml.forwardIfCurrent("[]")){
			data.cfml.removeSpace();
			rtn.append("[]");
		}
		return rtn.toString();
	}
	
	/**
	 * Liest ein return Statement ein.
	 * <br />
	 * EBNF:<br />
	 * <code>spaces expressionStatement spaces;</code>
	 * @return return Statement
	 * @throws TemplateException
	 */
	private final Return returnStatement(ExprData data) throws TemplateException {
	    if(!data.cfml.forwardIfCurrentAndNoVarExt("return")) return null;
	    
	    Position line = data.cfml.getPosition();
	    Return rtn;
	    
	    comments(data);
	    if(checkSemiColonLineFeed(data, false,false)) rtn=new Return(line,data.cfml.getPosition());
	    else {
	    	Expression expr = expression(data);
	    	checkSemiColonLineFeed(data, true,true);
	    	rtn=new Return(expr,line,data.cfml.getPosition());
	    }
		comments(data);

		return rtn;
	}

	
	private final Statement _singleAttrStatement(Body parent, ExprData data, TagLibTag tlt) throws TemplateException   {
		int pos = data.cfml.getPos();
		try {
			return __singleAttrStatement(parent,data,tlt, false);
		} 
		catch (ProcessingDirectiveException e) {
			throw e;
		} 
		catch (TemplateException e) {
			data.cfml.setPos(pos);
			try {
				return expressionStatement(data,parent);
			} catch (TemplateException e1) {
				throw e;
			}
		}
	}

	private final Statement __singleAttrStatement(Body parent, ExprData data, TagLibTag tlt, boolean allowTwiceAttr) throws TemplateException {
		String tagName = tlt.getName();
		if(data.cfml.forwardIfCurrent(tagName)){
			if(!data.cfml.isCurrent(' ') && !data.cfml.isCurrent(';')){
				data.cfml.setPos(data.cfml.getPos()-tagName.length());
				return null;
			}
		}
		else return null;
		
		
		int pos=data.cfml.getPos()-tagName.length();
		Position line = data.cfml.getPosition();
		//TagLibTag tlt = CFMLTransformer.getTLT(data.cfml,tagName.equals("pageencoding")?"processingdirective":tagName);
		
		Tag tag=getTag(data,parent,tlt,line,null);
		tag.setScriptBase(true);
		tag.setTagLibTag(tlt);
		
		comments(data);
		
		// attribute
		TagLibTagAttr attr = tlt.getScript().getSingleAttr();
		String attrName=null;
		Expression attrValue=null;
		short attrType=ATTR_TYPE_NONE;
		if(attr!=null){
			attrType = attr.getScriptSupport();
			char c = data.cfml.getCurrent();
			if(ATTR_TYPE_REQUIRED==attrType || (!data.cfml.isCurrent(';') && ATTR_TYPE_OPTIONAL==attrType)) {
				attrValue =attributeValue(data, tlt.getScript().getRtexpr());
				if(attrValue!=null && isOperator(c)) {
					data.cfml.setPos(pos);
					return null;
				}
			}
		}
		
		if(attrValue!=null){
			attrName=attr.getName();
			TagLibTagAttr tlta = tlt.getAttribute(attr.getName());
			tag.addAttribute(new Attribute(false,attrName,CastOther.toExpression(attrValue,tlta.getType()),tlta.getType()));
		}
		else if(ATTR_TYPE_REQUIRED==attrType){
			data.cfml.setPos(pos);
			return null;
		}
		
		checkSemiColonLineFeed(data,true,true);
		if(!StringUtil.isEmpty(tlt.getTteClassName()))data.ep.add(tlt, tag, data.flibs, data.cfml);
		
		if(!StringUtil.isEmpty(attrName))validateAttributeName(attrName, data.cfml, new ArrayList<String>(), tlt, new RefBooleanImpl(false), new StringBuffer(), allowTwiceAttr);
		tag.setEnd(data.cfml.getPosition());
		eval(tlt,data,tag);
		return tag;
	}

	private boolean isOperator(char c) {
		return c=='=' || c=='+' || c=='-';
	}

	/*protected Statement __singleAttrStatement(Body parent, Data data, String tagName,String attrName,int attrType, boolean allowExpression, boolean allowTwiceAttr) throws TemplateException {
		
		if(data.cfml.forwardIfCurrent(tagName)){
			if(!data.cfml.isCurrent(' ') && !data.cfml.isCurrent(';')){
				data.cfml.setPos(data.cfml.getPos()-tagName.length());
				return null;
			}
		}
		else return null;
		
		
		int pos=data.cfml.getPos()-tagName.length();
		int line=data.cfml.getLine();
		TagLibTag tlt = CFMLTransformer.getTLT(data.cfml,tagName.equals("pageencoding")?"processingdirective":tagName);
		
		Tag tag=getTag(parent,tlt,line);
		tag.setScriptBase(true);
		tag.setTagLibTag(tlt);
		
		comments(data);
		
		// attribute
		Expression attrValue=null;
		if(ATTR_TYPE_REQUIRED==attrType || (!data.cfml.isCurrent(';') && ATTR_TYPE_OPTIONAL==attrType))
			attrValue =attributeValue(data, allowExpression);
				//allowExpression?super.expression(data):string(data);
		
		if(attrValue!=null){
			TagLibTagAttr tlta = tlt.getAttribute(attrName);
			tag.addAttribute(new Attribute(false,attrName,Cast.toExpression(attrValue,tlta.getType()),tlta.getType()));
		}
		else if(ATTR_TYPE_REQUIRED==attrType){
			data.cfml.setPos(pos);
			return null;
		}
		
		checkSemiColonLineFeed(data,true);
		if(!StringUtil.isEmpty(tlt.getTteClassName()))data.ep.add(tlt, tag, data.fld, data.cfml);
		
		if(!StringUtil.isEmpty(attrName))validateAttributeName(attrName, data.cfml, new ArrayList<String>(), tlt, new RefBooleanImpl(false), new StringBuffer(), allowTwiceAttr);
		
		eval(tlt,data,tag);
		return tag;
	}*/
	
	

	private final void eval(TagLibTag tlt, railo.transformer.cfml.expression.CFMLExprTransformer.ExprData data, Tag tag) throws TemplateException {
		if(!StringUtil.isEmpty(tlt.getTteClassName())){
			try {
				tlt.getEvaluator().execute(data.config, tag, tlt,data.flibs, data);
			} catch (EvaluatorException e) {
				throw new TemplateException(e.getMessage());
			}
			data.ep.add(tlt, tag, data.flibs, data.cfml);
		}
	}

	private final Tag getTag(ExprData data,Body parent, TagLibTag tlt, Position start,Position end) throws TemplateException {
		try {
			Tag tag = tlt.getTag(start, end);
			tag.setParent(parent);
			return tag;
		} catch (TagLibException e) {
			throw new TemplateException(data.cfml,e);
		}
		/*if(StringUtil.isEmpty(tlt.getTttClassName()))tag= new TagBase(line);
		else {
			try {
				Class<Tag> clazz = ClassUtil.loadClass(tlt.getTttClassName());
				Constructor<Tag> constr = clazz.getConstructor(new Class[]{Position.class});
				tag = constr.newInstance(new Object[]{line});
				
			} 
			catch (Exception e) {
				e.printStackTrace();
				tag= new TagBase(line);
			}
		}*/
		
	}
	
	
	
	/**
	 * List mithilfe des data.cfmlExprTransformer einen Ausruck ein.
	 * <br />
	 * EBNF:<br />
	 * <code>expression ";";</code>
	 * @param parent 
	 * @return Ausdruck
	 * @throws TemplateException
	 */
	private Statement expressionStatement(ExprData data, Body parent) throws TemplateException {
		Expression expr=expression(data);
		checkSemiColonLineFeed(data,true,true);
		if(expr instanceof ClosureAsExpression)
			return ((ClosureAsExpression)expr).getClosure();
			
		return new ExpressionAsStatement(expr);
	}
	
	private final boolean checkSemiColonLineFeed(ExprData data,boolean throwError, boolean checkNLBefore) throws TemplateException {
		comments(data);
		if(!data.cfml.forwardIfCurrent(';')){
			if((!checkNLBefore || !data.cfml.hasNLBefore()) && !data.cfml.isCurrent("</",data.tagName) && !data.cfml.isCurrent('}')){
				if(!throwError) return false;
				throw new TemplateException(data.cfml,"Missing [;] or [line feed] after expression");
			}
		}
		return true;
	}

	
	/**
	 * Ruft die Methode expression der zu vererbenten Klasse auf 
	 * und prueft ob der Rueckgabewert einen boolschen Wert repraesentiert und castet den Wert allenfalls.
	 * <br />
	 * EBNF:<br />
	 * <code>TemplateException::expression;</code>
	 * @return condition
	 * @throws TemplateException
	 */
	private final ExprBoolean condition(ExprData data) throws TemplateException {
		ExprBoolean condition=null;
		comments(data);
		condition=CastBoolean.toExprBoolean(super.expression(data));
		comments(data);
		return condition;
	}
	
	/**
	 * Liest eine try Block ein
	 * <br />
	 * EBNF:<br />
	 * <code>;</code>
	 * @return Try Block
	 * @throws TemplateException
	*/
	private final TryCatchFinally tryStatement(ExprData data) throws TemplateException {
		if(!data.cfml.forwardIfCurrent("try",'{') && !data.cfml.forwardIfCurrent("try ") && !data.cfml.forwardIfCurrent("try",'/'))
			return null;
		data.cfml.previous();

		Body body=new BodyBase();
		TryCatchFinally tryCatchFinally=new TryCatchFinally(body,data.cfml.getPosition(),null);
		
		statement(data,body,CTX_TRY);
		comments(data);
		
		// catches
		short catchCount=0;
		while(data.cfml.forwardIfCurrent("catch",'(')) {
			catchCount++;
			comments(data);
			
			// type
			int pos=data.cfml.getPos();
			Position line=data.cfml.getPosition();
			Expression name = null,type = null;
			
			StringBuffer sbType=new StringBuffer();
            String id;
            while(true) {
            	id=identifier(data,false);
                if(id==null)break;
                sbType.append(id);
                data.cfml.removeSpace();
                if(!data.cfml.forwardIfCurrent('.'))break;
                sbType.append('.');
                data.cfml.removeSpace();
            }
				
            
			if(sbType.length()==0) {
			    type=string(data);
			    if(type==null)			    
			        throw new TemplateException(data.cfml,"a catch statement must begin with the throwing type (query, application ...).");
			}
			else {
				type=LitString.toExprString(sbType.toString());
			} 
            
            
			//name = expression();
			comments(data);
			
			// name
			if(!data.cfml.isCurrent(')')) {
				name=expression(data);
			}
			else {
				data.cfml.setPos(pos);
				name=expression(data);
				type = LitString.toExprString( "any" );
			}
			comments(data);

            Body b=new BodyBase();
			try {
				tryCatchFinally.addCatch(type,name,b,line);
			} 
			catch (BytecodeException e) {
				throw new TemplateException(data.cfml,e.getMessage());
			}
			comments(data);
			
			if(!data.cfml.forwardIfCurrent(')')) throw new TemplateException(data.cfml,"invalid catch statement, missing closing )");
			
            statement(data,b,CTX_CATCH);
			comments(data);	
		}
        
		
// finally
		 if(finallyStatement(data,tryCatchFinally)) {
			comments(data);
		 }
		 else if(catchCount==0)
			throw new TemplateException(data.cfml,"a try statement must have at least one catch statement");
		
        //if(body.isEmpty()) return null;
		tryCatchFinally.setEnd(data.cfml.getPosition());
		return tryCatchFinally;
	}
	
	/**
	 * Prueft ob sich der Zeiger am Ende eines Script Blockes befindet
	 * @return Ende ScriptBlock?
	 * @throws TemplateException
	 */
	private final boolean isFinish(ExprData data) throws TemplateException {
		comments(data);
		return data.cfml.isCurrent("</",data.tagName);		
	}
	
	
	/**
	 * Liest den Block mit Statements ein.
	 * <br />
	 * EBNF:<br />
	 * <code>"{" spaces {statements} "}" | statement;</code>
	 * @param block
	 * @return was a block
	 * @throws TemplateException
	 */
	private final boolean block(ExprData data,Body body) throws TemplateException {
		if(!data.cfml.forwardIfCurrent('{'))
			return false;
		comments(data);
		if(data.cfml.forwardIfCurrent('}')) {
			
			return true;
		}
		statements(data,body,false);
		
		if(!data.cfml.forwardIfCurrent('}'))
			throw new TemplateException(data.cfml,"Missing ending [}]");
		return true;
	}	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	private final Attribute[] attributes(Tag tag,TagLibTag tlt, ExprData data, EndCondition endCond,Expression defaultValue,Object oAllowExpression, 
			String ignoreAttrReqFor, boolean allowTwiceAttr) throws TemplateException {
		ArrayList<Attribute> attrs=new ArrayList<Attribute>();
		ArrayList<String> ids=new ArrayList<String>();
		
		while(data.cfml.isValidIndex())	{
			data.cfml.removeSpace();
			// if no more attributes break
			if(endCond.isEnd(data)) break;
			//if((allowBlock && data.cfml.isCurrent('{')) || data.cfml.isCurrent(';')) break;
			Attribute attr = attribute(tlt,data,ids,defaultValue,oAllowExpression, allowTwiceAttr);
			attrs.add(attr);
		}
		
		// not defined attributes
		if(tlt!=null){
			boolean hasAttributeCollection=attrs.contains("attributecollection");
			int type=tlt.getAttributeType();
			if(type==TagLibTag.ATTRIBUTE_TYPE_FIXED || type==TagLibTag.ATTRIBUTE_TYPE_MIXED)	{
				Map<String, TagLibTagAttr> hash = tlt.getAttributes();
				Iterator<String> it = hash.keySet().iterator();
				
				while(it.hasNext())	{
					TagLibTagAttr att=hash.get(it.next());
					if(att.isRequired() && !contains(attrs,att.getName()) && att.getDefaultValue()==null && !att.getName().equals(ignoreAttrReqFor))	{
						if(!hasAttributeCollection)throw new TemplateException(data.cfml,"attribute "+att.getName()+" is required for statement "+tlt.getName());
						if(tag!=null)tag.addMissingAttribute(att.getName(),att.getType());
					}
				}
			}
		}
		return attrs.toArray(new Attribute[attrs.size()]);
	}
	
	private final boolean contains(ArrayList<Attribute> attrs, String name) {
		Iterator<Attribute> it = attrs.iterator();
		while(it.hasNext()){
			if(it.next().getName().equals(name)) return true;
		}
		return false;
	}

	private final Attribute attribute(TagLibTag tlt, ExprData data, ArrayList<String> args, Expression defaultValue,Object oAllowExpression, boolean allowTwiceAttr) throws TemplateException {
		StringBuffer sbType=new StringBuffer();
    	RefBoolean dynamic=new RefBooleanImpl(false);
    	
		// Name
    	String name=attributeName(data.cfml,args,tlt,dynamic,sbType, allowTwiceAttr);
    	boolean allowExpression=false;
    	if(oAllowExpression instanceof Boolean)allowExpression=((Boolean)oAllowExpression).booleanValue();
    	else if(oAllowExpression instanceof String)allowExpression=((String)oAllowExpression).equalsIgnoreCase(name);

          Expression value=null;
    	
    	CFMLTransformer.comment(data.cfml,true);
    	
    	// value
    	if(data.cfml.forwardIfCurrent('='))	{
    		CFMLTransformer.comment(data.cfml,true);
    		value=attributeValue(data,allowExpression);	
    	}
    	else {
    		value=defaultValue;
    	}		
    	CFMLTransformer.comment(data.cfml,true);
    	
    	
    	// Type
    	TagLibTagAttr tlta=null;
		if(tlt!=null){
			tlta = tlt.getAttribute(name);
		}
		return new Attribute(dynamic.toBooleanValue(),name,tlta!=null?CastOther.toExpression(value, tlta.getType()):value,sbType.toString());
    }
	
	/*private String attributeName(CFMLString cfml, ArrayList<String> args,TagLibTag tag, RefBoolean dynamic, StringBuffer sbType) throws TemplateException {
		String id=StringUtil.toLowerCase(CFMLTransformer.identifier(cfml,true));
        if(args.contains(id)) throw new TemplateException(cfml,"you can't use the same attribute ["+id+"] twice");
		args.add(id);
		
		
		
		int typeDef=tag.getAttributeType();
		if("attributecollection".equals(id)){
			dynamic.setValue(tag.getAttribute(id)==null);
			sbType.append("struct");
		}
		else if(typeDef==TagLibTag.ATTRIBUTE_TYPE_FIXED || typeDef==TagLibTag.ATTRIBUTE_TYPE_MIXED) {
			TagLibTagAttr attr=tag.getAttribute(id);
			if(attr==null) {
				if(typeDef==TagLibTag.ATTRIBUTE_TYPE_FIXED) {
					String names=tag.getAttributeNames();
					if(StringUtil.isEmpty(names))
						throw new TemplateException(cfml,"Attribute "+id+" is not allowed for tag "+tag.getFullName());
					
						throw new TemplateException(cfml,
							"Attribute "+id+" is not allowed for statement "+tag.getName(),
							"valid attribute names are ["+names+"]");
				}
			}
			else {
				sbType.append(attr.getType());
				//parseExpression[0]=attr.getRtexpr();
			}
		}
		else if(typeDef==TagLibTag.ATTRIBUTE_TYPE_DYNAMIC){
			dynamic.setValue(true);
		}
		return id;
	}*/
	
	private final String attributeName(CFMLString cfml, ArrayList<String> args,TagLibTag tag, RefBoolean dynamic, StringBuffer sbType, boolean allowTwiceAttr) throws TemplateException {
		String id=StringUtil.toLowerCase(CFMLTransformer.identifier(cfml,true));
		return validateAttributeName(id, cfml, args, tag, dynamic, sbType,allowTwiceAttr);
	}
	
	
	
	private final String validateAttributeName(String id,CFMLString cfml, ArrayList<String> args,TagLibTag tag, RefBoolean dynamic, StringBuffer sbType, boolean allowTwiceAttr) throws TemplateException {
		if(args.contains(id) && !allowTwiceAttr) throw new TemplateException(cfml,"you can't use the same attribute ["+id+"] twice");
		args.add(id);
		
		
		if(tag==null) return id;
		int typeDef=tag.getAttributeType();
		if("attributecollection".equals(id)){
			dynamic.setValue(tag.getAttribute(id)==null);
			sbType.append("struct");
		}
		else if(typeDef==TagLibTag.ATTRIBUTE_TYPE_FIXED || typeDef==TagLibTag.ATTRIBUTE_TYPE_MIXED) {
			TagLibTagAttr attr=tag.getAttribute(id);
			if(attr==null) {
				if(typeDef==TagLibTag.ATTRIBUTE_TYPE_FIXED) {
					String names=tag.getAttributeNames();
					if(StringUtil.isEmpty(names))
						throw new TemplateException(cfml,"Attribute "+id+" is not allowed for tag "+tag.getFullName());
					
					throw new TemplateException(cfml,
						"Attribute "+id+" is not allowed for statement "+tag.getName(),
						"valid attribute names are ["+names+"]");
				}
				dynamic.setValue(true);
				
			}
			else {
				sbType.append(attr.getType());
				//parseExpression[0]=attr.getRtexpr();
			}
		}
		else if(typeDef==TagLibTag.ATTRIBUTE_TYPE_DYNAMIC){
			dynamic.setValue(true);
		}
		return id;
	}
	
		
	private final Expression attributeValue(ExprData data, boolean allowExpression) throws TemplateException {
		return allowExpression?super.expression(data):transformAsString(data,new String[]{" ", ";", "{"});
	}
	
	public static interface EndCondition {
		public boolean isEnd(ExprData data);
	}
}