Package com.singularsys.jep.misc.overloadedfunctions
Evaluation time overloading
The OverloadedUnaryFunction,
OverloadedBinaryFunction,
and OverloadedNaryFunction allows functions to be chosen
depending on the evaluation time values of the arguments.
These classes allow the functions and operators to be overloaded to work with new types,
without modifying the original function.
For example if you have a class MyVal which you want to use
class MyVal {
double value;
public MyVal(double value) { this.value = value; }
public MyVal add(MyVal obj) {
return new MyVal(value + obj.value);
}
public MyVal sub(MyVal obj) {
return new MyVal(value - obj.value);
}
public MyVal neg() {
return new MyVal(-value);
}
@Override
public boolean equals(Object obj) {
if(obj instanceof MyVal) {
MyVal obj2 = (MyVal) obj;
return value == obj2.value;
}
return false;
}
}
You can create overloaded functions for the operators +, -, and unary minus using the following code:
OverloadedUnaryFunction uminus = new OverloadedUnaryFunction(
UnaryFunction.instanceOf(MyVal.class,x->x.neg()),
new UMinus(),
x -> x instanceof MyVal);
OverloadedBinaryFunction sub = new OverloadedBinaryFunction(
BinaryFunction.instanceOf(MyVal.class,(x,y)->x.sub(y)),
new Subtract(),
(x,y) -> x instanceof MyVal && y instanceof MyVal);
OverloadedNaryFunction add = new OverloadedNaryFunction(
NaryBinaryFunction.instanceOf(MyVal.class,(x,y)->x.add(y)),
new Add(),
args -> Stream.of(args).allMatch(arg -> arg instanceof MyVal));
jep.getOperatorTable().getUMinus().setPFMC(uminus);
jep.getOperatorTable().getAdd().setPFMC(add);
jep.getOperatorTable().getSubtract().setPFMC(sub);
jep.reinitializeComponents();
With this setup you can now use the operators +, -, and unary minus with MyVal objects as well as regular numbers.
Node addnode = jep.parse("x+y");
Node subnode = jep.parse("x-y");
Node negnode = jep.parse("-x");
// Evaluating with MyVal objects
jep.setVariableValue("x", new MyVal(3.0));
jep.setVariableValue("y", new MyVal(4.0));
Object res = jep.evaluate(addnode);
Object res2 = jep.evaluate(subnode);
Object res3 = jep.evaluate(negnode);
assertEquals(new MyVal(7.0),res);
assertEquals(new MyVal(-1.0),res2);
assertEquals(new MyVal(-3.0),res3);
// Evaluating with regular numbers
jep.setVariableValue("x", 3.0);
jep.setVariableValue("y", 4.0);
Object res4 = jep.evaluate(addnode);
Object res5 = jep.evaluate(subnode);
Object res6 = jep.evaluate(negnode);
assertEquals(7.0,res4);
assertEquals(-1.,res5);
assertEquals(-3.,res6);
Parse time overloading
A set of alternate functions can be chosen at parse time
based on the number of arguments by using the AlternateFunctions class.
For example you might want to overload the atan function
so that it could have one argument and two arguments versions.
UnaryFunction uf = new ArcTangent();
BinaryFunction bf =new ArcTangent2();
AlternateFunctions af = new AlternateFunctions(uf, bf);
jep.addFunction("atan",af);
jep.reinitializeComponents();
{
Node node = jep.parse("atan(1)");
Object res = jep.evaluate(node);
assertEquals(Math.PI/4,(double) res,1e-9);
}
{
Node node = jep.parse("atan(1,2)");
Object res = jep.evaluate(node);
assertEquals(Math.atan2(1,2),(double) res,1e-9);
}
With a standard setup the functions are chosen during evaluation.
However, by using the AlternateFunctionGrammerMatcher class
the ConfigurableParser can be modified
to substitute the functions when it is parsed.
To setup the parser to use the AlternateFunctionGrammerMatcher
you could use the StandardConfigurableParser
and replace the default FunctionGrammarMatcher.
var cp = new StandardConfigurableParser();
cp.replaceGrammarMatcher(FunctionGrammarMatcher.class,
new AlternateFunctionGrammerMatcher(
new FunctionGrammarMatcher(
cp.getSymbolToken("("),
cp.getSymbolToken(")"),
cp.getSymbolToken(","))));
jep = new Jep(cp);
With this setup the function is replaced with the correct PFMC during parsing:
UnaryFunction uf = new ArcTangent();
BinaryFunction bf =new ArcTangent2();
AlternateFunctions af = new AlternateFunctions(uf, bf);
jep.addFunction("atan",af);
jep.reinitializeComponents();
Node node = jep.parse("atan(1)");
assertSame(uf,node.getPFMC());
Node node2 = jep.parse("atan(1,2)");
assertSame(bf,node2.getPFMC());
A further class OverloadedFunctionByStructure
allows overloaded functions chosen by the structure of parse tree.
With the same setup as above the functions are also resolved by the AlternateFunctionGrammerMatcher.
UnaryFunction uf = new ArcTangent();
BinaryFunction bf =new ArcTangent2();
var af = new OverloadedFunctionByStructure(uf, bf, n->n.jjtGetNumChildren()==1);
jep.addFunction("atan",af);
jep.reinitializeComponents();
Node node = jep.parse("atan(1)");
assertSame(uf,node.getPFMC());
Node node2 = jep.parse("atan(1,2)");
assertSame(bf,node2.getPFMC());
OverloadedOperators and the OverloadResolver
The ConfigurableParser can not resolve
operator is defined using an OverloadedFunctionByStructure
to the correct pfmc.
Here the OverloadResolver visitor can be used to
resolve operators and functions after parsing and before evaluation.
We might wish to extend the list operator, [ ] so it can work with two
different syntax:
[1,2,3]using the standard List operator and function[1..5]representing a sequence of numbers
Range PostfixMathCommand, which returns a list of values between the endpoints.
Operator dotdotOp = new Operator("..",new Range(),Operator.BINARY);
ot.appendOperator(dotdotOp, ot.getAssign());
OverloadedOperator.extendOperator()
static factory create an OverloadedOperator.
This adds three operators to the OperatorTable:
- A overloaded operator, used during parse time
- An alternate operator, with the
Rangepfmc - The original list operator, modified so its not used at parse time
n -> n.jjtGetChild(0).getOperator()==dotdotOp.
var overOp = OverloadedOperator.extendOperator(
jep,
new Identity(),
ot.getList(),
n -> n.jjtGetChild(0).getOperator()==dotdotOp);
OverloadedOperator.getOp1() and OverloadedOperator.getOp2()
methods.
var sequenceOp = overOp.getOp1();
var listOp = overOp.getOp2();
overOp will be used.
Node node = jep.parse("[1..3]");
assertSame(overOp,node.getOperator());
OverloadResolver.
Node node1 = jep.parse("[1..3]");
or.visit(node1);
assertSame(sequenceOp,node1.getOperator());
Object res = jep.evaluate(node1);
Node node2 = jep.parse("[1,3]");
or.visit(node2);
Object res2 = jep.evaluate(node2);
assertSame(listOp,node2.getOperator());
extendOperator factory method, performs all the necessary
initilisations, it adds all three operators to the operator table,
and sets the flags and precidences.
If the original operator, has alternate symbols for parsing those are
copied to the other operators, likewise if it has a print symbol thats
is also copied, and if has an associated PrintRuleI
in the PrintVisitor that rule
is used for the other two operators.
The OverloadedOperator can also be constructed manually:
Operator dotdotOp = new Operator("..",new Range(),Operator.BINARY);
ot.appendOperator(dotdotOp, ot.getAssign());
listOp = ot.getList();
int flags = listOp.getFlags();
// Ensure the orginal operator is not used by the parser
listOp.setFlag(Operator.NOT_IN_PARSER, true);
int flags2 = flags | Operator.NOT_IN_PARSER;
// Construct the alternate operator
var idFun = new Indentity();
sequenceOp = new Operator(
"LIST:alt",
listOp.getSymbol(),
idFun,
flags2);
// Construct the overloaded operator
overOp = new OverloadedOperator(
"LIST:overloaded",
listOp.getSymbol(),
sequenceOp,
listOp,
n -> n.jjtGetChild(0).getOperator()==dotdotOp,
flags,
listOp.getPrecedence());
// Adds all three operators to the table.
// Using replaceOperator ensures this operator is used the the
// Operator tables getList() method.
ot.replaceOperator(listOp, overOp);
ot.addOperator(listOp, overOp);
ot.addOperator(sequenceOp, overOp);
jep.reinitializeComponents();
// Ensure correct rules are used for printing
var rule = jep.getPrintVisitor().getSpecialRule(listOp);
if(rule!=null) {
jep.getPrintVisitor().addSpecialRule(overOp, rule);
jep.getPrintVisitor().addSpecialRule(sequenceOp, rule);
}
var printSym = listOp.getPrintSymbol();
if(printSym!=null) {
overOp.setPrintSymbol(printSym);
sequenceOp.setPrintSymbol(printSym);
}
// Copy any alternate symbols used for parsing
var altSym = listOp.getAltSymbols();
if(altSym != null) {
for(var sym:altSym) {
overOp.addAltSymbol(sym);
sequenceOp.addAltSymbol(sym);
}
}
OverloadedFunctionByStructure
Operator dotdotOp = new Operator("..",new Range(),Operator.BINARY);
ot.appendOperator(dotdotOp, ot.getAssign());
var listOp = ot.getList();
var idFun = new Identity();
var listFun = listOp.getPFMC();
var overFun = new OverloadedFunctionByStructure(
idFun,
listFun,
n -> n.jjtGetChild(0).getOperator()==dotdotOp
);
listOp.setPFMC(overFun);
jep.reinitializeComponents();
node.getPFMC() == node.getOperator().getPFMC().
Using the OverloadedOperator maintains that assumption.
This affects visitors like the SubstitutionVisitor
which builds nodes using the Operator PFMC.
It also affects MacroFunction
which uses these visitor internally.- Since:
- Jep 4.1
- See Also:
-
ClassesClassDescriptionA FunctionGrammarMatcher that works with
AlternateFunctionsandOverloadedFunctionByStructureselecting the function at parse time.Allows function to be overloaded with multiple candidate functions chosen by the number of arguments.Allows function to be overloaded with two candidate functions and a predicate that tests the values of the arguments to see which function should be applied.Allows function to be overloaded with two candidate functions and a predicate that tests the structure of the parse tree to see which function should be applied.Allows function to be overloaded with two candidate functions and a predicate that tests the values of the arguments to see which function should be applied.An operator representing two alternative operators that can be used in alternative contexts based on the structure of the node.Allows function to be overloaded with two candidate functions and a predicate that tests the values of the arguments to see which function should be applied.ResolvesOverloadedOperator,OverloadedFunctionByStructureandAlternateFunctionssetting the operator or PostfixMathCommand of the node based on a test of the node.