diff --git a/src/main/java/net/objecthunter/exp4j/Expression.java b/src/main/java/net/objecthunter/exp4j/Expression.java index 11069bef..80ab46b4 100644 --- a/src/main/java/net/objecthunter/exp4j/Expression.java +++ b/src/main/java/net/objecthunter/exp4j/Expression.java @@ -18,9 +18,8 @@ import java.util.*; import java.util.concurrent.*; -import net.objecthunter.exp4j.function.Function; -import net.objecthunter.exp4j.function.Functions; -import net.objecthunter.exp4j.operator.Operator; +import net.objecthunter.exp4j.function.*; +import net.objecthunter.exp4j.operator.*; import net.objecthunter.exp4j.tokenizer.*; public class Expression { @@ -208,4 +207,204 @@ private double[] reverseInPlace(double[] args) { } return args; } + + + /** + * Convert the Expression back to string expression. + *

Calling this function returns exactly the same result as calling {@link #toString(boolean) toString}(false), + * so it uses the '*' sign in multiplications

+ * + * @return a string representation of the Expression. + */ + public String toString() { + return toString(false); + } + + /** + * Convert the Expression back to string expression. + *

The argument implicitMultiplication determines whether to use the '*' sign + * in the returned string expression. + * Every occurrence of the '*' sign will be removed, except when there is a number to the right of it.

+ * + * @param implicitMultiplication + * if true, removes the '*' sign in multiplications (only when it's logical) + * + * @return a string representation of the Expression. + */ + public String toString(boolean implicitMultiplication) { + String expression = toString(Arrays.asList(tokens), implicitMultiplication); + + if(implicitMultiplication) { + expression = expression.replaceAll("\\*(\\D)", "$1"); + } + + return expression; + } + + private String toString(List tokens, boolean impMult) { + if(tokens.size() == 0) + return ""; + Token token = tokens.get(tokens.size()-1); + + switch (token.getType()) { + case Token.TOKEN_OPERATOR: + Operator operator = ((OperatorToken) token).getOperator(); + List> operands = getTokensArguments(tokens.subList(0, tokens.size()-1), operator.getNumOperands()); + List leftTokens, + rightTokens; + if(operator.getNumOperands() == 1) { + if(operator.isLeftAssociative()) { + leftTokens = operands.get(0); + rightTokens = new ArrayList(); + } + else { + leftTokens = new ArrayList(); + rightTokens = operands.get(0); + } + } + else { + if(operator.getSymbol().equals("*") && operands.get(1).size() == 1 && operands.get(0).get(operands.get(0).size()-1).getType() != Token.TOKEN_NUMBER) { + leftTokens = operands.get(1); + rightTokens = operands.get(0); + } else { + leftTokens = operands.get(0); + rightTokens = operands.get(1); + } + } + + boolean parentheses_left = leftTokens.size() > 1 && leftTokens.get(leftTokens.size()-1).getType() != Token.TOKEN_FUNCTION, + parentheses_right = rightTokens.size() > 1 && rightTokens.get(rightTokens.size()-1).getType() != Token.TOKEN_FUNCTION; + if(parentheses_left && leftTokens.get(leftTokens.size()-1).getType() == Token.TOKEN_OPERATOR) { + Operator leftOperator = ((OperatorToken) leftTokens.get(leftTokens.size()-1)).getOperator(); + if(leftOperator.getNumOperands() == 1 && leftOperator.getSymbol().matches("\\+|-") && !operator.getSymbol().matches("\\+|-")) { + parentheses_left = true; + } + else { + if(leftOperator.getSymbol().matches("\\+|-|\\*")) { + parentheses_left = operator.getPrecedence() > leftOperator.getPrecedence(); + } + else { + parentheses_left = operator.getPrecedence() >= leftOperator.getPrecedence(); + } + } + } + if(parentheses_right && rightTokens.get(rightTokens.size()-1).getType() == Token.TOKEN_OPERATOR) { + Operator rightOperator = ((OperatorToken) rightTokens.get(rightTokens.size()-1)).getOperator(); + if(rightOperator.getNumOperands() == 1 && rightOperator.getSymbol().matches("\\+|-")) { + parentheses_right = true; + } + else { + if(operator.getSymbol().matches("\\+|\\*") && rightOperator.getSymbol().matches("\\+|\\*")) { + parentheses_right = operator.getPrecedence() > rightOperator.getPrecedence(); + } + else { + parentheses_right = operator.getPrecedence() >= rightOperator.getPrecedence(); + } + } + } + + if(!parentheses_left && leftTokens.size() > 0 && leftTokens.get(leftTokens.size()-1).getType() == Token.TOKEN_NUMBER) { + parentheses_left = ((NumberToken) leftTokens.get(0)).getValue() < 0; + } + if(!parentheses_right && rightTokens.size() > 0 && rightTokens.get(rightTokens.size()-1).getType() == Token.TOKEN_NUMBER) { + parentheses_right = ((NumberToken) rightTokens.get(0)).getValue() < 0; + } + + String leftOperand = toString(leftTokens, impMult), + rightOperand = toString(rightTokens, impMult), + symbol = operator.getSymbol(); + + if(parentheses_left) { + leftOperand = "("+leftOperand+")"; + } + if(parentheses_right) { + rightOperand = "("+rightOperand+")"; + } + + return leftOperand + symbol + rightOperand; + + case Token.TOKEN_FUNCTION: + Function function = ((FunctionToken) token).getFunction(); + + if(function.getName().equals("pow")) { + tokens.set(tokens.size()-1, new OperatorToken(Operators.getBuiltinOperator('^', 2))); + return toString(tokens, impMult); + } + + String stringArgs = ""; + List> args = getTokensArguments(tokens.subList(0, tokens.size()-1), function.getNumArguments()); + for (List argument : args) { + stringArgs += ", "+toString(argument, impMult); + } + stringArgs = stringArgs.substring(2); + + return function.getName()+"("+stringArgs+")"; + + case Token.TOKEN_VARIABLE: + return ((VariableToken) token).getName(); + + case Token.TOKEN_NUMBER: + double num = ((NumberToken) token).getValue(); + if(num != (long) num) { + return String.valueOf(num); + } else { + return String.valueOf((long) num); + } + + default: + throw new UnsupportedOperationException("The token type '"+token.getClass().getName()+"' is not supported in this function yet"); + } + } + + + private List> getTokensArguments(List tokens, int numOperands) { + List> tArgs = new ArrayList>(2); + if(numOperands == 1) { + tArgs.add(tokens); + } + else { + int size = 0; + int[] pos = new int[numOperands-1]; + for (int i = 0; i < tokens.size()-1; i++) { + Token t = tokens.get(i); + switch (t.getType()) { + case Token.TOKEN_NUMBER: + size++; + break; + + case Token.TOKEN_VARIABLE: + size++; + break; + + case Token.TOKEN_OPERATOR: + Operator operator = ((OperatorToken) t).getOperator(); + if (operator.getNumOperands() == 2) + size --; + break; + + case Token.TOKEN_FUNCTION: + FunctionToken func = (FunctionToken) t; + for (int j = 0; j < func.getFunction().getNumArguments(); j++) { + size--; + } + size++; + break; + } + for (int j = 0; j < pos.length; j++) { + if(size == j+1) { + pos[j] = i; + } + } + } + + tArgs.add(tokens.subList(0, pos[0]+1)); + for (int i = 1; i < pos.length; i++) { + tArgs.add(tokens.subList(pos[i-1]+1, pos[i]+1)); + } + tArgs.add(tokens.subList(pos[pos.length-1]+1, tokens.size())); + } + + return tArgs; + } + }