1591995ea6987b4be8d3b42817dd511a5596d275
[debian/openrocket] / core / src / de / congrace / exp4j / Tokenizer.java
1 /*
2    Copyright 2011 frank asseg
3
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
7
8        http://www.apache.org/licenses/LICENSE-2.0
9
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.
15
16  */
17 package de.congrace.exp4j;
18
19 import java.util.ArrayList;
20 import java.util.HashSet;
21 import java.util.List;
22 import java.util.Set;
23
24 import de.congrace.exp4j.FunctionToken.Function;
25
26 /**
27  * Class for tokenizing mathematical expressions by breaking an expression up
28  * into multiple different {@link Token}s
29  * 
30  * @author fas@congrace.de
31  */
32 class Tokenizer {
33         private String[] variableNames;
34         private final Set<String> functionNames = new HashSet<String>();
35         private final Set<CustomFunction> customFunctions;
36
37         {
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");
55         }
56
57         Tokenizer() {
58                 super();
59                 customFunctions = null;
60         }
61
62         /**
63          * construct a new Tokenizer that recognizes variable names
64          * 
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
71          *            are not valid
72          */
73         Tokenizer(String[] variableNames, Set<CustomFunction> customFunctions) throws IllegalArgumentException {
74                 super();                
75                 this.variableNames = variableNames;
76                                 
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");
81                                 }
82                         }
83                 }
84                 this.customFunctions = customFunctions;
85         }
86
87         private Token getCustomFunctionToken(String name) throws UnknownFunctionException {
88                 for (CustomFunction func : customFunctions) {
89                         if (func.getValue().equals(name)) {
90                                 return func;
91                         }
92                 }
93                 throw new UnknownFunctionException(name);
94         }
95
96         private boolean isCustomFunction(String name) {
97                 if (customFunctions == null) {
98                         return false;
99                 }
100                 for (CustomFunction func : customFunctions) {
101                         if (func.getValue().equals(name)) {
102                                 return true;
103                         }
104                 }
105                 return false;
106         }
107
108         /**
109          * check if a char is part of a number
110          * 
111          * @param c
112          *            the char to be checked
113          * @return true if the char is part of a number
114          */
115         private boolean isDigit(char c) {
116                 return Character.isDigit(c) || c == '.';
117         }
118
119         private boolean isFunction(String name) {
120                 for (Function fn : Function.values()) {
121                         if (fn.name().equals(name.toUpperCase())) {
122                                 return true;
123                         }
124                 }
125                 return false;
126         }
127
128         /**
129          * check if a String is a variable name
130          * 
131          * @param name
132          *            the variable name which is checked to be valid the char to be
133          *            checked
134          * @return true if the char is a variable name (e.g. x)
135          */
136         private boolean isVariable(String name) {
137                 //String[] variableNames = variables.getVariableNames();
138                 
139                 if (variableNames != null) {
140                         for (String var : variableNames) {
141                                 if (name.equals(var)) {
142                                         return true;
143                                 }
144                         }
145                 }
146                 return false;
147         }
148
149         /**
150          * tokenize an infix expression by breaking it up into different
151          * {@link Token} that can represent operations,functions,numbers,
152          * parenthesis or variables
153          * 
154          * @param infix
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.
161          */
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
166                 Token lastToken;
167                 for (int i = 0; i < chars.length; i++) {
168                         char c = chars[i];
169                         if (c == ' ')
170                                 continue;
171                         if (isDigit(c)) {
172                                 final StringBuilder valueBuilder = new StringBuilder(1);
173                                 // handle the numbers of the expression
174                                 valueBuilder.append(c);
175                                 int numberLen = 1;
176                                 while (chars.length > i + numberLen && isDigit(chars[i + numberLen])) {
177                                         valueBuilder.append(chars[i + numberLen]);
178                                         numberLen++;
179                                 }
180                                 i += numberLen - 1;
181                                 lastToken = new NumberToken(valueBuilder.toString());
182                         } else if (Character.isLetter(c) || c == '_' || c == '#') {
183                                 // can be a variable or function
184                                 final StringBuilder nameBuilder = new StringBuilder();
185                                 nameBuilder.append(c);
186                                 int offset = 1;
187                                 while (chars.length > i + offset && (Character.isLetter(chars[i + offset]) || Character.isDigit(chars[i + offset]) || chars[i + offset] == '_' || chars[i + offset] == '#')) {
188                                         nameBuilder.append(chars[i + offset++]);
189                                 }
190                                 String name = nameBuilder.toString();
191                                 if (this.isVariable(name)) {
192                                         // a variable
193                                         i += offset - 1;
194                                         lastToken = new VariableToken(name);
195                                 } else if (this.isFunction(name)) {
196                                         // might be a function
197                                         i += offset - 1;
198                                         lastToken = new FunctionToken(name);
199                                 } else if (this.isCustomFunction(name)) {
200                                         // a custom function
201                                         i += offset - 1;
202                                         lastToken = getCustomFunctionToken(name);
203                                 } else {
204                                         // an unknown symbol was encountered
205                                         throw new UnparsableExpressionException(c, i);
206                                 }
207                         }else if (c == ',') {
208                             // a function separator, hopefully
209                             lastToken=new FunctionSeparatorToken();
210                         } else if (OperatorToken.isOperator(c)) {
211                                 lastToken = new OperatorToken(String.valueOf(c), OperatorToken.getOperation(c));
212                         } else if (c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}') {
213                                 lastToken = new ParenthesisToken(String.valueOf(c));
214                         } else {
215                                 // an unknown symbol was encountered
216                                 throw new UnparsableExpressionException(c, i);
217                         }
218                         tokens.add(lastToken);
219                 }
220                 return tokens.toArray(new Token[tokens.size()]);
221         }
222 }