blob: 4ee5a9d8f354b831265633c6fe85c13a77158666 [file] [log] [blame]
/**
* 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 com.taobao.weex.el.parse;
import com.taobao.weex.WXEnvironment;
import com.taobao.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;
}
code.charAt(position);
}
return ch;
}
}