Skip to content

Commit a4367e8

Browse files
committed
support for nested functions
1 parent 51e3fd1 commit a4367e8

3 files changed

Lines changed: 118 additions & 91 deletions

File tree

src/main/java/com/github/sidhant92/boolparser/parser/antlr/BooleanFilterListener.java

Lines changed: 100 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.antlr.v4.runtime.CommonToken;
88
import org.antlr.v4.runtime.Token;
99
import org.antlr.v4.runtime.tree.ParseTree;
10+
import org.antlr.v4.runtime.tree.TerminalNodeImpl;
1011
import org.apache.commons.lang3.StringUtils;
1112
import com.github.sidhant92.boolparser.constant.FunctionType;
1213
import com.github.sidhant92.boolparser.constant.DataType;
@@ -50,80 +51,28 @@ public Node getNode() {
5051

5152
@Override
5253
public void exitComparatorExpression(BooleanExpressionParser.ComparatorExpressionContext ctx) {
53-
final String variableName = getField(ctx.left.getText());
54-
final Operator operator = Operator.getOperatorFromSymbol(ctx.op.getText()).orElse(Operator.EQUALS);
55-
/*if ((ctx.right instanceof BooleanExpressionParser.ParentExpressionContext || ctx.right instanceof BooleanExpressionParser.ArithmeticFunctionExpressionContext) && !currentNodes.isEmpty()) {
56-
final Node value = currentNodes.pop();
57-
currentNodes.add(new ComparisonNode(variableName, value, operator, DataType.INTEGER));
58-
} else {
59-
final DataType dataType = getDataType(ctx.right.getStart());
60-
currentNodes.add(new ComparisonNode(variableName, ValueUtils.convertValue(ctx.right.getText(), dataType), operator, dataType));
61-
}*/
62-
if (!(ctx.right instanceof BooleanExpressionParser.TypesExpressionContext) && !currentNodes.isEmpty()) {
63-
final Node value = currentNodes.pop();
64-
currentNodes.add(new ComparisonNode(variableName, value, operator, DataType.INTEGER));
65-
} else {
66-
final DataType dataType = getDataType(ctx.right.getStart());
67-
currentNodes.add(new ComparisonNode(variableName, ValueUtils.convertValue(ctx.right.getText(), dataType), operator, dataType));
68-
}
54+
currentNodes.add(mapComparatorExpressionContext(ctx));
6955
super.enterComparatorExpression(ctx);
7056
}
7157

72-
private UnaryNode getUnaryNode(final BooleanExpressionParser.TypesExpressionContext ctx) {
73-
final DataType dataType = getDataType(ctx.getStart());
74-
final Object operand = ValueUtils.convertValue(ctx.getText(), dataType);
75-
return UnaryNode.builder().value(operand).dataType(dataType).build();
76-
}
77-
7858
@Override
7959
public void exitArithmeticExpression(BooleanExpressionParser.ArithmeticExpressionContext ctx) {
80-
final Operator operator = Operator.getOperatorFromSymbol(ctx.op.getText()).orElse(Operator.EQUALS);
81-
if (ctx.left instanceof BooleanExpressionParser.TypesExpressionContext && ctx.right instanceof BooleanExpressionParser.TypesExpressionContext) {
82-
final UnaryNode left = getUnaryNode((BooleanExpressionParser.TypesExpressionContext) ctx.left);
83-
final UnaryNode right = getUnaryNode((BooleanExpressionParser.TypesExpressionContext) ctx.right);
84-
final ArithmeticNode node = ArithmeticNode.builder().left(left).right(right).operator(operator).build();
85-
currentNodes.add(node);
86-
} else if (ctx.left instanceof BooleanExpressionParser.TypesExpressionContext) {
87-
final UnaryNode left = getUnaryNode((BooleanExpressionParser.TypesExpressionContext) ctx.left);
88-
final Node right = currentNodes.pop();
89-
final ArithmeticNode node = ArithmeticNode.builder().left(left).right(right).operator(operator).build();
90-
currentNodes.add(node);
91-
} else if (ctx.right instanceof BooleanExpressionParser.TypesExpressionContext) {
92-
final UnaryNode right = getUnaryNode((BooleanExpressionParser.TypesExpressionContext) ctx.right);
93-
final Node left = currentNodes.pop();
94-
final ArithmeticNode node = ArithmeticNode.builder().left(left).right(right).operator(operator).build();
95-
currentNodes.add(node);
96-
} else {
97-
if (currentNodes.size() < 2) {
98-
log.error("Error parsing expression for the string {}", ctx.getText());
99-
throw new InvalidExpressionException();
100-
}
101-
final Node right = currentNodes.pop();
102-
final Node left = currentNodes.pop();
103-
final ArithmeticNode node = ArithmeticNode.builder().left(left).right(right).operator(operator).build();
104-
currentNodes.add(node);
105-
}
60+
currentNodes.add(mapArithmeticExpressionContext(ctx));
10661
super.exitArithmeticExpression(ctx);
10762
}
10863

10964
@Override
11065
public void exitUnaryArithmeticExpression(BooleanExpressionParser.UnaryArithmeticExpressionContext ctx) {
11166
final DataType dataType = getDataType(ctx.exp.getStart());
11267
final Object operand = ValueUtils.convertValue(ctx.exp.getText(), dataType);
113-
final UnaryNode leafNode = UnaryNode.builder().value(operand).dataType(dataType).build();
68+
final Node leafNode = !currentNodes.isEmpty() ? currentNodes.pop() : UnaryNode.builder().value(operand).dataType(dataType).build();
11469
currentNodes.add(ArithmeticNode.builder().left(leafNode).operator(Operator.UNARY).build());
11570
super.enterUnaryArithmeticExpression(ctx);
11671
}
11772

11873
@Override
11974
public void exitToExpression(BooleanExpressionParser.ToExpressionContext ctx) {
120-
validateField(ctx.field, ctx.getText());
121-
final String field = getField(ctx.field.getText());
122-
final DataType lowerDataType = getDataType(ctx.lower.start);
123-
final Object lowerValue = ValueUtils.convertValue(ctx.lower.start.getText(), lowerDataType);
124-
final DataType upperDataType = getDataType(ctx.upper.start);
125-
final Object upperValue = ValueUtils.convertValue(ctx.upper.getText(), upperDataType);
126-
currentNodes.add(new NumericRangeNode(field, lowerValue, upperValue, lowerDataType, upperDataType));
75+
currentNodes.add(mapToExpressionContext(ctx));
12776
super.exitToExpression(ctx);
12877
}
12978

@@ -139,6 +88,51 @@ public void exitArrayExpression(BooleanExpressionParser.ArrayExpressionContext c
13988

14089
@Override
14190
public void exitArithmeticFunctionExpression(BooleanExpressionParser.ArithmeticFunctionExpressionContext ctx) {
91+
final Node node = mapArithmeticFunctionExpressionContext(ctx);
92+
currentNodes.add(node);
93+
super.exitArithmeticFunctionExpression(ctx);
94+
}
95+
96+
@Override
97+
public void exitInExpression(BooleanExpressionParser.InExpressionContext ctx) {
98+
currentNodes.add(mapInExpressionContext(ctx));
99+
super.exitInExpression(ctx);
100+
}
101+
102+
private List<Node> getArrayElements(final List<ParseTree> trees) {
103+
return trees
104+
.stream()
105+
.filter(child -> !(child instanceof TerminalNodeImpl))
106+
.map(this::mapContextToNode)
107+
.collect(Collectors.toList());
108+
}
109+
110+
private Node mapContextToNode(final ParseTree ctx) {
111+
if (ctx instanceof BooleanExpressionParser.ArithmeticExpressionContext) {
112+
return mapArithmeticExpressionContext((BooleanExpressionParser.ArithmeticExpressionContext) ctx);
113+
} else if (ctx instanceof BooleanExpressionParser.InExpressionContext) {
114+
return mapInExpressionContext((BooleanExpressionParser.InExpressionContext) ctx);
115+
} else if (ctx instanceof BooleanExpressionParser.ArithmeticFunctionExpressionContext) {
116+
return mapArithmeticFunctionExpressionContext((BooleanExpressionParser.ArithmeticFunctionExpressionContext) ctx);
117+
} else if (ctx instanceof BooleanExpressionParser.ComparatorExpressionContext) {
118+
return mapComparatorExpressionContext((BooleanExpressionParser.ComparatorExpressionContext) ctx);
119+
} else if (ctx instanceof BooleanExpressionParser.ToExpressionContext) {
120+
return mapToExpressionContext((BooleanExpressionParser.ToExpressionContext) ctx);
121+
} else if (ctx instanceof BooleanExpressionParser.TypesExpressionContext) {
122+
return mapTypesExpressionContext((BooleanExpressionParser.TypesExpressionContext) ctx);
123+
} else {
124+
log.error("Array does not support this expression {}", ctx.getText());
125+
throw new InvalidExpressionException();
126+
}
127+
}
128+
129+
private UnaryNode mapTypesExpressionContext(BooleanExpressionParser.TypesExpressionContext ctx) {
130+
final DataType dataType = getDataType(ctx.start);
131+
final Object value = ValueUtils.convertValue(ctx.getText(), dataType);
132+
return new UnaryNode(dataType, value);
133+
}
134+
135+
private ArithmeticFunctionNode mapArithmeticFunctionExpressionContext(BooleanExpressionParser.ArithmeticFunctionExpressionContext ctx) {
142136
if (ctx.data.exception != null) {
143137
log.error("Error parsing expression for the string {}", ctx.getText());
144138
throw new InvalidExpressionException();
@@ -148,35 +142,66 @@ public void exitArithmeticFunctionExpression(BooleanExpressionParser.ArithmeticF
148142
return new InvalidExpressionException();
149143
});
150144
final List<Node> items = getArrayElements(ctx.data.children);
151-
currentNodes.add(new ArithmeticFunctionNode(functionType, items));
152-
super.exitArithmeticFunctionExpression(ctx);
145+
return new ArithmeticFunctionNode(functionType, items);
153146
}
154147

155-
@Override
156-
public void exitInExpression(BooleanExpressionParser.InExpressionContext ctx) {
148+
private ComparisonNode mapComparatorExpressionContext(BooleanExpressionParser.ComparatorExpressionContext ctx) {
149+
final String variableName = getField(ctx.left.getText());
150+
final Operator operator = Operator.getOperatorFromSymbol(ctx.op.getText()).orElse(Operator.EQUALS);
151+
if (!(ctx.right instanceof BooleanExpressionParser.TypesExpressionContext) && !currentNodes.isEmpty()) {
152+
final Node value = currentNodes.pop();
153+
return new ComparisonNode(variableName, value, operator, DataType.INTEGER);
154+
} else {
155+
final DataType dataType = getDataType(ctx.right.getStart());
156+
return new ComparisonNode(variableName, ValueUtils.convertValue(ctx.right.getText(), dataType), operator, dataType);
157+
}
158+
}
159+
160+
private ArithmeticNode mapArithmeticExpressionContext(BooleanExpressionParser.ArithmeticExpressionContext ctx) {
161+
final Operator operator = Operator.getOperatorFromSymbol(ctx.op.getText()).orElse(Operator.EQUALS);
162+
if (ctx.left instanceof BooleanExpressionParser.TypesExpressionContext && ctx.right instanceof BooleanExpressionParser.TypesExpressionContext) {
163+
final UnaryNode left = mapTypesExpressionContext((BooleanExpressionParser.TypesExpressionContext) ctx.left);
164+
final UnaryNode right = mapTypesExpressionContext((BooleanExpressionParser.TypesExpressionContext) ctx.right);
165+
return ArithmeticNode.builder().left(left).right(right).operator(operator).build();
166+
} else if (ctx.left instanceof BooleanExpressionParser.TypesExpressionContext) {
167+
final UnaryNode left = mapTypesExpressionContext((BooleanExpressionParser.TypesExpressionContext) ctx.left);
168+
final Node right = currentNodes.pop();
169+
return ArithmeticNode.builder().left(left).right(right).operator(operator).build();
170+
} else if (ctx.right instanceof BooleanExpressionParser.TypesExpressionContext) {
171+
final UnaryNode right = mapTypesExpressionContext((BooleanExpressionParser.TypesExpressionContext) ctx.right);
172+
final Node left = currentNodes.pop();
173+
return ArithmeticNode.builder().left(left).right(right).operator(operator).build();
174+
} else {
175+
if (currentNodes.size() < 2) {
176+
log.error("Error parsing expression for the string {}", ctx.getText());
177+
throw new InvalidExpressionException();
178+
}
179+
final Node right = currentNodes.pop();
180+
final Node left = currentNodes.pop();
181+
return ArithmeticNode.builder().left(left).right(right).operator(operator).build();
182+
}
183+
}
184+
185+
private Node mapInExpressionContext(BooleanExpressionParser.InExpressionContext ctx) {
157186
validateField(ctx.field, ctx.getText());
158187
final String field = getField(ctx.field.getText());
159188
final List<Node> items = getArrayElements(ctx.data.children);
160189
final InNode inNode = new InNode(field, items);
161190
if (Objects.isNull(ctx.not)) {
162-
currentNodes.add(inNode);
191+
return inNode;
163192
} else {
164-
final BooleanNode booleanNode = new BooleanNode(inNode, null, LogicalOperationType.NOT);
165-
currentNodes.add(booleanNode);
193+
return new BooleanNode(inNode, null, LogicalOperationType.NOT);
166194
}
167-
super.exitInExpression(ctx);
168195
}
169196

170-
private List<Node> getArrayElements(final List<ParseTree> trees) {
171-
return trees
172-
.stream()
173-
.filter(child -> child instanceof BooleanExpressionParser.TypesExpressionContext)
174-
.map(child -> {
175-
final DataType dataType = getDataType(((BooleanExpressionParser.TypesExpressionContext) child).start);
176-
final Object value = ValueUtils.convertValue(child.getText(), dataType);
177-
return new UnaryNode(dataType, value);
178-
})
179-
.collect(Collectors.toList());
197+
private NumericRangeNode mapToExpressionContext(BooleanExpressionParser.ToExpressionContext ctx) {
198+
validateField(ctx.field, ctx.getText());
199+
final String field = getField(ctx.field.getText());
200+
final DataType lowerDataType = getDataType(ctx.lower.start);
201+
final Object lowerValue = ValueUtils.convertValue(ctx.lower.start.getText(), lowerDataType);
202+
final DataType upperDataType = getDataType(ctx.upper.start);
203+
final Object upperValue = ValueUtils.convertValue(ctx.upper.getText(), upperDataType);
204+
return new NumericRangeNode(field, lowerValue, upperValue, lowerDataType, upperDataType);
180205
}
181206

182207
private void validateField(final Token token, final String text) {
@@ -219,17 +244,8 @@ private LogicalOperationType getLogicalOperator(final org.antlr.v4.runtime.Token
219244

220245
@Override
221246
public void exitParse(BooleanExpressionParser.ParseContext ctx) {
222-
if (this.node == null && this.currentNodes.size() == 1) {
247+
if (this.node == null && !this.currentNodes.isEmpty()) {
223248
this.node = currentNodes.pop();
224-
} else if (this.node == null && this.currentNodes.size() == 2) {
225-
final Node firstNode = currentNodes.pop();
226-
final Node secondNode = currentNodes.pop();
227-
if (firstNode instanceof ArithmeticNode && secondNode instanceof ArithmeticNode && ((ArithmeticNode) secondNode).getRight() == null) {
228-
this.node = ArithmeticNode.builder().operator(Operator.UNARY).left(firstNode).build();
229-
}
230-
if (secondNode instanceof ArithmeticNode && firstNode instanceof ArithmeticNode && ((ArithmeticNode) firstNode).getRight() == null) {
231-
this.node = ArithmeticNode.builder().operator(Operator.UNARY).left(secondNode).build();
232-
}
233249
}
234250
if (this.node == null && tokenCount == 1 && lastToken instanceof CommonToken) {
235251
this.node = UnaryNode.builder().dataType(DataType.STRING).value(ValueUtils.convertValue(lastToken.getText(), DataType.STRING).toString())

src/test/java/com/github/sidhant92/boolparser/application/ArithmeticExpressionEvaluatorTest.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,4 +397,22 @@ public void testLenArithmeticFunctionIntegerVariable() {
397397
assertTrue(resultOptional.isSuccess());
398398
assertEquals(resultOptional.get(), 4);
399399
}
400+
401+
@Test
402+
public void testNestedFunctions() {
403+
final Map<String, Object> data = new HashMap<>();
404+
data.put("a", 2.7);
405+
final Try<Object> resultOptional = arithmeticExpressionEvaluator.evaluate("max(1,2,min(5,7,87))", data);
406+
assertTrue(resultOptional.isSuccess());
407+
assertEquals(resultOptional.get(), 5);
408+
}
409+
410+
@Test
411+
public void testNestedFunctions1() {
412+
final Map<String, Object> data = new HashMap<>();
413+
data.put("a", 2.7);
414+
final Try<Object> resultOptional = arithmeticExpressionEvaluator.evaluate("max(1,2,min(5,7,87,min(1,2)))", data);
415+
assertTrue(resultOptional.isSuccess());
416+
assertEquals(resultOptional.get(), 2);
417+
}
400418
}

src/test/java/com/github/sidhant92/boolparser/parser/antlr/BooleanFilterBoolParserTest.java

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -333,13 +333,6 @@ public void testSingleToken() {
333333
assertEquals(((UnaryNode) nodeOptional.get()).getValue(), "a");
334334
}
335335

336-
@Test
337-
public void testInvalidNotExpression() {
338-
final Try<Node> nodeOptional = boolExpressionBoolParser.parseExpression("not a > 5");
339-
assertTrue(nodeOptional.isFailure());
340-
assertTrue(nodeOptional.getCause() instanceof InvalidExpressionException);
341-
}
342-
343336
@Test
344337
public void testContainsAny() {
345338
final Try<Node> nodeOptional = boolExpressionBoolParser.parseExpression("a contains_any (1,2,3)");

0 commit comments

Comments
 (0)