/**
* Copyright (c) 2014, the Railo Company Ltd.
* Copyright (c) 2015, Lucee Assosication Switzerland
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
package lucee.transformer.cfml.script;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import lucee.commons.lang.ExceptionUtil;
import lucee.commons.lang.StringUtil;
import lucee.commons.lang.types.RefBoolean;
import lucee.commons.lang.types.RefBooleanImpl;
import lucee.loader.engine.CFMLEngine;
import lucee.runtime.Component;
import lucee.runtime.PageSource;
import lucee.runtime.component.Member;
import lucee.runtime.config.Constants;
import lucee.runtime.exp.PageRuntimeException;
import lucee.runtime.exp.TemplateException;
import lucee.runtime.functions.system.CFFunction;
import lucee.runtime.op.Caster;
import lucee.runtime.type.util.ArrayUtil;
import lucee.runtime.type.util.ComponentUtil;
import lucee.transformer.Position;
import lucee.transformer.TransformerException;
import lucee.transformer.bytecode.Body;
import lucee.transformer.bytecode.BodyBase;
import lucee.transformer.bytecode.FunctionBody;
import lucee.transformer.bytecode.ScriptBody;
import lucee.transformer.bytecode.Statement;
import lucee.transformer.bytecode.cast.CastBoolean;
import lucee.transformer.bytecode.cast.CastOther;
import lucee.transformer.bytecode.expression.FunctionAsExpression;
import lucee.transformer.bytecode.expression.var.Assign;
import lucee.transformer.bytecode.statement.Argument;
import lucee.transformer.bytecode.statement.Condition;
import lucee.transformer.bytecode.statement.Condition.Pair;
import lucee.transformer.bytecode.statement.DoWhile;
import lucee.transformer.bytecode.statement.ExpressionAsStatement;
import lucee.transformer.bytecode.statement.For;
import lucee.transformer.bytecode.statement.ForEach;
import lucee.transformer.bytecode.statement.Return;
import lucee.transformer.bytecode.statement.Switch;
import lucee.transformer.bytecode.statement.TryCatchFinally;
import lucee.transformer.bytecode.statement.While;
import lucee.transformer.bytecode.statement.tag.Attribute;
import lucee.transformer.bytecode.statement.tag.Tag;
import lucee.transformer.bytecode.statement.tag.TagComponent;
import lucee.transformer.bytecode.statement.tag.TagOther;
import lucee.transformer.bytecode.statement.tag.TagParam;
import lucee.transformer.bytecode.statement.udf.Closure;
import lucee.transformer.bytecode.statement.udf.Function;
import lucee.transformer.bytecode.statement.udf.FunctionImpl;
import lucee.transformer.bytecode.statement.udf.Lambda;
import lucee.transformer.bytecode.util.ASMUtil;
import lucee.transformer.cfml.evaluator.EvaluatorException;
import lucee.transformer.cfml.evaluator.impl.ProcessingDirectiveException;
import lucee.transformer.cfml.expression.AbstrCFMLExprTransformer;
import lucee.transformer.cfml.tag.CFMLTransformer;
import lucee.transformer.expression.ExprBoolean;
import lucee.transformer.expression.Expression;
import lucee.transformer.expression.literal.LitBoolean;
import lucee.transformer.expression.var.Variable;
import lucee.transformer.library.function.FunctionLibFunction;
import lucee.transformer.library.tag.TagLib;
import lucee.transformer.library.tag.TagLibException;
import lucee.transformer.library.tag.TagLibTag;
import lucee.transformer.library.tag.TagLibTagAttr;
import lucee.transformer.library.tag.TagLibTagScript;
import lucee.transformer.util.PageSourceCode;
import lucee.transformer.util.SourceCode;
/**
* Innerhalb des Tag script kann in CFML eine eigene Scriptsprache verwendet werden,
* welche sich an Javascript orientiert.
* Da der data.srcCode 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() {
@Override
public boolean isEnd(ExprData data) {
return data.srcCode.isCurrent('{') || data.srcCode.isCurrent(';');
}
};
private static EndCondition SEMI=new EndCondition() {
@Override
public boolean isEnd(ExprData data) {
return data.srcCode.isCurrent(';');
}
};
private static EndCondition COMMA_ENDBRACKED=new EndCondition() {
@Override
public boolean isEnd(ExprData data) {
return data.srcCode.isCurrent(',') || data.srcCode.isCurrent(')');
}
};
private static EndCondition BRACKED=new EndCondition() {
@Override
public boolean isEnd(ExprData data) {
return data.srcCode.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 = data.factory.createLitString("NULL");
//private static final Attribute ANY = new Attribute(false,"type",data.factory.createLitString("any"),"string");
private static final char NO_ATTR_SEP = 0;
/**
* 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(data.factory);
statements(data,body,true);
return body;
}
/**
* Liest saemtliche Statements des CFScriptString ein.
* <br />
* EBNF:<br />
* <code>{statement spaces};</code>
* @param parent uebergeornetes Element dem das Statement zugewiesen wird.
* @param isRoot befindet sich der Parser im root des data.srcCode 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.srcCode.isValidIndex() && !data.srcCode.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 uebergeornetes 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.srcCode.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(funcStatement(data,parent)); // do nothing, happen already inside the method
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(staticStatement(data,parent)) ; // do nothing, happen already inside the method
else if((child=staticStatement(data,parent))!=null) parent.addStatement(child);
else if((child=componentStatement(data,parent))!=null) parent.addStatement(child);
else if((child=tagStatement(data,parent))!=null) parent.addStatement(child);
else if((child=cftagStatement(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.srcCode.forwardIfCurrent("if",'(')) return null;
Position line = data.srcCode.getPosition();
Body body=new BodyBase(data.factory);
Condition cont=new Condition(data.factory,condition(data),body,line,null);
if(!data.srcCode.forwardIfCurrent(')')) throw new TemplateException(data.srcCode,"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.srcCode.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.srcCode.getPos();
if(!data.srcCode.forwardIfCurrent("else")) return false;
comments(data);
if(!data.srcCode.forwardIfCurrent("if",'(')) {
data.srcCode.setPos(pos);
return false;
}
Position line = data.srcCode.getPosition();
Body body=new BodyBase(data.factory);
Pair pair = cont.addElseIf(condition(data), body, line,null);
if(!data.srcCode.forwardIfCurrent(')'))
throw new TemplateException(data.srcCode,"else if statement must end with a [)]");
// ex block
statement(data,body,CTX_ELSE_IF);
pair.end=data.srcCode.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.srcCode.forwardIfCurrent("else",'{') && !data.srcCode.forwardIfCurrent("else ") && !data.srcCode.forwardIfCurrent("else",'/'))
return false;
// start (
data.srcCode.previous();
// ex block
Body body=new BodyBase(data.factory);
Pair p = cont.setElse(body, data.srcCode.getPosition(),null);
statement(data,body,CTX_ELSE);
p.end=data.srcCode.getPosition();
return true;
}
private final boolean finallyStatement(ExprData data,TryCatchFinally tcf) throws TemplateException {
if(!data.srcCode.forwardIfCurrent("finally",'{') && !data.srcCode.forwardIfCurrent("finally ") && !data.srcCode.forwardIfCurrent("finally",'/'))
return false;
// start (
data.srcCode.previous();
// ex block
Body body=new BodyBase(data.factory);
tcf.setFinally(body, data.srcCode.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.srcCode.getPos();
// id
String id=variableDec(data, false);
if(id==null) {
data.srcCode.setPos(pos);
return null;
}
if(id.equalsIgnoreCase("while")){
id=null;
data.srcCode.removeSpace();
if(!data.srcCode.forwardIfCurrent('(')){
data.srcCode.setPos(pos);
return null;
}
}
else {
data.srcCode.removeSpace();
if(!data.srcCode.forwardIfCurrent(':')){
data.srcCode.setPos(pos);
return null;
}
data.srcCode.removeSpace();
if(!data.srcCode.forwardIfCurrent("while",'(')){
data.srcCode.setPos(pos);
return null;
}
}
Position line = data.srcCode.getPosition();
Body body=new BodyBase(data.factory);
While whil=new While(condition(data),body,line,null,id);
if(!data.srcCode.forwardIfCurrent(')'))
throw new TemplateException(data.srcCode,"while statement must end with a [)]");
statement(data,body,CTX_WHILE);
whil.setEnd(data.srcCode.getPosition());
return whil;
}
/**
* Liest ein switch Statment ein
* @return switch Statement
* @throws TemplateException
*/
private final Switch switchStatement(ExprData data) throws TemplateException {
if(!data.srcCode.forwardIfCurrent("switch",'('))
return null;
Position line = data.srcCode.getPosition();
comments(data);
Expression expr = super.expression(data);
comments(data);
// end )
if(!data.srcCode.forwardIfCurrent(')'))
throw new TemplateException(data.srcCode,"switch statement must end with a [)]");
comments(data);
if(!data.srcCode.forwardIfCurrent('{'))
throw new TemplateException(data.srcCode,"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.srcCode.forwardIfCurrent('}'))
throw new TemplateException(data.srcCode,"invalid construct in switch statement");
swit.setEnd(data.srcCode.getPosition());
return swit;
}
private final TagComponent componentStatement(ExprData data, Body parent) throws TemplateException {
int pos = data.srcCode.getPos();
// get the idendifier in front of
String id=identifier(data, false);
if(id==null) {
data.srcCode.setPos(pos);
return null;
}
int mod=ComponentUtil.toModifier(id, Component.MODIFIER_NONE, Component.MODIFIER_NONE);
if(mod==Component.MODIFIER_NONE) {
data.srcCode.setPos(pos);
}
comments(data);
// do we have a starting component?
if(
!data.srcCode.isCurrent(getComponentName(data.srcCode.getDialect()))
&&
(data.srcCode.getDialect()==CFMLEngine.DIALECT_CFML || !data.srcCode.isCurrent(Constants.CFML_COMPONENT_TAG_NAME))) {
data.srcCode.setPos(pos);
return null;
}
// parse the component
TagLibTag tlt = CFMLTransformer.getTLT(data.srcCode,getComponentName(data.srcCode.getDialect()),
data.config.getIdentification());
TagComponent comp =(TagComponent) _multiAttrStatement(parent, data, tlt);
if(mod!=Component.MODIFIER_NONE)comp.addAttribute(new Attribute(false,"modifier",data.factory.createLitString(id),"string"));
return comp;
}
private String getComponentName(int dialect) {
return dialect==CFMLEngine.DIALECT_LUCEE?Constants.LUCEE_COMPONENT_TAG_NAME:Constants.CFML_COMPONENT_TAG_NAME;
}
/**
* Liest ein Case Statement ein
* @return case Statement
* @throws TemplateException
*/
private final boolean caseStatement(ExprData data,Switch swit) throws TemplateException {
if(!data.srcCode.forwardIfCurrentAndNoWordAfter("case"))
return false;
//int line=data.srcCode.getLine();
comments(data);
Expression expr = super.expression(data);
comments(data);
if(!data.srcCode.forwardIfCurrent(':'))
throw new TemplateException(data.srcCode,"case body must start with [:]");
Body body=new BodyBase(data.factory);
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.srcCode.forwardIfCurrent("default",':'))
return false;
//int line=data.srcCode.getLine();
Body body=new BodyBase(data.factory);
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.srcCode.isValidIndex()) {
comments(data);
if(data.srcCode.isCurrent("case ") || data.srcCode.isCurrent("default",':') || data.srcCode.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.srcCode.getPos();
// id
String id=variableDec(data, false);
if(id==null) {
data.srcCode.setPos(pos);
return null;
}
if(id.equalsIgnoreCase("do")){
id=null;
if(!data.srcCode.isCurrent('{') && !data.srcCode.isCurrent(' ') && !data.srcCode.isCurrent('/')) {
data.srcCode.setPos(pos);
return null;
}
}
else {
data.srcCode.removeSpace();
if(!data.srcCode.forwardIfCurrent(':')){
data.srcCode.setPos(pos);
return null;
}
data.srcCode.removeSpace();
if(!data.srcCode.forwardIfCurrent("do",'{') && !data.srcCode.forwardIfCurrent("do ") && !data.srcCode.forwardIfCurrent("do",'/')) {
data.srcCode.setPos(pos);
return null;
}
data.srcCode.previous();
}
//if(!data.srcCode.forwardIfCurrent("do",'{') && !data.srcCode.forwardIfCurrent("do ") && !data.srcCode.forwardIfCurrent("do",'/'))
// return null;
Position line = data.srcCode.getPosition();
Body body=new BodyBase(data.factory);
//data.srcCode.previous();
statement(data,body,CTX_DO_WHILE);
comments(data);
if(!data.srcCode.forwardIfCurrent("while",'('))
throw new TemplateException(data.srcCode,"do statement must have a while at the end");
DoWhile doWhile=new DoWhile(condition(data),body,line,data.srcCode.getPosition(),id);
if(!data.srcCode.forwardIfCurrent(')'))
throw new TemplateException(data.srcCode,"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.srcCode.getPos();
// id
String id=variableDec(data, false);
if(id==null) {
data.srcCode.setPos(pos);
return null;
}
if(id.equalsIgnoreCase("for")){
id=null;
data.srcCode.removeSpace();
if(!data.srcCode.forwardIfCurrent('(')){
data.srcCode.setPos(pos);
return null;
}
}
else {
data.srcCode.removeSpace();
if(!data.srcCode.forwardIfCurrent(':')){
data.srcCode.setPos(pos);
return null;
}
data.srcCode.removeSpace();
if(!data.srcCode.forwardIfCurrent("for",'(')){
data.srcCode.setPos(pos);
return null;
}
}
//if(!data.srcCode.forwardIfCurrent("for",'('))
// return null;
Expression left=null;
Body body=new BodyBase(data.factory);
Position line = data.srcCode.getPosition();
comments(data);
if(!data.srcCode.isCurrent(';')) {
// left
left=expression(data);
comments(data);
}
// middle for
if(data.srcCode.forwardIfCurrent(';')) {
Expression cont=null;
Expression update=null;
// condition
comments(data);
if(!data.srcCode.isCurrent(';')) {
cont=condition(data);
comments(data);
}
// middle
if(!data.srcCode.forwardIfCurrent(';'))
throw new TemplateException(data.srcCode,"invalid syntax in for statement");
// update
comments(data);
if(!data.srcCode.isCurrent(')')) {
update=expression(data);
comments(data);
}
// start )
if(!data.srcCode.forwardIfCurrent(')'))
throw new TemplateException(data.srcCode,"invalid syntax in for statement, for statement must end with a [)]");
// ex block
statement(data,body,CTX_FOR);
return new For(data.factory,left,cont,update,body,line,data.srcCode.getPosition(),id);
}
// middle foreach
else if(data.srcCode.forwardIfCurrent("in")) {
// condition
comments(data);
Expression value = expression(data);
comments(data);
if(!data.srcCode.forwardIfCurrent(')'))
throw new TemplateException(data.srcCode,"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.srcCode,"invalid syntax in for statement, left value is invalid");
//if(!(value instanceof Variable))
// throw new TemplateException(data.srcCode,"invalid syntax in for statement, right value is invalid");
return new ForEach((Variable)left,value,body,line,data.srcCode.getPosition(),id);
}
else
throw new TemplateException(data.srcCode,"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 Statement funcStatement(ExprData data,Body parent) throws TemplateException {
int pos=data.srcCode.getPos();
// read 5 tokens (returntype,access modifier,"abstract|final|static","function", function name)
String str=variableDec(data, false);
// if there is no token at all we have no function
if(str==null) {
data.srcCode.setPos(pos);
return null;
}
comments(data);
String[] tokens=new String[]{str,null,null,null,null};
tokens[1]=variableDec(data, false);
comments(data);
if(tokens[1]!=null){
tokens[2]=variableDec(data, false);
comments(data);
if(tokens[2]!=null){
tokens[3]=variableDec(data, false);
comments(data);
if(tokens[3]!=null){
tokens[4]=identifier(data, false);
comments(data);
}
}
}
// function name
String functionName=null;
for(int i=tokens.length-1;i>=0;i--){
// first from right is the function name
if(tokens[i]!=null) {
functionName=tokens[i];
tokens[i]=null;
break;
}
}
if(functionName==null || functionName.indexOf(',')!=-1 || functionName.indexOf('[')!=-1) {
data.srcCode.setPos(pos);
return null;
}
//throw new TemplateException(data.srcCode, "invalid syntax");
String returnType=null;
// search for "function"
boolean hasOthers=false, first=true;
for(int i=tokens.length-1;i>=0;i--){
if("function".equalsIgnoreCase(tokens[i])) {
// if it is the first "function" (from right) and we had already something else, the syntax is broken!
if(hasOthers && first) throw new TemplateException(data.srcCode, "invalid syntax");
// we already have a return type,so this is the 3th "function"!
else if(returnType!=null) throw new TemplateException(data.srcCode, "invalid syntax");
else if(!first)returnType=tokens[i];
first=false;
tokens[i]=null;
}
else if(tokens[i]!=null) {
hasOthers=true;
}
}
// no "function" found
if(first) {
data.srcCode.setPos(pos);
return null;
}
// access modifier
int _access,access=-1;
for(int i=0;i<tokens.length;i++){
if(tokens[i]!=null && (_access=ComponentUtil.toIntAccess(tokens[i], -1))!=-1) {
// we already have an access modifier
if(access!=-1) {
// we already have a return type
if(returnType!=null)throw new TemplateException(data.srcCode, "invalid syntax");
returnType=tokens[i];
}
else access=_access;
tokens[i]=null;
}
}
// no access defined
if(access==-1) access=Component.ACCESS_PUBLIC;
// Non access modifier
int _modifier,modifier=Component.MODIFIER_NONE;
boolean isStatic=false;
for(int i=0;i<tokens.length;i++){
if(tokens[i]!=null) {
_modifier=ComponentUtil.toModifier(tokens[i], Component.MODIFIER_NONE, Component.MODIFIER_NONE);
// abstract|final
if(_modifier!=Component.MODIFIER_NONE) {
// we already have an Non access modifier
if(modifier!=Component.MODIFIER_NONE || isStatic) {
// we already have a return type
if(returnType!=null)throw new TemplateException(data.srcCode, "invalid syntax");
returnType=tokens[i];
}
else modifier=_modifier;
tokens[i]=null;
}
// static
else if(tokens[i].equalsIgnoreCase("static")) {
// we already have an Non access modifier
if(modifier!=Component.MODIFIER_NONE || isStatic) {
// we already have a return type
if(returnType!=null)throw new TemplateException(data.srcCode, "invalid syntax");
returnType=tokens[i];
}
else isStatic=true;
tokens[i]=null;
}
}
}
// return type
for(int i=0;i<tokens.length;i++){
if(tokens[i]!=null) {
if(returnType!=null)throw new TemplateException(data.srcCode, "invalid syntax");
returnType=tokens[i];
}
}
Position line = data.srcCode.getPosition();
//comments(data);
// Name
if(!data.isCFC && !data.isInterface){
FunctionLibFunction flf = getFLF(data,functionName);
try {
if(flf!=null && flf.getFunctionClassDefinition().getClazz()!=CFFunction.class){
PageSource ps=null;
if(data.srcCode instanceof PageSourceCode) {
ps=((PageSourceCode)data.srcCode).getPageSource();
}
String path=null;
if(ps!=null) {
path = ps.getDisplayPath();
path=path.replace('\\', '/');
}
if(path==null || path.indexOf("/library/function/")==-1)// TODO make better
throw new TemplateException(data.srcCode,"The name ["+functionName+"] is already used by a built in Function");
}
}
catch(Throwable t) {
ExceptionUtil.rethrowIfNecessary(t);
throw new PageRuntimeException(Caster.toPageException(t));
}
}
Function res = closurePart(data, functionName,access,modifier,returnType,line,false);
if(isStatic) {
if(data.context==CTX_INTERFACE)
throw new TemplateException(data.srcCode, "static functions are not allowed within the interface body");
TagOther tag = createStaticTag(data, res.getStart());
tag.getBody().addStatement(res);
return tag;
}
return res;
}
@Override
public ArrayList<Argument> getScriptFunctionArguments(ExprData data) throws TemplateException {
// arguments
LitBoolean passByRef;
Expression displayName;
Expression hint;
Map<String,Attribute> meta;
String _name;
ArrayList<Argument> result = new ArrayList<Argument>();
do {
comments(data);
// finish
if(data.srcCode.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;
}
if(idName==null) throw new TemplateException(data.srcCode,"invalid argument definition");
}
String typeName="any";
if(idName==null) throw new TemplateException(data.srcCode,"invalid argument definition");
comments(data);
if(!data.srcCode.isCurrent(')') && !data.srcCode.isCurrent('=') && !data.srcCode.isCurrent(':') && !data.srcCode.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.srcCode,"invalid argument name ["+idName+"] definition");
}
if(idName==null) throw new TemplateException(data.srcCode,"invalid argument definition");
comments(data);
Expression defaultValue;
if(data.srcCode.isCurrent('=') || data.srcCode.isCurrent(':')) {
data.srcCode.next();
comments(data);
defaultValue=expression(data);
}
else defaultValue=null;
// assign meta data defined in doc comment
passByRef = data.factory.TRUE();
displayName=data.factory.EMPTY();
hint=data.factory.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=data.factory.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,data.factory.EMPTY(),Boolean.TRUE,null,false,NO_ATTR_SEP,true);
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);
}
}
result.add(new Argument(
data.factory.createLitString(idName),
data.factory.createLitString(typeName),
data.factory.createLitBoolean(required),
defaultValue,passByRef,displayName,hint,meta));
comments(data);
}
while(data.srcCode.forwardIfCurrent(','));
return result;
}
@Override
protected final Function closurePart(ExprData data, String id, int access,int modifier, String rtnType, Position line,boolean closure) throws TemplateException {
Body body=new FunctionBody(data.factory);
Function func=closure?
new Closure(data.root,id,access,modifier,rtnType,body,line,null)
:new FunctionImpl(data.root,id,access,modifier,rtnType,body,line,null);
comments(data);
if(!data.srcCode.forwardIfCurrent('('))
throw new TemplateException(data.srcCode,"invalid syntax in function head, missing begin [(]");
// arguments
ArrayList<Argument> args = getScriptFunctionArguments(data);
for(Argument arg : args) {
func.addArgument(arg.getName(),arg.getType(),arg.getRequired(),arg.getDefaultValue(),arg.isPassByReference(),arg.getDisplayName(),arg.getHint(),arg.getMetaData());
}
// end )
comments(data);
if(!data.srcCode.forwardIfCurrent(')'))
throw new TemplateException(data.srcCode,"invalid syntax in function head, missing ending [)]");
//TagLibTag tlt = CFMLTransformer.getTLT(data.srcCode,"function");
// doc comment
if(data.docComment!=null){
func.setHint(data.factory,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,data.factory.EMPTY(),Boolean.TRUE,null,false,NO_ATTR_SEP,true);
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.srcCode.getPosition());
if(closure) comments(data);
return func;
}
@Override
protected final Function lambdaPart(ExprData data, String id, int access,int modifier,String rtnType, Position line, ArrayList<Argument> args) throws TemplateException {
Body body=new FunctionBody(data.factory);
Function func=
new Lambda(data.root,id,access,modifier,rtnType,body,line,null);
//new FunctionImpl(data.page,id,access,rtnType,body,line,null);
comments(data);
// add arguments
for(Argument arg : args) {
func.addArgument(arg.getName(),arg.getType(),arg.getRequired(),arg.getDefaultValue(),arg.isPassByReference(),arg.getDisplayName(),arg.getHint(),arg.getMetaData());
}
comments(data);
// body
boolean oldInsideFunction=data.insideFunction;
data.insideFunction=true;
try {
if(data.srcCode.isCurrent('{')) {
statement(data,body,CTX_FUNCTION);
}
else {
if(data.srcCode.forwardIfCurrent("return ")) {
comments(data);
}
// ex block
short prior = data.context;
data.context = CTX_FUNCTION;
comments( data );
Expression expr = expression( data );
//checkSemiColonLineFeed( data, true ,true );
Return rtn = new Return( expr, line, data.srcCode.getPosition() );
body.addStatement( rtn );
data.docComment = null;
data.context = prior;
}
}
finally{
data.insideFunction=oldInsideFunction;
}
/*try {
// ex block
statement(data,body,CTX_FUNCTION);
}
finally{
data.insideFunction=oldInsideFunction;
}*/
func.setEnd(data.srcCode.getPosition());
comments(data);
return func;
}
private Statement tagStatement(ExprData data, Body parent) throws TemplateException {
Statement child;
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;
}
}
return null;
}
private final Statement _multiAttrStatement(Body parent, ExprData data,TagLibTag tlt) throws TemplateException {
int pos = data.srcCode.getPos();
try {
return __multiAttrStatement(parent,data,tlt);
}
catch (ProcessingDirectiveException e) {
throw e;
}
catch (TemplateException e) {
try {
data.srcCode.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.srcCode.forwardIfCurrent(type) ||
// lucee dialect support component as alias for class
(data.srcCode.getDialect()==CFMLEngine.DIALECT_LUCEE && type.equalsIgnoreCase(Constants.LUCEE_COMPONENT_TAG_NAME) && data.srcCode.forwardIfCurrent(Constants.CFML_COMPONENT_TAG_NAME))) {
boolean isValid=(data.srcCode.isCurrent(' ') || (tlt.getHasBody() && data.srcCode.isCurrent('{')));
if(!isValid){
data.srcCode.setPos(data.srcCode.getPos()-type.length());
return null;
}
}
else return null;
Position line = data.srcCode.getPosition();
TagLibTagScript script = tlt.getScript();
//TagLibTag tlt = CFMLTransformer.getTLT(data.srcCode,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,data.factory.EMPTY(),script.getRtexpr()?Boolean.TRUE:Boolean.FALSE,null,false,',',false);
for(int i=0;i<attrs.length;i++){
tag.addAttribute(attrs[i]);
}
comments(data);
// body
if(tlt.getHasBody()){
Body body=new BodyBase(data.factory);
boolean wasSemiColon=statement(data,body,script.getContext());
if(!wasSemiColon || !tlt.isBodyFree() || body.hasStatements())
tag.setBody(body);
}
else checkSemiColonLineFeed(data,true,true,true);
tag.setEnd(data.srcCode.getPosition());
eval(tlt,data,tag);
return tag;
}
private Statement cftagStatement(ExprData data, Body parent) throws TemplateException {
if(data.ep==null) return null; // that is because cfloop-contition evaluator does not pass this
int start = data.srcCode.getPos();
// namespace and separator
TagLib tagLib=CFMLTransformer.nameSpace(data);
if(tagLib==null) return null;
//print.e("namespace:"+tagLib.getNameSpaceAndSeparator());
// get the name of the tag
String id = CFMLTransformer.identifier(data.srcCode, false,true);
if(id==null) {
data.srcCode.setPos(start);
return null;
}
id=id.toLowerCase();
String appendix=null;
TagLibTag tlt=tagLib.getTag(id);
//print.e("tlt:"+tlt);
// get taglib
if(tlt==null) {
tlt=tagLib.getAppendixTag(id);
//print.e("appendix:"+tlt);
if(tlt==null) {
//if(tagLib.getIgnoreUnknowTags()){ if we do this a expression like the following no longer work cfwhatever=1;
data.srcCode.setPos(start);
return null;
//}
//throw new TemplateException(data.srcCode,"undefined tag ["+tagLib.getNameSpaceAndSeparator()+id+"]");
}
appendix=StringUtil.removeStartingIgnoreCase(id,tlt.getName());
}
if(tagLib.isCore() && tlt.getScript()==null) {
data.srcCode.setPos(start);
return null;
}
// check for opening bracked or closing semicolon
comments(data);
boolean noAttrs=false;
if(!data.srcCode.forwardIfCurrent('(')){
if(checkSemiColonLineFeed(data, false, false, false)){
noAttrs=true;
}
else {
data.srcCode.setPos(start);
return null;
}
}
Position line = data.srcCode.getPosition();
// script specific behavior
short context=CTX_OTHER;
Boolean allowExpression=Boolean.TRUE;
{
TagLibTagScript script = tlt.getScript();
if(script!=null) {
context=script.getContext();
// always true for this tags allowExpression=script.getRtexpr()?Boolean.TRUE:Boolean.FALSE;
if(context==CTX_CFC)data.isCFC=true;
else if(context==CTX_INTERFACE)data.isInterface=true;
}
}
Tag tag=getTag(data,parent,tlt, line,null);
if(appendix!=null) {
tag.setAppendix(appendix);
tag.setFullname(tlt.getFullName().concat(appendix));
}
else {
tag.setFullname(tlt.getFullName());
}
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);
}
comments(data);
// attributes
Attribute[] attrs = noAttrs?new Attribute[0] : attributes(tag,tlt,data,BRACKED,data.factory.EMPTY(),allowExpression,null,false,',',true);
data.srcCode.forwardIfCurrent(')');
for(int i=0;i<attrs.length;i++){
tag.addAttribute(attrs[i]);
}
comments(data);
// body
if(tlt.getHasBody()){
Body body=new BodyBase(data.factory);
boolean wasSemiColon=statement(data,body,context);
if(!wasSemiColon || !tlt.isBodyFree() || body.hasStatements())
tag.setBody(body);
}
else checkSemiColonLineFeed(data,true,true,true);
tag.setEnd(data.srcCode.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(data.factory));
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.srcCode.getPos();
try {
return _propertyStatement(data, parent);
} catch (TemplateException e) {
try {
data.srcCode.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.srcCode.forwardIfCurrent("property "))
return null;
Position line = data.srcCode.getPosition();
TagLibTag tlt = CFMLTransformer.getTLT(data.srcCode,"property",data.config.getIdentification());
Tag property=new TagOther(data.factory,line,null);
addMetaData(data, property,IGNORE_LIST_PROPERTY);
boolean hasName=false,hasType=false;
int pos = data.srcCode.getPos();
String tmp=variableDec(data, true);
if(!StringUtil.isEmpty(tmp)) {
if(tmp.indexOf('.')!=-1) {
property.addAttribute(new Attribute(false,"type",data.factory.createLitString(tmp),"string"));
hasType=true;
}
else {
data.srcCode.setPos(pos);
}
}
else data.srcCode.setPos(pos);
// folgend wird tlt extra nicht uebergeben, sonst findet pruefung statt
Attribute[] attrs = attributes(property,tlt,data,SEMI, data.factory.NULL(),Boolean.FALSE,"name",true,NO_ATTR_SEP,false);
checkSemiColonLineFeed(data,true,true,false);
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(data.factory.NULL())){
if(attr.getName().equalsIgnoreCase("name")){
hasName=true;
}
else if(attr.getName().equalsIgnoreCase("type")){
hasType=true;
}
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(data.factory.NULL())){
// type
if(first==null && ((!hasName && !hasType) || !hasName)){
first=attr.getNameOC();
}
// name
else if(second==null && !hasName && !hasType){
second=attr.getNameOC();
}
// attr with no value
else {
attr=new Attribute(true,attr.getName(),data.factory.EMPTY(),"string");
property.addAttribute(attr);
}
}
}
if(first!=null) {
hasName=true;
if(second!=null){
hasType=true;
property.addAttribute(new Attribute(false,"name",data.factory.createLitString(second),"string"));
property.addAttribute(new Attribute(false,"type",data.factory.createLitString(first),"string"));
}
else {
property.addAttribute(new Attribute(false,"name",data.factory.createLitString(first),"string"));
}
}
if(!hasType) {
property.addAttribute(new Attribute(false,"type",data.factory.createLitString("any"),"string"));
}
if(!hasName)
throw new TemplateException(data.srcCode,"missing name declaration for property");
/*Tag property=new TagBase(line);
property.setTagLibTag(tlt);
property.addAttribute(new Attribute(false,"name",data.factory.createLitString(name),"string"));
property.addAttribute(new Attribute(false,"type",data.factory.createLitString(type),"string"));
*/
property.setEnd(data.srcCode.getPosition());
return property;
}
private final Tag staticStatement(ExprData data, Body parent) throws TemplateException {
if(!data.srcCode.forwardIfCurrent("static",'{')) return null;
// get one back to have again { so the parser works
data.srcCode.previous();
Position start = data.srcCode.getPosition();
TagOther tag = createStaticTag(data,start);
statement(data,tag.getBody(),CTX_STATIC);
return tag;
}
public static TagOther createStaticTag(ExprData data, Position start) throws TemplateException {
TagLibTag tlt = CFMLTransformer.getTLT(data.srcCode,"static",data.config.getIdentification());
BodyBase body = new BodyBase(data.factory);
TagOther tag=new TagOther(data.factory, start, data.srcCode.getPosition());
tag.setTagLibTag(tlt);
tag.setBody(body);
data.ep.add(tlt, tag, data.flibs, data.srcCode);
return tag;
}
public Statement paramStatement(ExprData data,Body parent) throws TemplateException {
int pos = data.srcCode.getPos();
try {
return _paramStatement(data, parent);
} catch (TemplateException e) {
try {
data.srcCode.setPos(pos);
return expressionStatement(data,parent);
} catch (TemplateException e1) {
throw e;
}
}
}
private Tag _paramStatement(ExprData data,Body parent) throws TemplateException {
if(!data.srcCode.forwardIfCurrent("param "))
return null;
Position line = data.srcCode.getPosition();
TagLibTag tlt = CFMLTransformer.getTLT(data.srcCode,"param",data.config.getIdentification());
TagParam param=new TagParam(data.factory,line,null);
// type
boolean hasType=false;
boolean hasName=false;
int pos = data.srcCode.getPos();
// first 2 arguments can be type/name directly
String tmp=variableDec(data, true);
do {
if(!StringUtil.isEmpty(tmp)) {
TagLibTagAttr attr = tlt.getAttribute(tmp.toLowerCase(),true);
// name is not a defined attribute
if(attr==null) {
comments(data);
// it could be a name followed by default value
if(data.srcCode.forwardIfCurrent('=')) {
comments(data);
Expression v=attributeValue(data,true);
param.addAttribute(new Attribute(false,"name",data.factory.createLitString(tmp),"string"));
param.addAttribute(new Attribute(false,"default",v,"string"));
hasName=true;
break; // if we had a value this was already name
}
// can be type or name
int pos2 = data.srcCode.getPos();
// first could be type, followed by name
comments(data);
String tmp2=variableDec(data, true);
if(!StringUtil.isEmpty(tmp2)) {
attr = tlt.getAttribute(tmp2.toLowerCase(),true);
if(attr==null) {
param.addAttribute(new Attribute(false,"name",data.factory.createLitString(tmp2),"string"));
param.addAttribute(new Attribute(false,"type",data.factory.createLitString(tmp),"string"));
if(data.srcCode.forwardIfCurrent('=')) {
Expression v=attributeValue(data,true);
param.addAttribute(new Attribute(false,"default",v,"string"));
}
hasName=true;
hasType=true;
break;
}
}
param.addAttribute(new Attribute(false,"name",data.factory.createLitString(tmp),"string"));
data.srcCode.setPos(pos2);
hasName=true;
}
else data.srcCode.setPos(pos);
}
else data.srcCode.setPos(pos);
}while(false);
// folgend wird tlt extra nicht uebergeben, sonst findet pruefung statt
Attribute[] attrs = attributes(param,tlt,data,SEMI, data.factory.NULL(),Boolean.TRUE,"name",true,',',false);
checkSemiColonLineFeed(data,true,true,true);
param.setTagLibTag(tlt);
param.setScriptBase(true);
Attribute attr;
// first fill all regular attribute -> name="value"
boolean hasDynamic=false;
for(int i=attrs.length-1;i>=0;i--){
attr=attrs[i];
if(!attr.getValue().equals(data.factory.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.srcCode,tlt,attr.getName());
hasDynamic=true;
param.addAttribute(new Attribute(false,"name",data.factory.createLitString(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(data.factory.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(),data.factory.EMPTY(),"string");
param.addAttribute(attr);
}
}
}
if(first!=null) {
if(second!=null){
hasName=true;
hasType=true;
if(hasDynamic) throw attrNotSupported(data.srcCode,tlt,first);
hasDynamic=true;
param.addAttribute(new Attribute(false,"name",data.factory.createLitString(second),"string"));
param.addAttribute(new Attribute(false,"type",data.factory.createLitString(first),"string"));
}
else {
param.addAttribute(new Attribute(false,hasName?"type":"name",data.factory.createLitString(first),"string"));
hasName=true;
}
}
//if(!hasType)
// param.addAttribute(ANY);
if(!hasName)
throw new TemplateException(data.srcCode,"missing name declaration for param");
param.setEnd(data.srcCode.getPosition());
return param;
}
private TemplateException attrNotSupported(SourceCode 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.srcCode.removeSpace();
while(data.srcCode.forwardIfCurrent('.')){
data.srcCode.removeSpace();
rtn.append('.');
id=identifier(data, firstCanBeNumber);
if(id==null)return null;
rtn.append(id);
data.srcCode.removeSpace();
}
while(data.srcCode.forwardIfCurrent("[]")){
data.srcCode.removeSpace();
rtn.append("[]");
}
data.srcCode.revertRemoveSpace();
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.srcCode.forwardIfCurrentAndNoVarExt("return")) return null;
Position line = data.srcCode.getPosition();
Return rtn;
comments(data);
if(checkSemiColonLineFeed(data, false,false,false)) rtn=new Return(data.factory,line,data.srcCode.getPosition());
else {
Expression expr = expression(data);
checkSemiColonLineFeed(data, true,true,false);
rtn=new Return(expr,line,data.srcCode.getPosition());
}
comments(data);
return rtn;
}
private final Statement _singleAttrStatement(Body parent, ExprData data, TagLibTag tlt) throws TemplateException {
int pos = data.srcCode.getPos();
try {
return __singleAttrStatement(parent,data,tlt, false);
}
catch (ProcessingDirectiveException e) {
throw e;
}
catch (TemplateException e) {
data.srcCode.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.srcCode.forwardIfCurrent(tagName)){
if(!data.srcCode.isCurrent(' ') && !data.srcCode.isCurrent(';')){
data.srcCode.setPos(data.srcCode.getPos()-tagName.length());
return null;
}
}
else return null;
int pos=data.srcCode.getPos()-tagName.length();
Position line = data.srcCode.getPosition();
//TagLibTag tlt = CFMLTransformer.getTLT(data.srcCode,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.srcCode.getCurrent();
if(ATTR_TYPE_REQUIRED==attrType || (!data.srcCode.isCurrent(';') && ATTR_TYPE_OPTIONAL==attrType)) {
if(data.srcCode.isCurrent('{')) {// this can be only a json string
int p=data.srcCode.getPos();
try{
attrValue=isSimpleValue(attr.getType())?null:json(data,JSON_STRUCT,'{','}');
}
catch(Throwable t){
ExceptionUtil.rethrowIfNecessary(t);
data.srcCode.setPos(p);
}
}
else attrValue =attributeValue(data, tlt.getScript().getRtexpr());
if(attrValue!=null && isOperator(c)) {
data.srcCode.setPos(pos);
return null;
}
}
}
if(attrValue!=null){
attrName=attr.getName();
TagLibTagAttr tlta = tlt.getAttribute(attr.getName(),true);
tag.addAttribute(new Attribute(false,attrName,CastOther.toExpression(attrValue,tlta.getType()),tlta.getType()));
}
else if(ATTR_TYPE_REQUIRED==attrType){
data.srcCode.setPos(pos);
return null;
}
// body
if(tlt.getHasBody()){
Body body=new BodyBase(data.factory);
boolean wasSemiColon=statement(data,body,tlt.getScript().getContext());
if(!wasSemiColon || !tlt.isBodyFree() || body.hasStatements())
tag.setBody(body);
}
else checkSemiColonLineFeed(data,true,true,true);
if(tlt.hasTTE())
data.ep.add(tlt, tag, data.flibs, data.srcCode);
if(!StringUtil.isEmpty(attrName))validateAttributeName(attrName, data.srcCode, new ArrayList<String>(), tlt, new RefBooleanImpl(false), new StringBuffer(), allowTwiceAttr);
tag.setEnd(data.srcCode.getPosition());
eval(tlt,data,tag);
return tag;
}
private boolean isSimpleValue(String type) {
return type.equalsIgnoreCase("string") || type.equalsIgnoreCase("boolean") || type.equalsIgnoreCase("number") || type.equalsIgnoreCase("numeric");
}
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.srcCode.forwardIfCurrent(tagName)){
if(!data.srcCode.isCurrent(' ') && !data.srcCode.isCurrent(';')){
data.srcCode.setPos(data.srcCode.getPos()-tagName.length());
return null;
}
}
else return null;
int pos=data.srcCode.getPos()-tagName.length();
int line=data.srcCode.getLine();
TagLibTag tlt = CFMLTransformer.getTLT(data.srcCode,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.srcCode.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.srcCode.setPos(pos);
return null;
}
checkSemiColonLineFeed(data,true);
if(!StringUtil.isEmpty(tlt.getTteClassName()))data.ep.add(tlt, tag, data.fld, data.srcCode);
if(!StringUtil.isEmpty(attrName))validateAttributeName(attrName, data.srcCode, new ArrayList<String>(), tlt, new RefBooleanImpl(false), new StringBuffer(), allowTwiceAttr);
eval(tlt,data,tag);
return tag;
}*/
private final void eval(TagLibTag tlt, lucee.transformer.cfml.expression.CFMLExprTransformer.ExprData data, Tag tag) throws TemplateException {
if(tlt.hasTTE()){
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.srcCode);
}
}
private final Tag getTag(ExprData data,Body parent, TagLibTag tlt, Position start,Position end) throws TemplateException {
try {
Tag tag = tlt.getTag(data.factory,start, end);
tag.setParent(parent);
return tag;
} catch (TagLibException e) {
throw new TemplateException(data.srcCode,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 {
// first we check if we have a access modifier
int pos = data.srcCode.getPos();
int access=-1;
boolean _final=false;
if(data.context==CTX_CFC || data.context==CTX_STATIC) {
if(data.srcCode.forwardIfCurrent("final ")) {
_final=true;
comments(data);
}
if(data.srcCode.forwardIfCurrent("private ")) {
access=Component.ACCESS_PRIVATE;
comments(data);
}
else if(data.srcCode.forwardIfCurrent("package ")) {
access=Component.ACCESS_PACKAGE;
comments(data);
}
else if(data.srcCode.forwardIfCurrent("public ")) {
access=Component.ACCESS_PUBLIC;
comments(data);
}
else if(data.srcCode.forwardIfCurrent("remote ")) {
access=Component.ACCESS_REMOTE;
//comments(data);
throw new TemplateException(data.srcCode,"access modifier [remote] not supported in this context");
}
if(!_final && data.srcCode.forwardIfCurrent("final ")) {
_final=true;
comments(data);
}
}
Expression expr=expression(data);
checkSemiColonLineFeed(data,true,true,false);
// variable declaration (variable in body)
if(expr instanceof Variable) {
Variable v=(Variable) expr;
if(ASMUtil.isOnlyDataMember(v)){
expr=new Assign(v, data.srcCode.getDialect()==CFMLEngine.DIALECT_LUCEE || data.config.getFullNullSupport()?data.factory.createNull():data.factory.EMPTY(), data.srcCode.getPosition());
}
}
// if a specific access was defined
if(access>-1 || _final) {
if(!(expr instanceof Assign)) {
data.srcCode.setPos(pos);
throw new TemplateException(data.srcCode,"invalid syntax, access modifier cannot be used in this context");
}
if(access>-1){
// this is only supported with the Lucee dialect
//if(data.srcCode.getDialect()==CFMLEngine.DIALECT_CFML)
// throw new TemplateException(data.srcCode,
// "invalid syntax, access modifier cannot be used in this context");
((Assign)expr).setAccess(access);
}
if(_final)((Assign)expr).setModifier(Member.MODIFIER_FINAL);
}
if(expr instanceof FunctionAsExpression)
return ((FunctionAsExpression)expr).getFunction();
return new ExpressionAsStatement(expr);
}
private final boolean checkSemiColonLineFeed(ExprData data,boolean throwError, boolean checkNLBefore,boolean allowEmptyCurlyBracked) throws TemplateException {
comments(data);
if(!data.srcCode.forwardIfCurrent(';')){
// curly brackets?
if(allowEmptyCurlyBracked) {
int pos = data.srcCode.getPos();
if(data.srcCode.forwardIfCurrent('{')) {
comments(data);
if(data.srcCode.forwardIfCurrent('}')) return true;
data.srcCode.setPos(pos);
}
}
if((!checkNLBefore || !data.srcCode.hasNLBefore()) && !isFinish(data) && !data.srcCode.isCurrent('}')){
if(!throwError) return false;
throw new TemplateException(data.srcCode,"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.srcCode.forwardIfCurrent("try",'{') && !data.srcCode.forwardIfCurrent("try ") && !data.srcCode.forwardIfCurrent("try",'/'))
return null;
data.srcCode.previous();
Body body=new BodyBase(data.factory);
TryCatchFinally tryCatchFinally=new TryCatchFinally(data.factory,body,data.srcCode.getPosition(),null);
statement(data,body,CTX_TRY);
comments(data);
// catches
short catchCount=0;
while(data.srcCode.forwardIfCurrent("catch",'(')) {
catchCount++;
comments(data);
// type
int pos=data.srcCode.getPos();
Position line=data.srcCode.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.srcCode.removeSpace();
if(!data.srcCode.forwardIfCurrent('.'))break;
sbType.append('.');
data.srcCode.removeSpace();
}
if(sbType.length()==0) {
type=string(data);
if(type==null)
throw new TemplateException(data.srcCode,"a catch statement must begin with the throwing type (query, application ...).");
}
else {
type=data.factory.createLitString(sbType.toString());
}
//name = expression();
comments(data);
// name
if(!data.srcCode.isCurrent(')')) {
name=expression(data);
}
else {
data.srcCode.setPos(pos);
name=expression(data);
type = data.factory.createLitString( "any" );
}
comments(data);
Body b=new BodyBase(data.factory);
try {
tryCatchFinally.addCatch(type,name,b,line);
}
catch (TransformerException e) {
throw new TemplateException(data.srcCode,e.getMessage());
}
comments(data);
if(!data.srcCode.forwardIfCurrent(')')) throw new TemplateException(data.srcCode,"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.srcCode,"a try statement must have at least one catch statement");
//if(body.isEmpty()) return null;
tryCatchFinally.setEnd(data.srcCode.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);
if(data.tagName==null) return false;
return data.srcCode.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.srcCode.forwardIfCurrent('{'))
return false;
comments(data);
if(data.srcCode.forwardIfCurrent('}')) {
return true;
}
statements(data,body,false);
if(!data.srcCode.forwardIfCurrent('}'))
throw new TemplateException(data.srcCode,"Missing ending [}]");
return true;
}
private final Attribute[] attributes(Tag tag,TagLibTag tlt, ExprData data, EndCondition endCond,
Expression defaultValue,Object oAllowExpression, String ignoreAttrReqFor, boolean allowTwiceAttr,
char attributeSeparator,boolean allowColonAsNameValueSeparator) throws TemplateException {
ArrayList<Attribute> attrs=new ArrayList<Attribute>();
ArrayList<String> ids=new ArrayList<String>();
while(data.srcCode.isValidIndex()) {
data.srcCode.removeSpace();
// if no more attributes break
if(endCond.isEnd(data)) break;
Attribute attr = attribute(tlt,data,ids,defaultValue,oAllowExpression, allowTwiceAttr,allowColonAsNameValueSeparator);
attrs.add(attr);
// seperator
if(attributeSeparator>0) {
data.srcCode.removeSpace();
data.srcCode.forwardIfCurrent(attributeSeparator);
}
}
// not defined attributes
if(tlt!=null){
boolean hasAttributeCollection=false;
Iterator<Attribute> iii = attrs.iterator();
while(iii.hasNext()) {
if("attributecollection".equalsIgnoreCase(iii.next().getName())){
hasAttributeCollection=true;
break;
}
}
int type=tlt.getAttributeType();
if(type==TagLibTag.ATTRIBUTE_TYPE_FIXED || type==TagLibTag.ATTRIBUTE_TYPE_MIXED) {
Map<String, TagLibTagAttr> hash = tlt.getAttributes();
Iterator<Entry<String, TagLibTagAttr>> it = hash.entrySet().iterator();
Entry<String, TagLibTagAttr> e;
while(it.hasNext()) {
e = it.next();
TagLibTagAttr att=e.getValue();
if(att.isRequired() && !contains(attrs,att) && att.getDefaultValue()==null && !att.getName().equals(ignoreAttrReqFor)) {
if(!hasAttributeCollection)throw new TemplateException(data.srcCode,"attribute "+att.getName()+" is required for statement "+tlt.getName());
if(tag!=null)tag.addMissingAttribute(att);
}
}
}
}
return attrs.toArray(new Attribute[attrs.size()]);
}
private final boolean contains(ArrayList<Attribute> attrs, TagLibTagAttr attr) {
Iterator<Attribute> it = attrs.iterator();
String name;
String[] alias;
while(it.hasNext()){
name=it.next().getName();
// check name
if(name.equals(attr.getName())) return true;
// and aliases
alias = attr.getAlias();
if(!ArrayUtil.isEmpty(alias)) for(int i=0;i<alias.length;i++){
if(alias[i].equals(attr.getName())) return true;
}
}
return false;
}
private final Attribute attribute(TagLibTag tlt, ExprData data, ArrayList<String> args, Expression defaultValue,Object oAllowExpression, boolean allowTwiceAttr, boolean allowColonSeparator) throws TemplateException {
StringBuffer sbType=new StringBuffer();
RefBoolean dynamic=new RefBooleanImpl(false);
// Name
String name=attributeName(data.srcCode,args,tlt,dynamic,sbType, allowTwiceAttr,!allowColonSeparator);
String nameLC=name==null?null:name.toLowerCase();
boolean allowExpression=false;
if(oAllowExpression instanceof Boolean)allowExpression=((Boolean)oAllowExpression).booleanValue();
else if(oAllowExpression instanceof String)allowExpression=((String)oAllowExpression).equalsIgnoreCase(nameLC);
Expression value=null;
comments(data);
// value
boolean hasValue=data.srcCode.forwardIfCurrent('=') || (allowColonSeparator && data.srcCode.forwardIfCurrent(':'));
if(hasValue) {
comments(data);
value=attributeValue(data,allowExpression);
}
else {
value=defaultValue;
}
comments(data);
// Type
TagLibTagAttr tlta=null;
if(tlt!=null){
tlta = tlt.getAttribute(nameLC,true);
if(tlta!=null && tlta.getName()!=null)nameLC=tlta.getName();
}
return new Attribute(dynamic.toBooleanValue(),name,tlta!=null?CastOther.toExpression(value, tlta.getType()):value,sbType.toString(),!hasValue);
}
private final String attributeName(SourceCode cfml, ArrayList<String> args,TagLibTag tag, RefBoolean dynamic, StringBuffer sbType, boolean allowTwiceAttr, boolean allowColon) throws TemplateException {
String id=CFMLTransformer.identifier(cfml,true,allowColon);
return validateAttributeName(id, cfml, args, tag, dynamic, sbType,allowTwiceAttr);
}
private final String validateAttributeName(String idOC,SourceCode cfml, ArrayList<String> args,TagLibTag tag, RefBoolean dynamic, StringBuffer sbType, boolean allowTwiceAttr) throws TemplateException {
String idLC=idOC.toLowerCase();
if(args.contains(idLC) && !allowTwiceAttr) throw new TemplateException(cfml,"you can't use the same attribute ["+idOC+"] twice");
args.add(idLC);
if(tag==null) return idOC;
int typeDef=tag.getAttributeType();
if("attributecollection".equals(idLC)){
dynamic.setValue(tag.getAttribute(idLC,true)==null);
sbType.append("struct");
}
else if(typeDef==TagLibTag.ATTRIBUTE_TYPE_FIXED || typeDef==TagLibTag.ATTRIBUTE_TYPE_MIXED) {
TagLibTagAttr attr=tag.getAttribute(idLC,true);
if(attr==null) {
if(typeDef==TagLibTag.ATTRIBUTE_TYPE_FIXED) {
String names=tag.getAttributeNames();
if(StringUtil.isEmpty(names))
throw new TemplateException(cfml,"Attribute "+idOC+" is not allowed for tag "+tag.getFullName());
throw new TemplateException(cfml,
"Attribute "+idOC+" is not allowed for statement "+tag.getName(),
"valid attribute names are ["+names+"]");
}
dynamic.setValue(true);
}
else {
idOC=attr.getName();
idLC=idOC.toLowerCase();
sbType.append(attr.getType());
//parseExpression[0]=attr.getRtexpr();
}
}
else if(typeDef==TagLibTag.ATTRIBUTE_TYPE_DYNAMIC){
dynamic.setValue(true);
}
return idOC;
}
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);
}
}