Custom Functions

Jep allows you to add custom functions by calling getFunctionTable().addFunction(String name, PostfixMathCommandI function). Custom functions are no different from built-in functions, so you can use the source code of the built-in functions as a template. Throughout the documentation it is customary to use the abbreviation pfmc to refer to these classes.

To create a custom function, you must first create a new class which extends one of the following base classes:

UnaryFunction
For single argument functions; the Object eval(Object l) must be implemented.
BinaryFunction
For two argument functions; defines a Object eval(Object l,Object r) method.
NaryFunction
For functions taking an arbitrary number of arguments; defines a Object eval(Object[] args) method.
PostfixMathCommand
For functions taking an arbitrary number of arguments; uses a stack for passing arguments via the run(Stack<Object> stack) method.

UnaryFunction, BinaryFunction and NaryFunction are new in Jep 3.3 and are easier to implement and faster to evaluate.

UnaryFunction and BinaryFunction

These are the simplest to implement. A simple implementation of a unary square function would be:

import com.singularsys.jep.functions.UnaryFunction;

public class Square extends UnaryFunction {
  public Object eval(Object l) {
    double x = ((Number) l).doubleValue();
    return x*x; // result is auto-boxed to return a Double.
  }
}

which could be added to Jep using

jep.addFunction("square",new Square());

Binary functions are similar but the Object eval(Object l,Object r) method must be implemented.

Argument type conversion and Exceptions

The above square function will work but it does not handle illegal values well throwing a ClassCastException if a String is passed in. Improved behaviour is achieved if an EvaluationException or its subclass IllegalParameterException is thrown. The former is used for general errors and the latter is specific for illegal arguments to the function and generates a standard error message. An improved version of square would be

import com.singularsys.jep.functions.UnaryFunction;

public class Square extends UnaryFunction {
  public Object eval(Object l)  throws EvaluationException {
    // if incorrect type throw an exception 
    // specifying position in argument list, expected type and supplied value.    
    if(!(l instanceof Number)) 
      throw new IllegalParameterException(this,0,Number.class,l);
    double x = ((Number) l).doubleValue();
    return x*x; // result is auto-boxed to return a Double.
  }
}

PostfixMathCommand and its subclasses provide a number of convenience methods for converting arguments: asInt, asStrictInt, asLong, asDouble, asString, asBool which return the corresponding primitive type and throw exceptions if necessary. Using the asDouble method the code becomes

import com.singularsys.jep.functions.UnaryFunction;

public class Square extends UnaryFunction {
  public Object eval(Object l)  throws EvaluationException  {
    // Convert argument at position 0 to double, throwing exception if needed
    double x = asDouble(0,l);
    return x*x;
  }
}

Nary functions

If the function accepts 0, more than 2 or a variable number of arguments then the NaryFunction base class can be used. Here the arguments are passed as an array of objects Object eval(Object[] args). By default these functions will accept any number of argument. A fixed number of allowed arguments can be specified in the public NaryFunction(int nArgs) constructor. A restricted but variable number of arguments can be specified by overriding the public boolean checkNumberOfParameters(int n) method which returns true if n is a valid number of arguments. Both these methods will check the number of arguments at both parse and evaluation time.

For example the following function always takes zero arguments would return the current time.

import com.singularsys.jep.functions.NaryFunction;

public class Time extends NaryFunction {
  public Time() {
    super(0); // does not allow any arguments
  }
  public Object eval(Object[] args) {
   return System.currentTimeMillis();
  }
}

The following calculates the sum of squares allows two or three arguments

import com.singularsys.jep.functions.NaryFunction;

public class SumSq extends NaryFunction {
  // Use default constructor: variable number of arguments
  @Override
  public boolean checkNumberOfParameters(int n) {
    return (n == 2 || n == 3);
  }

  public Object eval(Object[] args) throws EvaluationException {
    double x = asDouble(0,args[0]);
    double y = asDouble(1,args[1]);
    if(args.length==2)
      return x*x+y*y;
    else {
      double z=asDouble(2,args[2]);
      return x*x+y*y+z*z;
    }
  }
}

A more general version allowing one or more arguments would be

import com.singularsys.jep.functions.NaryFunction;

public class SumSq extends NaryFunction {
  // Use default constructor: variable number of arguments

  // Ensure at least one argument  
  @Override
  public boolean checkNumberOfParameters(int n) {
    return (n == 2 || n == 3);
  }

  public Object eval(Object[] args) throws EvaluationException  {
    double x = asDouble(0,args[0]);
    double res = x*x;
    for(int i=1;i<args.length;++i) {
      double y = asDouble(i,args[i]);
      res += y*y;
    }
    return res;
  }
}

Stack based PostfixMathCommand

The old style PostfixMathCommand is still available. These use a stack based evaluation via the run(Stack<Object> inStack) method.

  1. Create a class that extends PostfixMathCommand (in the com.singularsys.jep.functions package). In this example we will name it "Half"
  2. In the constructor set the number of arguments to be taken. In our case this is one. Do this by writing numberOfParameters = 1;
    If you want to allow any number of parameters, initialize the numberOfParameters value to -1. It is highly recommended to study the Sum.java code as an example of a function that accepts any number of parameters.
  3. Implement the run(Stack<Object> inStack) method. The parameters are passed in a Stack object. This same stack is used as the output for the function. So we first need to pop the parameter off the stack, calculate the result, and finally pop the result back on the stack.
    public void run(Stack<Object> inStack) throws EvaluationException {
    
       // check the stack
       checkStack(inStack);
    
       // get the parameter from the stack
       Object param = inStack.pop();
    
       // check whether the argument is of the right type
       if (param instanceof Double) {
          // calculate the result
          double r = ((Double)param).doubleValue() / 2;
          // push the result on the inStack
          inStack.push(new Double(r)); 
       } else {
          throw new EvaluationException("Invalid parameter type");
       }
    }
  4. In your main program or applet that is using the parser, add the new function. Do this by writing
    jep.getFunctionTable().addFunction("half", new Half());
  5. If numberOfParameters == -1 you may wish to overwrite the boolean checkNumberOfParameters(int n) to catch an illegal number of arguments at parse time.
  6. In Jep version 3.3 a new IllegalParameterException is introduced. This simplifies and standadises the common case of function arguments of the wrong types. In addition the PostfixMathCommand class provides methods String asString(), int asInt(), int asStrictInt(), int asLong(), int asDouble() to ease conversion of an argument to the required type, these methods will all throw IllegalParameterException if the argument is not of the required type. The run method above could be simplified to
    public void run(Stack<Object> inStack) throws EvaluationException {
       checkStack(inStack);
    
       // get the parameter from the stack
       Object param = inStack.pop();
       
       // convert param to a double throwing an exception 
       // the first argument is the name of the function, and the second is its position 
       double val = asDouble("half",1,param);
       
       double r = val / 2;
       inStack.push(new Double(r)); 
    }

Source files

Specialised functions

A number of other base classes and interfaces are also available:

NaryBinaryFunction
Functions like addition and mulitplication which can take multiple arguments but the base implementation is a binary function. Just the Object eval(Object l,Object r) needs to be implemented.
CallbackEvaluationI
Rather than the values of the arguments the node tree is passed to the function, individual children can be evaluated by a callback function to the evaluator. This allows lazy evaluation where not all branches need to be evaluated. It also allows names of variables to be found.
LValueI
An interface for functions which can be used on the left hand side of an assignment.