Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added toString function to Expression #47

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 202 additions & 3 deletions src/main/java/net/objecthunter/exp4j/Expression.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -208,4 +207,204 @@ private double[] reverseInPlace(double[] args) {
}
return args;
}


/**
* Convert the <code>Expression</code> back to string expression.
* <p>Calling this function returns exactly the same result as calling <tt>{@link #toString(boolean) toString}(false)</tt>,
* so it uses the '*' sign in multiplications</p>
*
* @return a string representation of the <code>Expression</code>.
*/
public String toString() {
return toString(false);
}

/**
* Convert the <code>Expression</code> back to string expression.
* <p>The argument <code>implicitMultiplication</code> 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.</p>
*
* @param implicitMultiplication
* if <code>true</code>, removes the '*' sign in multiplications (only when it's logical)
*
* @return a string representation of the <code>Expression</code>.
*/
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<Token> 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<List<Token>> operands = getTokensArguments(tokens.subList(0, tokens.size()-1), operator.getNumOperands());
List<Token> leftTokens,
rightTokens;
if(operator.getNumOperands() == 1) {
if(operator.isLeftAssociative()) {
leftTokens = operands.get(0);
rightTokens = new ArrayList<Token>();
}
else {
leftTokens = new ArrayList<Token>();
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<List<Token>> args = getTokensArguments(tokens.subList(0, tokens.size()-1), function.getNumArguments());
for (List<Token> 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<List<Token>> getTokensArguments(List<Token> tokens, int numOperands) {
List<List<Token>> tArgs = new ArrayList<List<Token>>(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;
}

}