mirror of
https://github.com/apache/druid.git
synced 2025-02-22 10:16:12 +00:00
math expression language with parser/lexer generated using ANTLR
This commit is contained in:
parent
36ccfbd20e
commit
308211cc18
@ -161,6 +161,10 @@
|
||||
<artifactId>jets3t</artifactId>
|
||||
<version>0.9.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.antlr</groupId>
|
||||
<artifactId>antlr4-runtime</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Tests -->
|
||||
<dependency>
|
||||
@ -199,6 +203,17 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.antlr</groupId>
|
||||
<artifactId>antlr4-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>antlr4</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
38
common/src/main/antlr4/io/druid/math/expr/antlr/Expr.g4
Normal file
38
common/src/main/antlr4/io/druid/math/expr/antlr/Expr.g4
Normal file
@ -0,0 +1,38 @@
|
||||
grammar Expr;
|
||||
|
||||
expr : ('-'|'!') expr # unaryOpExpr
|
||||
|<assoc=right> expr '^' expr # powOpExpr
|
||||
| expr ('*'|'/'|'%') expr # mulDivModuloExpr
|
||||
| expr ('+'|'-') expr # addSubExpr
|
||||
| expr ('<'|'<='|'>'|'>='|'=='|'!=') expr # logicalOpExpr
|
||||
| expr ('&&'|'||') expr # logicalAndOrExpr
|
||||
| '(' expr ')' # nestedExpr
|
||||
| IDENTIFIER '(' fnArgs? ')' # functionExpr
|
||||
| IDENTIFIER # identifierExpr
|
||||
| DOUBLE # doubleExpr
|
||||
| LONG # longExpr
|
||||
;
|
||||
|
||||
fnArgs : expr (',' expr)* # functionArgs
|
||||
;
|
||||
|
||||
IDENTIFIER : [_$a-zA-Z][_$a-zA-Z0-9]* ;
|
||||
LONG : [0-9]+ ;
|
||||
DOUBLE : [0-9]+ '.' [0-9]* ;
|
||||
WS : [ \t\r\n]+ -> skip ;
|
||||
|
||||
MINUS : '-' ;
|
||||
NOT : '!' ;
|
||||
POW : '^' ;
|
||||
MUL : '*' ;
|
||||
DIV : '/' ;
|
||||
MODULO : '%' ;
|
||||
PLUS : '+' ;
|
||||
LT : '<' ;
|
||||
LEQ : '<=' ;
|
||||
GT : '>' ;
|
||||
GEQ : '>=' ;
|
||||
EQ : '==' ;
|
||||
NEQ : '!=' ;
|
||||
AND : '&&' ;
|
||||
OR : '||' ;
|
@ -31,209 +31,11 @@ public interface Expr
|
||||
Number eval(Map<String, Number> bindings);
|
||||
}
|
||||
|
||||
class SimpleExpr implements Expr
|
||||
{
|
||||
private final Atom atom;
|
||||
|
||||
public SimpleExpr(Atom atom)
|
||||
{
|
||||
this.atom = atom;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return atom.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number eval(Map<String, Number> bindings)
|
||||
{
|
||||
return atom.eval(bindings);
|
||||
}
|
||||
}
|
||||
|
||||
class BinExpr implements Expr
|
||||
{
|
||||
private final Token opToken;
|
||||
private final Expr left;
|
||||
private final Expr right;
|
||||
|
||||
public BinExpr(Token opToken, Expr left, Expr right)
|
||||
{
|
||||
this.opToken = opToken;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
public Number eval(Map<String, Number> bindings)
|
||||
{
|
||||
switch(opToken.getType()) {
|
||||
case Token.AND:
|
||||
Number leftVal = left.eval(bindings);
|
||||
Number rightVal = right.eval(bindings);
|
||||
if (isLong(leftVal, rightVal)) {
|
||||
long lval = leftVal.longValue();
|
||||
if (lval > 0) {
|
||||
long rval = rightVal.longValue();
|
||||
return rval > 0 ? 1 : 0;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
double lval = leftVal.doubleValue();
|
||||
if (lval > 0) {
|
||||
double rval = rightVal.doubleValue();
|
||||
return rval > 0 ? 1.0d : 0.0d;
|
||||
} else {
|
||||
return 0.0d;
|
||||
}
|
||||
}
|
||||
case Token.OR:
|
||||
leftVal = left.eval(bindings);
|
||||
rightVal = right.eval(bindings);
|
||||
if (isLong(leftVal, rightVal)) {
|
||||
long lval = leftVal.longValue();
|
||||
if (lval > 0) {
|
||||
return 1;
|
||||
} else {
|
||||
long rval = rightVal.longValue();
|
||||
return rval > 0 ? 1 : 0;
|
||||
}
|
||||
} else {
|
||||
double lval = leftVal.doubleValue();
|
||||
if (lval > 0) {
|
||||
return 1.0d;
|
||||
} else {
|
||||
double rval = rightVal.doubleValue();
|
||||
return rval > 0 ? 1.0d : 0.0d;
|
||||
}
|
||||
}
|
||||
case Token.LT:
|
||||
leftVal = left.eval(bindings);
|
||||
rightVal = right.eval(bindings);
|
||||
if (isLong(leftVal, rightVal)) {
|
||||
return leftVal.longValue() < rightVal.longValue() ? 1 : 0;
|
||||
} else {
|
||||
return leftVal.doubleValue() < rightVal.doubleValue() ? 1.0d : 0.0d;
|
||||
}
|
||||
case Token.LEQ:
|
||||
leftVal = left.eval(bindings);
|
||||
rightVal = right.eval(bindings);
|
||||
if (isLong(leftVal, rightVal)) {
|
||||
return leftVal.longValue() <= rightVal.longValue() ? 1 : 0;
|
||||
} else {
|
||||
return leftVal.doubleValue() <= rightVal.doubleValue() ? 1.0d : 0.0d;
|
||||
}
|
||||
case Token.GT:
|
||||
leftVal = left.eval(bindings);
|
||||
rightVal = right.eval(bindings);
|
||||
if (isLong(leftVal, rightVal)) {
|
||||
return leftVal.longValue() > rightVal.longValue() ? 1 : 0;
|
||||
} else {
|
||||
return leftVal.doubleValue() > rightVal.doubleValue() ? 1.0d : 0.0d;
|
||||
}
|
||||
case Token.GEQ:
|
||||
leftVal = left.eval(bindings);
|
||||
rightVal = right.eval(bindings);
|
||||
if (isLong(leftVal, rightVal)) {
|
||||
return leftVal.longValue() >= rightVal.longValue() ? 1 : 0;
|
||||
} else {
|
||||
return leftVal.doubleValue() >= rightVal.doubleValue() ? 1.0d : 0.0d;
|
||||
}
|
||||
case Token.EQ:
|
||||
leftVal = left.eval(bindings);
|
||||
rightVal = right.eval(bindings);
|
||||
if (isLong(leftVal, rightVal)) {
|
||||
return leftVal.longValue() == rightVal.longValue() ? 1 : 0;
|
||||
} else {
|
||||
return leftVal.doubleValue() == rightVal.doubleValue() ? 1.0d : 0.0d;
|
||||
}
|
||||
case Token.NEQ:
|
||||
leftVal = left.eval(bindings);
|
||||
rightVal = right.eval(bindings);
|
||||
if (isLong(leftVal, rightVal)) {
|
||||
return leftVal.longValue() != rightVal.longValue() ? 1 : 0;
|
||||
} else {
|
||||
return leftVal.doubleValue() != rightVal.doubleValue() ? 1.0d : 0.0d;
|
||||
}
|
||||
case Token.PLUS:
|
||||
leftVal = left.eval(bindings);
|
||||
rightVal = right.eval(bindings);
|
||||
if (isLong(leftVal, rightVal)) {
|
||||
return leftVal.longValue() + rightVal.longValue();
|
||||
} else {
|
||||
return leftVal.doubleValue() + rightVal.doubleValue();
|
||||
}
|
||||
case Token.MINUS:
|
||||
leftVal = left.eval(bindings);
|
||||
rightVal = right.eval(bindings);
|
||||
if (isLong(leftVal, rightVal)) {
|
||||
return leftVal.longValue() - rightVal.longValue();
|
||||
} else {
|
||||
return leftVal.doubleValue() - rightVal.doubleValue();
|
||||
}
|
||||
case Token.MUL:
|
||||
leftVal = left.eval(bindings);
|
||||
rightVal = right.eval(bindings);
|
||||
if (isLong(leftVal, rightVal)) {
|
||||
return leftVal.longValue() * rightVal.longValue();
|
||||
} else {
|
||||
return leftVal.doubleValue() * rightVal.doubleValue();
|
||||
}
|
||||
case Token.DIV:
|
||||
leftVal = left.eval(bindings);
|
||||
rightVal = right.eval(bindings);
|
||||
if (isLong(leftVal, rightVal)) {
|
||||
return leftVal.longValue() / rightVal.longValue();
|
||||
} else {
|
||||
return leftVal.doubleValue() / rightVal.doubleValue();
|
||||
}
|
||||
case Token.MODULO:
|
||||
leftVal = left.eval(bindings);
|
||||
rightVal = right.eval(bindings);
|
||||
if (isLong(leftVal, rightVal)) {
|
||||
return leftVal.longValue() % rightVal.longValue();
|
||||
} else {
|
||||
return leftVal.doubleValue() % rightVal.doubleValue();
|
||||
}
|
||||
case Token.CARROT:
|
||||
leftVal = left.eval(bindings);
|
||||
rightVal = right.eval(bindings);
|
||||
if (isLong(leftVal, rightVal)) {
|
||||
return LongMath.pow(leftVal.longValue(), rightVal.intValue());
|
||||
} else {
|
||||
return Math.pow(leftVal.doubleValue(), rightVal.doubleValue());
|
||||
}
|
||||
default:
|
||||
throw new RuntimeException("Unknown operator " + opToken.getMatch());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isLong(Number left, Number right)
|
||||
{
|
||||
return left instanceof Long && right instanceof Long;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "(" + opToken.getMatch() + " " + left + " " + right + ")";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
interface Atom
|
||||
{
|
||||
Number eval(Map<String, Number> bindings);
|
||||
}
|
||||
|
||||
class LongValueAtom implements Atom
|
||||
class LongExpr implements Expr
|
||||
{
|
||||
private final long value;
|
||||
|
||||
public LongValueAtom(long value)
|
||||
public LongExpr(long value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
@ -251,11 +53,11 @@ class LongValueAtom implements Atom
|
||||
}
|
||||
}
|
||||
|
||||
class DoubleValueAtom implements Atom
|
||||
class DoubleExpr implements Expr
|
||||
{
|
||||
private final double value;
|
||||
|
||||
public DoubleValueAtom(double value)
|
||||
public DoubleExpr(double value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
@ -273,11 +75,11 @@ class DoubleValueAtom implements Atom
|
||||
}
|
||||
}
|
||||
|
||||
class IdentifierAtom implements Atom
|
||||
class IdentifierExpr implements Expr
|
||||
{
|
||||
private final Token value;
|
||||
private final String value;
|
||||
|
||||
public IdentifierAtom(Token value)
|
||||
public IdentifierExpr(String value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
@ -285,100 +87,27 @@ class IdentifierAtom implements Atom
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return value.getMatch();
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number eval(Map<String, Number> bindings)
|
||||
{
|
||||
Number val = bindings.get(value.getMatch());
|
||||
Number val = bindings.get(value);
|
||||
if (val == null) {
|
||||
throw new RuntimeException("No binding found for " + value.getMatch());
|
||||
throw new RuntimeException("No binding found for " + value);
|
||||
} else {
|
||||
return val;
|
||||
return val instanceof Long ? val : val.doubleValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class UnaryNotExprAtom implements Atom
|
||||
{
|
||||
private final Expr expr;
|
||||
|
||||
public UnaryNotExprAtom(Expr expr)
|
||||
{
|
||||
this.expr = expr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "!" + expr.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number eval(Map<String, Number> bindings)
|
||||
{
|
||||
Number valObj = expr.eval(bindings);
|
||||
return valObj.doubleValue() > 0 ? 0.0d : 1.0d;
|
||||
}
|
||||
}
|
||||
|
||||
class UnaryMinusExprAtom implements Atom
|
||||
{
|
||||
private final Expr expr;
|
||||
|
||||
public UnaryMinusExprAtom(Expr expr)
|
||||
{
|
||||
this.expr = expr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "-" + expr.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number eval(Map<String, Number> bindings)
|
||||
{
|
||||
Number valObj = expr.eval(bindings);
|
||||
if (valObj instanceof Long) {
|
||||
return -1 * valObj.longValue();
|
||||
} else {
|
||||
return -1 * valObj.doubleValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class NestedExprAtom implements Atom
|
||||
{
|
||||
private final Expr expr;
|
||||
|
||||
public NestedExprAtom(Expr expr)
|
||||
{
|
||||
this.expr = expr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return expr.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number eval(Map<String, Number> bindings)
|
||||
{
|
||||
return expr.eval(bindings);
|
||||
}
|
||||
}
|
||||
|
||||
class FunctionAtom implements Atom
|
||||
class FunctionExpr implements Expr
|
||||
{
|
||||
private final String name;
|
||||
private final List<Expr> args;
|
||||
|
||||
public FunctionAtom(String name, List<Expr> args)
|
||||
public FunctionExpr(String name, List<Expr> args)
|
||||
{
|
||||
this.name = name;
|
||||
this.args = args;
|
||||
@ -396,3 +125,396 @@ class FunctionAtom implements Atom
|
||||
return Parser.func.get(name).apply(args, bindings);
|
||||
}
|
||||
}
|
||||
|
||||
class UnaryMinusExpr implements Expr
|
||||
{
|
||||
private final Expr expr;
|
||||
|
||||
UnaryMinusExpr(Expr expr)
|
||||
{
|
||||
this.expr = expr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number eval(Map<String, Number> bindings)
|
||||
{
|
||||
Number valObj = expr.eval(bindings);
|
||||
if (valObj instanceof Long) {
|
||||
return -1 * valObj.longValue();
|
||||
} else {
|
||||
return -1 * valObj.doubleValue();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "-" + expr.toString();
|
||||
}
|
||||
}
|
||||
|
||||
class UnaryNotExpr implements Expr
|
||||
{
|
||||
private final Expr expr;
|
||||
|
||||
UnaryNotExpr(Expr expr)
|
||||
{
|
||||
this.expr = expr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number eval(Map<String, Number> bindings)
|
||||
{
|
||||
Number valObj = expr.eval(bindings);
|
||||
return valObj.doubleValue() > 0 ? 0.0d : 1.0d;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "!" + expr.toString();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class BinaryOpExprBase implements Expr
|
||||
{
|
||||
protected final String op;
|
||||
protected final Expr left;
|
||||
protected final Expr right;
|
||||
|
||||
public BinaryOpExprBase(String op, Expr left, Expr right)
|
||||
{
|
||||
this.op = op;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
protected boolean isLong(Number left, Number right)
|
||||
{
|
||||
return left instanceof Long && right instanceof Long;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "(" + op + " " + left + " " + right + ")";
|
||||
}
|
||||
}
|
||||
|
||||
class BinMinusExpr extends BinaryOpExprBase
|
||||
{
|
||||
|
||||
BinMinusExpr(String op, Expr left, Expr right)
|
||||
{
|
||||
super(op, left, right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number eval(Map<String, Number> bindings)
|
||||
{
|
||||
Number leftVal = left.eval(bindings);
|
||||
Number rightVal = right.eval(bindings);
|
||||
if (isLong(leftVal, rightVal)) {
|
||||
return leftVal.longValue() - rightVal.longValue();
|
||||
} else {
|
||||
return leftVal.doubleValue() - rightVal.doubleValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BinPowExpr extends BinaryOpExprBase
|
||||
{
|
||||
|
||||
BinPowExpr(String op, Expr left, Expr right)
|
||||
{
|
||||
super(op, left, right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number eval(Map<String, Number> bindings)
|
||||
{
|
||||
Number leftVal = left.eval(bindings);
|
||||
Number rightVal = right.eval(bindings);
|
||||
if (isLong(leftVal, rightVal)) {
|
||||
return LongMath.pow(leftVal.longValue(), rightVal.intValue());
|
||||
} else {
|
||||
return Math.pow(leftVal.doubleValue(), rightVal.doubleValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BinMulExpr extends BinaryOpExprBase
|
||||
{
|
||||
|
||||
BinMulExpr(String op, Expr left, Expr right)
|
||||
{
|
||||
super(op, left, right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number eval(Map<String, Number> bindings)
|
||||
{
|
||||
Number leftVal = left.eval(bindings);
|
||||
Number rightVal = right.eval(bindings);
|
||||
if (isLong(leftVal, rightVal)) {
|
||||
return leftVal.longValue() * rightVal.longValue();
|
||||
} else {
|
||||
return leftVal.doubleValue() * rightVal.doubleValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BinDivExpr extends BinaryOpExprBase
|
||||
{
|
||||
|
||||
BinDivExpr(String op, Expr left, Expr right)
|
||||
{
|
||||
super(op, left, right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number eval(Map<String, Number> bindings)
|
||||
{
|
||||
Number leftVal = left.eval(bindings);
|
||||
Number rightVal = right.eval(bindings);
|
||||
if (isLong(leftVal, rightVal)) {
|
||||
return leftVal.longValue() / rightVal.longValue();
|
||||
} else {
|
||||
return leftVal.doubleValue() / rightVal.doubleValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BinModuloExpr extends BinaryOpExprBase
|
||||
{
|
||||
|
||||
BinModuloExpr(String op, Expr left, Expr right)
|
||||
{
|
||||
super(op, left, right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number eval(Map<String, Number> bindings)
|
||||
{
|
||||
Number leftVal = left.eval(bindings);
|
||||
Number rightVal = right.eval(bindings);
|
||||
if (isLong(leftVal, rightVal)) {
|
||||
return leftVal.longValue() % rightVal.longValue();
|
||||
} else {
|
||||
return leftVal.doubleValue() % rightVal.doubleValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BinPlusExpr extends BinaryOpExprBase
|
||||
{
|
||||
|
||||
BinPlusExpr(String op, Expr left, Expr right)
|
||||
{
|
||||
super(op, left, right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number eval(Map<String, Number> bindings)
|
||||
{
|
||||
Number leftVal = left.eval(bindings);
|
||||
Number rightVal = right.eval(bindings);
|
||||
if (isLong(leftVal, rightVal)) {
|
||||
return leftVal.longValue() + rightVal.longValue();
|
||||
} else {
|
||||
return leftVal.doubleValue() + rightVal.doubleValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BinLtExpr extends BinaryOpExprBase
|
||||
{
|
||||
|
||||
BinLtExpr(String op, Expr left, Expr right)
|
||||
{
|
||||
super(op, left, right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number eval(Map<String, Number> bindings)
|
||||
{
|
||||
Number leftVal = left.eval(bindings);
|
||||
Number rightVal = right.eval(bindings);
|
||||
if (isLong(leftVal, rightVal)) {
|
||||
return leftVal.longValue() < rightVal.longValue() ? 1 : 0;
|
||||
} else {
|
||||
return leftVal.doubleValue() < rightVal.doubleValue() ? 1.0d : 0.0d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BinLeqExpr extends BinaryOpExprBase
|
||||
{
|
||||
|
||||
BinLeqExpr(String op, Expr left, Expr right)
|
||||
{
|
||||
super(op, left, right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number eval(Map<String, Number> bindings)
|
||||
{
|
||||
Number leftVal = left.eval(bindings);
|
||||
Number rightVal = right.eval(bindings);
|
||||
if (isLong(leftVal, rightVal)) {
|
||||
return leftVal.longValue() <= rightVal.longValue() ? 1 : 0;
|
||||
} else {
|
||||
return leftVal.doubleValue() <= rightVal.doubleValue() ? 1.0d : 0.0d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BinGtExpr extends BinaryOpExprBase
|
||||
{
|
||||
|
||||
BinGtExpr(String op, Expr left, Expr right)
|
||||
{
|
||||
super(op, left, right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number eval(Map<String, Number> bindings)
|
||||
{
|
||||
Number leftVal = left.eval(bindings);
|
||||
Number rightVal = right.eval(bindings);
|
||||
if (isLong(leftVal, rightVal)) {
|
||||
return leftVal.longValue() > rightVal.longValue() ? 1 : 0;
|
||||
} else {
|
||||
return leftVal.doubleValue() > rightVal.doubleValue() ? 1.0d : 0.0d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BinGeqExpr extends BinaryOpExprBase
|
||||
{
|
||||
|
||||
BinGeqExpr(String op, Expr left, Expr right)
|
||||
{
|
||||
super(op, left, right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number eval(Map<String, Number> bindings)
|
||||
{
|
||||
Number leftVal = left.eval(bindings);
|
||||
Number rightVal = right.eval(bindings);
|
||||
if (isLong(leftVal, rightVal)) {
|
||||
return leftVal.longValue() >= rightVal.longValue() ? 1 : 0;
|
||||
} else {
|
||||
return leftVal.doubleValue() >= rightVal.doubleValue() ? 1.0d : 0.0d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BinEqExpr extends BinaryOpExprBase
|
||||
{
|
||||
|
||||
BinEqExpr(String op, Expr left, Expr right)
|
||||
{
|
||||
super(op, left, right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number eval(Map<String, Number> bindings)
|
||||
{
|
||||
Number leftVal = left.eval(bindings);
|
||||
Number rightVal = right.eval(bindings);
|
||||
if (isLong(leftVal, rightVal)) {
|
||||
return leftVal.longValue() == rightVal.longValue() ? 1 : 0;
|
||||
} else {
|
||||
return leftVal.doubleValue() == rightVal.doubleValue() ? 1.0d : 0.0d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BinNeqExpr extends BinaryOpExprBase
|
||||
{
|
||||
|
||||
BinNeqExpr(String op, Expr left, Expr right)
|
||||
{
|
||||
super(op, left, right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number eval(Map<String, Number> bindings)
|
||||
{
|
||||
Number leftVal = left.eval(bindings);
|
||||
Number rightVal = right.eval(bindings);
|
||||
if (isLong(leftVal, rightVal)) {
|
||||
return leftVal.longValue() != rightVal.longValue() ? 1 : 0;
|
||||
} else {
|
||||
return leftVal.doubleValue() != rightVal.doubleValue() ? 1.0d : 0.0d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BinAndExpr extends BinaryOpExprBase
|
||||
{
|
||||
|
||||
BinAndExpr(String op, Expr left, Expr right)
|
||||
{
|
||||
super(op, left, right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number eval(Map<String, Number> bindings)
|
||||
{
|
||||
Number leftVal = left.eval(bindings);
|
||||
Number rightVal = right.eval(bindings);
|
||||
if (isLong(leftVal, rightVal)) {
|
||||
long lval = leftVal.longValue();
|
||||
if (lval > 0) {
|
||||
long rval = rightVal.longValue();
|
||||
return rval > 0 ? 1 : 0;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
double lval = leftVal.doubleValue();
|
||||
if (lval > 0) {
|
||||
double rval = rightVal.doubleValue();
|
||||
return rval > 0 ? 1.0d : 0.0d;
|
||||
} else {
|
||||
return 0.0d;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BinOrExpr extends BinaryOpExprBase
|
||||
{
|
||||
|
||||
BinOrExpr(String op, Expr left, Expr right)
|
||||
{
|
||||
super(op, left, right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number eval(Map<String, Number> bindings)
|
||||
{
|
||||
Number leftVal = left.eval(bindings);
|
||||
Number rightVal = right.eval(bindings);
|
||||
if (isLong(leftVal, rightVal)) {
|
||||
long lval = leftVal.longValue();
|
||||
if (lval > 0) {
|
||||
return 1;
|
||||
} else {
|
||||
long rval = rightVal.longValue();
|
||||
return rval > 0 ? 1 : 0;
|
||||
}
|
||||
} else {
|
||||
double lval = leftVal.doubleValue();
|
||||
if (lval > 0) {
|
||||
return 1.0d;
|
||||
} else {
|
||||
double rval = rightVal.doubleValue();
|
||||
return rval > 0 ? 1.0d : 0.0d;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
312
common/src/main/java/io/druid/math/expr/ExprListenerImpl.java
Normal file
312
common/src/main/java/io/druid/math/expr/ExprListenerImpl.java
Normal file
@ -0,0 +1,312 @@
|
||||
/*
|
||||
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. Metamarkets licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package io.druid.math.expr;
|
||||
|
||||
import io.druid.math.expr.antlr.ExprBaseListener;
|
||||
import io.druid.math.expr.antlr.ExprParser;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class ExprListenerImpl extends ExprBaseListener
|
||||
{
|
||||
private final Map<ParseTree, Object> nodes;
|
||||
private final ParseTree rootNodeKey;
|
||||
|
||||
ExprListenerImpl(ParseTree rootNodeKey)
|
||||
{
|
||||
this.rootNodeKey = rootNodeKey;
|
||||
this.nodes = new HashMap<>();
|
||||
}
|
||||
|
||||
Expr getAST()
|
||||
{
|
||||
return (Expr) nodes.get(rootNodeKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exitUnaryOpExpr(ExprParser.UnaryOpExprContext ctx)
|
||||
{
|
||||
int opCode = ((TerminalNode) ctx.getChild(0)).getSymbol().getType();
|
||||
switch (opCode) {
|
||||
case ExprParser.MINUS:
|
||||
nodes.put(ctx, new UnaryMinusExpr((Expr) nodes.get(ctx.getChild(1))));
|
||||
break;
|
||||
case ExprParser.NOT:
|
||||
nodes.put(ctx, new UnaryNotExpr((Expr) nodes.get(ctx.getChild(1))));
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Unrecognized unary operator " + ctx.getChild(0).getText());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exitDoubleExpr(ExprParser.DoubleExprContext ctx)
|
||||
{
|
||||
nodes.put(
|
||||
ctx,
|
||||
new DoubleExpr(Double.parseDouble(ctx.getText()))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exitAddSubExpr(ExprParser.AddSubExprContext ctx)
|
||||
{
|
||||
int opCode = ((TerminalNode) ctx.getChild(1)).getSymbol().getType();
|
||||
switch (opCode) {
|
||||
case ExprParser.PLUS:
|
||||
nodes.put(
|
||||
ctx,
|
||||
new BinPlusExpr(
|
||||
ctx.getChild(1).getText(),
|
||||
(Expr) nodes.get(ctx.getChild(0)),
|
||||
(Expr) nodes.get(ctx.getChild(2))
|
||||
)
|
||||
);
|
||||
break;
|
||||
case ExprParser.MINUS:
|
||||
nodes.put(
|
||||
ctx,
|
||||
new BinMinusExpr(
|
||||
ctx.getChild(1).getText(),
|
||||
(Expr) nodes.get(ctx.getChild(0)),
|
||||
(Expr) nodes.get(ctx.getChild(2))
|
||||
)
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Unrecognized binary operator " + ctx.getChild(1).getText());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exitLongExpr(ExprParser.LongExprContext ctx)
|
||||
{
|
||||
nodes.put(
|
||||
ctx,
|
||||
new LongExpr(Long.parseLong(ctx.getText()))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exitLogicalAndOrExpr(ExprParser.LogicalAndOrExprContext ctx)
|
||||
{
|
||||
int opCode = ((TerminalNode) ctx.getChild(1)).getSymbol().getType();
|
||||
switch (opCode) {
|
||||
case ExprParser.AND:
|
||||
nodes.put(
|
||||
ctx,
|
||||
new BinAndExpr(
|
||||
ctx.getChild(1).getText(),
|
||||
(Expr) nodes.get(ctx.getChild(0)),
|
||||
(Expr) nodes.get(ctx.getChild(2))
|
||||
)
|
||||
);
|
||||
break;
|
||||
case ExprParser.OR:
|
||||
nodes.put(
|
||||
ctx,
|
||||
new BinOrExpr(
|
||||
ctx.getChild(1).getText(),
|
||||
(Expr) nodes.get(ctx.getChild(0)),
|
||||
(Expr) nodes.get(ctx.getChild(2))
|
||||
)
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Unrecognized binary operator " + ctx.getChild(1).getText());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exitNestedExpr(ExprParser.NestedExprContext ctx)
|
||||
{
|
||||
nodes.put(ctx, nodes.get(ctx.getChild(1)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exitLogicalOpExpr(ExprParser.LogicalOpExprContext ctx)
|
||||
{
|
||||
int opCode = ((TerminalNode) ctx.getChild(1)).getSymbol().getType();
|
||||
switch (opCode) {
|
||||
case ExprParser.LT:
|
||||
nodes.put(
|
||||
ctx,
|
||||
new BinLtExpr(
|
||||
ctx.getChild(1).getText(),
|
||||
(Expr) nodes.get(ctx.getChild(0)),
|
||||
(Expr) nodes.get(ctx.getChild(2))
|
||||
)
|
||||
);
|
||||
break;
|
||||
case ExprParser.LEQ:
|
||||
nodes.put(
|
||||
ctx,
|
||||
new BinLeqExpr(
|
||||
ctx.getChild(1).getText(),
|
||||
(Expr) nodes.get(ctx.getChild(0)),
|
||||
(Expr) nodes.get(ctx.getChild(2))
|
||||
)
|
||||
);
|
||||
break;
|
||||
case ExprParser.GT:
|
||||
nodes.put(
|
||||
ctx,
|
||||
new BinGtExpr(
|
||||
ctx.getChild(1).getText(),
|
||||
(Expr) nodes.get(ctx.getChild(0)),
|
||||
(Expr) nodes.get(ctx.getChild(2))
|
||||
)
|
||||
);
|
||||
break;
|
||||
case ExprParser.GEQ:
|
||||
nodes.put(
|
||||
ctx,
|
||||
new BinGeqExpr(
|
||||
ctx.getChild(1).getText(),
|
||||
(Expr) nodes.get(ctx.getChild(0)),
|
||||
(Expr) nodes.get(ctx.getChild(2))
|
||||
)
|
||||
);
|
||||
break;
|
||||
case ExprParser.EQ:
|
||||
nodes.put(
|
||||
ctx,
|
||||
new BinEqExpr(
|
||||
ctx.getChild(1).getText(),
|
||||
(Expr) nodes.get(ctx.getChild(0)),
|
||||
(Expr) nodes.get(ctx.getChild(2))
|
||||
)
|
||||
);
|
||||
break;
|
||||
case ExprParser.NEQ:
|
||||
nodes.put(
|
||||
ctx,
|
||||
new BinNeqExpr(
|
||||
ctx.getChild(1).getText(),
|
||||
(Expr) nodes.get(ctx.getChild(0)),
|
||||
(Expr) nodes.get(ctx.getChild(2))
|
||||
)
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Unrecognized binary operator " + ctx.getChild(1).getText());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exitMulDivModuloExpr(ExprParser.MulDivModuloExprContext ctx)
|
||||
{
|
||||
int opCode = ((TerminalNode) ctx.getChild(1)).getSymbol().getType();
|
||||
switch (opCode) {
|
||||
case ExprParser.MUL:
|
||||
nodes.put(
|
||||
ctx,
|
||||
new BinMulExpr(
|
||||
ctx.getChild(1).getText(),
|
||||
(Expr) nodes.get(ctx.getChild(0)),
|
||||
(Expr) nodes.get(ctx.getChild(2))
|
||||
)
|
||||
);
|
||||
break;
|
||||
case ExprParser.DIV:
|
||||
nodes.put(
|
||||
ctx,
|
||||
new BinDivExpr(
|
||||
ctx.getChild(1).getText(),
|
||||
(Expr) nodes.get(ctx.getChild(0)),
|
||||
(Expr) nodes.get(ctx.getChild(2))
|
||||
)
|
||||
);
|
||||
break;
|
||||
case ExprParser.MODULO:
|
||||
nodes.put(
|
||||
ctx,
|
||||
new BinModuloExpr(
|
||||
ctx.getChild(1).getText(),
|
||||
(Expr) nodes.get(ctx.getChild(0)),
|
||||
(Expr) nodes.get(ctx.getChild(2))
|
||||
)
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Unrecognized binary operator " + ctx.getChild(1).getText());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exitPowOpExpr(ExprParser.PowOpExprContext ctx)
|
||||
{
|
||||
nodes.put(
|
||||
ctx,
|
||||
new BinPowExpr(
|
||||
ctx.getChild(1).getText(),
|
||||
(Expr) nodes.get(ctx.getChild(0)),
|
||||
(Expr) nodes.get(ctx.getChild(2))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exitFunctionExpr(ExprParser.FunctionExprContext ctx)
|
||||
{
|
||||
String fnName = ctx.getChild(0).getText();
|
||||
if (!Parser.func.containsKey(fnName)) {
|
||||
throw new RuntimeException("function " + fnName + " is not defined.");
|
||||
}
|
||||
|
||||
List<Expr> args = ctx.getChildCount() > 3 ? (List<Expr>) nodes.get(ctx.getChild(2)) : Collections.<Expr>emptyList();
|
||||
nodes.put(
|
||||
ctx,
|
||||
new FunctionExpr(fnName, args)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exitIdentifierExpr(ExprParser.IdentifierExprContext ctx)
|
||||
{
|
||||
nodes.put(
|
||||
ctx,
|
||||
new IdentifierExpr(ctx.getText())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exitFunctionArgs(ExprParser.FunctionArgsContext ctx)
|
||||
{
|
||||
List<Expr> args = new ArrayList<>();
|
||||
args.add((Expr) nodes.get(ctx.getChild(0)));
|
||||
|
||||
if (ctx.getChildCount() > 1) {
|
||||
for (int i = 1; i <= ctx.getChildCount() / 2; i++) {
|
||||
args.add((Expr) nodes.get(ctx.getChild(2 * i)));
|
||||
}
|
||||
}
|
||||
|
||||
nodes.put(ctx, args);
|
||||
}
|
||||
}
|
@ -1,229 +0,0 @@
|
||||
/*
|
||||
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. Metamarkets licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package io.druid.math.expr;
|
||||
|
||||
class Lexer
|
||||
{
|
||||
private String input;
|
||||
private int currPos;
|
||||
|
||||
private Token next;
|
||||
|
||||
Lexer(String input)
|
||||
{
|
||||
this.input = input;
|
||||
currPos = 0;
|
||||
|
||||
next = nextToken();
|
||||
}
|
||||
|
||||
Token peek()
|
||||
{
|
||||
return next;
|
||||
}
|
||||
|
||||
Token consume()
|
||||
{
|
||||
Token old = next;
|
||||
next = nextToken();
|
||||
return old;
|
||||
}
|
||||
|
||||
private Token nextToken()
|
||||
{
|
||||
if (currPos >= input.length()) {
|
||||
return new Token(Token.EOF);
|
||||
}
|
||||
|
||||
char c = input.charAt(currPos);
|
||||
while(c == ' ') {
|
||||
currPos++;
|
||||
if (currPos >= input.length()) {
|
||||
return new Token(Token.EOF);
|
||||
}
|
||||
c = input.charAt(currPos);
|
||||
}
|
||||
|
||||
switch(c)
|
||||
{
|
||||
case '<':
|
||||
currPos++;
|
||||
if (currPos < input.length() && input.charAt(currPos) == '=') {
|
||||
currPos++;
|
||||
return new Token(Token.LEQ, "<=");
|
||||
} else {
|
||||
return new Token(Token.LT, "<");
|
||||
}
|
||||
case '>':
|
||||
currPos++;
|
||||
if (currPos < input.length() && input.charAt(currPos) == '=') {
|
||||
currPos++;
|
||||
return new Token(Token.GEQ, ">=");
|
||||
} else {
|
||||
return new Token(Token.GT, ">");
|
||||
}
|
||||
case '=':
|
||||
currPos++;
|
||||
if (currPos < input.length() && input.charAt(currPos) == '=') {
|
||||
currPos++;
|
||||
return new Token(Token.EQ, "==");
|
||||
} else {
|
||||
throw new IllegalArgumentException("unknown operator '='");
|
||||
}
|
||||
case '!':
|
||||
currPos++;
|
||||
if (currPos < input.length() && input.charAt(currPos) == '=') {
|
||||
currPos++;
|
||||
return new Token(Token.NEQ, "!=");
|
||||
} else {
|
||||
return new Token(Token.NOT, "!");
|
||||
}
|
||||
|
||||
case '+':
|
||||
currPos++;
|
||||
return new Token(Token.PLUS, "+");
|
||||
case '-':
|
||||
currPos++;
|
||||
return new Token(Token.MINUS, "-");
|
||||
|
||||
case '*':
|
||||
currPos++;
|
||||
return new Token(Token.MUL, "*");
|
||||
case '/':
|
||||
currPos++;
|
||||
return new Token(Token.DIV, "/");
|
||||
case '%':
|
||||
currPos++;
|
||||
return new Token(Token.MODULO, "%");
|
||||
|
||||
case '^':
|
||||
currPos++;
|
||||
return new Token(Token.CARROT, "^");
|
||||
|
||||
case '(':
|
||||
currPos++;
|
||||
return new Token(Token.LPAREN, "(");
|
||||
case ')':
|
||||
currPos++;
|
||||
return new Token(Token.RPAREN, ")");
|
||||
case ',':
|
||||
currPos++;
|
||||
return new Token(Token.COMMA, ",");
|
||||
|
||||
default:
|
||||
if (isNumberStartingChar(c)) {
|
||||
return parseNumber();
|
||||
} else if (isIdentifierStartingChar(c)){
|
||||
return parseIdentifierOrKeyword();
|
||||
} else {
|
||||
throw new RuntimeException("Illegal expression " + toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isNumberStartingChar(char c)
|
||||
{
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
private boolean isIdentifierStartingChar(char c)
|
||||
{
|
||||
return (c >= 'a' && c <= 'z') ||
|
||||
(c >= 'A' && c <= 'Z') ||
|
||||
c == '$' ||
|
||||
c == '_';
|
||||
}
|
||||
|
||||
private boolean isIdentifierChar(char c)
|
||||
{
|
||||
return (c >= 'a' && c <= 'z') ||
|
||||
(c >= 'A' && c <= 'Z') ||
|
||||
(c >= '0' && c <= '9') ||
|
||||
c == '$' ||
|
||||
c == '_';
|
||||
}
|
||||
|
||||
private Token parseIdentifierOrKeyword()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
char c = input.charAt(currPos++);
|
||||
while (isIdentifierChar(c)) {
|
||||
sb.append(c);
|
||||
|
||||
if (currPos < input.length()) {
|
||||
c = input.charAt(currPos++);
|
||||
} else {
|
||||
currPos++;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
currPos--;
|
||||
|
||||
String str = sb.toString();
|
||||
if(str.equals("and")) {
|
||||
return new Token(Token.AND, str);
|
||||
} else if(str.equals("or")) {
|
||||
return new Token(Token.OR, str);
|
||||
} else {
|
||||
return new Token(Token.IDENTIFIER, sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// Numbers
|
||||
// long : [0-9]+
|
||||
// double : [0-9]+.[0-9]+
|
||||
private Token parseNumber()
|
||||
{
|
||||
boolean isLong = true;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
char c = input.charAt(currPos++);
|
||||
|
||||
while (
|
||||
('0' <= c && c <= '9') || c == '.'
|
||||
) {
|
||||
if (c == '.') {
|
||||
isLong = false;
|
||||
}
|
||||
|
||||
sb.append(c);
|
||||
|
||||
if (currPos < input.length()) {
|
||||
c = input.charAt(currPos++);
|
||||
} else {
|
||||
currPos++;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
currPos--;
|
||||
if (isLong) {
|
||||
return new Token(Token.LONG, sb.toString());
|
||||
} else {
|
||||
return new Token(Token.DOUBLE, sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "at " + currPos + ", " + input;
|
||||
}
|
||||
}
|
@ -20,108 +20,37 @@
|
||||
package io.druid.math.expr;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import io.druid.math.expr.antlr.ExprLexer;
|
||||
import io.druid.math.expr.antlr.ExprParser;
|
||||
import org.antlr.v4.runtime.ANTLRInputStream;
|
||||
import org.antlr.v4.runtime.CommonTokenStream;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
import org.antlr.v4.runtime.tree.ParseTreeWalker;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class Parser
|
||||
{
|
||||
static final Map<String, Function> func = new HashMap<>();
|
||||
static final Map<String, Function> func;
|
||||
|
||||
static {
|
||||
func.put("sqrt", new SqrtFunc());
|
||||
func.put("if", new ConditionFunc());
|
||||
func = ImmutableMap.<String, Function>builder()
|
||||
.put("sqrt", new SqrtFunc())
|
||||
.put("if", new ConditionFunc())
|
||||
.build();
|
||||
}
|
||||
|
||||
private final Lexer lexer;
|
||||
|
||||
private Parser(Lexer lexer)
|
||||
public static Expr parse(String in)
|
||||
{
|
||||
this.lexer = lexer;
|
||||
}
|
||||
|
||||
public static Expr parse(String input)
|
||||
{
|
||||
return new Parser(new Lexer(input)).parseExpr(0);
|
||||
}
|
||||
|
||||
private Expr parseExpr(int p)
|
||||
{
|
||||
Expr result = new SimpleExpr(parseAtom());
|
||||
|
||||
Token t = lexer.peek();
|
||||
while (
|
||||
t.getType() < Token.NUM_BINARY_OPERATORS
|
||||
&&
|
||||
Token.PRECEDENCE[t.getType()] >= p
|
||||
) {
|
||||
lexer.consume();
|
||||
result = new BinExpr(
|
||||
t,
|
||||
result,
|
||||
parseExpr(Token.R_PRECEDENCE[t.getType()])
|
||||
);
|
||||
|
||||
t = lexer.peek();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Atom parseAtom()
|
||||
{
|
||||
Token t = lexer.peek();
|
||||
|
||||
switch(t.getType()) {
|
||||
case Token.IDENTIFIER:
|
||||
lexer.consume();
|
||||
String id = t.getMatch();
|
||||
|
||||
if (func.containsKey(id)) {
|
||||
expect(Token.LPAREN);
|
||||
List<Expr> args = new ArrayList<>();
|
||||
t = lexer.peek();
|
||||
while(t.getType() != Token.RPAREN) {
|
||||
args.add(parseExpr(0));
|
||||
t = lexer.peek();
|
||||
if (t.getType() == Token.COMMA) {
|
||||
lexer.consume();
|
||||
}
|
||||
}
|
||||
expect(Token.RPAREN);
|
||||
return new FunctionAtom(id, args);
|
||||
}
|
||||
|
||||
return new IdentifierAtom(t);
|
||||
case Token.LONG:
|
||||
lexer.consume();
|
||||
return new LongValueAtom(Long.valueOf(t.getMatch()));
|
||||
case Token.DOUBLE:
|
||||
lexer.consume();
|
||||
return new DoubleValueAtom(Double.valueOf(t.getMatch()));
|
||||
case Token.MINUS:
|
||||
lexer.consume();
|
||||
return new UnaryMinusExprAtom(parseExpr(Token.UNARY_MINUS_PRECEDENCE));
|
||||
case Token.NOT:
|
||||
lexer.consume();
|
||||
return new UnaryNotExprAtom(parseExpr(Token.UNARY_NOT_PRECEDENCE));
|
||||
case Token.LPAREN:
|
||||
lexer.consume();
|
||||
Expr expression = parseExpr(0);
|
||||
if(lexer.consume().getType() == Token.RPAREN) {
|
||||
return new NestedExprAtom(expression);
|
||||
}
|
||||
default:
|
||||
throw new RuntimeException("Invalid token found " + t + " in input " + lexer);
|
||||
}
|
||||
}
|
||||
|
||||
private void expect(int type)
|
||||
{
|
||||
Token t = lexer.consume();
|
||||
if(t.getType() != type) {
|
||||
throw new RuntimeException("Invalid token found " + t + " in input " + lexer);
|
||||
}
|
||||
ExprLexer lexer = new ExprLexer(new ANTLRInputStream(in));
|
||||
CommonTokenStream tokens = new CommonTokenStream(lexer);
|
||||
ExprParser parser = new ExprParser(tokens);
|
||||
parser.setBuildParseTree(true);
|
||||
ParseTree parseTree = parser.expr();
|
||||
ParseTreeWalker walker = new ParseTreeWalker();
|
||||
ExprListenerImpl listener = new ExprListenerImpl(parseTree);
|
||||
walker.walk(listener, parseTree);
|
||||
return listener.getAST();
|
||||
}
|
||||
}
|
||||
|
@ -1,180 +0,0 @@
|
||||
/*
|
||||
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. Metamarkets licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package io.druid.math.expr;
|
||||
|
||||
/**
|
||||
*/
|
||||
class Token
|
||||
{
|
||||
static final int AND = 0;
|
||||
static final int OR = 1;
|
||||
|
||||
static final int LT = 2;
|
||||
static final int LEQ = 3;
|
||||
static final int GT = 4;
|
||||
static final int GEQ = 5;
|
||||
static final int EQ = 6;
|
||||
static final int NEQ = 7;
|
||||
|
||||
static final int PLUS = 8;
|
||||
static final int MINUS = 9;
|
||||
|
||||
static final int MUL = 10;
|
||||
static final int DIV = 11;
|
||||
static final int MODULO = 12;
|
||||
|
||||
static final int CARROT = 13;
|
||||
static final int NUM_BINARY_OPERATORS = 14;
|
||||
|
||||
static final int LPAREN = 51;
|
||||
static final int RPAREN = 52;
|
||||
static final int COMMA = 53;
|
||||
static final int NOT = 54;
|
||||
static final int IDENTIFIER = 55;
|
||||
static final int LONG = 56;
|
||||
static final int DOUBLE = 57;
|
||||
|
||||
static final int EOF = 100;
|
||||
|
||||
static final int[] PRECEDENCE;
|
||||
static final int[] R_PRECEDENCE;
|
||||
static final int UNARY_MINUS_PRECEDENCE;
|
||||
static final int UNARY_NOT_PRECEDENCE;
|
||||
|
||||
static {
|
||||
PRECEDENCE = new int[NUM_BINARY_OPERATORS];
|
||||
R_PRECEDENCE = new int[NUM_BINARY_OPERATORS];
|
||||
//R_RECEDENCE = (op is left-associative) ? PRECEDENCE + 1 : PRECEDENCE
|
||||
|
||||
int precedenceCounter = 0;
|
||||
|
||||
PRECEDENCE[AND] = precedenceCounter;
|
||||
R_PRECEDENCE[AND] = PRECEDENCE[AND] + 1;
|
||||
|
||||
PRECEDENCE[OR] = precedenceCounter;
|
||||
R_PRECEDENCE[OR] = PRECEDENCE[OR] + 1;
|
||||
|
||||
precedenceCounter++;
|
||||
|
||||
PRECEDENCE[EQ] = precedenceCounter;
|
||||
R_PRECEDENCE[EQ] = PRECEDENCE[EQ] + 1;
|
||||
|
||||
PRECEDENCE[NEQ] = precedenceCounter;
|
||||
R_PRECEDENCE[NEQ] = PRECEDENCE[NEQ] + 1;
|
||||
|
||||
PRECEDENCE[LT] = precedenceCounter;
|
||||
R_PRECEDENCE[LT] = PRECEDENCE[LT] + 1;
|
||||
|
||||
PRECEDENCE[LEQ] = precedenceCounter;
|
||||
R_PRECEDENCE[LEQ] = PRECEDENCE[LEQ] + 1;
|
||||
|
||||
PRECEDENCE[GT] = precedenceCounter;
|
||||
R_PRECEDENCE[GT] = PRECEDENCE[GT] + 1;
|
||||
|
||||
PRECEDENCE[GEQ] = precedenceCounter;
|
||||
R_PRECEDENCE[GEQ] = PRECEDENCE[GEQ] + 1;
|
||||
|
||||
precedenceCounter++;
|
||||
|
||||
PRECEDENCE[PLUS] = precedenceCounter;
|
||||
R_PRECEDENCE[PLUS] = PRECEDENCE[PLUS] + 1;
|
||||
|
||||
PRECEDENCE[MINUS] = precedenceCounter;
|
||||
R_PRECEDENCE[MINUS] = PRECEDENCE[MINUS] + 1;
|
||||
|
||||
precedenceCounter++;
|
||||
|
||||
PRECEDENCE[MUL] = precedenceCounter;
|
||||
R_PRECEDENCE[MUL] = PRECEDENCE[MUL] + 1;
|
||||
|
||||
PRECEDENCE[DIV] = precedenceCounter;
|
||||
R_PRECEDENCE[DIV] = PRECEDENCE[DIV] + 1;
|
||||
|
||||
PRECEDENCE[MODULO] = precedenceCounter;
|
||||
R_PRECEDENCE[MODULO] = PRECEDENCE[MODULO] + 1;
|
||||
|
||||
precedenceCounter++;
|
||||
|
||||
PRECEDENCE[CARROT] = precedenceCounter;
|
||||
R_PRECEDENCE[CARROT] = PRECEDENCE[CARROT];
|
||||
|
||||
precedenceCounter++;
|
||||
|
||||
UNARY_MINUS_PRECEDENCE = precedenceCounter;
|
||||
UNARY_NOT_PRECEDENCE = precedenceCounter;
|
||||
}
|
||||
|
||||
private final int type;
|
||||
private final String match;
|
||||
|
||||
Token(int type)
|
||||
{
|
||||
this(type, "");
|
||||
}
|
||||
|
||||
Token(int type, String match)
|
||||
{
|
||||
this.type = type;
|
||||
this.match = match;
|
||||
}
|
||||
|
||||
int getType()
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
String getMatch()
|
||||
{
|
||||
return match;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return match;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Token token = (Token) o;
|
||||
|
||||
if (type != token.type) {
|
||||
return false;
|
||||
}
|
||||
return !(match != null ? !match.equals(token.match) : token.match != null);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
int result = type;
|
||||
result = 31 * result + (match != null ? match.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
@ -37,11 +37,11 @@ public class EvalTest
|
||||
|
||||
Assert.assertEquals(2.0, Parser.parse("x").eval(bindings).doubleValue(), 0.0001);
|
||||
|
||||
Assert.assertFalse(Parser.parse("1.0 and 0.0").eval(bindings).doubleValue() > 0.0);
|
||||
Assert.assertTrue(Parser.parse("1.0 and 2.0").eval(bindings).doubleValue() > 0.0);
|
||||
Assert.assertFalse(Parser.parse("1.0 && 0.0").eval(bindings).doubleValue() > 0.0);
|
||||
Assert.assertTrue(Parser.parse("1.0 && 2.0").eval(bindings).doubleValue() > 0.0);
|
||||
|
||||
Assert.assertTrue(Parser.parse("1.0 or 0.0").eval(bindings).doubleValue() > 0.0);
|
||||
Assert.assertFalse(Parser.parse("0.0 or 0.0").eval(bindings).doubleValue() > 0.0);
|
||||
Assert.assertTrue(Parser.parse("1.0 || 0.0").eval(bindings).doubleValue() > 0.0);
|
||||
Assert.assertFalse(Parser.parse("0.0 || 0.0").eval(bindings).doubleValue() > 0.0);
|
||||
|
||||
Assert.assertTrue(Parser.parse("2.0 > 1.0").eval(bindings).doubleValue() > 0.0);
|
||||
Assert.assertTrue(Parser.parse("2.0 >= 2.0").eval(bindings).doubleValue() > 0.0);
|
||||
@ -71,17 +71,17 @@ public class EvalTest
|
||||
public void testLongEval()
|
||||
{
|
||||
Map<String, Number> bindings = new HashMap<>();
|
||||
bindings.put("x", 9223372036854775807l);
|
||||
bindings.put("x", 9223372036854775807L);
|
||||
|
||||
Assert.assertEquals(9223372036854775807l, Parser.parse("x").eval(bindings).longValue());
|
||||
Assert.assertEquals(9223372036854775807L, Parser.parse("x").eval(bindings).longValue());
|
||||
|
||||
Assert.assertFalse(Parser.parse("9223372036854775807 and 0").eval(bindings).longValue() > 0);
|
||||
Assert.assertTrue(Parser.parse("9223372036854775807 and 9223372036854775806").eval(bindings).longValue() > 0);
|
||||
Assert.assertFalse(Parser.parse("9223372036854775807 && 0").eval(bindings).longValue() > 0);
|
||||
Assert.assertTrue(Parser.parse("9223372036854775807 && 9223372036854775806").eval(bindings).longValue() > 0);
|
||||
|
||||
Assert.assertTrue(Parser.parse("9223372036854775807 or 0").eval(bindings).longValue() > 0);
|
||||
Assert.assertFalse(Parser.parse("-9223372036854775807 or -9223372036854775807").eval(bindings).longValue() > 0);
|
||||
Assert.assertTrue(Parser.parse("-9223372036854775807 or 9223372036854775807").eval(bindings).longValue() > 0);
|
||||
Assert.assertFalse(Parser.parse("0 or 0").eval(bindings).longValue() > 0);
|
||||
Assert.assertTrue(Parser.parse("9223372036854775807 || 0").eval(bindings).longValue() > 0);
|
||||
Assert.assertFalse(Parser.parse("-9223372036854775807 || -9223372036854775807").eval(bindings).longValue() > 0);
|
||||
Assert.assertTrue(Parser.parse("-9223372036854775807 || 9223372036854775807").eval(bindings).longValue() > 0);
|
||||
Assert.assertFalse(Parser.parse("0 || 0").eval(bindings).longValue() > 0);
|
||||
|
||||
Assert.assertTrue(Parser.parse("9223372036854775807 > 9223372036854775806").eval(bindings).longValue() > 0);
|
||||
Assert.assertTrue(Parser.parse("9223372036854775807 >= 9223372036854775807").eval(bindings).longValue() > 0);
|
||||
@ -90,20 +90,20 @@ public class EvalTest
|
||||
Assert.assertTrue(Parser.parse("9223372036854775807 == 9223372036854775807").eval(bindings).longValue() > 0);
|
||||
Assert.assertTrue(Parser.parse("9223372036854775807 != 9223372036854775806").eval(bindings).longValue() > 0);
|
||||
|
||||
Assert.assertEquals(9223372036854775807l, Parser.parse("9223372036854775806 + 1").eval(bindings).longValue());
|
||||
Assert.assertEquals(9223372036854775806l, Parser.parse("9223372036854775807 - 1").eval(bindings).longValue());
|
||||
Assert.assertEquals(9223372036854775806l, Parser.parse("4611686018427387903 * 2").eval(bindings).longValue());
|
||||
Assert.assertEquals(4611686018427387903l, Parser.parse("9223372036854775806 / 2").eval(bindings).longValue());
|
||||
Assert.assertEquals(7, Parser.parse("9223372036854775807 % 9223372036854775800").eval(bindings).longValue());
|
||||
Assert.assertEquals( 9223372030926249001l, Parser.parse("3037000499 ^ 2").eval(bindings).longValue());
|
||||
Assert.assertEquals(-9223372036854775807l, Parser.parse("-9223372036854775807").eval(bindings).longValue());
|
||||
Assert.assertEquals(9223372036854775807L, Parser.parse("9223372036854775806 + 1").eval(bindings).longValue());
|
||||
Assert.assertEquals(9223372036854775806L, Parser.parse("9223372036854775807 - 1").eval(bindings).longValue());
|
||||
Assert.assertEquals(9223372036854775806L, Parser.parse("4611686018427387903 * 2").eval(bindings).longValue());
|
||||
Assert.assertEquals(4611686018427387903L, Parser.parse("9223372036854775806 / 2").eval(bindings).longValue());
|
||||
Assert.assertEquals(7L, Parser.parse("9223372036854775807 % 9223372036854775800").eval(bindings).longValue());
|
||||
Assert.assertEquals( 9223372030926249001L, Parser.parse("3037000499 ^ 2").eval(bindings).longValue());
|
||||
Assert.assertEquals(-9223372036854775807L, Parser.parse("-9223372036854775807").eval(bindings).longValue());
|
||||
|
||||
Assert.assertTrue(Parser.parse("!-9223372036854775807").eval(bindings).longValue() > 0);
|
||||
Assert.assertTrue(Parser.parse("!0").eval(bindings).longValue() > 0);
|
||||
Assert.assertFalse(Parser.parse("!9223372036854775807").eval(bindings).longValue() > 0);
|
||||
|
||||
Assert.assertEquals(3037000499l, Parser.parse("sqrt(9223372036854775807)").eval(bindings).longValue());
|
||||
Assert.assertEquals(9223372036854775807l, Parser.parse("if(9223372036854775807, 9223372036854775807, 9223372036854775806)").eval(bindings).longValue());
|
||||
Assert.assertEquals(9223372036854775806l, Parser.parse("if(0, 9223372036854775807, 9223372036854775806)").eval(bindings).longValue());
|
||||
Assert.assertEquals(3037000499L, Parser.parse("sqrt(9223372036854775807)").eval(bindings).longValue());
|
||||
Assert.assertEquals(9223372036854775807L, Parser.parse("if(9223372036854775807, 9223372036854775807, 9223372036854775806)").eval(bindings).longValue());
|
||||
Assert.assertEquals(9223372036854775806L, Parser.parse("if(0, 9223372036854775807, 9223372036854775806)").eval(bindings).longValue());
|
||||
}
|
||||
}
|
||||
|
@ -1,61 +0,0 @@
|
||||
/*
|
||||
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. Metamarkets licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package io.druid.math.expr;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class LexerTest
|
||||
{
|
||||
@Test
|
||||
public void testAllTokens()
|
||||
{
|
||||
testAllTokensAsserts("abcd<<=>>===!!=+-*/%^(),123 01.23");
|
||||
testAllTokensAsserts(" abcd < <= > >= == ! !=+ -*/ %^ () , 123 01.23 ");
|
||||
}
|
||||
|
||||
private void testAllTokensAsserts(String input)
|
||||
{
|
||||
Lexer lexer = new Lexer(input);
|
||||
Assert.assertEquals(new Token(Token.IDENTIFIER, "abcd"), lexer.consume());
|
||||
Assert.assertEquals(new Token(Token.LT, "<"), lexer.consume());
|
||||
Assert.assertEquals(new Token(Token.LEQ, "<="), lexer.consume());
|
||||
Assert.assertEquals(new Token(Token.GT, ">"), lexer.consume());
|
||||
Assert.assertEquals(new Token(Token.GEQ, ">="), lexer.consume());
|
||||
Assert.assertEquals(new Token(Token.EQ, "=="), lexer.consume());
|
||||
Assert.assertEquals(new Token(Token.NOT, "!"), lexer.consume());
|
||||
Assert.assertEquals(new Token(Token.NEQ, "!="), lexer.consume());
|
||||
Assert.assertEquals(new Token(Token.PLUS, "+"), lexer.consume());
|
||||
Assert.assertEquals(new Token(Token.MINUS, "-"), lexer.consume());
|
||||
Assert.assertEquals(new Token(Token.MUL, "*"), lexer.consume());
|
||||
Assert.assertEquals(new Token(Token.DIV, "/"), lexer.consume());
|
||||
Assert.assertEquals(new Token(Token.MODULO, "%"), lexer.consume());
|
||||
Assert.assertEquals(new Token(Token.CARROT, "^"), lexer.consume());
|
||||
Assert.assertEquals(new Token(Token.LPAREN, "("), lexer.consume());
|
||||
Assert.assertEquals(new Token(Token.RPAREN, ")"), lexer.consume());
|
||||
Assert.assertEquals(new Token(Token.COMMA, ","), lexer.consume());
|
||||
Assert.assertEquals(new Token(Token.LONG, "123"), lexer.consume());
|
||||
Assert.assertEquals(new Token(Token.DOUBLE, "01.23"), lexer.consume());
|
||||
Assert.assertEquals(new Token(Token.EOF, ""), lexer.consume());
|
||||
Assert.assertEquals(new Token(Token.EOF, ""), lexer.consume());
|
||||
}
|
||||
}
|
@ -97,12 +97,12 @@ public class ParserTest
|
||||
expected = "(!= x y)";
|
||||
Assert.assertEquals(expected, actual);
|
||||
|
||||
actual = Parser.parse("x and y").toString();
|
||||
expected = "(and x y)";
|
||||
actual = Parser.parse("x && y").toString();
|
||||
expected = "(&& x y)";
|
||||
Assert.assertEquals(expected, actual);
|
||||
|
||||
actual = Parser.parse("x or y").toString();
|
||||
expected = "(or x y)";
|
||||
actual = Parser.parse("x || y").toString();
|
||||
expected = "(|| x y)";
|
||||
Assert.assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
@ -217,20 +217,20 @@ public class ParserTest
|
||||
Assert.assertEquals(expected, actual);
|
||||
|
||||
|
||||
actual = Parser.parse("1+2^3)").toString();
|
||||
actual = Parser.parse("1+2^3").toString();
|
||||
expected = "(+ 1 (^ 2 3))";
|
||||
Assert.assertEquals(expected, actual);
|
||||
|
||||
actual = Parser.parse("1+(2^3))").toString();
|
||||
actual = Parser.parse("1+(2^3)").toString();
|
||||
expected = "(+ 1 (^ 2 3))";
|
||||
Assert.assertEquals(expected, actual);
|
||||
|
||||
actual = Parser.parse("(1+2)^3)").toString();
|
||||
actual = Parser.parse("(1+2)^3").toString();
|
||||
expected = "(^ (+ 1 2) 3)";
|
||||
Assert.assertEquals(expected, actual);
|
||||
|
||||
|
||||
actual = Parser.parse("1^2+3)").toString();
|
||||
actual = Parser.parse("1^2+3").toString();
|
||||
expected = "(+ (^ 1 2) 3)";
|
||||
Assert.assertEquals(expected, actual);
|
||||
|
||||
@ -243,11 +243,11 @@ public class ParserTest
|
||||
Assert.assertEquals(expected, actual);
|
||||
|
||||
|
||||
actual = Parser.parse("1^2*3+4)").toString();
|
||||
actual = Parser.parse("1^2*3+4").toString();
|
||||
expected = "(+ (* (^ 1 2) 3) 4)";
|
||||
Assert.assertEquals(expected, actual);
|
||||
|
||||
actual = Parser.parse("-1^-2*-3+-4)").toString();
|
||||
actual = Parser.parse("-1^-2*-3+-4").toString();
|
||||
expected = "(+ (* (^ -1 -2) -3) -4)";
|
||||
Assert.assertEquals(expected, actual);
|
||||
}
|
||||
|
@ -2,33 +2,27 @@
|
||||
layout: doc_page
|
||||
---
|
||||
|
||||
This expression language supports following operators (listed in increasing order of precedence).
|
||||
This expression language supports the following operators (listed in decreasing order of precedence).
|
||||
|
||||
|Operators|Description|
|
||||
|---------|-----------|
|
||||
|and,or|Binary Logical AND, OR|
|
||||
|<, <=, >, >=, ==, !=|Binary Comparison|
|
||||
|+, -|Binary additive|
|
||||
|*, /, %|Binary multiplicative|
|
||||
|^|Binary power op|
|
||||
|!, -|Unary NOT and Minus|
|
||||
|^|Binary power op|
|
||||
|*, /, %|Binary multiplicative|
|
||||
|+, -|Binary additive|
|
||||
|<, <=, >, >=, ==, !=|Binary Comparison|
|
||||
|&&,\|\||Binary Logical AND, OR|
|
||||
|
||||
long and double data types are supported, number containing a dot is interpreted as a double or else a long. That means, always add a '.' to your number if you want it intepreted as a double value.
|
||||
You can use variables inside the expression which must start with an alphabet or '_' or '$' and can contain a digit as well in subsequent characters. For logical operators, positive number is considered a boolean true and false otherwise.
|
||||
Long and double data types are supported. If a number contains a dot, it is interpreted as a double, otherwise it is interpreted as a long. That means, always add a '.' to your number if you want it intepreted as a double value.
|
||||
|
||||
Also, following in-built functions are supported.
|
||||
Expressions can contain variables. Variable names may contain letters, digits, '\_' and '$'. Variable names must not begin with a digit.
|
||||
|
||||
For logical operators, a number is true if and only if it is positive. (0 means false)
|
||||
|
||||
Also, the following in-built functions are supported.
|
||||
|
||||
|name|description|
|
||||
|----|-----------|
|
||||
|sqrt|sqrt(x) would return square root of x|
|
||||
|if|if(predicate,then,else) would return `then` if predicate evaluates to a positive number or `else` is returned|
|
||||
|
||||
### How to use?
|
||||
|
||||
```
|
||||
Map<String, Number> bindings = new HashMap<>();
|
||||
bindings.put("x", 2);
|
||||
|
||||
Number result = Parser.parse("x + 2").eval(bindings);
|
||||
Assert.assertEquals(4, result.longValue());
|
||||
```
|
||||
|if|if(predicate,then,else) returns 'then' if 'predicate' evaluates to a positive number, otherwise it returns 'else'|
|
||||
|
||||
|
6
pom.xml
6
pom.xml
@ -444,12 +444,12 @@
|
||||
<dependency>
|
||||
<groupId>org.antlr</groupId>
|
||||
<artifactId>antlr4-runtime</artifactId>
|
||||
<version>4.0</version>
|
||||
<version>4.5.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.antlr</groupId>
|
||||
<artifactId>antlr4-coordinator</artifactId>
|
||||
<version>4.0</version>
|
||||
<version>4.5.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-cli</groupId>
|
||||
@ -740,7 +740,7 @@
|
||||
<plugin>
|
||||
<groupId>org.antlr</groupId>
|
||||
<artifactId>antlr4-maven-plugin</artifactId>
|
||||
<version>4.0</version>
|
||||
<version>4.5.1</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
|
Loading…
x
Reference in New Issue
Block a user