A light weight(<50Kb), customisable expression evaluator with no external dependencies.
Maven dependency
<dependency>
<groupId>com.shahrukh</groupId>
<artifactId>expeval</artifactId>
<version>1.0.1</version>
</dependency>
Gradle dependency
compile 'com.shahrukh:expeval:1.0.1'
- Key features
- Supported data types
- Quick demo
- Predefined native operators
- Custom operators and functions
- Using context
-
Support for variables.
-
Ability to add custom operators (unary and binary both supported) and custom functions at runtime.
-
Support for defining a context or scope in which the expression will be evaluated.
-
Compiled expression can be saved to avoid repetitive compilation for future evaluations.
-
Exceptions with detailed messages are thrown during expression compilation.
-
Custom functions can be defined with any number of arguments.
-
Out-of-the-box support for standard boolean and mathematical operators.
The expression is compiled against following grammar:
E -> E binary_operator T
E -> T
T -> unary_operator T
T -> Z
Z -> ( E )
Z -> id
Z -> fid ( )
Z -> fid ( F )
F -> F , E
F -> E
- Number (
double
andlong
) - Boolean (
true
andfalse
) - String (Should be inside double quotes)
- Null (
null
)
assertEquals(true, Expression.parse("\"a\" == \"a\"").eval());
assertEquals(0, Expression.parse("-1+1").eval());
assertEquals(2, Expression.parse("4/2").eval());
assertEquals(true, Expression.parse("3.9999999<=4 && 5.0000000001>5").eval());
asssertEquals(true, Expression.parse("true&&!false").eval());
Var x = new Var("x");
x.setValue(10);
Context.getDefault().registerVar(x);
Var myList = new Var("list");
List<Object> l = new ArrayList<Object>();
l.add(1);
l.add(true);
l.add("xyz");
myList.setValue(l);
Context.getDefault().registerVar(myList);
Context.getDefault().registerFunction(new Function("if") {
@Override
public Object onEvaluation(List<Object> arguments) {
if (arguments.size() == 1) {
return arguments.get(0);
} else {
Boolean result = (Boolean) arguments.get(0);
return result ? arguments.get(1) : arguments.get(2);
}
}
});
Context.getDefault().registerBinaryOperator(new BinaryOperator("contains", BinaryOperator.EQ.getPrecedence()) {
@Override
public Object onEvaluation(Object leftOperand, Object rightOperand) {
List<Object> list = (List<Object>) leftOperand;
for (Object o : list) {
if ((Boolean) BinaryOperator.EQ.onEvaluation(o, rightOperand)) {
return true;
}
}
return false;
}
});
Expression expression = Expression.parse("x+1");
assertEquals(10,expression.eval());
Context.getDefault().getVar("x").setValue(100);
assertEquals(101,expression.eval());
assertEquals(3,Expression.parse("if(2>1,3,4)").eval());
assertEquals(true,Expression.parse("list contains 1").eval());
assertEquals(2,Expression.parse("1+if(list contains true,1,2)").eval());
Operator | Description | Precedence |
---|---|---|
! | Logical NOT | 51 |
+ | Unary plus | 51 |
- | Unary minus | 51 |
~ | Bitwise NOT | 51 |
/ | Division | 50 |
% | Modulus | 50 |
* | Multiplication | 50 |
- | Subtraction | 49 |
+ | Adddition | 49 |
<< | Bitwise left shift | 48 |
>> | Bitwise right shift | 48 |
< | Less than | 47 |
> | Greater than | 47 |
<= | Less than or equal | 47 |
>= | Greater than or equal | 47 |
!= | Not equals | 46 |
== | Equals | 46 |
& | Bitwise AND | 45 |
^ | Bitwise XOR | 44 |
| | Bitwise OR | 43 |
&& | Logical AND | 42 |
|| | Logical OR | 42 |
Custom operators and functions can be registered to any defined context. The identifier or name of custom operator and function should be a valid java identifier, else an exception is thrown during registration.
Context.getDefault().registerUnaryOperator(new UnaryOperator("decBy2", UnaryOperator.NEGATE.getPrecedence()) {
@Override
public Object onEvaluation(Object rightOperand) {
List<Number> list = (List<Number>) rightOperand;
List<Double> result = new ArrayList<Double>();
for (Number number : list) {
result.add(number.doubleValue() - 2);
}
return result;
}
});
Context.getDefault().registerFunction(new Function("asList") {
@Override
public Object onEvaluation(List<Object> arguments) {
return arguments;
}
});
Context.getDefault().registerBinaryOperator(new BinaryOperator("contains", BinaryOperator.EQ.getPrecedence()) {
@Override
public Object onEvaluation(Object leftOperand, Object rightOperand) {
List<Object> list = (List<Object>) leftOperand;
for (Object o : list) {
if ((Boolean) BinaryOperator.EQ.onEvaluation(o, rightOperand)) {
return true;
}
}
return false;
}
});
assertEquals(true, Expression.parse("decBy2 asList(1,2,3) contains 0").eval());
- Context is an environment to which you can register your custom operators, functions and variables.
- Operators or functions registered to one context are not visible to another context.
- All
predefined native operators
are available to every context by default
Context.getDefault().registerFunction(new Function("sin") {
@Override
public Object onEvaluation(List<Object> arguments) {
return null;
}
});
Context context1 = Context.newContext("scope1");
context1.registerFunction(new Function("cos") {
@Override
public Object onEvaluation(List<Object> arguments) {
return null;
}
});
context1.registerBinaryOperator(new BinaryOperator("add", BinaryOperator.ADD.getPrecedence()) {
@Override
public Object onEvaluation(Object leftOperand, Object rightOperand) {
return null;
}
});
assertNotNull(Context.getDefault().getFunction("sin"));
assertNull(Context.getDefault().getFunction("cos"));
assertNull(Context.getDefault().getBinaryOperator("add"));
assertNotNull(Context.get("scope1").getFunction("cos"));
assertNotNull(Context.get("scope1").getBinaryOperator("add"));
assertNull(Context.get("scope1").getFunction("sin"));
Context.resetDefault();
assertNull(Context.getDefault().getFunction("sin"));
assertNull(Context.getDefault().getFunction("cos"));
assertNull(Context.getDefault().getBinaryOperator("add"));
assertNotNull(Context.get("scope1").getFunction("cos"));
assertNotNull(Context.get("scope1").getBinaryOperator("add"));
Context.removeContext("scope1");
assertNull(Context.get("scope1"));