/**
   * 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 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();
 }
  /**
   * 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 ExpressionException("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 ExpressionException(
          "Invalid String Literal Syntax Closing [" + quoter + "] not found");

    cfml.removeSpace();
    mode = STATIC;
    if (value != null) {
      if (str.isEmpty()) return value;
      return new Concat(pc, 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;

    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());
  }
  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);
  }
  /**
   * 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;
  }
  /**
   * 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);
  }
  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 ExpressionException("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(pc, function, args);
      cfml.removeSpace();
      return bif;
    }
    throw new ExpressionException("invalid Component declaration ");
  }
  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;
  }
  /**
   * Hier werden die verschiedenen M￶glichen 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
    throw new ExpressionException(
        "Syntax Error, Invalid Construct",
        "at position " + cfml.getPos() + " in [" + cfml.toString() + "]");
  }
 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;
 }
  /**
   * <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;
  }
  /**
   * 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;
    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 ExpressionException("too many Attributes in function [" + name + "]");
        }
        // Fix
        else {
          if (libLen <= count)
            throw new ExpressionException("too many Attributes in function [" + name + "]");
        }
      }

      if (checkLibrary && !isDynamic) {
        // current attribues from library
        FunctionLibFunctionArg funcLibAtt = (FunctionLibFunctionArg) arrFuncLibAtt.get(count);
        short type = CFTypes.toShort(funcLibAtt.getType(), CFTypes.TYPE_UNKNOW);
        if (type == CFTypes.TYPE_VARIABLE_STRING) {
          arr.add(functionArgDeclarationVarString());
        } else {
          arr.add(new Casting(pc, funcLibAtt.getTypeAsString(), type, functionArgDeclaration()));
        }
      } else {
        arr.add(functionArgDeclaration());
      }

      // obj=andOrXor();
      cfml.removeSpace();
      count++;
    } while (cfml.isCurrent(','));

    // end with ) ??
    if (!cfml.forwardIfCurrent(end)) {
      if (name.startsWith("_json"))
        throw new ExpressionException("Invalid Syntax Closing [" + end + "] not found");
      throw new ExpressionException(
          "Invalid Syntax Closing [" + end + "] for function [" + name + "] not found");
    }

    // check min attributes
    if (checkLibrary && flf.getArgMin() > count)
      throw new ExpressionException("to less Attributes in function [" + name + "]");

    cfml.removeSpace();
    return (Ref[]) arr.toArray(new Ref[arr.size()]);
  }