Merge commit '42b2e5ca519766e37ce6941ba4faecc9691cc403' into upstream
[debian/openrocket] / core / src / de / congrace / exp4j / Tokenizer.java
diff --git a/core/src/de/congrace/exp4j/Tokenizer.java b/core/src/de/congrace/exp4j/Tokenizer.java
new file mode 100644 (file)
index 0000000..c2c99d9
--- /dev/null
@@ -0,0 +1,223 @@
+/*
+   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()]);
+       }
+}