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;
  }
  /**
   * 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;
    }

    StringBuffer sb = new StringBuffer();
    // if(CASE_TYPE_UPPER==caseType)
    sb.append(cfml.getCurrentUpper());
    /*else if(CASE_TYPE_ORIGINAL==caseType)
    	sb.append(cfml.getCurrent());
    else
    	sb.append(cfml.getCurrentLower());*/
    do {
      cfml.next();
      if (!(cfml.isCurrentLetter() || cfml.isCurrentDigit() || cfml.isCurrentSpecial())) {
        break;
      }

      // if(CASE_TYPE_UPPER==caseType)
      sb.append(cfml.getCurrentUpper());
      /*else if(CASE_TYPE_ORIGINAL==caseType)
      	sb.append(cfml.getCurrent());
      else
      	sb.append(cfml.getCurrentLower());*/

    } while (cfml.isValidIndex());
    return sb.toString(); // cfml.substringLower(start,cfml.getPos()-start);
  }
 /**
  * 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;
 }
  /**
   * 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(StringBuffer rtn) {

    while (cfml.isValidIndex()) {
      if (!cfml.isCurrentDigit()) break;
      rtn.append(cfml.getCurrentLower());
      cfml.next();
    }
  }
 /**
  * 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;
 }
 /**
  * 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 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 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;
  }
  /**
   * 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;

    StringBuffer rtn = new StringBuffer(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')) {
        if (cfml.isCurrentDigit()) {
          rtn.append('e');
          digit(rtn);
        } else {
          cfml.previous();
        }
      }

      // read right side of the dot
      if (before == cfml.getPos()) throw new ExpressionException("Number can't end with [.]");
      // rtn.append(rightSite);
    }
    cfml.removeSpace();
    mode = STATIC;
    return new LNumber(rtn.toString());
  }
  /**
   * 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;
  }
 /**
  * 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 ExpressionException("Syntax Error, Invalid Construct");
   cfml.removeSpace();
   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;
  }
 private Ref _concat(Ref ref) throws PageException {
   // &=
   if (cfml.forwardIfCurrent('=')) {
     cfml.removeSpace();
     Ref right = assignOp();
     Ref res = new Concat(pc, ref, right);
     ref = new Assign(ref, res);
   } else {
     cfml.removeSpace();
     ref = new Concat(pc, ref, plusMinusOp());
   }
   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(pc, ref, assignOp());
      } else {
        ref = new Assign(ref, assignOp());
      }
    }
    return ref;
  }
 private Ref _multi(Ref ref) throws PageException {
   // \=
   if (cfml.forwardIfCurrent('=')) {
     cfml.removeSpace();
     Ref right = assignOp();
     Ref res = new Multi(ref, right);
     ref = new Assign(ref, res);
   } else {
     cfml.removeSpace();
     ref = new Multi(ref, expoOp());
   }
   return ref;
 }
 private Ref contOp() throws PageException {
   Ref ref = impOp();
   while (cfml.forwardIfCurrent('?')) {
     cfml.removeSpace();
     Ref left = assignOp();
     if (!cfml.forwardIfCurrent(':'))
       throw new ExpressionException(
           "Syntax Error, invalid conditional operator [" + cfml.toString() + "]");
     cfml.removeSpace();
     Ref right = assignOp();
     ref = new Cont(ref, left, right);
   }
   return ref;
 }
  /**
   * 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 ExpressionException("Invalid Syntax Closing [)] not found");
      cfml.removeSpace();
      return subDynamic(ref);
    }

    // Element el;
    cfml.removeSpace();
    char first = name.charAt(0);

    // Boolean constant
    if (first == 'T' && name.equals("TRUE")) {
      cfml.removeSpace();
      return LBoolean.TRUE;
    } else if (first == 'F' && name.equals("FALSE")) {
      cfml.removeSpace();
      return LBoolean.FALSE;
    } else if (first == 'Y' && name.equals("YES")) {
      cfml.removeSpace();
      return LBoolean.TRUE;
    } else if (first == 'N') {
      if (name.equals("NO")) {
        cfml.removeSpace();
        return LBoolean.FALSE;
      } else if (allowNullConstant && name.equals("NULL")) {
        cfml.removeSpace();
        return new LString(null);
      } else if (name.equals("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 ExpressionException("Invalid identifier");
        cfml.removeSpace();
        ref = new Variable(pc, ref, name);
      }
      // []
      else if (cfml.forwardIfCurrent('[')) {
        cfml.removeSpace();
        ref = new Variable(pc, ref, assignOp());
        cfml.removeSpace();
        if (!cfml.forwardIfCurrent(']'))
          throw new ExpressionException("Invalid Syntax Closing []] not found");
      }
      // finish
      else {
        break;
      }

      cfml.removeSpace();

      if (cfml.isCurrent('(')) {
        if (!(ref instanceof Set))
          throw new ExpressionException(
              "invalid syntax " + ref.getTypeName() + " can't called as function");
        Set set = (Set) ref;
        ref = new UDFCall(pc, set.getParent(), set.getKey(), 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;
  }
  /**
   * Wird aufgerufen um aus dem bergebenen CFMLString einen Ausdruck auszulesen und diese zu
   * interpretieren. <br>
   * Beispiel eines bergebenen String:<br>
   * <code>session.firstName</code> oder <code>trim(left('test'&var1,3))</code> <br>
   * EBNF:<br>
   * <code>spaces impOp;</code>
   *
   * @param pc
   * @param cfml
   * @return
   * @throws PageException
   */
  public Object interpret(PageContext pc, ParserString cfml) throws PageException {
    this.cfml = cfml;
    this.pc = 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");

    cfml.removeSpace();
    Ref ref = assignOp();
    cfml.removeSpace();

    if (cfml.isAfterLast()) {
      return ref.getValue();
    }
    throw new ExpressionException("Syntax Error, invalid Expression [" + cfml.toString() + "]");
  }
  protected Object interpretPart(PageContext pc, ParserString cfml) throws PageException {
    this.cfml = cfml;
    this.pc = pc;
    if (pc != null) fld = ((ConfigImpl) pc.getConfig()).getCombinedFLDs();

    cfml.removeSpace();
    return assignOp().getValue();
  }
  /**
   * 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 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;
  }
  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 ExpressionException("Invalid Syntax Closing ["+end+"] not found");

    return new BIFCall(pc, flf, args);
  }
 private Ref _minus(Ref ref) throws PageException {
   // -=
   if (cfml.isCurrent('=')) {
     cfml.next();
     cfml.removeSpace();
     Ref right = assignOp();
     Ref res = 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 = new Minus(ref, modOp());
   }
   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();
 }
  /**
   * 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(pc, function, arguments);

      Ref ref = new railo.runtime.interpreter.ref.var.Scope(pc, Scope.SCOPE_UNDEFINED);
      return new UDFCall(pc, ref, name, arguments);
    }
    // check scope
    return scope(name);
  }
 /**
  * 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(
           pc, new railo.runtime.interpreter.ref.var.Scope(pc, ScopeSupport.SCOPE_VAR), name);
     }
   }
   int scope = VariableInterpreter.scopeString2Int(idStr);
   if (scope == Scope.SCOPE_UNDEFINED) {
     return new Variable(
         pc, new railo.runtime.interpreter.ref.var.Scope(pc, Scope.SCOPE_UNDEFINED), idStr);
   }
   return new railo.runtime.interpreter.ref.var.Scope(pc, scope);
 }
  /**
   * 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 ExpressionException("invalid variable name definition");
  }