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:
Object eval(Object l)
must be implemented.Object eval(Object l,Object r)
method.Object eval(Object[] args)
method.run(Stack<Object> stack)
method.UnaryFunction, BinaryFunction and NaryFunction are new in Jep 3.3 and are easier to implement and faster to evaluate.
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.
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; } }
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; } }
The old style PostfixMathCommand is still available. These use a stack based evaluation via the
run(Stack<Object> inStack)
method.
com.singularsys.jep.functions
package). In this example we will name it "Half"numberOfParameters = 1;
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. 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"); } }
jep.getFunctionTable().addFunction("half", new Half());
boolean
checkNumberOfParameters(int n)
to catch an illegal number of arguments
at parse time.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
A number of other base classes and interfaces are also available:
Object eval(Object l,Object r)
needs to be implemented.