Fix to streamer loading of materials; missing material in Giant Leaps file.
[debian/openrocket] / core / src / net / sf / openrocket / util / 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 net.sf.openrocket.util.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 net.sf.openrocket.util.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                 if (variableNames != null) {
77                         for (String varName : variableNames) {
78                                 if (functionNames.contains(varName.toLowerCase())) {
79                                         throw new IllegalArgumentException("Variable '" + varName + "' can not have the same name as a function");
80                                 }
81                         }
82                 }
83                 this.customFunctions = customFunctions;
84         }
85
86         private Token getCustomFunctionToken(String name) throws UnknownFunctionException {
87                 for (CustomFunction func : customFunctions) {
88                         if (func.getValue().equals(name)) {
89                                 return func;
90                         }
91                 }
92                 throw new UnknownFunctionException(name);
93         }
94
95         private boolean isCustomFunction(String name) {
96                 if (customFunctions == null) {
97                         return false;
98                 }
99                 for (CustomFunction func : customFunctions) {
100                         if (func.getValue().equals(name)) {
101                                 return true;
102                         }
103                 }
104                 return false;
105         }
106
107         /**
108          * check if a char is part of a number
109          * 
110          * @param c
111          *            the char to be checked
112          * @return true if the char is part of a number
113          */
114         private boolean isDigit(char c) {
115                 return Character.isDigit(c) || c == '.';
116         }
117
118         private boolean isFunction(String name) {
119                 for (Function fn : Function.values()) {
120                         if (fn.name().equals(name.toUpperCase())) {
121                                 return true;
122                         }
123                 }
124                 return false;
125         }
126
127         /**
128          * check if a String is a variable name
129          * 
130          * @param name
131          *            the variable name which is checked to be valid the char to be
132          *            checked
133          * @return true if the char is a variable name (e.g. x)
134          */
135         private boolean isVariable(String name) {
136                 if (variableNames != null) {
137                         for (String var : variableNames) {
138                                 if (name.equals(var)) {
139                                         return true;
140                                 }
141                         }
142                 }
143                 return false;
144         }
145
146         /**
147          * tokenize an infix expression by breaking it up into different
148          * {@link Token} that can represent operations,functions,numbers,
149          * parenthesis or variables
150          * 
151          * @param infix
152          *            the infix expression to be tokenized
153          * @return the {@link Token}s representing the expression
154          * @throws UnparsableExpressionException
155          *             when the expression is invalid
156          * @throws UnknownFunctionException
157          *             when an unknown function name has been used.
158          */
159         Token[] tokenize(String infix) throws UnparsableExpressionException, UnknownFunctionException {
160                 final List<Token> tokens = new ArrayList<Token>();
161                 final char[] chars = infix.toCharArray();
162                 // iterate over the chars and fork on different types of input
163                 Token lastToken;
164                 for (int i = 0; i < chars.length; i++) {
165                         char c = chars[i];
166                         if (c == ' ')
167                                 continue;
168                         if (isDigit(c)) {
169                                 final StringBuilder valueBuilder = new StringBuilder(1);
170                                 // handle the numbers of the expression
171                                 valueBuilder.append(c);
172                                 int numberLen = 1;
173                                 while (chars.length > i + numberLen && isDigit(chars[i + numberLen])) {
174                                         valueBuilder.append(chars[i + numberLen]);
175                                         numberLen++;
176                                 }
177                                 i += numberLen - 1;
178                                 lastToken = new NumberToken(valueBuilder.toString());
179                         } else if (Character.isLetter(c) || c == '_') {
180                                 // can be a variable or function
181                                 final StringBuilder nameBuilder = new StringBuilder();
182                                 nameBuilder.append(c);
183                                 int offset = 1;
184                                 while (chars.length > i + offset && (Character.isLetter(chars[i + offset]) || Character.isDigit(chars[i + offset]) || chars[i + offset] == '_')) {
185                                         nameBuilder.append(chars[i + offset++]);
186                                 }
187                                 String name = nameBuilder.toString();
188                                 if (this.isVariable(name)) {
189                                         // a variable
190                                         i += offset - 1;
191                                         lastToken = new VariableToken(name);
192                                 } else if (this.isFunction(name)) {
193                                         // might be a function
194                                         i += offset - 1;
195                                         lastToken = new FunctionToken(name);
196                                 } else if (this.isCustomFunction(name)) {
197                                         // a custom function
198                                         i += offset - 1;
199                                         lastToken = getCustomFunctionToken(name);
200                                 } else {
201                                         // an unknown symbol was encountered
202                                         throw new UnparsableExpressionException(c, i);
203                                 }
204                         }else if (c == ',') {
205                             // a function separator, hopefully
206                             lastToken=new FunctionSeparatorToken();
207                         } else if (OperatorToken.isOperator(c)) {
208                                 lastToken = new OperatorToken(String.valueOf(c), OperatorToken.getOperation(c));
209                         } else if (c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}') {
210                                 lastToken = new ParenthesisToken(String.valueOf(c));
211                         } else {
212                                 // an unknown symbol was encountered
213                                 throw new UnparsableExpressionException(c, i);
214                         }
215                         tokens.add(lastToken);
216                 }
217                 return tokens.toArray(new Token[tokens.size()]);
218         }
219 }