blob: ba9103ea3f2738f49863c5756802994ee1d7e149 [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.utils;
import android.support.annotation.NonNull;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* Parser for function like "rotate(30 ) transform(50 , 20)".
* This class will translate the raw string presentation of a group of function(s) to give type
* according to the {@link com.taobao.weex.utils.FunctionParser.Mapper}
*/
public class FunctionParser<K, V> {
public static final char SPACE = ' ';
private Mapper<K, V> mapper;
private Lexer lexer;
/**
* Construct a function parser
* @param source the raw string representation of a group of function(s)
* @param mapper the mapping rule between string and corresponding type of object.
*/
public FunctionParser(@NonNull String source, @NonNull Mapper<K, V> mapper) {
this.lexer = new Lexer(source);
this.mapper = mapper;
}
/**
* Start to parse the raw string. The result will be stored in a sorted map where the order
* is defined by the function order in the raw string.
* @return
*/
public LinkedHashMap<K, V> parse() {
lexer.moveOn();
return definition();
}
private LinkedHashMap<K, V> definition() {
LinkedHashMap<K, V> result = new LinkedHashMap<>();
do {
result.putAll(function());
} while (lexer.getCurrentToken() == Token.FUNC_NAME);
return result;
}
private Map<K, V> function() {
List<String> list = new LinkedList<>();
String functionName = match(Token.FUNC_NAME);
match(Token.LEFT_PARENT);
list.add(match(Token.PARAM_VALUE));
while (lexer.getCurrentToken() == Token.COMMA) {
match(Token.COMMA);
list.add(match(Token.PARAM_VALUE));
}
match(Token.RIGHT_PARENT);
return mapper.map(functionName, list);
}
private String match(Token token) {
if (token == lexer.getCurrentToken()) {
String value = lexer.getCurrentTokenValue();
lexer.moveOn();
return value;
}
throw new WXInterpretationException("Token doesn't match");
}
private enum Token {
FUNC_NAME, PARAM_VALUE, LEFT_PARENT, RIGHT_PARENT, COMMA
}
public interface Mapper<K, V> {
/**
* Map one function to a specified type of object
* @param functionName the name of the raw function.
* @param raw the list of parameter of the raw function
* @return the expected mapping relationship, where the key in the map is the same as the
* key in the return value of {{@link #parse()}},
* and the value in the map is the type of object that expected by user.
*/
Map<K, V> map(String functionName, List<String> raw);
}
private static class WXInterpretationException extends RuntimeException {
private WXInterpretationException(String msg) {
super(msg);
}
}
/**
* Lexer for the parser. For now, digit, alphabet, '(', ')', '.', ',', '+', '-' and '%' is
* valid character, and '(', ')', ',', parameter value and function name is valid token.
* Parameter value is defined as "(?i)[\+-]?[0-9]+(\.[0-9]+)?(%||deg||px)?" while function
* name is defined as "[a-zA-Z]+".
*
* The Lexer can also be expressed using the following EBNF format.
* <ul>
* <li>definition = {function};</li>
* <li>function = name, "(", value, { ",", value } , ")";</li>
* <li>name = character, {character};</li>
* <li>value = identifier, {identifier};</li>
* <li>identifier = character | "." | "%" | "+" | "-";</li>
* <li>character = digit | letter;</li>
* <li>digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;</li>
* <li>letter = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" |
* "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" | "a" | "b" |
* "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" |
* "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" ;</li>
* </ul>
*/
private static class Lexer {
private static final String LEFT_PARENT = "(";
private static final String RIGHT_PARENT = ")";
private static final String COMMA = ",";
private static final char A_LOWER = 'a';
private static final char Z_LOWER = 'z';
private static final char A_UPPER = 'A';
private static final char Z_UPPER = 'Z';
private static final char ZERO = '0';
private static final char NINE = '9';
private static final char DOT = '.';
private static final char MINUS = '-';
private static final char PLUS = '+';
private String source;
private Token current;
private String value;
private int pointer = 0;
private Lexer(String source) {
this.source = source;
}
private Token getCurrentToken() {
return current;
}
private String getCurrentTokenValue() {
return value;
}
private boolean moveOn() {
int start = pointer;
char curChar;
while (pointer < source.length()) {
curChar = source.charAt(pointer);
if (curChar == SPACE) {
if (start == pointer++) {
start++;
} else {
break;
}
} else if (isCharacterOrDigit(curChar) || curChar == DOT
|| curChar == WXUtils.PERCENT || curChar == MINUS || curChar == PLUS) {
pointer++;
} else {
if (start == pointer) {
pointer++;
}
break;
}
}
if (start != pointer) {
String symbol = source.substring(start, pointer);
moveOn(symbol);
return true;
} else {
current = null;
value = null;
return false;
}
}
private void moveOn(String token) {
if (LEFT_PARENT.equals(token)) {
current = Token.LEFT_PARENT;
value = LEFT_PARENT;
} else if (RIGHT_PARENT.equals(token)) {
current = Token.RIGHT_PARENT;
value = RIGHT_PARENT;
} else if (COMMA.equals(token)) {
current = Token.COMMA;
value = COMMA;
} else if (isFuncName(token)) {
current = Token.FUNC_NAME;
value = token;
} else {
current = Token.PARAM_VALUE;
value = token;
}
}
private boolean isFuncName(CharSequence funcName) {
char letter;
for (int i = 0; i < funcName.length(); i++) {
letter = funcName.charAt(i);
if (!((A_LOWER <= letter && letter <= Z_LOWER) ||
(A_UPPER <= letter && letter <= Z_UPPER) ||
letter == MINUS)) {
return false;
}
}
return true;
}
private boolean isCharacterOrDigit(char letter) {
return (ZERO <= letter && letter <= NINE) || (A_LOWER <= letter && letter <= Z_LOWER) ||
(A_UPPER <= letter && letter <= Z_UPPER);
}
}
}