Other advanced features

The com.singularsys.jep.misc and com.singularsys.jep.walkers packages contain various other optional advanced features.

Bitwise and Java operators

The BitwiseOperatorTable adds a set of bitwise operators: bitwise AND a & b, bitwise OR a | b, 1's complement ~a, XOR a ^ b, Left shift a >> b, Right shift a << b Right shift with zero extension: a <<< b

The JavaOperatorTable adds the bitwise operators and the full set of Java operators x?y:z, ++x, --x, x++, x--, x+=y, x-=y, x*=y, x/=y, x%=y, x&=y, x|=y, x^=y, x<<=y, x>>=y, x<<<=y.

Line Numbering

The com.singularsys.jep.misc.lineNumbering package adds to add line and column number information to nodes which can be used for error detection. It can be set up with:

Jep jep = new Jep(
  new StandardConfigurableParser(),
  new LineNumberingNodeFactory(),
  new LineNumberingShuntingYard.LineNumberGrammarParserFactory());

The line and column number of each node can be found with

int line = (Integer) node.getHook(LineNumberingNodeFactory.Numbering.LINENUMBER);
int col = (Integer) node.getHook(LineNumberingNodeFactory.Numbering.COLUMNNUMBER);

Null Wrapped Functions

The com.singularsys.jep.misc.nullwrapper package allows functions to handle null in the same way as SQL's NULL. null will be treated as unknown and most operations involving null will return null. So sin(null) returns null and null + 5 returns null. The logic AND and OR operations follow slightly different rules with null || true giving true and null && false giving false.

The package works by using the Decorator pattern which takes and existing PostfixMathCommand and wraps it in another class which handles null correctly. The NullWrappedFunctionTable and NullWrappedOperatorTable take existing FunctionTable and OperatorTable and wrap each function and operator in them.

To set up this functionality use

jep.setComponent(new NullWrappedOperatorTable(jep.getOperatorTable2(),true));
jep.setComponent(new NullWrappedFunctionTable(jep.getFunctionTable()));
jep.setComponent(new StandardConfigurableParser());
((FastEvaluator) jep.getEvaluator()).setTrapNullValues(false);

This also adds a null-safe equals operator <=> for which null<=>null is true. If you do not need this operator you can use the standard parser and omit thirds line setting the parser.

Tree traversal

Various methods for tree traversal provides by the com.singularsys.jep.walkers package are discussed in the manipulating expressions page. These include: the TreeAnalyzer which can calculate statistics about an expression, and the SubstitutionVisitor which can substitute one expression into another.

Functions

The com.singularsys.jep.misc.functions packages contain various other functions, see Optional functions for details.

Bound variables

The com.singularsys.jep.misc.boundvariable packages allows a direct binding between Jep variables and Java objects so that when the Jep variable is changed the corresponding object field also changed and vice versa.

The simplest way to use this is to use the FieldVariableBinding to bind a Jep variable to a specific field of an object using the reflection mechanism. First set up an object with publicly accessible fields.

public class MyObj {
    public Double a;
    public double b;
}

Then set up Jep using the BoundVariableFactory

// Create Jep with a variable factory which allows bound variables
Jep jep = new Jep(new BoundVariableFactory());
// Create an object, with a Double field a and a double field b
MyObj obj = new MyObj();
obj.a = new Double(3.0);
obj.b = 5.0;
// Create binding objects for the two fields
FieldVariableBinding bindA = new FieldVariableBinding(obj,"a");
FieldVariableBinding bindB = new FieldVariableBinding(obj,"b");
// Create variables bound to these fields
jep.addVariable("a", bindA);
jep.addVariable("a", bindB);

These variables can then be used as normal in Jep, and changes will affect the field values.

// Parse an equation using these values
Node n = jep.parse("b=4*a");
Object res = jep.evaluate(n);
System.out.println("Result is "+res+" value of field b is "+obj.b);

// setting the value of objects field alters the jep value
obj.a = 7.0;
res = jep.evaluate(n);
System.out.println("Result is "+res+" value of field b is "+obj.b);

The MutableDouble provides a simple class with a single Double value which can be bound to a Jep variable.

Other way of mapping Jep variables to Java objects can be created by implementing the VariableBinding interface and the VariableBindingMapper interface allows variable names to be translated to specific variables. For instance the ChainedObjectVariableBindingMapper creates VariableBinding objects by translating the variable name into a chain of object references so "foo.a.b" might be translated into a reference to field "b" of the object at field "a" of an object.

Overloaded functions

The com.singularsys.jep.misc.overloadedfunctions allows functions and operators to be overloaded so that different implementations of a function can be used depending on the types of the arguments or structure of the tree.

