2 Copyright 2011 frank asseg
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
8 http://www.apache.org/licenses/LICENSE-2.0
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
17 package de.congrace.exp4j;
19 import java.util.ArrayList;
20 import java.util.HashSet;
21 import java.util.List;
24 import de.congrace.exp4j.FunctionToken.Function;
27 * Class for tokenizing mathematical expressions by breaking an expression up
28 * into multiple different {@link Token}s
30 * @author fas@congrace.de
33 private String[] variableNames;
34 private final Set<String> functionNames = new HashSet<String>();
35 private final Set<CustomFunction> customFunctions;
38 functionNames.add("abs");
39 functionNames.add("acos");
40 functionNames.add("asin");
41 functionNames.add("atan");
42 functionNames.add("cbrt");
43 functionNames.add("ceil");
44 functionNames.add("cos");
45 functionNames.add("cosh");
46 functionNames.add("exp");
47 functionNames.add("expm1");
48 functionNames.add("floor");
49 functionNames.add("log");
50 functionNames.add("sin");
51 functionNames.add("sinh");
52 functionNames.add("sqrt");
53 functionNames.add("tan");
54 functionNames.add("tanh");
59 customFunctions = null;
63 * construct a new Tokenizer that recognizes variable names
65 * @param variableNames
66 * the variable names in the expression
67 * @throws IllegalArgumentException
68 * if a variable has the name as a function
69 * @param customFunctions
70 * the CustomFunction implementations used if the variableNames
73 Tokenizer(String[] variableNames, Set<CustomFunction> customFunctions) throws IllegalArgumentException {
75 this.variableNames = variableNames;
77 if (variableNames != null) {
78 for (String varName : variableNames) {
79 if (functionNames.contains(varName.toLowerCase())) {
80 throw new IllegalArgumentException("Variable '" + varName + "' can not have the same name as a function");
84 this.customFunctions = customFunctions;
87 private Token getCustomFunctionToken(String name) throws UnknownFunctionException {
88 for (CustomFunction func : customFunctions) {
89 if (func.getValue().equals(name)) {
93 throw new UnknownFunctionException(name);
96 private boolean isCustomFunction(String name) {
97 if (customFunctions == null) {
100 for (CustomFunction func : customFunctions) {
101 if (func.getValue().equals(name)) {
109 * check if a char is part of a number
112 * the char to be checked
113 * @return true if the char is part of a number
115 private boolean isDigit(char c) {
116 return Character.isDigit(c) || c == '.';
119 private boolean isFunction(String name) {
120 for (Function fn : Function.values()) {
121 if (fn.name().equals(name.toUpperCase())) {
129 * check if a String is a variable name
132 * the variable name which is checked to be valid the char to be
134 * @return true if the char is a variable name (e.g. x)
136 private boolean isVariable(String name) {
137 //String[] variableNames = variables.getVariableNames();
139 if (variableNames != null) {
140 for (String var : variableNames) {
141 if (name.equals(var)) {
150 * tokenize an infix expression by breaking it up into different
151 * {@link Token} that can represent operations,functions,numbers,
152 * parenthesis or variables
155 * the infix expression to be tokenized
156 * @return the {@link Token}s representing the expression
157 * @throws UnparsableExpressionException
158 * when the expression is invalid
159 * @throws UnknownFunctionException
160 * when an unknown function name has been used.
162 Token[] tokenize(String infix) throws UnparsableExpressionException, UnknownFunctionException {
163 final List<Token> tokens = new ArrayList<Token>();
164 final char[] chars = infix.toCharArray();
165 // iterate over the chars and fork on different types of input
167 for (int i = 0; i < chars.length; i++) {
173 final StringBuilder valueBuilder = new StringBuilder(1);
174 // handle the numbers of the expression
175 valueBuilder.append(c);
177 while (chars.length > i + numberLen && isDigit(chars[i + numberLen])) {
178 valueBuilder.append(chars[i + numberLen]);
182 lastToken = new NumberToken(valueBuilder.toString());
183 } else if (Character.isLetter(c) || c == '_' || c == '$') {
184 // can be a variable or function
185 final StringBuilder nameBuilder = new StringBuilder();
186 nameBuilder.append(c);
188 while (chars.length > i + offset && (Character.isLetter(chars[i + offset]) || Character.isDigit(chars[i + offset]) || chars[i + offset] == '_' || chars[i + offset] == '$')) {
189 nameBuilder.append(chars[i + offset++]);
191 String name = nameBuilder.toString();
192 if (this.isVariable(name)) {
195 lastToken = new VariableToken(name);
196 } else if (this.isFunction(name)) {
197 // might be a function
199 lastToken = new FunctionToken(name);
200 } else if (this.isCustomFunction(name)) {
203 lastToken = getCustomFunctionToken(name);
205 // an unknown symbol was encountered
206 throw new UnparsableExpressionException(c, i);
208 }else if (c == ',') {
209 // a function separator, hopefully
210 lastToken=new FunctionSeparatorToken();
211 } else if (OperatorToken.isOperator(c)) {
212 lastToken = new OperatorToken(String.valueOf(c), OperatorToken.getOperation(c));
213 } else if (c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}') {
214 lastToken = new ParenthesisToken(String.valueOf(c));
216 // an unknown symbol was encountered
217 throw new UnparsableExpressionException(c, i);
219 tokens.add(lastToken);
221 return tokens.toArray(new Token[tokens.size()]);