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 the chance to declare Functions with variable number of arguments #42

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
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
13 changes: 8 additions & 5 deletions src/main/java/net/objecthunter/exp4j/Expression.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ public ValidationResult validate(boolean checkVariablesSet) {
break;
case Token.TOKEN_FUNCTION:
final Function func = ((FunctionToken) tok).getFunction();
final int argsNum = func.getNumArguments();
if (argsNum > count) {
final int argsNum = ((FunctionToken) tok).getArgumentCount();
if (func.getMinNumArguments() > argsNum || func.getMaxNumArguments() < argsNum) {
errors.add("Not enough arguments for '" + func.getName() + "'");
}
if (argsNum > 1) {
Expand Down Expand Up @@ -161,12 +161,15 @@ public double evaluate() {
}
} else if (t.getType() == Token.TOKEN_FUNCTION) {
FunctionToken func = (FunctionToken) t;
if (output.size() < func.getFunction().getNumArguments()) {
int functionArgs = func.getArgumentCount();
if (functionArgs < func.getFunction().getMinNumArguments() || functionArgs > func.getFunction().getMaxNumArguments() || output.isEmpty()) {
throw new IllegalArgumentException("Invalid number of arguments available for '" + func.getFunction().getName() + "' function");
}
/* collect the arguments from the stack */
double[] args = new double[func.getFunction().getNumArguments()];
for (int j = 0; j < func.getFunction().getNumArguments(); j++) {
double[] args = new double[functionArgs];


for (int j = 0; j < functionArgs ; j++) {
args[j] = output.pop();
}
output.push(func.getFunction().apply(this.reverseInPlace(args)));
Expand Down
45 changes: 36 additions & 9 deletions src/main/java/net/objecthunter/exp4j/function/Function.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,26 @@ public abstract class Function {

protected final String name;

protected final int numArguments;
protected final int minArguments;
protected final int maxArguments;

/**
* Create a new Function with a given name and number of arguments
*
* @param name the name of the Function
* @param numArguments the number of arguments the function takes
* @param minArguments the number of arguments the function takes
*/
public Function(String name, int numArguments) {
if (numArguments < 0) {
throw new IllegalArgumentException("The number of function arguments can not be less than 0 for '" +
public Function(String name, int minArguments, int maxArguments) {
if (minArguments < 0 || minArguments > maxArguments || maxArguments > Integer.MAX_VALUE) {
throw new IllegalArgumentException("The number of function arguments can not be less than 0 or more than " +Integer.MAX_VALUE+" for '" +
name + "'");
}
if (!isValidFunctionName(name)) {
throw new IllegalArgumentException("The function name '" + name + "' is invalid");
}
this.name = name;
this.numArguments = numArguments;
this.minArguments = minArguments;
this.maxArguments = maxArguments;

}

Expand All @@ -50,7 +52,26 @@ public Function(String name, int numArguments) {
* @param name the name of the Function
*/
public Function(String name) {
this(name, 1);
this(name, 1,1);
}


public Function(String name, int numArguments) {
this(name, numArguments,numArguments);
}

/**
* Get the number of arguments of a function with fixed arguments length.
* This function may be called only on functions with a fixed number of arguments and will throw an @UnsupportedOperationException otherwise.
* When using functions with variable arguments length use @getMaxNumArguments and @getMinNumArguments instead.
*
* @return the number of arguments
*/
public int getNumArguments() {
if (minArguments != maxArguments) {
throw new UnsupportedOperationException("Calling getNumArgument() is not supported for var arg functions, please use getMaxNumArguments() or getMinNumArguments()");
}
return minArguments;
}

/**
Expand All @@ -67,10 +88,16 @@ public String getName() {
*
* @return the number of arguments
*/
public int getNumArguments() {
return numArguments;
public int getMinNumArguments() {
return minArguments;
}

public int getMaxNumArguments() {
return maxArguments;
}



/**
* Method that does the actual calculation of the function value given the arguments
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import net.objecthunter.exp4j.function.Function;
import net.objecthunter.exp4j.operator.Operator;
import net.objecthunter.exp4j.tokenizer.FunctionToken;
import net.objecthunter.exp4j.tokenizer.OperatorToken;
import net.objecthunter.exp4j.tokenizer.Token;
import net.objecthunter.exp4j.tokenizer.Tokenizer;
Expand All @@ -40,7 +41,7 @@ public static Token[] convertToRPN(final String expression, final Map<String, Fu
final Map<String, Operator> userOperators, final Set<String> variableNames){
final Stack<Token> stack = new Stack<Token>();
final List<Token> output = new ArrayList<Token>();

final Stack<FunctionToken> functionTokenStack = new Stack<FunctionToken>();
final Tokenizer tokenizer = new Tokenizer(expression, userFunctions, userOperators, variableNames);
while (tokenizer.hasNext()) {
Token token = tokenizer.nextToken();
Expand All @@ -50,9 +51,12 @@ public static Token[] convertToRPN(final String expression, final Map<String, Fu
output.add(token);
break;
case Token.TOKEN_FUNCTION:
functionTokenStack.add((FunctionToken) token);
stack.add(token);
break;
case Token.TOKEN_SEPARATOR:
if (!functionTokenStack.empty())
functionTokenStack.peek().incArgument();
while (!stack.empty() && stack.peek().getType() != Token.TOKEN_PARENTHESES_OPEN) {
output.add(stack.pop());
}
Expand All @@ -79,11 +83,13 @@ public static Token[] convertToRPN(final String expression, final Map<String, Fu
stack.push(token);
break;
case Token.TOKEN_PARENTHESES_CLOSE:

while (stack.peek().getType() != Token.TOKEN_PARENTHESES_OPEN) {
output.add(stack.pop());
}
stack.pop();
if (!stack.isEmpty() && stack.peek().getType() == Token.TOKEN_FUNCTION) {
functionTokenStack.pop();
output.add(stack.pop());
}
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,19 @@

public class FunctionToken extends Token{
private final Function function;
private int functionArgumentsCount;
public FunctionToken(final Function function) {
super(Token.TOKEN_FUNCTION);
this.function = function;
this.functionArgumentsCount = 1;
}

public void incArgument(){
functionArgumentsCount++;
}

public int getArgumentCount() {return functionArgumentsCount;}

public Function getFunction() {
return function;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ public void testFunction12() throws Exception {
@Override
public double apply(double... values) {
double max = values[0];
for (int i = 1; i < numArguments; i++) {
for (int i = 1; i < values.length; i++) {
if (values[i] > max) {
max = values[i];
}
Expand All @@ -488,7 +488,7 @@ public void testFunction13() throws Exception {
@Override
public double apply(double... values) {
double max = values[0];
for (int i = 1; i < numArguments; i++) {
for (int i = 1; i < values.length; i++) {
if (values[i] > max) {
max = values[i];
}
Expand Down Expand Up @@ -633,7 +633,7 @@ public void testFunction20() throws Exception {
@Override
public double apply(double... values) {
double max = values[0];
for (int i = 1; i < numArguments; i++) {
for (int i = 1; i < values.length; i++) {
if (values[i] > max) {
max = values[i];
}
Expand All @@ -644,7 +644,7 @@ public double apply(double... values) {
ExpressionBuilder b = new ExpressionBuilder("max(1,2,3)")
.function(maxFunction);
double calculated = b.build().evaluate();
assertTrue(maxFunction.getNumArguments() == 3);
assertEquals(maxFunction.getMaxNumArguments(),maxFunction.getMinNumArguments(), 3);
assertTrue(calculated == 3);
}

Expand Down
Loading