/**
* 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.runtime.interpreter;
import java.util.ArrayList;
import java.util.List;
import lucee.commons.lang.CFTypes;
import lucee.commons.lang.ParserString;
import lucee.loader.engine.CFMLEngine;
import lucee.loader.engine.CFMLEngineFactory;
import lucee.runtime.MappingImpl;
import lucee.runtime.PageContext;
import lucee.runtime.PageSource;
import lucee.runtime.config.ConfigImpl;
import lucee.runtime.config.ConfigWebImpl;
import lucee.runtime.engine.ThreadLocalPageContext;
import lucee.runtime.exp.PageException;
import lucee.runtime.exp.TemplateException;
import lucee.runtime.interpreter.ref.Ref;
import lucee.runtime.interpreter.ref.Set;
import lucee.runtime.interpreter.ref.cast.Casting;
import lucee.runtime.interpreter.ref.func.BIFCall;
import lucee.runtime.interpreter.ref.func.UDFCall;
import lucee.runtime.interpreter.ref.literal.LBoolean;
import lucee.runtime.interpreter.ref.literal.LFunctionValue;
import lucee.runtime.interpreter.ref.literal.LNumber;
import lucee.runtime.interpreter.ref.literal.LString;
import lucee.runtime.interpreter.ref.literal.LStringBuffer;
import lucee.runtime.interpreter.ref.literal.Literal;
import lucee.runtime.interpreter.ref.op.And;
import lucee.runtime.interpreter.ref.op.BigDiv;
import lucee.runtime.interpreter.ref.op.BigIntDiv;
import lucee.runtime.interpreter.ref.op.BigMinus;
import lucee.runtime.interpreter.ref.op.BigMod;
import lucee.runtime.interpreter.ref.op.BigMulti;
import lucee.runtime.interpreter.ref.op.BigPlus;
import lucee.runtime.interpreter.ref.op.CT;
import lucee.runtime.interpreter.ref.op.Concat;
import lucee.runtime.interpreter.ref.op.Cont;
import lucee.runtime.interpreter.ref.op.Div;
import lucee.runtime.interpreter.ref.op.EEQ;
import lucee.runtime.interpreter.ref.op.EQ;
import lucee.runtime.interpreter.ref.op.EQV;
import lucee.runtime.interpreter.ref.op.Elvis;
import lucee.runtime.interpreter.ref.op.Exp;
import lucee.runtime.interpreter.ref.op.GT;
import lucee.runtime.interpreter.ref.op.GTE;
import lucee.runtime.interpreter.ref.op.Imp;
import lucee.runtime.interpreter.ref.op.IntDiv;
import lucee.runtime.interpreter.ref.op.LT;
import lucee.runtime.interpreter.ref.op.LTE;
import lucee.runtime.interpreter.ref.op.Minus;
import lucee.runtime.interpreter.ref.op.Mod;
import lucee.runtime.interpreter.ref.op.Multi;
import lucee.runtime.interpreter.ref.op.NCT;
import lucee.runtime.interpreter.ref.op.NEEQ;
import lucee.runtime.interpreter.ref.op.NEQ;
import lucee.runtime.interpreter.ref.op.Negate;
import lucee.runtime.interpreter.ref.op.Not;
import lucee.runtime.interpreter.ref.op.Or;
import lucee.runtime.interpreter.ref.op.Plus;
import lucee.runtime.interpreter.ref.op.Xor;
import lucee.runtime.interpreter.ref.var.Assign;
import lucee.runtime.interpreter.ref.var.Bind;
import lucee.runtime.interpreter.ref.var.DynAssign;
import lucee.runtime.interpreter.ref.var.Variable;
import lucee.runtime.type.scope.Scope;
import lucee.runtime.type.scope.ScopeSupport;
import lucee.transformer.library.function.FunctionLib;
import lucee.transformer.library.function.FunctionLibFunction;
import lucee.transformer.library.function.FunctionLibFunctionArg;
/**
*
*
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 FunctionLib's, 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 class CFMLExpressionInterpreter {
private static final LNumber PLUS_ONE = new LNumber(new Double(1));
private static final LNumber MINUS_ONE = new LNumber(new Double(-1));
protected static final short STATIC=0;
private static final short DYNAMIC=1;
private static FunctionLibFunction LITERAL_ARRAY = null;
private static FunctionLibFunction LITERAL_STRUCT = null;
private static FunctionLibFunction JSON_ARRAY = null;
private static FunctionLibFunction JSON_STRUCT = null;
private static FunctionLibFunction LITERAL_ORDERED_STRUCT = null;
//private static final int CASE_TYPE_UPPER = 0;
//private static final int CASE_TYPE_LOWER = 1;
//private static final int CASE_TYPE_ORIGINAL = 2;
protected short mode=0;
protected ParserString cfml;
//protected Document doc;
//protected FunctionLib[] fld;
protected PageContext pc;
private FunctionLib fld;
protected boolean allowNullConstant=false;
private boolean preciseMath;
private final boolean isJson;
private final boolean limited;
private ConfigImpl config;
public CFMLExpressionInterpreter() {
this(true);
}
public CFMLExpressionInterpreter(boolean limited) {
this.isJson=this instanceof JSONExpressionInterpreter;
this.limited=limited || isJson; // json is always limited
}
public Object interpret(PageContext pc,String str) throws PageException {
return interpret(pc,str,false);
}
public Object interpret(PageContext pc,String str, boolean preciseMath) throws PageException {
this.cfml=new ParserString(str);
this.preciseMath = preciseMath;
init();
if(LITERAL_ARRAY==null)LITERAL_ARRAY=fld.getFunction("_literalArray");
if(LITERAL_STRUCT==null)LITERAL_STRUCT=fld.getFunction("_literalStruct");
if(JSON_ARRAY==null)JSON_ARRAY=fld.getFunction("_jsonArray");
if(JSON_STRUCT==null)JSON_STRUCT=fld.getFunction("_jsonStruct");
if(LITERAL_ORDERED_STRUCT==null)LITERAL_ORDERED_STRUCT=fld.getFunction("_literalOrderedStruct");
cfml.removeSpace();
Ref ref = assignOp();
cfml.removeSpace();
if(cfml.isAfterLast()) {
//data.put(str+":"+preciseMath,ref);
return ref.getValue(pc);
}
throw new InterpreterException("Syntax Error, invalid Expression ["+cfml.toString()+"]");
}
private void init() {
this.pc=ThreadLocalPageContext.get(pc);
int dialect=CFMLEngine.DIALECT_CFML;
if(this.pc!=null) {
this.config=(ConfigImpl) pc.getConfig();
dialect=pc.getCurrentTemplateDialect();
}
else {
this.config = (ConfigImpl)ThreadLocalPageContext.getConfig();
if(config==null) {
try {
config=(ConfigImpl)CFMLEngineFactory.getInstance().createConfig(null,"localhost", "/index.cfm");// TODO set a context root
}
catch (Exception e){}
}
}
fld=config.getCombinedFLDs(dialect);
}
/*private FunctionLibFunction getFLF(String name) {
FunctionLibFunction flf=null;
for (int i = 0; i < flds.length; i++) {
flf = flds[i].getFunction(name);
if (flf != null)
break;
}
return flf;
}*/
protected Object interpretPart(PageContext pc,ParserString cfml) throws PageException {
this.cfml = cfml;
init();
cfml.removeSpace();
return assignOp().getValue(pc);
}
/**
* Liest einen gelableten Funktionsparamter ein
* <br />
* EBNF:<br />
* <code>assignOp [":" spaces assignOp];</code>
* @return CFXD Element
* @throws PageException
*/
private Ref functionArgDeclarationVarString() throws PageException {
cfml.removeSpace();
StringBuilder str=new StringBuilder();
String id=null;
while((id=identifier(false))!=null) {
if(str.length()>0)str.append('.');
str.append(id);
cfml.removeSpace();
if(!cfml.forwardIfCurrent('.')) break;
cfml.removeSpace();
}
cfml.removeSpace();
if(str.length()>0 && cfml.charAt(cfml.getPos()-1)!='.')
return new LString(str.toString());
throw new InterpreterException("invalid variable name definition");
}
/**
* Liest einen gelableten Funktionsparamter ein
* <br />
* EBNF:<br />
* <code>assignOp [":" spaces assignOp];</code>
* @return CFXD Element
* @throws PageException
*/
private Ref functionArgDeclaration() throws PageException {
Ref ref = impOp();
if (cfml.forwardIfCurrent(':') || cfml.forwardIfCurrent('=')) {
cfml.removeSpace();
ref=new LFunctionValue(ref,assignOp());
}
return ref;
}
/**
* Transfomiert Zuweisungs Operation.
* <br />
* EBNF:<br />
* <code>eqvOp ["=" spaces assignOp];</code>
* @return CFXD Element
* @throws PageException
*/
protected Ref assignOp() throws PageException {
Ref ref = contOp();
if (cfml.forwardIfCurrent('=')) {
cfml.removeSpace();
if(mode==STATIC || ref instanceof Literal) {
ref=new DynAssign(ref,assignOp(),limited);
}
else {
ref=new Assign(ref,assignOp(),limited);
}
}
return ref;
}
private Ref contOp() throws PageException {
Ref ref = impOp();
while(cfml.forwardIfCurrent('?')) {
cfml.removeSpace();
if(cfml.forwardIfCurrent(':')){
cfml.removeSpace();
Ref right = assignOp();
ref=new Elvis(ref,right,limited);
}
else {
Ref left = assignOp();
if(!cfml.forwardIfCurrent(':'))
throw new InterpreterException("Syntax Error, invalid conditional operator ["+cfml.toString()+"]");
cfml.removeSpace();
Ref right = assignOp();
ref=new Cont(ref,left,right,limited);
}
}
return ref;
}
/**
* Transfomiert eine Implication (imp) Operation.
* <br />
* EBNF:<br />
* <code>eqvOp {"imp" spaces eqvOp};</code>
* @return CFXD Element
* @throws PageException
*/
private Ref impOp() throws PageException {
Ref ref = eqvOp();
while(cfml.forwardIfCurrentAndNoWordAfter("imp")) {
cfml.removeSpace();
ref=new Imp(ref,eqvOp(),limited);
}
return ref;
}
/**
* Transfomiert eine Equivalence (eqv) Operation.
* <br />
* EBNF:<br />
* <code>xorOp {"eqv" spaces xorOp};</code>
* @return CFXD Element
* @throws PageException
*/
private Ref eqvOp() throws PageException {
Ref ref = xorOp();
while(cfml.forwardIfCurrent("eqv")) {
cfml.removeSpace();
ref=new EQV(ref,xorOp(),limited);
}
return ref;
}
/**
* Transfomiert eine Xor (xor) Operation.
* <br />
* EBNF:<br />
* <code>orOp {"xor" spaces orOp};</code>
* @return CFXD Element
* @throws PageException
*/
private Ref xorOp() throws PageException {
Ref ref = orOp();
while(cfml.forwardIfCurrent("xor")) {
cfml.removeSpace();
ref=new Xor(ref,orOp(),limited);
}
return ref;
}
/**
* 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 PageException
*/
private Ref orOp() throws PageException {
Ref ref = andOp();
while(cfml.isValidIndex() && (cfml.forwardIfCurrent("||") || cfml.forwardIfCurrent("or"))) {
cfml.removeSpace();
ref=new Or(ref,andOp(),limited);
}
return ref;
}
/**
* 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 PageException
*/
private Ref andOp() throws PageException {
Ref ref = notOp();
while(cfml.isValidIndex() && (cfml.forwardIfCurrent("&&") || cfml.forwardIfCurrent("and"))) {
cfml.removeSpace();
ref=new And(ref,notOp(),limited);
}
return ref;
}
/**
* 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 PageException
*/
private Ref notOp() throws PageException {
if(cfml.isValidIndex()) {
if (cfml.isCurrent('!') && !cfml.isCurrent("!=")) {
cfml.next();
cfml.removeSpace();
return new Not(decsionOp(),limited);
}
else if (cfml.forwardIfCurrentAndNoWordAfter("not")) {
cfml.removeSpace();
return new Not(decsionOp(),limited);
}
}
return decsionOp();
}
/**
* <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 PageException
*/
private Ref decsionOp() throws PageException {
Ref ref = concatOp();
boolean hasChanged=false;
// ct, contains
if(cfml.isValidIndex()){
do {
hasChanged=false;
if(cfml.isCurrent('c')) {
if (cfml.forwardIfCurrent("ct")) {
cfml.removeSpace();
ref=new CT(ref,concatOp(),limited);
hasChanged=true;
}
else if (cfml.forwardIfCurrent("contains")){
cfml.removeSpace();
ref=new CT(ref,concatOp(),limited);
hasChanged=true;
}
}
// does not contain
else if (cfml.forwardIfCurrent("does","not","contain")){
cfml.removeSpace();
ref=new NCT(ref,concatOp(),limited);
hasChanged=true;
}
// equal, eq
else if (cfml.isCurrent("eq") && !cfml.isCurrent("eqv")) {
cfml.setPos(cfml.getPos()+2);
cfml.forwardIfCurrent("ual");
cfml.removeSpace();
ref=new EQ(ref,concatOp(),limited);
hasChanged=true;
}
// ==
else if (cfml.forwardIfCurrent("==")) {
if(cfml.forwardIfCurrent('=')) {
cfml.removeSpace();
ref = new EEQ(ref,concatOp(),limited);
}
else {
cfml.removeSpace();
ref=new EQ(ref,concatOp(),limited);
}
hasChanged=true;
}
// !=
else if (cfml.forwardIfCurrent("!=")) {
if(cfml.forwardIfCurrent('=')) {
cfml.removeSpace();
ref = new NEEQ(ref,concatOp(),limited);
}
else {
cfml.removeSpace();
ref=new NEQ(ref,concatOp(),limited);
}
hasChanged=true;
}
// <=/</<>
else if (cfml.forwardIfCurrent('<')) {
if(cfml.forwardIfCurrent('=')) {
cfml.removeSpace();
ref = new LTE(ref,concatOp(),limited);
}
else if(cfml.forwardIfCurrent('>')) {
cfml.removeSpace();
ref = new NEQ(ref,concatOp(),limited);
}
else {
cfml.removeSpace();
ref = new LT(ref,concatOp(),limited);
}
hasChanged=true;
}
// >/>=
else if (cfml.forwardIfCurrent('>')) {
if(cfml.forwardIfCurrent('=')) {
cfml.removeSpace();
ref = new GTE(ref,concatOp(),limited);
}
else {
cfml.removeSpace();
ref = new GT(ref,concatOp(),limited);
}
hasChanged=true;
}
// gt, gte, greater than or equal to, greater than
else if (cfml.isCurrent('g')) {
if (cfml.forwardIfCurrent("gt")) {
if(cfml.forwardIfCurrent('e')) {
cfml.removeSpace();
ref=new GTE(ref,concatOp(),limited);
}
else {
cfml.removeSpace();
ref=new GT(ref,concatOp(),limited);
}
hasChanged=true;
}
else if (cfml.forwardIfCurrent("greater","than")) {
if(cfml.forwardIfCurrent("or" ,"equal", "to",true)) {
cfml.removeSpace();
ref=new GTE(ref,concatOp(),limited);
}
else {
cfml.removeSpace();
ref=new GT(ref,concatOp(),limited);
}
hasChanged=true;
}
else if (cfml.forwardIfCurrent("ge")) {
cfml.removeSpace();
ref=new GTE(ref,concatOp(),limited);
hasChanged=true;
}
}
// is, is not
else if (cfml.forwardIfCurrent("is")) {
if(cfml.forwardIfCurrent("not",true)) {
cfml.removeSpace();
ref=new NEQ(ref,concatOp(),limited);
}
else {
cfml.removeSpace();
ref=new EQ(ref,concatOp(),limited);
}
hasChanged=true;
}
// lt, lte, less than, less than or equal to
else if (cfml.isCurrent('l')) {
if (cfml.forwardIfCurrent("lt")) {
if(cfml.forwardIfCurrent('e')) {
cfml.removeSpace();
ref=new LTE(ref,concatOp(),limited);
}
else {
cfml.removeSpace();
ref=new LT(ref,concatOp(),limited);
}
hasChanged=true;
}
else if (cfml.forwardIfCurrent("less","than")) {
if(cfml.forwardIfCurrent("or", "equal", "to",true)) {
cfml.removeSpace();
ref=new LTE(ref,concatOp(),limited);
}
else {
cfml.removeSpace();
ref=new LT(ref,concatOp(),limited);
}
hasChanged=true;
}
else if (cfml.forwardIfCurrent("le")) {
cfml.removeSpace();
ref=new LTE(ref,concatOp(),limited);
hasChanged=true;
}
}
// neq, not equal, nct
else if (cfml.isCurrent('n')) {
// Not Equal
if (cfml.forwardIfCurrent("neq")) {
cfml.removeSpace();
ref=new NEQ(ref,concatOp(),limited);
hasChanged=true;
}
// Not Equal (Alias)
else if (cfml.forwardIfCurrent("not","equal")){
cfml.removeSpace();
ref=new NEQ(ref,concatOp(),limited);
hasChanged=true;
}
// nct
else if (cfml.forwardIfCurrent("nct")) {
cfml.removeSpace();
ref=new NCT(ref,concatOp(),limited);
hasChanged=true;
}
}
}while(hasChanged);
}
return ref;
}
/**
* 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 PageException
*/
private Ref concatOp() throws PageException {
Ref ref = plusMinusOp();
while(cfml.isCurrent('&') && !cfml.isNext('&')) {
cfml.next();
ref=_concat(ref);
}
return ref;
}
/**
* Transfomiert die mathematischen Operatoren Plus und Minus (1,-).
* <br />
* EBNF:<br />
* <code>modOp [("-"|"+") spaces plusMinusOp];</code>
* @return CFXD Element
* @throws PageException
*/
private Ref plusMinusOp() throws PageException {
Ref ref = modOp();
while(!cfml.isLast()) {
// Plus Operation
if (cfml.forwardIfCurrent('+')) {
ref=_plus(ref);
}
// Minus Operation
else if (cfml.forwardIfCurrent('-')) {
ref=_minus(ref);
}
else break;
}
return ref;
}
private Ref _plus(Ref ref) throws PageException {
// +=
if (cfml.isCurrent('=')) {
cfml.next();
cfml.removeSpace();
Ref right = assignOp();
Ref res = preciseMath?new BigPlus(ref,right,limited):new Plus(ref,right,limited);
ref=new Assign(ref,res,limited);
}
else {
cfml.removeSpace();
ref=preciseMath?new BigPlus(ref,modOp(),limited):new Plus(ref,modOp(),limited);
}
return ref;
}
private Ref _minus(Ref ref) throws PageException {
// -=
if (cfml.isCurrent('=')) {
cfml.next();
cfml.removeSpace();
Ref right = assignOp();
Ref res = preciseMath?new BigMinus(ref,right,limited):new Minus(ref,right,limited);
ref=new Assign(ref,res,limited);
}
else {
cfml.removeSpace();
ref=preciseMath?new BigMinus(ref,modOp(),limited):new Minus(ref,modOp(),limited);
}
return ref;
}
private Ref _div(Ref ref) throws PageException {
// /=
if (cfml.forwardIfCurrent('=')) {
cfml.removeSpace();
Ref right = assignOp();
Ref res = preciseMath?new BigDiv(ref, right,limited):new Div(ref,right,limited);
ref=new Assign(ref,res,limited);
}
else {
cfml.removeSpace();
ref=preciseMath?new BigDiv(ref,expoOp(),limited):new Div(ref,expoOp(),limited);
}
return ref;
}
private Ref _intdiv(Ref ref) throws PageException {
// \=
if (cfml.forwardIfCurrent('=')) {
cfml.removeSpace();
Ref right = assignOp();
Ref res = preciseMath?new BigIntDiv(ref,right,limited):new IntDiv(ref,right,limited);
ref=new Assign(ref,res,limited);
}
else {
cfml.removeSpace();
ref=preciseMath?new BigIntDiv(ref,expoOp(),limited):new IntDiv(ref,expoOp(),limited);
}
return ref;
}
private Ref _mod(Ref ref) throws PageException {
// %=
if (cfml.forwardIfCurrent('=')) {
cfml.removeSpace();
Ref right = assignOp();
Ref res = preciseMath?new BigMod(ref,right,limited):new Mod(ref,right,limited);
ref=new Assign(ref,res,limited);
}
else {
cfml.removeSpace();
ref=preciseMath?new BigMod(ref,divMultiOp(),limited):new Mod(ref,divMultiOp(),limited);
}
return ref;
}
private Ref _concat(Ref ref) throws PageException {
// &=
if (cfml.forwardIfCurrent('=')) {
cfml.removeSpace();
Ref right = assignOp();
Ref res = new Concat(ref,right,limited);
ref=new Assign(ref,res,limited);
}
else {
cfml.removeSpace();
ref=new Concat(ref,plusMinusOp(),limited);
}
return ref;
}
private Ref _multi(Ref ref) throws PageException {
// \=
if (cfml.forwardIfCurrent('=')) {
cfml.removeSpace();
Ref right = assignOp();
Ref res = preciseMath?new BigMulti(ref,right,limited):new Multi(ref,right,limited);
ref=new Assign(ref,res,limited);
}
else {
cfml.removeSpace();
ref=preciseMath?new BigMulti(ref,expoOp(),limited):new Multi(ref,expoOp(),limited);
}
return ref;
}
/**
* 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 PageException
*/
private Ref modOp() throws PageException {
Ref ref = divMultiOp();
while(cfml.isValidIndex() && (cfml.forwardIfCurrent('%') || cfml.forwardIfCurrent("mod"))) {
ref=_mod(ref);
}
return ref;
}
/**
* Transfomiert die mathematischen Operatoren Mal und Durch (*,/).
* <br />
* EBNF:<br />
* <code>expoOp {("*"|"/") spaces expoOp};</code>
* @return CFXD Element
* @throws PageException
*/
private Ref divMultiOp() throws PageException {
Ref ref = expoOp();
while (!cfml.isLast()) {
// Multiply Operation
if(cfml.forwardIfCurrent('*')) {
ref=_multi(ref);
}
// Divide Operation
else if (cfml.isCurrent('/') && (!cfml.isCurrent("/>") )) {
cfml.next();
ref=_div(ref);
}
// Divide Operation
else if (cfml.isCurrent('\\')) {
cfml.next();
ref=_intdiv(ref);
}
else {
break;
}
}
return ref;
}
/**
* 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 PageException
*/
private Ref expoOp() throws PageException {
Ref ref = unaryOp();
while(cfml.isValidIndex() && (cfml.forwardIfCurrent('^') || cfml.forwardIfCurrent("exp"))) {
cfml.removeSpace();
ref=new Exp(ref,unaryOp(),limited);
}
return ref;
}
private Ref unaryOp() throws PageException {
Ref ref = negateMinusOp();
if (cfml.forwardIfCurrent("--"))
ref=_unaryOp(ref, false);
else if (cfml.forwardIfCurrent("++"))
ref=_unaryOp(ref, true);
return ref;
}
private Ref _unaryOp(Ref ref,boolean isPlus) throws PageException {
cfml.removeSpace();
Ref res = preciseMath?new BigPlus(ref,isPlus?PLUS_ONE:MINUS_ONE,limited):new Plus(ref,isPlus?PLUS_ONE:MINUS_ONE,limited);
ref=new Assign(ref,res,limited);
return preciseMath?new BigPlus(ref,isPlus?MINUS_ONE:PLUS_ONE,limited):new Plus(ref,isPlus?MINUS_ONE:PLUS_ONE,limited);
}
/**
* Liest die Vordlobe einer Zahl ein
* @return CFXD Element
* @throws PageException
*/
private Ref negateMinusOp() throws PageException {
// And Operation
if (cfml.forwardIfCurrent('-')) {
if (cfml.forwardIfCurrent('-')) {
cfml.removeSpace();
Ref expr = clip();
Ref res = preciseMath?new BigMinus(expr,new LNumber(new Double(1)),limited):new Minus(expr,new LNumber(new Double(1)),limited);
return new Assign(expr,res,limited);
}
cfml.removeSpace();
return new Negate(clip(),limited);
}
if (cfml.forwardIfCurrent('+')) {
if (cfml.forwardIfCurrent('+')) {
cfml.removeSpace();
Ref expr = clip();
Ref res = preciseMath?new BigPlus(expr,new LNumber(new Double(1)),limited):new Plus(expr,new LNumber(new Double(1)),limited);
return new Assign(expr,res,limited);
}
cfml.removeSpace();
return new Casting("numeric",CFTypes.TYPE_NUMERIC,clip());
}
return clip();
}
/**
* Verarbeitet Ausdruecke die inerhalb einer Klammer stehen.
* <br />
* EBNF:<br />
* <code>("(" spaces impOp ")" spaces) | checker;</code>
* @return CFXD Element
* @throws PageException
*/
private Ref clip() throws PageException {
return checker();
}
/**
* 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 PageException
*/
private Ref checker() throws PageException {
Ref ref=null;
// String
if(cfml.isCurrentQuoter()) {
// mode=STATIC; is at the end of the string function because must set after execution
return string();
}
// Number
if(cfml.isCurrentDigit() || cfml.isCurrent('.')) {
// mode=STATIC; is at the end of the string function because must set after execution
return number();
}
// Dynamic
if((ref=dynamic())!=null) {
mode=DYNAMIC;
return ref;
}
// Sharp
if(!limited &&(ref=sharp())!=null) {
mode=DYNAMIC;
return ref;
}
// JSON
if((ref=json(isJson?JSON_ARRAY:LITERAL_ARRAY,'[',']'))!=null) {
mode=DYNAMIC;
return ref;
}
if((ref=json(isJson?JSON_STRUCT:LITERAL_STRUCT,'{','}'))!=null) {
mode=DYNAMIC;
return ref;
}
if(cfml.isAfterLast() && cfml.toString().trim().length()==0)
return new LString("");
// else Error
String str=cfml.toString();
int pos=cfml.getPos();
if(str.length()>100) {
// Failure is in the beginning
if(pos<=10) {
str=str.substring(0,20)+" ...";
}
// Failure is in the end
else if((str.length()-pos)<=10) {
str="... "+str.substring(str.length()-20,str.length());
}
else {
str="... "+str.substring(pos-10,pos+10)+" ...";
}
}
throw new InterpreterException("Syntax Error, Invalid Construct","at position "+(pos+1)+" in ["+str+"]");
}
protected Ref json(FunctionLibFunction flf, char start, char end) throws PageException {
if(!cfml.isCurrent(start))return null;
if(cfml.forwardIfCurrent('[',':',']') || cfml.forwardIfCurrent('[','=',']')) {
return new BIFCall(LITERAL_ORDERED_STRUCT,new Ref[0]);
}
Ref[] args = functionArg(flf.getName(), false, flf,end);
if(args!=null && args.length>0 && flf==LITERAL_ARRAY) {
if(args[0] instanceof LFunctionValue) {
for(int i=1;i<args.length;i++) {
if(!(args[i] instanceof LFunctionValue))
throw new TemplateException("invalid argument for literal ordered struct, only named arguments are allowed like {name:\"value\",name2:\"value2\"}");
}
flf=LITERAL_ORDERED_STRUCT;
}
else {
for(int i=1;i<args.length;i++) {
if(args[i] instanceof LFunctionValue)
throw new TemplateException("invalid argument for literal array, no named arguments are allowed");
}
}
}
return new BIFCall(flf,args);
}
/**
* Transfomiert einen lierale Zeichenkette.
* <br />
* EBNF:<br />
* <code>("'" {"##"|"''"|"#" impOp "#"| ?-"#"-"'" } "'") |
(""" {"##"|""""|"#" impOp "#"| ?-"#"-""" } """);</code>
* @return CFXD Element
* @throws PageException
*/
protected Ref string() throws PageException {
// Init Parameter
char quoter = cfml.getCurrentLower();
LStringBuffer str=new LStringBuffer();
Ref value=null;
while(cfml.hasNext()) {
cfml.next();
// check sharp
if(!limited && cfml.isCurrent('#')) {
if(cfml.isNext('#')){
cfml.next();
str.append('#');
}
else {
cfml.next();
cfml.removeSpace();
if(!str.isEmpty() || value!=null) str.append(assignOp());
else value=assignOp();
cfml.removeSpace();
if (!cfml.isCurrent('#')) throw new InterpreterException("Invalid Syntax Closing [#] not found");
}
}
else if(cfml.isCurrent(quoter)) {
if(cfml.isNext(quoter)){
cfml.next();
str.append(quoter);
}
else {
break;
}
}
// all other character
else {
str.append(cfml.getCurrent());
}
}
if(!cfml.forwardIfCurrent(quoter))
throw new InterpreterException("Invalid String Literal Syntax Closing ["+quoter+"] not found");
cfml.removeSpace();
mode=STATIC;
if(value!=null) {
if(str.isEmpty()) return value;
return new Concat(value,str,limited);
}
return str;
}
/**
* 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 PageException
*/
private Ref number() throws PageException {
// check first character is a number literal representation
StringBuilder rtn=new StringBuilder(6);
// get digit on the left site of the dot
if(cfml.isCurrent('.')) rtn.append('0');
else digit(rtn);
// read dot if exist
if(cfml.forwardIfCurrent('.')) {
rtn.append('.');
int before=cfml.getPos();
digit(rtn);
if(before<cfml.getPos() && cfml.forwardIfCurrent('e')) {
Boolean expOp=null;
if(cfml.forwardIfCurrent('+')) expOp=Boolean.TRUE;
else if(cfml.forwardIfCurrent('-')) expOp=Boolean.FALSE;
if(cfml.isCurrentDigit()) {
if(expOp==Boolean.FALSE) rtn.append("e-");
else if(expOp==Boolean.TRUE) rtn.append("e+");
else rtn.append('e');
digit(rtn);
}
else {
if(expOp!=null) cfml.previous();
cfml.previous();
}
}
// read right side of the dot
if(before==cfml.getPos())
throw new InterpreterException("Number can't end with [.]");
}
cfml.removeSpace();
mode=STATIC;
return new LNumber(rtn.toString());
}
/**
* Liest die reinen Zahlen innerhalb des CFMLString aus und gibt diese als Zeichenkette zurueck.
* <br />
* EBNF:<br />
* <code>"0"|..|"9";</code>
* @param rtn
*/
private void digit(StringBuilder rtn) {
while (cfml.isValidIndex()) {
if(!cfml.isCurrentDigit())break;
rtn.append(cfml.getCurrentLower());
cfml.next();
}
}
/**
* 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 PageException
*/
private Ref dynamic() throws PageException {
// get First Element of the Variable
int pos = cfml.getPos();
String name = identifier(false);
if(name == null) {
if (!cfml.forwardIfCurrent('('))return null;
cfml.removeSpace();
Ref ref = assignOp();
if (!cfml.forwardIfCurrent(')'))
throw new InterpreterException("Invalid Syntax Closing [)] not found");
cfml.removeSpace();
return limited?ref:subDynamic(ref);
}
cfml.removeSpace();
// Boolean constant
if(name.equalsIgnoreCase("TRUE")) {
cfml.removeSpace();
return LBoolean.TRUE;
}
else if(name.equalsIgnoreCase("FALSE")) {
cfml.removeSpace();
return LBoolean.FALSE;
}
else if(!isJson && name.equalsIgnoreCase("YES")) {
cfml.removeSpace();
return LBoolean.TRUE;
}
else if(!isJson && name.equalsIgnoreCase("NO")){
cfml.removeSpace();
return LBoolean.FALSE;
}
else if(allowNullConstant && name.equalsIgnoreCase("NULL")){
cfml.removeSpace();
return new LString(null);
}
else if(!limited && name.equalsIgnoreCase("NEW")){
Ref res = newOp();
if(res!=null) return res;
}
return limited?startElement(name):subDynamic(startElement(name));
}
private Ref subDynamic(Ref ref) throws PageException {
String name=null;
// Loop over nested Variables
while (cfml.isValidIndex()) {
// .
if (cfml.forwardIfCurrent('.')) {
// Extract next Var String
cfml.removeSpace();
name = identifier(true);
if(name==null) throw new InterpreterException("Invalid identifier");
cfml.removeSpace();
ref=new Variable(ref,name,limited);
}
// []
else if (cfml.forwardIfCurrent('[')) {
cfml.removeSpace();
ref=new Variable(ref,assignOp(),limited);
cfml.removeSpace();
if (!cfml.forwardIfCurrent(']'))
throw new InterpreterException("Invalid Syntax Closing []] not found");
}
// finish
else {
break;
}
cfml.removeSpace();
if (cfml.isCurrent('(')) {
if(!(ref instanceof Set)) throw new InterpreterException("invalid syntax "+ref.getTypeName()+" can't called as function");
Set set=(Set) ref;
ref=new UDFCall(set.getParent(pc),set.getKey(pc),functionArg(name,false, null,')'));
}
}
if(ref instanceof lucee.runtime.interpreter.ref.var.Scope) {
lucee.runtime.interpreter.ref.var.Scope s=(lucee.runtime.interpreter.ref.var.Scope)ref;
if(s.getScope()==Scope.SCOPE_ARGUMENTS || s.getScope()==Scope.SCOPE_LOCAL || s.getScope()==ScopeSupport.SCOPE_VAR) {
ref=new Bind(s);
}
}
return ref;
}
/**
* 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 PageException
*/
private Ref startElement(String name) throws PageException {
// check function
if (!limited && cfml.isCurrent('(')) {
FunctionLibFunction function = fld.getFunction(name);
Ref[] arguments = functionArg(name,true, function,')');
if(function!=null) return new BIFCall(function,arguments);
Ref ref = new lucee.runtime.interpreter.ref.var.Scope(Scope.SCOPE_UNDEFINED);
return new UDFCall(ref,name,arguments);
}
//check scope
return scope(name);
}
private Ref newOp() throws PageException {
int start=cfml.getPos();
String name=null;
cfml.removeSpace();
// first identifier
name = identifier(true);
Ref refName=null;
if(name!=null) {
StringBuilder fullName=new StringBuilder();
fullName.append(name);
// Loop over addional identifier
while (cfml.isValidIndex()) {
if (cfml.forwardIfCurrent('.')) {
cfml.removeSpace();
name = identifier(true);
if(name==null) throw new InterpreterException("invalid Component declaration");
cfml.removeSpace();
fullName.append('.');
fullName.append(name);
}
else break;
}
refName=new LString(fullName.toString());
}
else {
if(cfml.isCurrentQuoter())refName=string();
if(refName==null){
cfml.setPos(start);
return null;
}
}
cfml.removeSpace();
if (cfml.isCurrent('(')) {
FunctionLibFunction function = fld.getFunction("_createComponent");
Ref[] arguments = functionArg("_createComponent",true, function,')');
Ref[] args=new Ref[arguments.length+1];
for(int i=0;i<arguments.length;i++){
args[i]=arguments[i];
}
args[args.length-1]=refName;
BIFCall bif = new BIFCall(function,args);
cfml.removeSpace();
return bif;
}
throw new InterpreterException("invalid Component declaration ");
}
/**
* 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 idStr String identifier,
* wird aus Optimierungszwechen nicht innerhalb dieser Funktion ausgelsen.
* @return CFXD Variable Element oder null
*/
private Ref scope(String idStr) {
if (!limited && idStr.equals("var")) {
String name=identifier(false);
if(name!=null){
cfml.removeSpace();
return new Variable(new lucee.runtime.interpreter.ref.var.Scope(ScopeSupport.SCOPE_VAR),name,limited);
}
}
int scope = limited?Scope.SCOPE_UNDEFINED:VariableInterpreter.scopeString2Int(pc!=null && pc.ignoreScopes(),idStr);
if(scope==Scope.SCOPE_UNDEFINED) {
return new Variable(new lucee.runtime.interpreter.ref.var.Scope(Scope.SCOPE_UNDEFINED),idStr,limited);
}
return new lucee.runtime.interpreter.ref.var.Scope(scope);
}
/**
* Liest einen Identifier aus und gibt diesen als String zurueck.
* <br />
* EBNF:<br />
* <code>(letter | "_") {letter | "_"|digit};</code>
* @param firstCanBeNumber
* @return Identifier.
*/
private String identifier(boolean firstCanBeNumber) {
if(!cfml.isCurrentLetter() && !cfml.isCurrentSpecial()) {
if(!firstCanBeNumber)return null;
else if(!cfml.isCurrentDigit())return null;
}
boolean doUpper;
PageSource ps = pc==null?null:pc.getCurrentPageSource();
if(ps!=null) doUpper= !isJson && ps.getDialect()==CFMLEngine.DIALECT_CFML && ((MappingImpl)ps.getMapping()).getDotNotationUpperCase();
else doUpper = !isJson && ((ConfigWebImpl)config).getDotNotationUpperCase(); // MUST .lucee should not be upper case
StringBuilder sb=new StringBuilder();
sb.append(doUpper?cfml.getCurrentUpper():cfml.getCurrent());
do {
cfml.next();
if(!(cfml.isCurrentLetter()
|| cfml.isCurrentDigit()
|| cfml.isCurrentSpecial())) {
break;
}
sb.append(doUpper?cfml.getCurrentUpper():cfml.getCurrent());
}
while (cfml.isValidIndex());
return sb.toString();//cfml.substringLower(start,cfml.getPos()-start);
}
/**
* 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.
* @param flf FLD Function definition .
* @return CFXD Element
* @throws PageException
*/
private Ref[] functionArg(String name,boolean checkLibrary,FunctionLibFunction flf,char end) throws PageException {
// get Function Library
checkLibrary=checkLibrary && flf!=null;
// Function Attributes
List<Ref> arr = new ArrayList<Ref>();
List<FunctionLibFunctionArg> arrFuncLibAtt = null;
int libLen = 0;
if (checkLibrary) {
arrFuncLibAtt = flf.getArg();
libLen = arrFuncLibAtt.size();
}
int count = 0;
Ref ref;
do {
cfml.next();
cfml.removeSpace();
// finish
if (cfml.isCurrent(end))
break;
// too many Attributes
boolean isDynamic=false;
int max=-1;
if(checkLibrary) {
isDynamic=isDynamic(flf);
max=flf.getArgMax();
// Dynamic
if(isDynamic) {
if(max!=-1 && max <= count)
throw new InterpreterException("too many Attributes in function [" + name + "]");
}
// Fix
else {
if(libLen <= count)
throw new InterpreterException("too many Attributes in function [" + name + "]");
}
}
if (checkLibrary && !isDynamic) {
// current attribues from library
FunctionLibFunctionArg funcLibAtt = (FunctionLibFunctionArg) arrFuncLibAtt.get(count);
short type=CFTypes.toShort(funcLibAtt.getTypeAsString(),false,CFTypes.TYPE_UNKNOW);
if(type==CFTypes.TYPE_VARIABLE_STRING) {
arr.add(functionArgDeclarationVarString());
}
else {
ref = functionArgDeclaration();
arr.add(new Casting(funcLibAtt.getTypeAsString(),type,ref));
}
}
else {
arr.add(functionArgDeclaration());
}
cfml.removeSpace();
count++;
}
while (cfml.isCurrent(','));
// end with ) ??
if (!cfml.forwardIfCurrent(end)) {
if(name.startsWith("_json")) throw new InterpreterException("Invalid Syntax Closing ["+end+"] not found");
throw new InterpreterException("Invalid Syntax Closing ["+end+"] for function ["+ name + "] not found");
}
// check min attributes
if (checkLibrary && flf.getArgMin() > count)
throw new InterpreterException("to less Attributes in function [" + name + "]");
cfml.removeSpace();
return (Ref[]) arr.toArray(new Ref[arr.size()]);
}
private boolean isDynamic(FunctionLibFunction flf) {
return flf.getArgType()==FunctionLibFunction.ARG_DYNAMIC;
}
/**
* 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 PageException
*/
private Ref sharp() throws PageException {
if(!cfml.forwardIfCurrent('#'))
return null;
Ref ref;
cfml.removeSpace();
ref = assignOp();
cfml.removeSpace();
if (!cfml.forwardIfCurrent('#'))
throw new InterpreterException("Syntax Error, Invalid Construct");
cfml.removeSpace();
return ref;
}
}