package railo.runtime.interpreter;
import java.util.ArrayList;
import java.util.Map;
import org.apache.commons.collections.map.ReferenceMap;
import railo.commons.lang.CFTypes;
import railo.commons.lang.ParserString;
import railo.runtime.PageContext;
import railo.runtime.config.ConfigImpl;
import railo.runtime.config.ConfigWebImpl;
import railo.runtime.engine.ThreadLocalPageContext;
import railo.runtime.exp.PageException;
import railo.runtime.interpreter.ref.Ref;
import railo.runtime.interpreter.ref.Set;
import railo.runtime.interpreter.ref.cast.Casting;
import railo.runtime.interpreter.ref.func.BIFCall;
import railo.runtime.interpreter.ref.func.UDFCall;
import railo.runtime.interpreter.ref.literal.LBoolean;
import railo.runtime.interpreter.ref.literal.LFunctionValue;
import railo.runtime.interpreter.ref.literal.LNumber;
import railo.runtime.interpreter.ref.literal.LString;
import railo.runtime.interpreter.ref.literal.LStringBuffer;
import railo.runtime.interpreter.ref.literal.Literal;
import railo.runtime.interpreter.ref.op.And;
import railo.runtime.interpreter.ref.op.BigDiv;
import railo.runtime.interpreter.ref.op.BigIntDiv;
import railo.runtime.interpreter.ref.op.BigMinus;
import railo.runtime.interpreter.ref.op.BigMod;
import railo.runtime.interpreter.ref.op.BigMulti;
import railo.runtime.interpreter.ref.op.BigPlus;
import railo.runtime.interpreter.ref.op.CT;
import railo.runtime.interpreter.ref.op.Concat;
import railo.runtime.interpreter.ref.op.Cont;
import railo.runtime.interpreter.ref.op.Div;
import railo.runtime.interpreter.ref.op.EEQ;
import railo.runtime.interpreter.ref.op.EQ;
import railo.runtime.interpreter.ref.op.EQV;
import railo.runtime.interpreter.ref.op.Elvis;
import railo.runtime.interpreter.ref.op.Exp;
import railo.runtime.interpreter.ref.op.GT;
import railo.runtime.interpreter.ref.op.GTE;
import railo.runtime.interpreter.ref.op.Imp;
import railo.runtime.interpreter.ref.op.IntDiv;
import railo.runtime.interpreter.ref.op.LT;
import railo.runtime.interpreter.ref.op.LTE;
import railo.runtime.interpreter.ref.op.Minus;
import railo.runtime.interpreter.ref.op.Mod;
import railo.runtime.interpreter.ref.op.Multi;
import railo.runtime.interpreter.ref.op.NCT;
import railo.runtime.interpreter.ref.op.NEEQ;
import railo.runtime.interpreter.ref.op.NEQ;
import railo.runtime.interpreter.ref.op.Negate;
import railo.runtime.interpreter.ref.op.Not;
import railo.runtime.interpreter.ref.op.Or;
import railo.runtime.interpreter.ref.op.Plus;
import railo.runtime.interpreter.ref.op.Xor;
import railo.runtime.interpreter.ref.var.Assign;
import railo.runtime.interpreter.ref.var.Bind;
import railo.runtime.interpreter.ref.var.DynAssign;
import railo.runtime.interpreter.ref.var.Variable;
import railo.runtime.type.scope.Scope;
import railo.runtime.type.scope.ScopeSupport;
import railo.transformer.library.function.FunctionLib;
import railo.transformer.library.function.FunctionLibFunction;
import railo.transformer.library.function.FunctionLibFunctionArg;
/**
*
*
Der CFMLExprTransfomer implementiert das Interface ExprTransfomer,
er bildet die Parser Grammatik ab, die unten definiert ist.
Er erh¦lt als Eingabe CFML Code, als String oder CFMLString,
der einen CFML Expression erh¦lt und liefert ein CFXD Element zurck,
das diesen Ausdruck abbildet.
Mithilfe der FunctionLibメs, kann er Funktionsaufrufe,
die Teil eines Ausdruck sein knnen, 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 bernommen,
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 JSON_ARRAY = null;
private static FunctionLibFunction JSON_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 boolean isJson;
private final static Map<String,Ref> data=new ReferenceMap(ReferenceMap.SOFT,ReferenceMap.SOFT);
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 {
//Ref ref = data.get(str+":"+preciseMath);
//if(ref!=null)return ref.getValue();
this.cfml=new ParserString(str);
this.preciseMath = preciseMath;
this.pc=ThreadLocalPageContext.get(pc);
if(pc!=null)fld=((ConfigImpl)pc.getConfig()).getCombinedFLDs();
if(JSON_ARRAY==null)JSON_ARRAY=fld.getFunction("_jsonArray");
if(JSON_STRUCT==null)JSON_STRUCT=fld.getFunction("_jsonStruct");
isJson=this instanceof JSONExpressionInterpreter;
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 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;
this.pc=ThreadLocalPageContext.get(pc);
if(pc!=null)fld=((ConfigImpl)pc.getConfig()).getCombinedFLDs();
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();
StringBuffer str=new StringBuffer();
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());
}
else {
ref=new Assign(ref,assignOp());
}
}
return ref;
}
private Ref contOp() throws PageException {
Ref ref = impOp();
while(cfml.forwardIfCurrent('?')) {
cfml.removeSpace();
if(cfml.forwardIfCurrent(':')){
cfml.removeSpace();
Ref right = assignOp();
//if(!(ref instanceof Variable))
// throw new InterpreterException("left operant of the Elvis operator has to be a variable declaration "+ref.getClass().getName());
ref=new Elvis(ref,right);
}
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);
}
}
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());
}
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());
}
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());
}
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());
}
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());
}
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());
}
else if (cfml.forwardIfCurrentAndNoWordAfter("not")) {
cfml.removeSpace();
return new Not(decsionOp());
}
}
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());
hasChanged=true;
}
else if (cfml.forwardIfCurrent("contains")){
cfml.removeSpace();
ref=new CT(ref,concatOp());
hasChanged=true;
}
}
// does not contain
else if (cfml.forwardIfCurrent("does","not","contain")){
cfml.removeSpace();
ref=new NCT(ref,concatOp());
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());
hasChanged=true;
}
// ==
else if (cfml.forwardIfCurrent("==")) {
if(cfml.forwardIfCurrent('=')) {
cfml.removeSpace();
ref = new EEQ(ref,concatOp());
}
else {
cfml.removeSpace();
ref=new EQ(ref,concatOp());
}
hasChanged=true;
}
// !=
else if (cfml.forwardIfCurrent("!=")) {
if(cfml.forwardIfCurrent('=')) {
cfml.removeSpace();
ref = new NEEQ(ref,concatOp());
}
else {
cfml.removeSpace();
ref=new NEQ(ref,concatOp());
}
hasChanged=true;
}
// <=/</<>
else if (cfml.forwardIfCurrent('<')) {
if(cfml.forwardIfCurrent('=')) {
cfml.removeSpace();
ref = new LTE(ref,concatOp());
}
else if(cfml.forwardIfCurrent('>')) {
cfml.removeSpace();
ref = new NEQ(ref,concatOp());
}
else {
cfml.removeSpace();
ref = new LT(ref,concatOp());
}
hasChanged=true;
}
// >/>=
else if (cfml.forwardIfCurrent('>')) {
if(cfml.forwardIfCurrent('=')) {
cfml.removeSpace();
ref = new GTE(ref,concatOp());
}
else {
cfml.removeSpace();
ref = new GT(ref,concatOp());
}
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());
}
else {
cfml.removeSpace();
ref=new GT(ref,concatOp());
}
hasChanged=true;
}
else if (cfml.forwardIfCurrent("greater","than")) {
if(cfml.forwardIfCurrent("or" ,"equal", "to",true)) {
cfml.removeSpace();
ref=new GTE(ref,concatOp());
}
else {
cfml.removeSpace();
ref=new GT(ref,concatOp());
}
hasChanged=true;
}
else if (cfml.forwardIfCurrent("ge")) {
cfml.removeSpace();
ref=new GTE(ref,concatOp());
hasChanged=true;
}
}
// is, is not
else if (cfml.forwardIfCurrent("is")) {
if(cfml.forwardIfCurrent("not",true)) {
cfml.removeSpace();
ref=new NEQ(ref,concatOp());
}
else {
cfml.removeSpace();
ref=new EQ(ref,concatOp());
}
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());
}
else {
cfml.removeSpace();
ref=new LT(ref,concatOp());
}
hasChanged=true;
}
else if (cfml.forwardIfCurrent("less","than")) {
if(cfml.forwardIfCurrent("or", "equal", "to",true)) {
cfml.removeSpace();
ref=new LTE(ref,concatOp());
}
else {
cfml.removeSpace();
ref=new LT(ref,concatOp());
}
hasChanged=true;
}
else if (cfml.forwardIfCurrent("le")) {
cfml.removeSpace();
ref=new LTE(ref,concatOp());
hasChanged=true;
}
}
// neq, not equal, nct
else if (cfml.isCurrent('n')) {
// Not Equal
if (cfml.forwardIfCurrent("neq")) {
cfml.removeSpace();
ref=new NEQ(ref,concatOp());
hasChanged=true;
}
// Not Equal (Alias)
else if (cfml.forwardIfCurrent("not","equal")){
cfml.removeSpace();
ref=new NEQ(ref,concatOp());
hasChanged=true;
}
// nct
else if (cfml.forwardIfCurrent("nct")) {
cfml.removeSpace();
ref=new NCT(ref,concatOp());
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);
//cfml.removeSpace();
//ref=new Concat(pc,ref,plusMinusOp());
}
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);
//cfml.removeSpace();
//ref=new Plus(ref,modOp());
}
// Minus Operation
else if (cfml.forwardIfCurrent('-')) {
ref=_minus(ref);
//cfml.removeSpace();
//ref=new Minus(ref,modOp());
}
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):new Plus(ref,right);
ref=new Assign(ref,res);
}
/*/ ++
else if (cfml.isCurrent('+')) {
cfml.next();
cfml.removeSpace();
Ref res = new Plus(ref,new LNumber(new Double(1)));
ref=new Assign(ref,res);
ref=new Minus(ref,new LNumber(new Double(1)));
}*/
else {
cfml.removeSpace();
ref=preciseMath?new BigPlus(ref,modOp()):new Plus(ref,modOp());
}
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):new Minus(ref,right);
ref=new Assign(ref,res);
}
/*/ --
else if (cfml.isCurrent('-')) {
cfml.next();
cfml.removeSpace();
Ref res = new Minus(ref,new LNumber(new Double(1)));
ref=new Assign(ref,res);
ref=new Plus(ref,new LNumber(new Double(1)));
}*/
else {
cfml.removeSpace();
ref=preciseMath?new BigMinus(ref,modOp()):new Minus(ref,modOp());
}
return ref;
}
private Ref _div(Ref ref) throws PageException {
// /=
if (cfml.forwardIfCurrent('=')) {
cfml.removeSpace();
Ref right = assignOp();
Ref res = preciseMath?new BigDiv(ref, right):new Div(ref,right);
ref=new Assign(ref,res);
}
else {
cfml.removeSpace();
ref=preciseMath?new BigDiv(ref,expoOp()):new Div(ref,expoOp());
}
return ref;
}
private Ref _intdiv(Ref ref) throws PageException {
// \=
if (cfml.forwardIfCurrent('=')) {
cfml.removeSpace();
Ref right = assignOp();
Ref res = preciseMath?new BigIntDiv(ref,right):new IntDiv(ref,right);
ref=new Assign(ref,res);
}
else {
cfml.removeSpace();
ref=preciseMath?new BigIntDiv(ref,expoOp()):new IntDiv(ref,expoOp());
}
return ref;
}
private Ref _mod(Ref ref) throws PageException {
// %=
if (cfml.forwardIfCurrent('=')) {
cfml.removeSpace();
Ref right = assignOp();
Ref res = preciseMath?new BigMod(ref,right):new Mod(ref,right);
ref=new Assign(ref,res);
}
else {
cfml.removeSpace();
ref=preciseMath?new BigMod(ref,divMultiOp()):new Mod(ref,divMultiOp());
}
return ref;
}
private Ref _concat(Ref ref) throws PageException {
// &=
if (cfml.forwardIfCurrent('=')) {
cfml.removeSpace();
Ref right = assignOp();
Ref res = new Concat(ref,right);
ref=new Assign(ref,res);
}
else {
cfml.removeSpace();
ref=new Concat(ref,plusMinusOp());
}
return ref;
}
private Ref _multi(Ref ref) throws PageException {
// \=
if (cfml.forwardIfCurrent('=')) {
cfml.removeSpace();
Ref right = assignOp();
Ref res = preciseMath?new BigMulti(ref,right):new Multi(ref,right);
ref=new Assign(ref,res);
}
else {
cfml.removeSpace();
ref=preciseMath?new BigMulti(ref,expoOp()):new Multi(ref,expoOp());
}
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);
//cfml.removeSpace();
//ref=new Mod(ref,divMultiOp());
}
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);
//cfml.removeSpace();
//ref=new Multi(ref,expoOp());
}
// Divide Operation
else if (cfml.isCurrent('/') && (!cfml.isCurrent("/>") )) {
cfml.next();
ref=_div(ref);
//cfml.removeSpace();
//ref=new Div(ref,expoOp());
}
// Divide Operation
else if (cfml.isCurrent('\\')) {
cfml.next();
ref=_intdiv(ref);
//cfml.removeSpace();
//ref=new IntDiv(ref,expoOp());
}
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());
}
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):new Plus(ref,isPlus?PLUS_ONE:MINUS_ONE);
ref=new Assign(ref,res);
return preciseMath?new BigPlus(ref,isPlus?MINUS_ONE:PLUS_ONE):new Plus(ref,isPlus?MINUS_ONE:PLUS_ONE);
}
/**
* 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))):new Minus(expr,new LNumber(new Double(1)));
return new Assign(expr,res);
}
cfml.removeSpace();
return new Negate(clip());
}
if (cfml.forwardIfCurrent('+')) {
if (cfml.forwardIfCurrent('+')) {
cfml.removeSpace();
Ref expr = clip();
Ref res = preciseMath?new BigPlus(expr,new LNumber(new Double(1))):new Plus(expr,new LNumber(new Double(1)));
return new Assign(expr,res);
}
cfml.removeSpace();
return new Casting("numeric",CFTypes.TYPE_NUMERIC,clip());
}
return clip();
}
/**
* Verarbeitet Ausdrcke 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 Mglichen 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((ref=sharp())!=null) {
mode=DYNAMIC;
return ref;
}
// JSON
if((ref=json(JSON_ARRAY,'[',']'))!=null) {
mode=DYNAMIC;
return ref;
}
if((ref=json(JSON_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 {
//print.out("start:"+start+":"+cfml.getCurrent());
if(!cfml.isCurrent(start))return null;
Ref[] args = functionArg(flf.getName(), false, flf,end);
//if (!cfml.forwardIfCurrent(end))
// throw new InterpreterException("Invalid Syntax Closing ["+end+"] not found");
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();
//String str="";
LStringBuffer str=new LStringBuffer();
Ref value=null;
while(cfml.hasNext()) {
cfml.next();
// check sharp
if(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);
}
return str;
}
/**
* Transfomiert einen numerische Wert.
* Die L¦nge des numerischen Wertes interessiert nicht zu ᅵbersetzungszeit,
* ein "Overflow" fhrt 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
//if(!cfml.isCurrentDigit()) return null;
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 [.]");
//rtn.append(rightSite);
}
cfml.removeSpace();
mode=STATIC;
return new LNumber(rtn.toString());
}
/**
* Liest die reinen Zahlen innerhalb des CFMLString aus und gibt diese als Zeichenkette zurck.
* <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 prft 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 {
// 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
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 subDynamic(ref);
}
//Element el;
cfml.removeSpace();
//char first=name.charAt(0);
// Boolean constant
if(name.equalsIgnoreCase("TRUE")) {
cfml.removeSpace();
return LBoolean.TRUE;
}
else if(name.equalsIgnoreCase("FALSE")) {
cfml.removeSpace();
return LBoolean.FALSE;
}
else if(name.equalsIgnoreCase("YES")) {
cfml.removeSpace();
return LBoolean.TRUE;
}
else if(name.equalsIgnoreCase("NO")){
cfml.removeSpace();
return LBoolean.FALSE;
}
else if(allowNullConstant && name.equalsIgnoreCase("NULL")){
cfml.removeSpace();
return new LString(null);
}
else if(name.equalsIgnoreCase("NEW")){
Ref res = newOp();
if(res!=null) return res;
}
// Extract Scope from the Variable
//Object value = startElement(name);
return 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);
}
// []
else if (cfml.forwardIfCurrent('[')) {
cfml.removeSpace();
ref=new Variable(ref,assignOp());
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 railo.runtime.interpreter.ref.var.Scope) {
railo.runtime.interpreter.ref.var.Scope s=(railo.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 (cfml.isCurrent('(')) {
FunctionLibFunction function = fld.getFunction(name);
Ref[] arguments = functionArg(name,true, function,')');
//print.out(name+":"+(function!=null));
if(function!=null) return new BIFCall(function,arguments);
Ref ref = new railo.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 zurck.
* <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 (idStr.equals("var")) {
String name=identifier(false);
if(name!=null){
cfml.removeSpace();
return new Variable(new railo.runtime.interpreter.ref.var.Scope(ScopeSupport.SCOPE_VAR),name);
}
}
int scope = VariableInterpreter.scopeString2Int(idStr);
if(scope==Scope.SCOPE_UNDEFINED) {
return new Variable(new railo.runtime.interpreter.ref.var.Scope(Scope.SCOPE_UNDEFINED),idStr);
}
return new railo.runtime.interpreter.ref.var.Scope(scope);
}
/**
* Liest einen Identifier aus und gibt diesen als String zurck.
* <br />
* EBNF:<br />
* <code>(letter | "_") {letter | "_"|digit};</code>
* @param firstCanBeNumber
* @return Identifier.
*/
private String identifier(boolean firstCanBeNumber) {
//int start = cfml.getPos();
if(!cfml.isCurrentLetter() && !cfml.isCurrentSpecial()) {
if(!firstCanBeNumber)return null;
else if(!cfml.isCurrentDigit())return null;
}
boolean doUpper = !isJson && ((ConfigWebImpl)pc.getConfig()).getDotNotationUpperCase();
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);
}
/* *
* Transfomiert ein Collection Element das in eckigen Klammern aufgerufen wird.
* <br />
* EBNF:<br />
* <code>"[" impOp "]"</code>
* @return CFXD Element
* @throws PageException
* /
private Ref structElement() throws PageException {
cfml.removeSpace();
Ref ref = new Casting(pc,"string",CFTypes.TYPE_STRING,assignOp());
cfml.removeSpace();
return ref;
}*/
/**
* Liest die Argumente eines Funktonsaufruf ein und prft ob die Funktion
* innerhalb der FLD (Function Library Descriptor) definiert ist.
* Falls sie existiert wird die Funktion gegen diese geprft 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 geprft 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
ArrayList arr = new ArrayList();
ArrayList 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());
}
// obj=andOrXor();
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;
}
/* *
* Wandelt eine variable und ein key in eine reference um
* @param value
* @param name
* @return cast a vlue in a reference
* @throws PageException
* /
private Reference toReference(Object value, String name) {
return NativeReference.getInstance(value, name);
}*/
}