Skip to content

Shahrukh931/expeval

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ExpEval

A light weight(<50Kb), customisable expression evaluator with no external dependencies.

Integration

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'

Overview



Key features:

  • 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

Supported Data types

  • Number (double and long)
  • Boolean (true and false)
  • String (Should be inside double quotes)
  • Null (null)

Quick Demo:

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());

Predefined Native Operators

OperatorDescriptionPrecedence
!Logical NOT51
+Unary plus51
-Unary minus51
~Bitwise NOT51
/Division50
%Modulus50
*Multiplication50
-Subtraction49
+Adddition49
<<Bitwise left shift48
>>Bitwise right shift48
<Less than47
>Greater than47
<=Less than or equal47
>=Greater than or equal47
!=Not equals46
==Equals46
&Bitwise AND45
^Bitwise XOR44
|Bitwise OR43
&&Logical AND42
||Logical OR42

Custom operators and functions

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());
 

Using Context

  • 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"));

About

A simple, light weight expression evaluator

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages