content: /**
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   *   http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing,
   * software distributed under the License is distributed on an
   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   * KIND, either express or implied.  See the License for the
   * specific language governing permissions and limitations
   * under the License.
   */
  package org.apache.weex.el.parse;
  
  import org.apache.weex.WXEnvironment;
  import org.apache.weex.utils.WXLogUtils;
  
  import java.util.ArrayList;
  import java.util.List;
  
  
  /**
   * Created by furture on 2017/8/28.
   * simple expression parse, less ast node and save memory
   */
  public class Parser {
  
      private String code;
      private int position;
      private ArrayStack<Token> stacks;
      private ArrayStack<Symbol> operators;
  
      public Parser(String code){
          this.code = code;
          this.position = 0;
          this.stacks = new ArrayStack<>();
          this.operators = new ArrayStack<>();
      }
  
  
      public final Token parse(){
          while (hasNextToken()){
              scanNextToken();
          }
          while (!operators.isEmpty()){
              Symbol op = operators.pop();
              doOperator(op);
          }
          if(stacks.size() == 1){
              return  stacks.pop();
          }
          Block block = new Block(stacks.getList(), Token.TYPE_BLOCK);
          return block;
      }
  
  
      /**
       * parse code to ast block.
       * */
      public static Token parse(String code){
          try{
              Parser parser = new Parser(code);
              return parser.parse();
          }catch (Exception e){
              if(WXEnvironment.isApkDebugable()){
                  WXLogUtils.e("code " + code, e);
              }
              return new Block(null, Token.TYPE_BLOCK);
          }
      }
  
  
      final char scanNextToken(){
          char ch = nextToken();
          if(ch == Operators.DOLLAR){
              position++;
              return ch;
          }else if(Character.isJavaIdentifierStart(ch)){
              scanIdentifier();
          }else if (ch == Operators.BRACKET_START || ch == Operators.BLOCK_START) {
              scanBracket();
          }else if (ch == Operators.ARRAY_START) {
              scanArray();
          }else if (ch ==  Operators.QUOTE || ch == Operators.SINGLE_QUOTE) {
              scanString();
          }else if((ch == Operators.DOT && Character.isDigit(code.charAt(position + 1)))
                  || Character.isDigit(ch)){ //number .00 .00e6
              scanNumber();
          }else if(ch ==  Operators.CONDITION_IF){
              scanIf();
          }else if(ch ==  Operators.CONDITION_IF_MIDDLE
                  || ch ==  Operators.BRACKET_END
                  || ch == Operators.BLOCK_END
                  || ch == Operators.SPACE
                  || ch == Operators.ARRAY_END){
              position++;
              return ch;
          }else{
              scanOperator();
          }
          return ch;
      }
  
  
      final void  scanArray(){
          int stackSize = stacks.size();
          int opSize = operators.size();
          int type = Token.TYPE_IDENTIFIER;
          if(position - 1 < 0 || !Character.isJavaIdentifierPart(code.charAt(position - 1))){
              type = Token.TYPE_ARRAY;
          }
          operators.push(new Symbol(Operators.ARRAY_START_STR, stacks.size()));
          position++;
          while (hasNextToken()){
              char token = scanNextToken();
              if(token == Operators.ARRAY_END){
                  break;
              }
          }
  
          if(stacks.size() <= stackSize){ // empty bracket, none need, save memory
              while (operators.size() > opSize){
                  operators.pop();
              }
              return;
          }
  
          while (operators.size() > opSize){
              Symbol op = operators.pop();
              if(stacks.size() > stackSize){
                  doOperator(op);
              }
          }
          List<Token> tokens = new ArrayList<>(4);
          for(int i=stackSize; i<stacks.size(); i++){
              tokens.add(stacks.get(i));
          }
          while (stacks.size() > stackSize){
              stacks.pop();
          }
          if(type == Token.TYPE_ARRAY || stacks.size() == 0){
              Block block = new Block(tokens, Token.TYPE_ARRAY);
              stacks.push(block);
              return;
          }
  
          Token identifer = stacks.pop();
          Token second = null;
          if(tokens.size() == 1){
              second = tokens.get(0);
          }else{
              second = new Block(tokens, Token.TYPE_BLOCK);
          }
          Operator operator = new Operator(Operators.DOT_STR, type);
          operator.first = identifer;
          operator.second = second;
          stacks.push(operator);
      }
  
      void  scanBracket(){
          int stackSize = stacks.size();
          int opSize = operators.size();
          if(code.charAt(position) == Operators.BLOCK_START){
              operators.push(new Symbol(Operators.BLOCK_START_STR, stacks.size()));
              position++;
              while (hasNextToken()){
                  if(scanNextToken() == Operators.BLOCK_END){
                      break;
                  }
              }
          }else{
              operators.push(new Symbol(Operators.BRACKET_START_STR, stacks.size()));
              position++;
              while (hasNextToken()){
                  if(scanNextToken() == Operators.BRACKET_END){
                      break;
                  }
              }
          }
          if(stacks.size() <= stackSize){ // empty bracket, none need, save memory
              while (operators.size() > opSize){
                  operators.pop();
              }
              return;
          }
          while (operators.size() > opSize){
              Symbol op = operators.pop();
              if(stacks.size() > stackSize){
                  doOperator(op);
              }
          }
          List<Token> tokens = new ArrayList<>(4);
          for(int i=stackSize; i<stacks.size(); i++){
              tokens.add(stacks.get(i));
          }
          while (stacks.size() > stackSize){
              stacks.pop();
          }
          if(tokens.size() == 1){
              stacks.push(tokens.get(0));
          }else{
              Block block = new Block(tokens, Token.TYPE_BLOCK);
              stacks.push(block);
          }
      }
  
      /**
       * 1  === (1 + 3) &&  1
       * scan operator
       * */
      void scanOperator(){
          int start = position;
          int length = Math.min(position + 3, code.length());
          String operator = code.substring(position, length);
          if(operator.length() >= 3){
              if(!Operators.OPERATORS_PRIORITY.containsKey(operator)){
                  operator = operator.substring(0, 2);
              }
          }
          if(operator.length() >= 2){
              if(!Operators.OPERATORS_PRIORITY.containsKey(operator)){
                  operator = operator.substring(0, 1);
              }
          }
          if(!Operators.OPERATORS_PRIORITY.containsKey(operator)){
              //just skip illegal character
              int illegalChar = Math.min(start + 1, code.length());
              WXLogUtils.e("weex", new IllegalArgumentException(code.substring(0, illegalChar) + " illegal code operator" + operator));
              position += operator.length();
              return;
          }
          if((!operators.isEmpty() && operators.peek() != null)){
              String preOp = operators.peek().op;
              if(Operators.OPERATORS_PRIORITY.get(preOp) >= Operators.OPERATORS_PRIORITY.get(operator)){
                  Symbol op = operators.pop();
                  doOperator(op);
              }
          }
          if(!Operators.isOpEnd(operator)){
              operators.push(new Symbol(operator, stacks.size()));
          }
          position += operator.length();
      }
  
  
  
      void doOperator(Symbol symbol){
          String op = symbol.op;
          if(Operators.BRACKET_START_STR.equals(symbol.op)
                  || Operators.BLOCK_START_STR.equals(symbol.op)
                  || Operators.ARRAY_START_STR.equals(symbol.op)
                  || Operators.DOLLAR_STR.equals(symbol.op)){
              return;
          }
          if(Operators.BLOCK_START_STR.equals(symbol.op)){
              return;
          }
          int second = symbol.pos;
          int first  = Math.max(symbol.pos - 1, 0);
          if(!operators.isEmpty()){
              first = Math.max(first, operators.peek().pos);
          }
  
          Operator operator = new Operator(op, Token.TYPE_OPERATOR);
          if(Operators.AND_NOT.equals(op)){
              if(stacks.size() > second) {
                  Token token = stacks.remove(second);
                  operator.self = token;
                  stacks.add(second, operator);
                  return;
              }
              return; //invalid
          }
          if(stacks.size() > second) {
              operator.second = stacks.remove(second);
          }else{
              return;
          }
          if(stacks.size() > first) {
              operator.first = stacks.remove(first);
          }else{
              if(operator.second == null){
                  return;
              }
          }
          stacks.add(first, operator);
      }
  
      /**
       * condition if
       * */
      void scanIf(){
          Operator operator = new Operator(Operators.CONDITION_IF_STRING, Token.TYPE_OPERATOR);
          int selfIndex = 0;
          doStackOperators(0);
          if(operators.size() > 0){
              selfIndex = Math.max(operators.peek().pos, selfIndex);
          }
          if(stacks.size() > selfIndex){
              operator.self = stacks.pop();
          }
  
          int stackSize = stacks.size();
          int leftOperatorSize = operators.size();
          position++;
          while (hasNextToken()){
              if(scanNextToken() == Operators.CONDITION_IF_MIDDLE){
                  break;
              }
          }
          while (operators.size() > leftOperatorSize){
              Symbol symbol = operators.pop();
              doOperator(symbol);
          }
  
          while (stacks.size() > stackSize){
              operator.first = stacks.pop();
          }
          int rightOperatorsSize = operators.size();
          while (hasNextToken()){
              scanNextToken();
              if(hasNextToken()){
                  scanNextToken();
              }
              if(operators.size() <= rightOperatorsSize){
                  break;
              }
          }
          doStackOperators(rightOperatorsSize);
          while (stacks.size() > stackSize){
              operator.second = stacks.pop();
          }
          stacks.push(operator);
      }
  
      private final void doStackOperators(int operatorSize){
          while (operators.size() > operatorSize){
              Symbol symbol = operators.pop();
              doOperator(symbol);
          }
      }
  
      /**
       * 1+e6
       * .00
       * .00e6
       * 100
       * 199e5
       * */
      final void scanNumber(){
          boolean isInt = true;
          int start = position;
          if(code.charAt(position) == 'e' || code.charAt(position) == '.'){
              isInt = false;
          }
          position++;
          while (hasNext()){
              char ch = code.charAt(position);
              if(Character.isDigit(ch)
                      || ch == '.'
                      || ch =='e'){
                  if(ch == 'e'
                          || ch == '.'){
                      isInt = false;
                  }
                  position++;
              }else{
                  break;
              }
          }
          String number = code.substring(start, position);
          if(".".equals(number)){
              return;
          }
          Token stack = null;
          if(isInt){
              stack = new Token(number, Token.TYPE_INT);
          }else{
              stack = new Token(number, Token.TYPE_DOUBLE);
          }
          stacks.push(stack);
      }
  
  
      final void  scanString(){
          int start = position;
          ArrayStack operator = new ArrayStack();
          char quote = code.charAt(start);
          operator.push(quote);
          StringBuilder builder = new StringBuilder();
          for(position= start + 1; position<code.length(); position++){
              char ch = code.charAt(position);
              if(ch == quote){
                  if(code.charAt(position -1) != '\\'){
                      operator.pop();
                      if(operator.size() == 0){
                         position++;
                         break;
                      }
                  }else{
                      builder.deleteCharAt(builder.length()-1);
                      builder.append(ch);
                  }
              }else{
                  builder.append(ch);
              }
          }
          String string =  builder.toString();
          Token token = new Token(string, Token.TYPE_STRING);
          stacks.push(token);
      }
  
  
  
      /**
       * scan el expression.
       * item.ddd
       * ${item.dd}
       * $item.dd
       * */
      final void scanIdentifier(){
          int start = position;
          position++;
          while (hasNext()){
              char ch = code.charAt(position);
              if(Character.isJavaIdentifierPart(ch)){
                 position++;
              }else{
                  break;
              }
              //2.true
          }
          String el = code.substring(start, position);
          if(el.startsWith(Operators.DOLLAR_STR)){
              if(el.length() == Operators.DOLLAR_STR.length()){
                  return;
              }
              el = el.substring(Operators.DOLLAR_STR.length());
          }
          int type = Token.TYPE_IDENTIFIER;
          if(Operators.KEYWORDS.containsKey(el)){
              if(!(!operators.isEmpty() && Operators.isDot(operators.peek().op))){
                  type = Token.TYPE_KEYWORD;
              }
          }
          Token token = new Token(el, type);
          stacks.push(token);
      }
  
  
      final boolean hasNext(){
          return position < code.length();
      }
  
  
  
      final boolean hasNextToken(){
          while (hasNext()){
              char ch = code.charAt(position);
              if(ch == ' '){
                  position++;
                  continue;
              }
              return  true;
          }
          return false;
      }
  
      final char nextToken(){
          char ch = code.charAt(position);
          while (ch == ' '){
              position ++;
              if(code.length() <= position){
                  break;
              }
              ch = code.charAt(position);
          }
          return ch;
      }
  }
  