/* Copyright 2011 frank asseg Licensed 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 de.congrace.exp4j; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import de.congrace.exp4j.FunctionToken.Function; /** * Class for tokenizing mathematical expressions by breaking an expression up * into multiple different {@link Token}s * * @author fas@congrace.de */ class Tokenizer { private String[] variableNames; private final Set<String> functionNames = new HashSet<String>(); private final Set<CustomFunction> customFunctions; { functionNames.add("abs"); functionNames.add("acos"); functionNames.add("asin"); functionNames.add("atan"); functionNames.add("cbrt"); functionNames.add("ceil"); functionNames.add("cos"); functionNames.add("cosh"); functionNames.add("exp"); functionNames.add("expm1"); functionNames.add("floor"); functionNames.add("log"); functionNames.add("sin"); functionNames.add("sinh"); functionNames.add("sqrt"); functionNames.add("tan"); functionNames.add("tanh"); } Tokenizer() { super(); customFunctions = null; } /** * construct a new Tokenizer that recognizes variable names * * @param variableNames * the variable names in the expression * @throws IllegalArgumentException * if a variable has the name as a function * @param customFunctions * the CustomFunction implementations used if the variableNames * are not valid */ Tokenizer(String[] variableNames, Set<CustomFunction> customFunctions) throws IllegalArgumentException { super(); this.variableNames = variableNames; if (variableNames != null) { for (String varName : variableNames) { if (functionNames.contains(varName.toLowerCase())) { throw new IllegalArgumentException("Variable '" + varName + "' can not have the same name as a function"); } } } this.customFunctions = customFunctions; } private Token getCustomFunctionToken(String name) throws UnknownFunctionException { for (CustomFunction func : customFunctions) { if (func.getValue().equals(name)) { return func; } } throw new UnknownFunctionException(name); } private boolean isCustomFunction(String name) { if (customFunctions == null) { return false; } for (CustomFunction func : customFunctions) { if (func.getValue().equals(name)) { return true; } } return false; } /** * check if a char is part of a number * * @param c * the char to be checked * @return true if the char is part of a number */ private boolean isDigit(char c) { return Character.isDigit(c) || c == '.'; } private boolean isFunction(String name) { for (Function fn : Function.values()) { if (fn.name().equals(name.toUpperCase())) { return true; } } return false; } /** * check if a String is a variable name * * @param name * the variable name which is checked to be valid the char to be * checked * @return true if the char is a variable name (e.g. x) */ private boolean isVariable(String name) { //String[] variableNames = variables.getVariableNames(); if (variableNames != null) { for (String var : variableNames) { if (name.equals(var)) { return true; } } } return false; } /** * tokenize an infix expression by breaking it up into different * {@link Token} that can represent operations,functions,numbers, * parenthesis or variables * * @param infix * the infix expression to be tokenized * @return the {@link Token}s representing the expression * @throws UnparsableExpressionException * when the expression is invalid * @throws UnknownFunctionException * when an unknown function name has been used. */ Token[] tokenize(String infix) throws UnparsableExpressionException, UnknownFunctionException { final List<Token> tokens = new ArrayList<Token>(); final char[] chars = infix.toCharArray(); // iterate over the chars and fork on different types of input Token lastToken; for (int i = 0; i < chars.length; i++) { char c = chars[i]; if (c == ' ') continue; if (isDigit(c)) { final StringBuilder valueBuilder = new StringBuilder(1); // handle the numbers of the expression valueBuilder.append(c); int numberLen = 1; while (chars.length > i + numberLen && isDigit(chars[i + numberLen])) { valueBuilder.append(chars[i + numberLen]); numberLen++; } i += numberLen - 1; lastToken = new NumberToken(valueBuilder.toString()); } else if (Character.isLetter(c) || c == '_' || c == '$') { // can be a variable or function final StringBuilder nameBuilder = new StringBuilder(); nameBuilder.append(c); int offset = 1; while (chars.length > i + offset && (Character.isLetter(chars[i + offset]) || Character.isDigit(chars[i + offset]) || chars[i + offset] == '_' || chars[i + offset] == '$')) { nameBuilder.append(chars[i + offset++]); } String name = nameBuilder.toString(); if (this.isVariable(name)) { // a variable i += offset - 1; lastToken = new VariableToken(name); } else if (this.isFunction(name)) { // might be a function i += offset - 1; lastToken = new FunctionToken(name); } else if (this.isCustomFunction(name)) { // a custom function i += offset - 1; lastToken = getCustomFunctionToken(name); } else { // an unknown symbol was encountered throw new UnparsableExpressionException(c, i); } }else if (c == ',') { // a function separator, hopefully lastToken=new FunctionSeparatorToken(); } else if (OperatorToken.isOperator(c)) { lastToken = new OperatorToken(String.valueOf(c), OperatorToken.getOperation(c)); } else if (c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}') { lastToken = new ParenthesisToken(String.valueOf(c)); } else { // an unknown symbol was encountered throw new UnparsableExpressionException(c, i); } tokens.add(lastToken); } return tokens.toArray(new Token[tokens.size()]); } }