/**
* 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.expression;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import lucee.commons.lang.ExceptionUtil;
import lucee.loader.engine.CFMLEngine;
import lucee.runtime.Component;
import lucee.runtime.exp.CasterException;
import lucee.runtime.exp.PageRuntimeException;
import lucee.runtime.exp.TemplateException;
import lucee.runtime.functions.other.CreateUniqueId;
import lucee.runtime.op.Caster;
import lucee.runtime.type.scope.Scope;
import lucee.runtime.type.scope.ScopeSupport;
import lucee.runtime.type.util.UDFUtil;
import lucee.transformer.Factory;
import lucee.transformer.Position;
import lucee.transformer.TransformerException;
import lucee.transformer.bytecode.Root;
import lucee.transformer.bytecode.expression.ExpressionInvoker;
import lucee.transformer.bytecode.expression.FunctionAsExpression;
import lucee.transformer.bytecode.expression.var.Argument;
import lucee.transformer.bytecode.expression.var.Assign;
import lucee.transformer.bytecode.expression.var.BIF;
import lucee.transformer.bytecode.expression.var.DynAssign;
import lucee.transformer.bytecode.expression.var.FunctionMember;
import lucee.transformer.bytecode.expression.var.NamedArgument;
import lucee.transformer.bytecode.expression.var.UDF;
import lucee.transformer.bytecode.literal.Identifier;
import lucee.transformer.bytecode.literal.Null;
import lucee.transformer.bytecode.op.OPDecision;
import lucee.transformer.bytecode.op.OPUnary;
import lucee.transformer.bytecode.op.OpContional;
import lucee.transformer.bytecode.op.OpDouble;
import lucee.transformer.bytecode.op.OpElvis;
import lucee.transformer.bytecode.op.OpNegate;
import lucee.transformer.bytecode.op.OpNegateNumber;
import lucee.transformer.bytecode.op.OpVariable;
import lucee.transformer.bytecode.statement.udf.Function;
import lucee.transformer.bytecode.util.ASMUtil;
import lucee.transformer.cfml.Data;
import lucee.transformer.cfml.TransfomerSettings;
import lucee.transformer.cfml.evaluator.EvaluatorPool;
import lucee.transformer.cfml.script.DocComment;
import lucee.transformer.cfml.script.DocCommentTransformer;
import lucee.transformer.cfml.tag.CFMLTransformer;
import lucee.transformer.expression.ExprDouble;
import lucee.transformer.expression.ExprString;
import lucee.transformer.expression.Expression;
import lucee.transformer.expression.Invoker;
import lucee.transformer.expression.literal.LitDouble;
import lucee.transformer.expression.literal.LitString;
import lucee.transformer.expression.literal.Literal;
import lucee.transformer.expression.var.DataMember;
import lucee.transformer.expression.var.Member;
import lucee.transformer.expression.var.Variable;
import lucee.transformer.library.function.FunctionLib;
import lucee.transformer.library.function.FunctionLibFunction;
import lucee.transformer.library.function.FunctionLibFunctionArg;
import lucee.transformer.library.tag.TagLib;
import lucee.transformer.library.tag.TagLibTag;
import lucee.transformer.library.tag.TagLibTagAttr;
import lucee.transformer.library.tag.TagLibTagScript;
import lucee.transformer.util.SourceCode;
/**
*
*
Der CFMLExprTransfomer implementiert das Interface ExprTransfomer,
er bildet die Parser Grammatik ab, die unten definiert ist.
Er erhaelt als Eingabe CFML Code, als String oder CFMLString,
der einen CFML Expression erhaelt und liefert ein CFXD Element zurueck,
das diesen Ausdruck abbildet.
Mithilfe der FunctionLibs, kann er Funktionsaufrufe,
die Teil eines Ausdruck sein koennen, erkennen und validieren.
Dies geschieht innerhalb der Methode function.
Falls ein Funktionsaufruf, einer Funktion innerhalb einer FunctionLib entspricht,
werden diese gegeneinander verglichen und der Aufruf wird als Build-In-Funktion uebernommen,
andernfalls wird der Funktionsaufruf als User-Defined-Funktion interpretiert.
Die Klasse Cast, Operator und ElementFactory (siehe 3.2) helfen ihm beim erstellen des Ausgabedokument CFXD.
* <pre>
* Parser Grammatik EBNF (Extended Backus-Naur Form)
transform = spaces impOp;
impOp = eqvOp {"imp" spaces eqvOp};
eqvOp = xorOp {"eqv" spaces xorOp};
xorOp = orOp {"xor" spaces orOp};
orOp = andOp {("or" | "||") spaces andOp};
(* "||" Existiert in CFMX nicht *)
andOp = notOp {("and" | "&&") spaces notOp};
(* "&&" Existiert in CFMX nicht *)
notOp = [("not"|"!") spaces] decsionOp;
(* "!" Existiert in CFMX nicht *)
decsionOp = concatOp {("neq"|"eq"|"gte"|"gt"|"lte"|"lt"|"ct"|
"contains"|"nct"|"does not contain") spaces concatOp};
(* "ct"=conatains und "nct"=does not contain; Existiert in CFMX nicht *)
concatOp = plusMinusOp {"&" spaces plusMinusOp};
plusMinusOp = modOp {("-"|"+") spaces modOp};
modOp = divMultiOp {("mod" | "%") spaces divMultiOp};
(* modulus operator , "%" Existiert in CFMX nicht *)
divMultiOp = expoOp {("*"|"/") spaces expoOp};
expoOp = clip {("exp"|"^") spaces clip};
(*exponent operator, " exp " Existiert in CFMX nicht *)
clip = ("(" spaces impOp ")" spaces) | checker;
checker = string | number | dynamic | sharp;
string = ("'" {"##"|"''"|"#" impOp "#"| ?-"#"-"'" } "'") |
(""" {"##"|""""|"#" impOp "#"| ?-"#"-""" } """);
number = ["+"|"-"] digit {digit} {"." digit {digit}};
digit = "0"|..|"9";
dynamic = "true" | "false" | "yes" | "no" | startElement
{("." identifier | "[" structElement "]")[function] };
startElement = identifier "(" functionArg ")" | scope | identifier;
scope = "variable" | "cgi" | "url" | "form" | "session" | "application" |
"arguments" | "cookie" | "client ";
identifier = (letter | "_") {letter | "_"|digit};
structElement = "[" impOp "]";
functionArg = [impOp{"," impOp}];
sharp = "#" checker "#";
spaces = {space};
space = "\s"|"\t"|"\f"|"\t"|"\n";
letter = "a"|..|"z"|"A"|..|"Z";
{"x"}= 0 bis n mal "x"
["x"]= 0 bis 1 mal "x"
("x" | "y")"z" = "xz" oder "yz"
</pre>
*
*/
public abstract class AbstrCFMLExprTransformer {
private static final short STATIC=0;
private static final short DYNAMIC=1;
private static FunctionLibFunction GET_STATIC_SCOPE = null;
private static FunctionLibFunction GET_SUPER_STATIC_SCOPE = null;
private static FunctionLibFunction JSON_ARRAY = null;
protected static FunctionLibFunction JSON_STRUCT = null;
public static final short CTX_OTHER = TagLibTagScript.CTX_OTHER;
public static final short CTX_NONE = TagLibTagScript.CTX_NONE;
public static final short CTX_IF = TagLibTagScript.CTX_IF;
public static final short CTX_ELSE_IF = TagLibTagScript.CTX_ELSE_IF;
public static final short CTX_ELSE = TagLibTagScript.CTX_ELSE;
public static final short CTX_FOR = TagLibTagScript.CTX_FOR;
public static final short CTX_WHILE = TagLibTagScript.CTX_WHILE;
public static final short CTX_DO_WHILE = TagLibTagScript.CTX_DO_WHILE;
public static final short CTX_CFC = TagLibTagScript.CTX_CFC;
public static final short CTX_INTERFACE = TagLibTagScript.CTX_INTERFACE;
public static final short CTX_FUNCTION =TagLibTagScript.CTX_FUNCTION;
public static final short CTX_BLOCK = TagLibTagScript.CTX_BLOCK;
public static final short CTX_FINALLY = TagLibTagScript.CTX_FINALLY;
public static final short CTX_SWITCH = TagLibTagScript.CTX_SWITCH;
public static final short CTX_TRY = TagLibTagScript.CTX_TRY;
public static final short CTX_CATCH = TagLibTagScript.CTX_CATCH;
public static final short CTX_TRANSACTION = TagLibTagScript.CTX_TRANSACTION;
public static final short CTX_THREAD = TagLibTagScript.CTX_THREAD;
public static final short CTX_SAVECONTENT = TagLibTagScript.CTX_SAVECONTENT;
public static final short CTX_LOCK = TagLibTagScript.CTX_LOCK;
public static final short CTX_LOOP = TagLibTagScript.CTX_LOOP;
public static final short CTX_QUERY = TagLibTagScript.CTX_QUERY;
public static final short CTX_ZIP = TagLibTagScript.CTX_ZIP;
public static final short CTX_STATIC = TagLibTagScript.CTX_STATIC;
private DocCommentTransformer docCommentTransformer= new DocCommentTransformer();
protected short ATTR_TYPE_NONE=TagLibTagAttr.SCRIPT_SUPPORT_NONE;
protected short ATTR_TYPE_OPTIONAL=TagLibTagAttr.SCRIPT_SUPPORT_OPTIONAL;
protected short ATTR_TYPE_REQUIRED=TagLibTagAttr.SCRIPT_SUPPORT_REQUIRED;
protected static EndCondition SEMI_BLOCK=new EndCondition() {
@Override
public boolean isEnd(ExprData data) {
return data.srcCode.isCurrent('{') || data.srcCode.isCurrent(';');
}
};
protected static EndCondition SEMI=new EndCondition() {
@Override
public boolean isEnd(ExprData data) {
return data.srcCode.isCurrent(';');
}
};
protected static EndCondition COMMA_ENDBRACKED=new EndCondition() {
@Override
public boolean isEnd(ExprData data) {
return data.srcCode.isCurrent(',') || data.srcCode.isCurrent(')');
}
};
public static interface EndCondition {
public boolean isEnd(ExprData data);
}
/*private short mode=0;
protected CFMLString cfml;
protected FunctionLib[] fld;
private boolean ignoreScopes=false;
private boolean allowLowerThan;*/
public class ExprData extends Data {
private short mode=0;
private boolean allowLowerThan;
public boolean insideFunction;
public String tagName;
public boolean isCFC;
public boolean isInterface;
public short context=CTX_NONE;
public DocComment docComment;
public ExprData(Factory factory,Root root, EvaluatorPool ep, SourceCode cfml, TagLib[][] tlibs,FunctionLib[] flibs, TransfomerSettings settings,boolean allowLowerThan,TagLibTag[] scriptTags) {
super(factory,root,cfml,ep,settings,tlibs,flibs,scriptTags);
this.allowLowerThan=allowLowerThan;
}
}
protected Expression transformAsString(ExprData data,String[] breakConditions) throws TemplateException {
Expression el=null;
// parse the houle Page String
comments(data);
// String
if((el=string(data))!=null) {
data.mode=STATIC;
return el;
}
// Sharp
if((el=sharp(data))!=null) {
data.mode=DYNAMIC;
return el;
}
// Simple
return simple(data,breakConditions);
}
/**
* Initialmethode, wird aufgerufen um den internen Zustand des Objektes zu setzten.
* @param fld Function Libraries zum validieren der Funktionen
* @param cfml CFML Code der transfomiert werden soll.
*/
protected ExprData init(Factory factory,Root root,EvaluatorPool ep,TagLib[][] tld, FunctionLib[] fld,TagLibTag[] scriptTags, SourceCode cfml, TransfomerSettings settings, boolean allowLowerThan) {
ExprData data = new ExprData(factory,root,ep,cfml,tld,fld,settings,allowLowerThan,scriptTags);
if(JSON_ARRAY==null)JSON_ARRAY=getFLF(data,"_literalArray");
if(JSON_STRUCT==null)JSON_STRUCT=getFLF(data,"_literalStruct");
if(GET_STATIC_SCOPE==null)GET_STATIC_SCOPE=getFLF(data,"_getStaticScope");
if(GET_SUPER_STATIC_SCOPE==null)GET_SUPER_STATIC_SCOPE=getFLF(data,"_getSuperStaticScope");
//print.e(""+(GET_STATIC_SCOPE==null));
return data;
//this.allowLowerThan=allowLowerThan;
//this.fld = fld;
//this.cfml = cfml;
}
/**
* Startpunkt zum transfomieren einer Expression, ohne dass das Objekt neu initialisiert wird,
* dient vererbten Objekten als Einstiegspunkt.
* @return Element
* @throws TemplateException
*/
protected Expression expression(ExprData data) throws TemplateException {
return assignOp(data);
}
/**
* Liest einen gelableten Funktionsparamter ein
* <br />
* EBNF:<br />
* <code>assignOp [":" spaces assignOp];</code>
* @return CFXD Element
* @throws TemplateException
*/
private Argument functionArgument(ExprData data, boolean varKeyUpperCase) throws TemplateException {
return functionArgument(data,null,varKeyUpperCase);
}
private Argument functionArgument(ExprData data,String type, boolean varKeyUpperCase) throws TemplateException {
Expression expr = assignOp(data);
try{
if (data.srcCode.forwardIfCurrent(":")) {
comments(data);
return new NamedArgument(expr,assignOp(data),type,varKeyUpperCase);
}
else if(expr instanceof DynAssign){
DynAssign da=(DynAssign) expr;
return new NamedArgument(da.getName(),da.getValue(),type,varKeyUpperCase);
}
else if(expr instanceof Assign && !(expr instanceof OpVariable)){
Assign a=(Assign) expr;
return new NamedArgument(a.getVariable(),a.getValue(),type,varKeyUpperCase);
}
}
catch(TransformerException be) {
throw new TemplateException(data.srcCode,be.getMessage());
}
return new Argument(expr,type);
}
/**
* Transfomiert Zuweisungs Operation.
* <br />
* EBNF:<br />
* <code>eqvOp ["=" spaces assignOp];</code>
* @return CFXD Element
* @throws TemplateException
*/
protected Expression assignOp(ExprData data) throws TemplateException {
Expression expr = conditionalOp(data);
if (data.srcCode.forwardIfCurrent('=')) {
comments(data);
if(data.mode==STATIC) expr=new DynAssign(expr,assignOp(data));
else {
if(expr instanceof Variable) {
Expression value = assignOp(data);
expr=new Assign((Variable)expr,value,data.srcCode.getPosition());
}
else if(expr instanceof Null) {
Variable var = ((Null)expr).toVariable();
Expression value = assignOp(data);
expr=new Assign(var,value,data.srcCode.getPosition());
}
else
throw new TemplateException(data.srcCode,"invalid assignment left-hand side ("+expr.getClass().getName()+")");
}
}
return expr;
}
private Expression conditionalOp(ExprData data) throws TemplateException {
Expression expr = impOp(data);
if (data.srcCode.forwardIfCurrent('?')) {
comments(data);
// Elvis
if(data.srcCode.forwardIfCurrent(':')) {
comments(data);
Expression right = assignOp(data);
if(!(expr instanceof Variable) )
throw new TemplateException(data.srcCode,"left operant of the Elvis operator has to be a variable or a function call");
Variable left = (Variable)expr;
/// LDEV-1201
/*List<Member> members = left.getMembers();
Member last=null;
for(Member m:members) {
last=m;
m.setSafeNavigated(true);
}
if(last!=null) {
last.setSafeNavigatedValue(right);
}
return left;*/
return OpElvis.toExpr(left, right);
}
Expression left = assignOp(data);
comments(data);
if(!data.srcCode.forwardIfCurrent(':'))throw new TemplateException("invalid conditional operator");
comments(data);
Expression right = assignOp(data);
expr=OpContional.toExpr(expr, left, right);
}
return expr;
}
/**
* Transfomiert eine Implication (imp) Operation.
* <br />
* EBNF:<br />
* <code>eqvOp {"imp" spaces eqvOp};</code>
* @return CFXD Element
* @throws TemplateException
*/
private Expression impOp(ExprData data) throws TemplateException {
Expression expr = eqvOp(data);
while(data.srcCode.forwardIfCurrentAndNoWordAfter("imp")) {
comments(data);
expr=data.factory.opBool(expr, eqvOp(data), Factory.OP_BOOL_IMP);
}
return expr;
}
/**
* Transfomiert eine Equivalence (eqv) Operation.
* <br />
* EBNF:<br />
* <code>xorOp {"eqv" spaces xorOp};</code>
* @return CFXD Element
* @throws TemplateException
*/
private Expression eqvOp(ExprData data) throws TemplateException {
Expression expr = xorOp(data);
while(data.srcCode.forwardIfCurrentAndNoWordAfter("eqv")) {
comments(data);
expr=data.factory.opBool(expr, xorOp(data), Factory.OP_BOOL_EQV);
}
return expr;
}
/**
* Transfomiert eine Xor (xor) Operation.
* <br />
* EBNF:<br />
* <code>orOp {"xor" spaces orOp};</code>
* @return CFXD Element
* @throws TemplateException
*/
private Expression xorOp(ExprData data) throws TemplateException {
Expression expr = orOp(data);
while(data.srcCode.forwardIfCurrentAndNoWordAfter("xor")) {
comments(data);
expr=data.factory.opBool(expr, orOp(data), Factory.OP_BOOL_XOR);
}
return expr;
}
/**
* Transfomiert eine Or (or) Operation. Im Gegensatz zu CFMX ,
* werden "||" Zeichen auch als Or Operatoren anerkannt.
* <br />
* EBNF:<br />
* <code>andOp {("or" | "||") spaces andOp}; (* "||" Existiert in CFMX nicht *)</code>
* @return CFXD Element
* @throws TemplateException
*/
private Expression orOp(ExprData data) throws TemplateException {
Expression expr = andOp(data);
while(data.srcCode.forwardIfCurrent("||") || data.srcCode.forwardIfCurrentAndNoWordAfter("or")) {
comments(data);
expr=data.factory.opBool(expr, andOp(data), Factory.OP_BOOL_OR);
}
return expr;
}
/**
* Transfomiert eine And (and) Operation. Im Gegensatz zu CFMX ,
* werden "&&" Zeichen auch als And Operatoren anerkannt.
* <br />
* EBNF:<br />
* <code>notOp {("and" | "&&") spaces notOp}; (* "&&" Existiert in CFMX nicht *)</code>
* @return CFXD Element
* @throws TemplateException
*/
private Expression andOp(ExprData data) throws TemplateException {
Expression expr = notOp(data);
while(data.srcCode.forwardIfCurrent("&&") || data.srcCode.forwardIfCurrentAndNoWordAfter("and")) {
comments(data);
expr=data.factory.opBool(expr, notOp(data), Factory.OP_BOOL_AND);
}
return expr;
}
/**
* Transfomiert eine Not (not) Operation. Im Gegensatz zu CFMX ,
* wird das "!" Zeichen auch als Not Operator anerkannt.
* <br />
* EBNF:<br />
* <code>[("not"|"!") spaces] decsionOp; (* "!" Existiert in CFMX nicht *)</code>
* @return CFXD Element
* @throws TemplateException
*/
private Expression notOp(ExprData data) throws TemplateException {
// And Operation
Position line = data.srcCode.getPosition();
if (data.srcCode.isCurrent('!') && !data.srcCode.isCurrent("!=")) {
data.srcCode.next();
comments(data);
return OpNegate.toExprBoolean(notOp(data),line,data.srcCode.getPosition());
}
else if (data.srcCode.forwardIfCurrentAndNoWordAfter("not")) {
comments(data);
return OpNegate.toExprBoolean(notOp(data),line,data.srcCode.getPosition());
}
return decsionOp(data);
}
/**
* <font f>Transfomiert eine Vergleichs Operation.
* <br />
* EBNF:<br />
* <code>concatOp {("neq"|"eq"|"gte"|"gt"|"lte"|"lt"|"ct"|
"contains"|"nct"|"does not contain") spaces concatOp};
(* "ct"=conatains und "nct"=does not contain; Existiert in CFMX nicht *)</code>
* @return CFXD Element
* @throws TemplateException
*/
private Expression decsionOp(ExprData data) throws TemplateException {
Expression expr = concatOp(data);
boolean hasChanged=false;
// ct, contains
do {
hasChanged=false;
if(data.srcCode.isCurrent('c')) {
if (data.srcCode.forwardIfCurrent("ct",false,true)) {expr = decisionOpCreate(data,OPDecision.CT,expr);hasChanged=true;}
else if (data.srcCode.forwardIfCurrent("contains",false,true)){ expr = decisionOpCreate(data,OPDecision.CT,expr);hasChanged=true;}
}
// does not contain
else if (data.srcCode.forwardIfCurrent("does","not","contain",false,true)){ expr = decisionOpCreate(data,OPDecision.NCT,expr); hasChanged=true;}
// equal, eq
else if (data.srcCode.isCurrent("eq") && !data.srcCode.isCurrent("eqv")) {
int plus=2;
data.srcCode.setPos(data.srcCode.getPos()+2);
if(data.srcCode.forwardIfCurrent("ual"))plus=5;
if(data.srcCode.isCurrentVariableCharacter()) {
data.srcCode.setPos(data.srcCode.getPos()-plus);
}
else {
expr = decisionOpCreate(data,OPDecision.EQ,expr);
hasChanged=true;
}
}
// ==
else if (data.srcCode.forwardIfCurrent("==")) {
if(data.srcCode.forwardIfCurrent('=')) expr = decisionOpCreate(data,OPDecision.EEQ,expr);
else expr = decisionOpCreate(data,OPDecision.EQ,expr);
hasChanged=true;
}
// !=
else if (data.srcCode.forwardIfCurrent("!=")) {
if(data.srcCode.forwardIfCurrent('=')) expr = decisionOpCreate(data,OPDecision.NEEQ,expr);
else expr = decisionOpCreate(data,OPDecision.NEQ,expr);
hasChanged=true;
}
// <=/</<>
else if (data.srcCode.isCurrent('<')) {
hasChanged=true;
if(data.srcCode.isNext('=')) {
data.srcCode.next();data.srcCode.next();
expr = decisionOpCreate(data,OPDecision.LTE,expr);
}
else if(data.srcCode.isNext('>')) {
data.srcCode.next();data.srcCode.next();
expr = decisionOpCreate(data,OPDecision.NEQ,expr);
}
else if(data.srcCode.isNext('/')) {
hasChanged=false;
}
else {
data.srcCode.next();
expr = decisionOpCreate(data,OPDecision.LT,expr);
}
}
// >=/>
else if (data.allowLowerThan && data.srcCode.forwardIfCurrent('>')) {
if(data.srcCode.forwardIfCurrent('=')) expr = decisionOpCreate(data,OPDecision.GTE,expr);
else expr = decisionOpCreate(data,OPDecision.GT,expr);
hasChanged=true;
}
// gt, gte, greater than or equal to, greater than
else if (data.srcCode.isCurrent('g')) {
if (data.srcCode.forwardIfCurrent("gt")) {
if(data.srcCode.forwardIfCurrentAndNoWordAfter("e")) {
if(data.srcCode.isCurrentVariableCharacter()) {
data.srcCode.setPos(data.srcCode.getPos()-3);
}
else {
expr = decisionOpCreate(data,OPDecision.GTE,expr);
hasChanged=true;
}
}
else {
if(data.srcCode.isCurrentVariableCharacter()) {
data.srcCode.setPos(data.srcCode.getPos()-2);
}
else {
expr = decisionOpCreate(data,OPDecision.GT,expr);
hasChanged=true;
}
}
}
else if (data.srcCode.forwardIfCurrent("greater", "than",false,true)) {
if(data.srcCode.forwardIfCurrent("or","equal", "to",true,true)) expr = decisionOpCreate(data,OPDecision.GTE,expr);
else expr = decisionOpCreate(data,OPDecision.GT,expr);
hasChanged=true;
}
else if (data.srcCode.forwardIfCurrent("ge",false,true)) {
expr = decisionOpCreate(data,OPDecision.GTE,expr);
hasChanged=true;
}
}
// is, is not
else if (data.srcCode.forwardIfCurrent("is",false,true)) {
if(data.srcCode.forwardIfCurrent("not",true,true)) expr = decisionOpCreate(data,OPDecision.NEQ,expr);
else expr = decisionOpCreate(data,OPDecision.EQ,expr);
hasChanged=true;
}
// lt, lte, less than, less than or equal to
else if (data.srcCode.isCurrent('l')) {
if (data.srcCode.forwardIfCurrent("lt")) {
if(data.srcCode.forwardIfCurrentAndNoWordAfter("e")) {
if(data.srcCode.isCurrentVariableCharacter()) {
data.srcCode.setPos(data.srcCode.getPos()-3);
}
else {
expr = decisionOpCreate(data,OPDecision.LTE,expr);
hasChanged=true;
}
}
else {
if(data.srcCode.isCurrentVariableCharacter()) {
data.srcCode.setPos(data.srcCode.getPos()-2);
}
else {
expr = decisionOpCreate(data,OPDecision.LT,expr);
hasChanged=true;
}
}
}
else if (data.srcCode.forwardIfCurrent("less","than",false,true)) {
if(data.srcCode.forwardIfCurrent("or", "equal", "to",true,true)) expr = decisionOpCreate(data,OPDecision.LTE,expr);
else expr = decisionOpCreate(data,OPDecision.LT,expr);
hasChanged=true;
}
else if (data.srcCode.forwardIfCurrent("le",false,true)) {
expr = decisionOpCreate(data,OPDecision.LTE,expr);
hasChanged=true;
}
}
// neq, not equal, nct
else if (data.srcCode.isCurrent('n')) {
// Not Equal
if (data.srcCode.forwardIfCurrent("neq",false,true)){ expr = decisionOpCreate(data,OPDecision.NEQ,expr); hasChanged=true;}
// Not Equal (Alias)
else if (data.srcCode.forwardIfCurrent("not","equal",false,true)){ expr = decisionOpCreate(data,OPDecision.NEQ,expr);hasChanged=true; }
// nct
else if (data.srcCode.forwardIfCurrent("nct",false,true)){ expr = decisionOpCreate(data,OPDecision.NCT,expr); hasChanged=true;}
}
}
while(hasChanged);
return expr;
}
private Expression decisionOpCreate(ExprData data,int operation, Expression left) throws TemplateException {
comments(data);
return OPDecision.toExprBoolean(left, concatOp(data), operation);
}
/**
* Transfomiert eine Konkatinations-Operator (&) Operation. Im Gegensatz zu CFMX ,
* wird das "!" Zeichen auch als Not Operator anerkannt.
* <br />
* EBNF:<br />
* <code>plusMinusOp {"&" spaces concatOp};</code>
* @return CFXD Element
* @throws TemplateException
*/
private Expression concatOp(ExprData data) throws TemplateException {
Expression expr = plusMinusOp(data);
while(data.srcCode.isCurrent('&') && !data.srcCode.isCurrent("&&")) {
data.srcCode.next();
// &=
if (data.srcCode.isCurrent('=') && expr instanceof Variable) {
data.srcCode.next();
comments(data);
Expression value = assignOp(data);
expr = new OPUnary((Variable)expr,value,OPUnary.PRE,OPUnary.CONCAT,expr.getStart(),data.srcCode.getPosition());
//ExprString res = OpString.toExprString(expr, right);
//expr=new OpVariable((Variable)expr,res,data.cfml.getPosition());
}
else {
comments(data);
expr=data.factory.opString(expr, plusMinusOp(data));
}
}
return expr;
}
/**
* Transfomiert die mathematischen Operatoren Plus und Minus (1,-).
* <br />
* EBNF:<br />
* <code>modOp [("-"|"+") spaces plusMinusOp];</code>
* @return CFXD Element
* @throws TemplateException
*/
private Expression plusMinusOp(ExprData data) throws TemplateException {
Expression expr = modOp(data);
while(!data.srcCode.isLast()) {
// Plus Operation
if (data.srcCode.forwardIfCurrent('+')) expr=_plusMinusOp(data,expr,OpDouble.PLUS);
// Minus Operation
else if (data.srcCode.forwardIfCurrent('-')) expr=_plusMinusOp(data,expr,OpDouble.MINUS);
else break;
}
return expr;
}
private Expression _plusMinusOp(ExprData data,Expression expr,int opr) throws TemplateException {
// +=
// plus|Minus Assignment
if (data.srcCode.isCurrent('=') && expr instanceof Variable) {
data.srcCode.next();
comments(data);
Expression value = assignOp(data);
//if(opr==OpDouble.MINUS) value=OpNegateNumber.toExprDouble(value, null, null);
expr = new OPUnary((Variable)expr,value,OPUnary.PRE,opr,expr.getStart(),data.srcCode.getPosition());
//ExprDouble res = OpDouble.toExprDouble(expr, right,opr);
//expr=new OpVariable((Variable)expr,res,data.cfml.getPosition());
}
else {
comments(data);
expr=OpDouble.toExprDouble(expr, modOp(data), opr);
}
return expr;
}
/**
* Transfomiert eine Modulus Operation. Im Gegensatz zu CFMX ,
* wird das "%" Zeichen auch als Modulus Operator anerkannt.
* <br />
* EBNF:<br />
* <code>divMultiOp {("mod" | "%") spaces divMultiOp}; (* modulus operator , "%" Existiert in CFMX nicht *)</code>
* @return CFXD Element
* @throws TemplateException
*/
private Expression modOp(ExprData data) throws TemplateException {
Expression expr = divMultiOp(data);
// Modulus Operation
while(data.srcCode.forwardIfCurrent('%') || data.srcCode.forwardIfCurrentAndNoWordAfter("mod")) {
expr=_modOp(data,expr);
//comments(data);
//expr=OpDouble.toExprDouble(expr, divMultiOp(), OpDouble.MODULUS);
}
return expr;
}
private Expression _modOp(ExprData data,Expression expr) throws TemplateException {
if (data.srcCode.isCurrent('=') && expr instanceof Variable) {
data.srcCode.next();
comments(data);
Expression right = assignOp(data);
ExprDouble res = OpDouble.toExprDouble(expr, right,OpDouble.MODULUS);
return new OpVariable((Variable)expr,res,data.srcCode.getPosition());
}
comments(data);
return OpDouble.toExprDouble(expr, expoOp(data), OpDouble.MODULUS);
}
/**
* Transfomiert die mathematischen Operatoren Mal und Durch (*,/).
* <br />
* EBNF:<br />
* <code>expoOp {("*"|"/") spaces expoOp};</code>
* @return CFXD Element
* @throws TemplateException
*/
private Expression divMultiOp(ExprData data) throws TemplateException {
Expression expr = expoOp(data);
while (!data.srcCode.isLast()) {
// Multiply Operation
if(data.srcCode.forwardIfCurrent('*')) {
expr=_divMultiOp(data,expr,OpDouble.MULTIPLY);
//comments(data);
//expr=OpDouble.toExprDouble(expr, expoOp(), OpDouble.MULTIPLY);
}
// Divide Operation
else if (data.srcCode.isCurrent('/') && (!data.srcCode.isCurrent('/','>') )) {
data.srcCode.next();
expr=_divMultiOp(data,expr,OpDouble.DIVIDE);
//comments(data);
//expr=OpDouble.toExprDouble(expr, expoOp(), OpDouble.DIVIDE);
}
// Divide Operation
else if (data.srcCode.isCurrent('\\')) {
data.srcCode.next();
expr=_divMultiOp(data,expr,OpDouble.INTDIV);
//comments(data);
//expr=OpDouble.toExprDouble(expr, expoOp(), OpDouble.INTDIV);
}
else {
break;
}
}
return expr;
}
private Expression _divMultiOp(ExprData data,Expression expr, int iOp) throws TemplateException {
if (data.srcCode.isCurrent('=') && expr instanceof Variable) {
data.srcCode.next();
comments(data);
Expression value = assignOp(data);
return new OPUnary((Variable)expr,value,OPUnary.PRE,iOp,expr.getStart(),data.srcCode.getPosition());
//ExprDouble res = OpDouble.toExprDouble(expr, right,iOp);
//return new OpVariable((Variable)expr,res,data.cfml.getPosition());
}
comments(data);
return OpDouble.toExprDouble(expr, expoOp(data), iOp);
}
/**
* Transfomiert den Exponent Operator (^,exp). Im Gegensatz zu CFMX ,
* werden die Zeichen " exp " auch als Exponent anerkannt.
* <br />
* EBNF:<br />
* <code>clip {("exp"|"^") spaces clip};</code>
* @return CFXD Element
* @throws TemplateException
*/
private Expression expoOp(ExprData data) throws TemplateException {
Expression expr = unaryOp(data);
// Modulus Operation
while(data.srcCode.forwardIfCurrent('^') || data.srcCode.forwardIfCurrentAndNoWordAfter("exp")) {
comments(data);
expr=OpDouble.toExprDouble(expr, unaryOp(data), OpDouble.EXP);
}
return expr;
}
private Expression unaryOp(ExprData data) throws TemplateException {
Expression expr = negatePlusMinusOp(data);
// Plus Operation
if (data.srcCode.forwardIfCurrent("++") && expr instanceof Variable)
expr=_unaryOp(data,expr,OpDouble.PLUS);
// Minus Operation
else if (data.srcCode.forwardIfCurrent("--") && expr instanceof Variable)
expr=_unaryOp(data,expr,OpDouble.MINUS);
return expr;
}
private Expression _unaryOp(ExprData data,Expression expr,int op) throws TemplateException {
Position leftEnd = expr.getEnd(),start=null,end=null;
comments(data);
if(leftEnd!=null){
start=leftEnd;
end=new Position(leftEnd.line, leftEnd.column+2, leftEnd.pos+2);
}
return new OPUnary((Variable)expr,data.factory.DOUBLE_ONE(),OPUnary.POST,op,start,end);
//ExprDouble res = OpDouble.toExprDouble(expr, LitDouble.toExprDouble(1D,start,end),opr);
//expr=new OpVariable((Variable)expr,res,data.cfml.getPosition());
//return OpDouble.toExprDouble(expr,LitDouble.toExprDouble(1D,start,end),opr==OpDouble.PLUS? OpDouble.MINUS:OpDouble.PLUS);
}
/**
* Negate Numbers
* @return CFXD Element
* @throws TemplateException
*/
private Expression negatePlusMinusOp(ExprData data) throws TemplateException {
// And Operation
Position line=data.srcCode.getPosition();
if (data.srcCode.forwardIfCurrent('-')) {
// pre increment
if (data.srcCode.forwardIfCurrent('-')) {
comments(data);
Expression expr = clip(data);
return new OPUnary((Variable)expr,data.factory.DOUBLE_ONE(),OPUnary.PRE,OpDouble.MINUS,line,data.srcCode.getPosition());
//ExprDouble res = OpDouble.toExprDouble(expr, LitDouble.toExprDouble(1D),OpDouble.MINUS);
//return new OpVariable((Variable)expr,res,data.cfml.getPosition());
}
comments(data);
return OpNegateNumber.toExprDouble(clip(data),OpNegateNumber.MINUS,line,data.srcCode.getPosition());
}
else if (data.srcCode.forwardIfCurrent('+')) {
if (data.srcCode.forwardIfCurrent('+')) {
comments(data);
Expression expr = clip(data);
return new OPUnary((Variable)expr,data.factory.DOUBLE_ONE(),OPUnary.PRE,OpDouble.PLUS,line,data.srcCode.getPosition());
}
comments(data);
return data.factory.toExprDouble(clip(data));//OpNegateNumber.toExprDouble(clip(),OpNegateNumber.PLUS,line);
}
return clip(data);
}
/**
* Verarbeitet Ausdruecke die inerhalb einer Klammer stehen.
* <br />
* EBNF:<br />
* <code>("(" spaces impOp ")" spaces) | checker;</code>
* @return CFXD Element
* @throws TemplateException
*/
private Expression clip(ExprData data) throws TemplateException {
return checker(data);
}
/**
* Hier werden die verschiedenen Moeglichen Werte erkannt
* und jenachdem wird mit der passenden Methode weitergefahren
* <br />
* EBNF:<br />
* <code>string | number | dynamic | sharp;</code>
* @return CFXD Element
* @throws TemplateException
*/
private Expression checker(ExprData data) throws TemplateException {
Expression expr=null;
// String
if((expr=string(data))!=null) {
expr = subDynamic(data,expr,false,false);
data.mode=STATIC;//(expr instanceof Literal)?STATIC:DYNAMIC;// STATIC
return expr;
}
// Number
if((expr=number(data))!=null) {
expr = subDynamic(data,expr,false,false);
data.mode=STATIC;//(expr instanceof Literal)?STATIC:DYNAMIC;// STATIC
return expr;
}
// closure
if((expr=closure(data))!=null) {
data.mode=DYNAMIC;
return expr;
}
// lambda
if((expr=lambda(data))!=null) {
data.mode=DYNAMIC;
return expr;
}
// Dynamic
if((expr=dynamic(data))!=null) {
expr = newOp(data, expr);
expr = subDynamic(data,expr,true,false);
data.mode=DYNAMIC;
return expr;
}
// Sharp
if((expr=sharp(data))!=null) {
data.mode=DYNAMIC;
return expr;
}
// JSON
if((expr=json(data,JSON_ARRAY,'[',']'))!=null) {
expr = subDynamic(data,expr,false,false);
data.mode=DYNAMIC;
return expr;
}
if((expr=json(data,JSON_STRUCT,'{','}'))!=null) {
expr = subDynamic(data,expr,false,false);
data.mode=DYNAMIC;
return expr;
}
// else Error
throw new TemplateException(data.srcCode,"Syntax Error, Invalid Construct");
}
/*private Expression variable(Data data) throws TemplateException {
Expression expr=null;
// Dynamic
if((expr=dynamic(data))!=null) {
expr = subDynamic(data,expr);
data.mode=DYNAMIC;
return expr;
}
return null;
}*/
/**
* Transfomiert einen lierale Zeichenkette.
* <br />
* EBNF:<br />
* <code>("'" {"##"|"''"|"#" impOp "#"| ?-"#"-"'" } "'") |
(""" {"##"|""""|"#" impOp "#"| ?-"#"-""" } """);</code>
* @param data
* @return CFXD Element
* @throws TemplateException
*/
protected Expression string(ExprData data) throws TemplateException {
// check starting character for a string literal
if(!data.srcCode.isCurrent('"')&& !data.srcCode.isCurrent('\''))
return null;
Position line = data.srcCode.getPosition();
// Init Parameter
char quoter = data.srcCode.getCurrentLower();
StringBuilder str=new StringBuilder();
Expression expr=null;
while(data.srcCode.hasNext()) {
data.srcCode.next();
// check sharp
if(data.srcCode.isCurrent('#')) {
// Ecaped sharp
if(data.srcCode.isNext('#')){
data.srcCode.next();
str.append('#');
}
// get Content of sharp
else {
data.srcCode.next();
comments(data);
Expression inner=assignOp(data);
comments(data);
if (!data.srcCode.isCurrent('#'))
throw new TemplateException(data.srcCode,"Invalid Syntax Closing [#] not found");
ExprString exprStr=null;
if(str.length()!=0) {
exprStr=data.factory.createLitString(str.toString(),line,data.srcCode.getPosition());
if(expr!=null){
expr = data.factory.opString(expr, exprStr);
}
else expr=exprStr;
str=new StringBuilder();
}
if(expr==null) {
expr=inner;
}
else {
expr = data.factory.opString(expr, inner);
}
}
}
// check quoter
else if(data.srcCode.isCurrent(quoter)) {
// Ecaped sharp
if(data.srcCode.isNext(quoter)){
data.srcCode.next();
str.append(quoter);
}
// finsish
else {
break;
}
}
// all other character
else {
str.append(data.srcCode.getCurrent());
}
}
if(!data.srcCode.forwardIfCurrent(quoter))
throw new TemplateException(data.srcCode,"Invalid Syntax Closing ["+quoter+"] not found");
if(expr==null)
expr=data.factory.createLitString(str.toString(),line,data.srcCode.getPosition());
else if(str.length()!=0) {
expr = data.factory.opString(expr, data.factory.createLitString(str.toString(),line,data.srcCode.getPosition()));
}
comments(data);
if(expr instanceof Variable) {
Variable var=(Variable) expr;
var.fromHash(true);
}
return expr;
}
/**
* Transfomiert einen numerische Wert.
* Die Laenge des numerischen Wertes interessiert nicht zu uebersetzungszeit,
* ein "Overflow" fuehrt zu einem Laufzeitfehler.
* Da die zu erstellende CFXD, bzw. dieser Transfomer, keine Vorwegnahme des Laufzeitsystems vornimmt.
* <br />
* EBNF:<br />
* <code>["+"|"-"] digit {digit} {"." digit {digit}};</code>
* @return CFXD Element
* @throws TemplateException
*/
private LitDouble number(ExprData data) throws TemplateException {
// check first character is a number literal representation
if(!(data.srcCode.isCurrentBetween('0','9') || data.srcCode.isCurrent('.'))) return null;
Position line = data.srcCode.getPosition();
StringBuffer rtn=new StringBuffer();
// get digit on the left site of the dot
if(data.srcCode.isCurrent('.')) rtn.append('0');
else rtn.append(digit(data));
// read dot if exist
if(data.srcCode.forwardIfCurrent('.')) {
rtn.append('.');
String rightSite=digit(data);
if(rightSite.length()> 0 && data.srcCode.forwardIfCurrent('e')) {
Boolean expOp=null;
if(data.srcCode.forwardIfCurrent('+')) expOp=Boolean.TRUE;
else if(data.srcCode.forwardIfCurrent('-')) expOp=Boolean.FALSE;
if(data.srcCode.isCurrentBetween('0','9')) {
if(expOp==Boolean.FALSE) rightSite+="e-";
else if(expOp==Boolean.TRUE) rightSite+="e+";
else rightSite+="e";
rightSite+=digit(data);
}
else {
if(expOp!=null) data.srcCode.previous();
data.srcCode.previous();
}
}
// read right side of the dot
if(rightSite.length()==0)
rightSite="0";//throw new TemplateException(cfml, "Number can't end with [.]"); // DIFF 23
rtn.append(rightSite);
}
// scientific notation
else if(data.srcCode.forwardIfCurrent('e')) {
Boolean expOp=null;
if(data.srcCode.forwardIfCurrent('+')) expOp=Boolean.TRUE;
else if(data.srcCode.forwardIfCurrent('-')) expOp=Boolean.FALSE;
if(data.srcCode.isCurrentBetween('0','9')) {
String rightSite = "e";
if(expOp==Boolean.FALSE) rightSite+="-";
else if(expOp==Boolean.TRUE) rightSite+="+";
rightSite+=digit(data);
rtn.append(rightSite);
}
else {
if(expOp!=null) data.srcCode.previous();
data.srcCode.previous();
}
}
comments(data);
try {
return data.factory.createLitDouble(Caster.toDoubleValue(rtn.toString()),line,data.srcCode.getPosition());
} catch (CasterException e) {
throw new TemplateException(data.srcCode,e.getMessage());
}
}
/**
* Liest die reinen Zahlen innerhalb des CFMLString aus und gibt diese als Zeichenkette zurueck.
* <br />
* EBNF:<br />
* <code>"0"|..|"9";</code>
* @return digit Ausgelesene Zahlen als Zeichenkette.
*/
private String digit(ExprData data) {
String rtn="";
while (data.srcCode.isValidIndex()) {
if(!data.srcCode.isCurrentBetween('0','9'))break;
rtn+=data.srcCode.getCurrentLower();
data.srcCode.next();
}
return rtn;
}
/**
* Liest den folgenden idetifier ein und prueft ob dieser ein boolscher Wert ist.
* Im Gegensatz zu CFMX wird auch "yes" und "no" als bolscher <wert akzeptiert,
* was bei CFMX nur beim Umwandeln einer Zeichenkette zu einem boolschen Wert der Fall ist.<br />
* Wenn es sich um keinen bolschen Wert handelt wird der folgende Wert eingelesen mit seiner ganzen Hirarchie.
* <br />
* EBNF:<br />
* <code>"true" | "false" | "yes" | "no" | startElement {("." identifier | "[" structElement "]" )[function] };</code>
* @return CFXD Element
* @throws TemplateException
*/
private Expression dynamic(ExprData data) throws TemplateException {
// Die Implementation weicht ein wenig von der Grammatik ab,
// aber nicht in der Logik sondern rein wie es umgesetzt wurde.
// get First Element of the Variable
Position line = data.srcCode.getPosition();
Identifier id = identifier(data,false,true);
if(id == null) {
if (!data.srcCode.forwardIfCurrent('(')) return null;
comments(data);
Expression expr = assignOp(data);
if (!data.srcCode.forwardIfCurrent(')'))
throw new TemplateException(
data.srcCode,
"Invalid Syntax Closing [)] not found");
comments(data);
return expr;//subDynamic(expr);
}
Variable var;
comments(data);
// Boolean constant
if(id.getString().equalsIgnoreCase("TRUE")) {// || name.equals("YES")) {
comments(data);
return id.getFactory().createLitBoolean(true,line,data.srcCode.getPosition());
}
else if(id.getString().equalsIgnoreCase("FALSE")) {// || name.equals("NO")) {
comments(data);
return id.getFactory().createLitBoolean(false,line,data.srcCode.getPosition());
}
else if((data.srcCode.getDialect()!=CFMLEngine.DIALECT_CFML || data.config.getFullNullSupport()) && id.getString().equalsIgnoreCase("NULL")) {
comments(data);
return id.getFactory().createNull(line,data.srcCode.getPosition());
}
// Extract Scope from the Variable
var = startElement(data,id,line);
var.setStart(line);
var.setEnd(data.srcCode.getPosition());
return var;
}
protected Expression json(ExprData data,FunctionLibFunction flf, char start, char end) throws TemplateException {
if(!data.srcCode.forwardIfCurrent(start))return null;
Position line = data.srcCode.getPosition();
data.srcCode.removeSpace();
// [:|=]
if(data.srcCode.forwardIfCurrent(':', ']') || data.srcCode.forwardIfCurrent('=', ']')) {
flf=flf.getFunctionLib().getFunction("_literalOrderedStruct");
BIF bif=new BIF(data.factory,data.settings,flf);
bif.setArgType(flf.getArgType());
try {
bif.setClassDefinition(flf.getFunctionClassDefinition());
} catch(Throwable t) {
ExceptionUtil.rethrowIfNecessary(t);
throw new PageRuntimeException(t);
}
bif.setReturnType(flf.getReturnTypeAsString());
data.ep.add(flf, bif, data.srcCode);
Variable var=data.factory.createVariable(line,data.srcCode.getPosition());
var.addMember(bif);
return var;
}
BIF bif=new BIF(data.factory,data.settings,flf);
bif.setArgType(flf.getArgType());
try {
bif.setClassDefinition(flf.getFunctionClassDefinition());
} catch(Throwable t) {
ExceptionUtil.rethrowIfNecessary(t);
throw new PageRuntimeException(t);
}
bif.setReturnType(flf.getReturnTypeAsString());
do {
comments(data);
if (data.srcCode.isCurrent(end))break;
bif.addArgument(functionArgument(data,data.settings.dotNotationUpper));
comments(data);
}
while (data.srcCode.forwardIfCurrent(','));
comments(data);
if (!data.srcCode.forwardIfCurrent(end))
throw new TemplateException(data.srcCode,"Invalid Syntax Closing ["+end+"] not found");
comments(data);
if(flf.hasTteClass()) {
FunctionLibFunction tmp = flf.getEvaluator().pre(bif, flf);
if(tmp!=null && tmp!=flf) {
bif.setFlf(flf=tmp);
bif.setArgType(flf.getArgType());
try {
bif.setClassDefinition(flf.getFunctionClassDefinition());
} catch(Throwable t) {
ExceptionUtil.rethrowIfNecessary(t);
throw new PageRuntimeException(t);
}
bif.setReturnType(flf.getReturnTypeAsString());
}
}
data.ep.add(flf, bif, data.srcCode);
Variable var=data.factory.createVariable(line,data.srcCode.getPosition());
var.addMember(bif);
return var;
}
private Expression closure(ExprData data) throws TemplateException {
if(!data.srcCode.forwardIfCurrent("function",'('))return null;
data.srcCode.previous();
return new FunctionAsExpression(closurePart(data, "closure_"+CreateUniqueId.invoke(), Component.ACCESS_PUBLIC,Component.MODIFIER_NONE, "any", data.srcCode.getPosition(),true));
}
protected abstract Function closurePart(ExprData data, String id, int access,int modifier, String rtnType, Position line,boolean closure) throws TemplateException;
private Expression lambda(ExprData data) throws TemplateException {
int pos = data.srcCode.getPos();
if(!data.srcCode.forwardIfCurrent("(")) return null;
ArrayList<lucee.transformer.bytecode.statement.Argument> args = null;
//data.cfml.previous();
try {
args = getScriptFunctionArguments(data);
} catch (TemplateException e) {
// if there is a template exception, the argument syntax is not correct, and must not be a lambda expression
//TODO find a better way to test for lambda than to attempt processing the arguments and catch an exception if it fails.
data.srcCode.setPos(pos);
return null;
}
if(!data.srcCode.forwardIfCurrent(")")) {
data.srcCode.setPos(pos);
return null;
}
data.srcCode.removeSpace();
if(!data.srcCode.forwardIfCurrent("=>")) {
data.srcCode.setPos(pos);
return null;
}
return new FunctionAsExpression(lambdaPart(data, "lambda_"+CreateUniqueId.invoke(), Component.ACCESS_PUBLIC,Component.MODIFIER_NONE, "any", data.srcCode.getPosition(),args));
}
protected abstract Function lambdaPart(ExprData data, String id, int access,int modifier, String rtnType, Position line, ArrayList<lucee.transformer.bytecode.statement.Argument> args) throws TemplateException;
protected abstract ArrayList<lucee.transformer.bytecode.statement.Argument> getScriptFunctionArguments(ExprData data) throws TemplateException;
protected FunctionLibFunction getFLF(ExprData data,String name) {
FunctionLibFunction flf=null;
for (int i = 0; i < data.flibs.length; i++) {
flf = data.flibs[i].getFunction(name);
if (flf != null)
break;
}
return flf;
}
private Expression subDynamic(ExprData data,Expression expr, boolean tryStatic, boolean isStaticChild) throws TemplateException {
String name=null;
Invoker invoker=null;
// Loop over nested Variables
boolean safeNavigation;
while (data.srcCode.isValidIndex()) {
safeNavigation=false;
ExprString nameProp = null,namePropUC = null;
// []
if (data.srcCode.forwardIfCurrent('[')) {
isStaticChild=false;
// get Next Var
nameProp = structElement(data);
namePropUC=nameProp;
// Valid Syntax ???
if (!data.srcCode.forwardIfCurrent(']'))
throw new TemplateException(
data.srcCode,
"Invalid Syntax Closing []] not found");
}
// .
else if (isStaticChild || data.srcCode.forwardIfCurrent('.') || (safeNavigation=data.srcCode.forwardIfCurrent('?','.'))) {
isStaticChild=false;
// Extract next Var String
comments(data);
Position line=data.srcCode.getPosition();
name = identifier(data,true);
if(name==null)
throw new TemplateException(data.srcCode, "Invalid identifier");
comments(data);
nameProp=Identifier.toIdentifier(data.factory,name,line,data.srcCode.getPosition());
namePropUC=Identifier.toIdentifier(data.factory,name,data.settings.dotNotationUpper?Identifier.CASE_UPPER:Identifier.CASE_ORIGNAL,line,data.srcCode.getPosition());
}
// finish
else {
break;
}
comments(data);
if(expr instanceof Invoker) {
invoker=(Invoker) expr;
}
else {
invoker=new ExpressionInvoker(expr);
expr=invoker;
}
// safe navigation
Member member;
if(safeNavigation) {
List<Member> members = invoker.getMembers();
if(members.size()>0) {
member = members.get(members.size()-1);
member.setSafeNavigated(true);
}
}
// Method
if (data.srcCode.isCurrent('(')) {
if(nameProp==null && name!=null)nameProp=Identifier.toIdentifier(data.factory,name, Identifier.CASE_ORIGNAL,null,null);// properly this is never used
invoker.addMember(member=getFunctionMember(data,nameProp, false));
}
// property
else invoker.addMember(member=data.factory.createDataMember(namePropUC));
if(safeNavigation) {
member.setSafeNavigated(true);
}
}
// static scipe call?
// STATIC SCOPE CALL
if(tryStatic) {
comments(data);
Expression staticCall = staticScope(data,expr);
if(staticCall!=null) return staticCall;
}
return expr;
}
private Expression staticScope(ExprData data, Expression expr) throws TemplateException {
if(data.srcCode.forwardIfCurrent("::")) {
if (!(expr instanceof Variable))
throw new TemplateException(data.srcCode,"invalid syntax before [::]");
Variable old=(Variable) expr;
// set back to read again as a component path
data.srcCode.setPos(old.getStart().pos);
// now we read the component path
ExprString componentPath = readComponentPath(data);
if (!data.srcCode.forwardIfCurrent("::"))
throw new TemplateException(data.srcCode,"invalid syntax before [::]"+data.srcCode.getCurrent());
comments(data);
BIF bif=null;
if(componentPath instanceof LitString) {
LitString ls = (LitString)componentPath;
if("super".equalsIgnoreCase(ls.getString())) {
bif=ASMUtil.createBif(data,GET_SUPER_STATIC_SCOPE);
}
}
// now we generate a _getStaticScope function call with that path
if(bif==null) {
bif=ASMUtil.createBif(data,GET_STATIC_SCOPE);
bif.addArgument(new Argument(componentPath,"string"));
}
Variable var=data.factory.createVariable(old.getStart(),data.srcCode.getPosition());
var.addMember(bif);
// now we are reading what is coming after ":::"
Expression sd = subDynamic(data,var,false,true);
return sd;
}
return null;
}
private Expression newOp(ExprData data,Expression expr) throws TemplateException {
if(!(expr instanceof Variable)) return expr;
Variable var=(Variable) expr;
Member m= var.getFirstMember();
if(!(m instanceof DataMember)) return expr;
ExprString n = ((DataMember)m).getName();
if(!(n instanceof LitString)) return expr;
LitString ls=(LitString) n;
if(!"new".equalsIgnoreCase(ls.getString())) return expr;
int start=data.srcCode.getPos();
ExprString exprName = readComponentPath(data);
if(exprName==null) {
data.srcCode.setPos(start);
return expr;
}
comments(data);
if (data.srcCode.isCurrent('(')) {
FunctionMember func = getFunctionMember(data,Identifier.toIdentifier(data.factory,"_createComponent",Identifier.CASE_ORIGNAL,null,null), true);
func.addArgument(new Argument(exprName,"string"));
Variable v=expr.getFactory().createVariable(expr.getStart(),expr.getEnd());
v.addMember(func);
comments(data);
return v;
}
data.srcCode.setPos(start);
return expr;
}
private ExprString readComponentPath(ExprData data) throws TemplateException {
// first identifier
String name = identifier(data,true);
if(name!=null) {
StringBuilder fullName=new StringBuilder();
fullName.append(name);
// Loop over addional identifier
while (data.srcCode.isValidIndex()) {
if (data.srcCode.forwardIfCurrent('.')) {
comments(data);
name = identifier(data,true);
if(name==null) return null;
fullName.append('.');
fullName.append(name);
comments(data);
}
else break;
}
// sub component
/*if (data.srcCode.forwardIfCurrent(':')) {
fullName.append(':');
name = identifier(data,true);
if(name==null) return null;
fullName.append(name);
}*/
return data.factory.createLitString(fullName.toString());
}
Expression str=string(data);
if(str!=null){
return data.factory.toExprString(str);
}
return null;
}
/**
* Extrahiert den Start Element einer Variale,
* dies ist entweder eine Funktion, eine Scope Definition oder eine undefinierte Variable.
* <br />
* EBNF:<br />
* <code>identifier "(" functionArg ")" | scope | identifier;</code>
* @param name Einstiegsname
* @return CFXD Element
* @throws TemplateException
*/
private Variable startElement(ExprData data,Identifier name, Position line) throws TemplateException {
// check function
if (data.srcCode.isCurrent('(')) {
FunctionMember func = getFunctionMember(data,name, true);
Variable var=name.getFactory().createVariable(line,data.srcCode.getPosition());
var.addMember(func);
comments(data);
return var;
}
//check scope
Variable var = scope(data,name,line);
if(var!=null) return var;
// undefined variable
var=name.getFactory().createVariable(line,data.srcCode.getPosition());
var.addMember(data.factory.createDataMember(name));
comments(data);
return var;
}
/**
* Liest einen CFML Scope aus,
* falls der folgende identifier keinem Scope entspricht,
* gibt die Variable null zurueck.
* <br />
* EBNF:<br />
* <code>"variable" | "cgi" | "url" | "form" | "session" | "application" | "arguments" | "cookie" | " client";</code>
* @param id String identifier,
* wird aus Optimierungszwechen nicht innerhalb dieser Funktion ausgelsen.
* @return CFXD Variable Element oder null
* @throws TemplateException
*/
private Variable scope(ExprData data,Identifier id, Position line) throws TemplateException {
String idStr=id.getUpper();
if (idStr.equals("ARGUMENTS")) return data.factory.createVariable(Scope.SCOPE_ARGUMENTS,line,data.srcCode.getPosition());
else if (idStr.equals("LOCAL")) return data.factory.createVariable(Scope.SCOPE_LOCAL,line,data.srcCode.getPosition());
else if (idStr.equals("VAR")) {
Identifier _id = identifier(data,false,true);
if(_id!=null){
comments(data);
Variable local = data.factory.createVariable(ScopeSupport.SCOPE_VAR,line,data.srcCode.getPosition());
if(!"LOCAL".equalsIgnoreCase(_id.getString()))local.addMember(data.factory.createDataMember(_id));
else {
local.ignoredFirstMember(true);
}
return local;
}
}
else if (idStr.equals("VARIABLES")) return data.factory.createVariable(Scope.SCOPE_VARIABLES,line,data.srcCode.getPosition());
else if (idStr.equals("REQUEST")) return data.factory.createVariable(Scope.SCOPE_REQUEST,line,data.srcCode.getPosition());
else if (idStr.equals("SERVER")) return data.factory.createVariable(Scope.SCOPE_SERVER,line,data.srcCode.getPosition());
if(data.settings.ignoreScopes)return null;
if (idStr.equals("CGI")) return data.factory.createVariable(Scope.SCOPE_CGI,line,data.srcCode.getPosition());
else if (idStr.equals("SESSION")) return data.factory.createVariable(Scope.SCOPE_SESSION,line,data.srcCode.getPosition());
else if (idStr.equals("APPLICATION")) return data.factory.createVariable(Scope.SCOPE_APPLICATION,line,data.srcCode.getPosition());
else if (idStr.equals("FORM")) return data.factory.createVariable(Scope.SCOPE_FORM,line,data.srcCode.getPosition());
else if (idStr.equals("URL")) return data.factory.createVariable(Scope.SCOPE_URL,line,data.srcCode.getPosition());
else if (idStr.equals("CLIENT")) return data.factory.createVariable(Scope.SCOPE_CLIENT,line,data.srcCode.getPosition());
else if (idStr.equals("COOKIE")) return data.factory.createVariable(Scope.SCOPE_COOKIE,line,data.srcCode.getPosition());
else if (idStr.equals("CLUSTER")) return data.factory.createVariable(Scope.SCOPE_CLUSTER,line,data.srcCode.getPosition());
return null;
}
/**
* Liest einen Identifier aus und gibt diesen als String zurueck.
* <br />
* EBNF:<br />
* <code>(letter | "_") {letter | "_"|digit};</code>
* @param firstCanBeNumber
* @param upper
* @return Identifier.
*/
protected Identifier identifier(ExprData data,boolean firstCanBeNumber,boolean upper) {
Position start = data.srcCode.getPosition();
if(!data.srcCode.isCurrentLetter() && !data.srcCode.isCurrentSpecial() ) {
if(!firstCanBeNumber) return null;
else if(!data.srcCode.isCurrentBetween('0','9'))return null;
}
do {
data.srcCode.next();
if(!(data.srcCode.isCurrentLetter()
|| data.srcCode.isCurrentBetween('0','9')
|| data.srcCode.isCurrentSpecial())) {
break;
}
}
while (data.srcCode.isValidIndex());
return Identifier.toIdentifier(data.factory,data.srcCode.substring(start.pos,data.srcCode.getPos()-start.pos),
upper && data.settings.dotNotationUpper?Identifier.CASE_UPPER:Identifier.CASE_ORIGNAL, start,data.srcCode.getPosition());
}
protected String identifier(ExprData data,boolean firstCanBeNumber) {
int start = data.srcCode.getPos();
if(!data.srcCode.isCurrentLetter() && !data.srcCode.isCurrentSpecial() ) {
if(!firstCanBeNumber) return null;
else if(!data.srcCode.isCurrentBetween('0','9'))return null;
}
do {
data.srcCode.next();
if(!(data.srcCode.isCurrentLetter()
|| data.srcCode.isCurrentBetween('0','9')
|| data.srcCode.isCurrentSpecial())) {
break;
}
}
while (data.srcCode.isValidIndex());
return data.srcCode.substring(start,data.srcCode.getPos()-start);
}
/**
* Transfomiert ein Collection Element das in eckigen Klammern aufgerufen wird.
* <br />
* EBNF:<br />
* <code>"[" impOp "]"</code>
* @return CFXD Element
* @throws TemplateException
*/
private ExprString structElement(ExprData data) throws TemplateException {
comments(data);
ExprString name = data.factory.toExprString(assignOp(data));
if(name instanceof LitString)((LitString)name).fromBracket(true);
comments(data);
return name;
}
/**
* Liest die Argumente eines Funktonsaufruf ein und prueft ob die Funktion
* innerhalb der FLD (Function Library Descriptor) definiert ist.
* Falls sie existiert wird die Funktion gegen diese geprueft und ein build-in-function CFXD Element generiert,
* ansonsten ein normales funcion-call Element.
* <br />
* EBNF:<br />
* <code>[impOp{"," impOp}];</code>
* @param name Identifier der Funktion als Zeichenkette
* @param checkLibrary Soll geprueft werden ob die Funktion innerhalb der Library existiert.
* @return CFXD Element
* @throws TemplateException
*/
private FunctionMember getFunctionMember(ExprData data,
final ExprString name,
boolean checkLibrary)
throws TemplateException {
// get Function Library
checkLibrary=checkLibrary && data.flibs!=null;
FunctionLibFunction flf = null;
if (checkLibrary) {
if(!(name instanceof Literal))
throw new TemplateException(data.srcCode,"syntax error"); // should never happen!
for (int i = 0; i < data.flibs.length; i++) {
flf = data.flibs[i].getFunction(((Literal)name).getString());
if (flf != null)break;
}
if (flf == null) {
checkLibrary = false;
}
}
FunctionMember fm=null;
while(true) {
int pos = data.srcCode.getPos();
// Element Function
if(checkLibrary) {
BIF bif=new BIF(data.factory,data.settings,flf);
// TODO data.ep.add(flf, bif, data.srcCode);
bif.setArgType(flf.getArgType());
try {
bif.setClassDefinition(flf.getFunctionClassDefinition());
} catch(Throwable t) {
ExceptionUtil.rethrowIfNecessary(t);
throw new PageRuntimeException(t);
}
bif.setReturnType(flf.getReturnTypeAsString());
fm=bif;
if(flf.getArgType()== FunctionLibFunction.ARG_DYNAMIC && flf.hasDefaultValues()){
ArrayList<FunctionLibFunctionArg> args = flf.getArg();
Iterator<FunctionLibFunctionArg> it = args.iterator();
FunctionLibFunctionArg arg;
while(it.hasNext()){
arg=it.next();
if(arg.getDefaultValue()!=null)
bif.addArgument(
new NamedArgument(
data.factory.createLitString(arg.getName()),
data.factory.createLitString(arg.getDefaultValue()),
arg.getTypeAsString(),false
));
}
}
}
else {
fm = new UDF(name);
}
// Function Attributes
ArrayList<FunctionLibFunctionArg> arrFuncLibAtt = null;
//int libLen = 0;
if (checkLibrary) {
arrFuncLibAtt = flf.getArg();
//libLen = arrFuncLibAtt.size();
}
int count = 0;
do {
data.srcCode.next();
comments(data);
// finish
if (count==0 && data.srcCode.isCurrent(')'))
break;
//Argument arg;
if (checkLibrary && flf.getArgType()!=FunctionLibFunction.ARG_DYNAMIC) {
// current attribues from library
String _type;
try{
_type = arrFuncLibAtt.get(count).getTypeAsString();
}
catch(IndexOutOfBoundsException e) {
_type=null;
}
fm.addArgument(functionArgument(data,_type,false));
}
else {
fm.addArgument(functionArgument(data,false));
}
comments(data);
count++;
if (data.srcCode.isCurrent(')'))
break;
}
while (data.srcCode.isCurrent(','));
// end with ) ??
if (!data.srcCode.forwardIfCurrent(')'))
throw new TemplateException(
data.srcCode,
"Invalid Syntax Closing [)] for function ["
+ (flf!=null?flf.getName():ASMUtil.display(name))
+ "] not found");
if (checkLibrary) {
// pre
if(flf.hasTteClass()){
FunctionLibFunction tmp = flf.getEvaluator().pre((BIF) fm, flf);
if(tmp!=null && tmp!=flf) {
flf=tmp;
data.srcCode.setPos(pos);
continue;
}
}
// check max attributes
{
boolean isDynamic = flf.getArgType()==FunctionLibFunction.ARG_DYNAMIC;
int max = flf.getArgMax();
// Dynamic
if(isDynamic) {
if(max!=-1 && max < fm.getArguments().length)
throw new TemplateException(
data.srcCode,
"too many Attributes ("+max+":"+fm.getArguments().length+") in function [ " + ASMUtil.display(name) + " ]");
}
// Fix
else {
if(flf.getArg().size() < fm.getArguments().length){
TemplateException te = new TemplateException(
data.srcCode,
"too many Attributes ("+flf.getArg().size()+":"+fm.getArguments().length+") in function call [" + ASMUtil.display(name) + "]");
UDFUtil.addFunctionDoc(te, flf);
throw te;
}
}
}
// check min attributes
if (flf.getArgMin() > count){
TemplateException te = new TemplateException(
data.srcCode,
"too few attributes in function [" + ASMUtil.display(name) + "]");
if(flf.getArgType()==FunctionLibFunction.ARG_FIX) UDFUtil.addFunctionDoc(te, flf);
throw te;
}
// evaluator
if(flf.hasTteClass()){
flf.getEvaluator().execute((BIF) fm, flf);
}
}
comments(data);
if(checkLibrary)data.ep.add(flf, (BIF)fm, data.srcCode);
break;
}
return fm;
}
/**
* Sharps (#) die innerhalb von Expressions auftauchen haben in CFML keine weitere Beteutung
* und werden durch diese Methode einfach entfernt.
* <br />
* Beispiel:<br />
* <code>arrayLen(#arr#)</code> und <code>arrayLen(arr)</code> sind identisch.
* EBNF:<br />
* <code>"#" checker "#";</code>
* @return CFXD Element
* @throws TemplateException
*/
private Expression sharp(ExprData data) throws TemplateException {
if(!data.srcCode.forwardIfCurrent('#'))
return null;
Expression expr;
comments(data);
boolean old=data.allowLowerThan;
data.allowLowerThan=true;
expr = assignOp(data);
data.allowLowerThan=old;
comments(data);
if (!data.srcCode.forwardIfCurrent('#'))
throw new TemplateException(
data.srcCode,
"Syntax Error, Invalid Construct "+(data.srcCode.length()<30?data.srcCode.toString():""));
comments(data);
return expr;
}
/**
* @param data
* @return parsed Element
* @throws TemplateException
*/
private Expression simple(ExprData data,String[] breakConditions) throws TemplateException {
StringBuffer sb=new StringBuffer();
Position line = data.srcCode.getPosition();
outer:while(data.srcCode.isValidIndex()) {
for(int i=0;i<breakConditions.length;i++){
if(data.srcCode.isCurrent(breakConditions[i]))break outer;
}
if(data.srcCode.isCurrent('"') || data.srcCode.isCurrent('#') || data.srcCode.isCurrent('\'')) {
throw new TemplateException(data.srcCode,"simple attribute value can't contain ["+data.srcCode.getCurrent()+"]");
}
sb.append(data.srcCode.getCurrent());
data.srcCode.next();
}
comments(data);
return data.factory.createLitString(sb.toString(),line,data.srcCode.getPosition());
}
/**
* Liest alle folgenden Komentare ein.
* <br />
* EBNF:<br />
* <code>{?-"\n"} "\n";</code>
* @param data
* @throws TemplateException
*/
protected void comments(ExprData data) throws TemplateException {
data.srcCode.removeSpace();
while(comment(data)){data.srcCode.removeSpace();}
}
/**
* Liest einen Einzeiligen Kommentar ein.
* <br />
* EBNF:<br />
* <code>{?-"\n"} "\n";</code>
* @return bool Wurde ein Kommentar entfernt?
* @throws TemplateException
*/
private boolean comment(ExprData data) throws TemplateException {
if(singleLineComment(data.srcCode) || multiLineComment(data) || CFMLTransformer.comment(data.srcCode)) return true;
return false;
}
/**
* Liest einen Mehrzeiligen Kommentar ein.
* <br />
* EBNF:<br />
* <code>?-"*<!-- -->/";</code>
* @return bool Wurde ein Kommentar entfernt?
* @throws TemplateException
*/
private boolean multiLineComment(ExprData data) throws TemplateException {
SourceCode cfml = data.srcCode;
if(!cfml.forwardIfCurrent("/*")) return false;
int pos=cfml.getPos();
boolean isDocComment=cfml.isCurrent('*');
while(cfml.isValidIndex()) {
if(cfml.isCurrent("*/")) break;
cfml.next();
}
if(!cfml.forwardIfCurrent("*/")){
cfml.setPos(pos);
throw new TemplateException(cfml,"comment is not closed");
}
if(isDocComment) {
String comment = cfml.substring(pos-2,cfml.getPos()-pos);
data.docComment=docCommentTransformer.transform(data.factory,comment);
}
return true;
}
/**
* Liest einen Einzeiligen Kommentar ein.
* <br />
* EBNF:<br />
* <code>{?-"\n"} "\n";</code>
* @return bool Wurde ein Kommentar entfernt?
*/
private boolean singleLineComment(SourceCode cfml) {
if(!cfml.forwardIfCurrent("//")) return false;
return cfml.nextLine();
}
}