The OverloadedUnaryFunction, OverloadedBinaryFunction, and OverloadedNaryFunction allows functions to be chosen depending on the evaluation time values of the arguments.

This makes it easy to extend Jep with custom number types. For instance if you have your own value type with an add method

class MyVal {
    double value;

    public MyVal(double value) { this.value = value; }

    public MyVal add(MyVal obj) {
        return new MyVal(value + obj.value);
    }
}

An overloaded function can be defined with

OverloadedNaryFunction addPfmc = new OverloadedNaryFunction(
     NaryBinaryFunction.instanceOf(MyVal.class,(x,y)->x.add(y)),
     new Add(),
     args -> Stream.of(args).allMatch(arg -> arg instanceof MyVal));
Using static factory to create an a NaryBinaryFunction for our type and a predicate to determin which method to call. The add operator can use the overloaded function.
jep.getOperatorTable().getAdd().setPFMC(addPfmc);

The AlternateFunctions, class allow functions to be selected base on the number of arguments. For example an single atan function could be define to allow both the atan(x) and atan(y,x) syntax.

UnaryFunction uf = new ArcTangent();
BinaryFunction bf =new ArcTangent2();
AlternateFunctions af = new AlternateFunctions(uf, bf);
jep.addFunction("atan",af);
jep.reinitializeComponents();

The OverloadedFunctionByStructure class allows overloaded functions chosen by the structure of parse tree. For example

UnaryFunction uf = new ArcTangent();
BinaryFunction bf =new ArcTangent2();
var af = new OverloadedFunctionByStructure(uf, bf,
                              n->n.jjtGetNumChildren()==1);
jep.addFunction("atan",af);
jep.reinitializeComponents();
uses a predicate base on the number of children of a node.

The OverloadedOperator class allows operators to be overloaded. It's static factory extendOperator() is the easiest way to create an overloaded operator. This takes a new PostfixMathCommandI, an existing operator, and a predicate. It modifies then modifies operator to allow different syntaxs. The new function is used when the predicate is true and the function associated with the existing operator is used when it is false.

For example to allow the list operator [ ] to work with its normal syntax [1,2,3] and also specify a range [1..10] we can

var ot = jep.getOperatorTable();
Operator dotdotOp = new Operator("..",new Range(),Operator.BINARY);
ot.appendOperator(dotdotOp, ot.getAssign());
OverloadedOperator.extendOperator(
		jep,
		new Identity(),
		ot.getList(),
		n -> n.jjtGetChild(0).getOperator()==dotdotOp);
jep.reinitializeComponents();
This first define a .. operator using the Range function, a binary function that returns a list of numbers when evaluated. Then it extends the list operator using the Identity function, the original List operator and a predicate. During evaluation if the first child of our new [ ] matches the .. the the identity function is called, this simple returns it argument: the result of evalution the child node, which is the result of evaluating the Range function. If the first child does not match the .. then the normal list operator is used which creates a list of the arguments.

Publishing Variables

Jep 4.1 introduces the com.singularsys.jep.misc.publishingvariable package allows other classes to monitor changes to variables and the variable table. It replaces the com.singularsys.jep.misc.VariableTableObserver class which relied on the deprecated java.util.Observable. This package relies on the java.util.concurrent.Flow Publisher and Subscriber.

A typical usage would be:

SubmissionPublisher<VariableTableAction> pub
    = PublishingVariableTable. createDefaultPublisher();
var vt = new PublishingVariableTable(pub);
PublishingVariableFactory vf = new PublishingVariableFactory(pub);
Jep jep = new Jep(vt, vf);
var vts = new VariableTableSubscriber() {
   @Override
   protected void variableAdded(String name, Object value) {
       System.out.println("variableAdded " + name + " " + value);
   }

   @Override
   protected void variableChanged(String name,Object oldval,Object newval) {
        System.out.println("variableChanged " + name + " " + newval);
   }
};
pub.subscribe(vts);
jep.setVariable("x", 5.0);
Node n = jep.parse("y=x^2");
jep.evaluate(n);

Which will print:

variableAdded x 5.0
variableAdded y null
variableChanged y 25.0

Dictionary support

Jep 4.1 introduces the com.singularsys.jep.misc.dictionary package provides a grammar matcher supporting a JSON like syntax for dictionaries map = {'x':2, 'y':3} } and functions like put(map,'x', 5), get(map,'x'), and keys(map), for operation on on them.

Parallel Parsing

Jep 4.1 introduces the com.singularsys.jep.misc.parallelparsing package that has several methods for parsing equations in parallel. For most cases these do not offer significant advantages over parsing all variables in a single thread.