diff --git a/data-prepper-expression/src/main/antlr/DataPrepperExpression.g4 b/data-prepper-expression/src/main/antlr/DataPrepperExpression.g4 index 7d66d9a6bf..4dd50026b9 100644 --- a/data-prepper-expression/src/main/antlr/DataPrepperExpression.g4 +++ b/data-prepper-expression/src/main/antlr/DataPrepperExpression.g4 @@ -29,7 +29,7 @@ arithmeticExpression ; multiplicativeExpression - : multiplicativeExpression (MULTIPLY | DIVIDE) arithmeticTerm + : multiplicativeExpression (MULTIPLY | DIVIDE | MOD) arithmeticTerm | arithmeticTerm ; @@ -334,6 +334,7 @@ ZERO : '0'; PLUS: '+'; MULTIPLY: '*'; DOT : '.'; +MOD: '%'; EXPONENTLETTER : 'E' | 'e' diff --git a/data-prepper-expression/src/main/java/org/opensearch/dataprepper/expression/ArithmeticBinaryOperator.java b/data-prepper-expression/src/main/java/org/opensearch/dataprepper/expression/ArithmeticBinaryOperator.java index d830db3390..b19733bb70 100644 --- a/data-prepper-expression/src/main/java/org/opensearch/dataprepper/expression/ArithmeticBinaryOperator.java +++ b/data-prepper-expression/src/main/java/org/opensearch/dataprepper/expression/ArithmeticBinaryOperator.java @@ -49,12 +49,20 @@ public Number evaluate(final Object ... args) { final Class leftValueClass = leftValue.getClass(); final Class rightValueClass = rightValue.getClass(); if (!operandsToOperationMap.containsKey(leftValueClass)) { - throw new IllegalArgumentException(displayName + " requires left operand to be either Float or Integer."); + if (symbol == DataPrepperExpressionParser.MOD) { + throw new IllegalArgumentException(displayName + " requires left operand to be an Integer."); + } else { + throw new IllegalArgumentException(displayName + " requires left operand to be either Float or Integer."); + } } Map, BiFunction> rightOperandToOperation = operandsToOperationMap.get(leftValueClass); if (!rightOperandToOperation.containsKey(rightValueClass)) { - throw new IllegalArgumentException(displayName + " requires right operand to be either Float or Integer."); + if (symbol == DataPrepperExpressionParser.MOD) { + throw new IllegalArgumentException(displayName + " requires right operand to be an Integer."); + } else { + throw new IllegalArgumentException(displayName + " requires right operand to be either Float or Integer."); + } } final BiFunction operation = rightOperandToOperation.get(rightValueClass); return operation.apply(leftValue, rightValue); diff --git a/data-prepper-expression/src/main/java/org/opensearch/dataprepper/expression/OperatorConfiguration.java b/data-prepper-expression/src/main/java/org/opensearch/dataprepper/expression/OperatorConfiguration.java index 89c1781e0a..c88ec7490a 100644 --- a/data-prepper-expression/src/main/java/org/opensearch/dataprepper/expression/OperatorConfiguration.java +++ b/data-prepper-expression/src/main/java/org/opensearch/dataprepper/expression/OperatorConfiguration.java @@ -417,7 +417,7 @@ public ArithmeticBinaryOperator divideOperator() { operandsToOperationMap = new HashMap<>(); final Map, BiFunction> intOperations = Map.of( - Integer.class, (lhs, rhs) -> ((double)(int)lhs) /((double)(int)rhs), + Integer.class, (lhs, rhs) -> ((double)(int)lhs) / ((double)(int)rhs), Float.class, (lhs, rhs) -> (float)(int)lhs / (Float) rhs, Long.class, (lhs, rhs) -> (double)(int)lhs / (Long) rhs, Double.class, (lhs, rhs) -> (double)(int)lhs / (Double) rhs @@ -451,4 +451,18 @@ public ArithmeticBinaryOperator divideOperator() { return new ArithmeticBinaryOperator(DataPrepperExpressionParser.DIVIDE, operandsToOperationMap); } + + @Bean + public ArithmeticBinaryOperator modOperator() { + final Map, Map, BiFunction>> + operandsToOperationMap = new HashMap<>(); + final Map, BiFunction> intOperations = + Map.of( + Integer.class, (lhs, rhs) -> ((int)lhs) % ((int)rhs) + ); + + operandsToOperationMap.put(Integer.class, intOperations); + + return new ArithmeticBinaryOperator(DataPrepperExpressionParser.MOD, operandsToOperationMap); + } } diff --git a/data-prepper-expression/src/test/java/org/opensearch/dataprepper/expression/ArithmeticBinaryOperatorTest.java b/data-prepper-expression/src/test/java/org/opensearch/dataprepper/expression/ArithmeticBinaryOperatorTest.java index 50fe2acdbe..9e55c5c7a7 100644 --- a/data-prepper-expression/src/test/java/org/opensearch/dataprepper/expression/ArithmeticBinaryOperatorTest.java +++ b/data-prepper-expression/src/test/java/org/opensearch/dataprepper/expression/ArithmeticBinaryOperatorTest.java @@ -37,6 +37,10 @@ private Operator createDivideOperatorUnderTest() { return new OperatorConfiguration().divideOperator(); } + private Operator createModOperatorUnderTest() { + return new OperatorConfiguration().modOperator(); + } + @Mock private ParserRuleContext ctx; @@ -48,6 +52,8 @@ void testGetNumberOfOperands() { assertThat(objectUnderTest.getNumberOfOperands(ctx), is(2)); objectUnderTest = createDivideOperatorUnderTest(); assertThat(objectUnderTest.getNumberOfOperands(ctx), is(2)); + objectUnderTest = createModOperatorUnderTest(); + assertThat(objectUnderTest.getNumberOfOperands(ctx), is(2)); when(ctx.getRuleIndex()).thenReturn(DataPrepperExpressionParser.RULE_arithmeticExpression); objectUnderTest = createSubtractOperatorUnderTest(); @@ -82,6 +88,12 @@ void testShouldEvaluate() { assertThat(objectUnderTest.shouldEvaluate(ctx), is(true)); when(ctx.getRuleIndex()).thenReturn(-1); assertThat(objectUnderTest.shouldEvaluate(ctx), is(false)); + + objectUnderTest = createModOperatorUnderTest(); + when(ctx.getRuleIndex()).thenReturn(DataPrepperExpressionParser.RULE_multiplicativeExpression); + assertThat(objectUnderTest.shouldEvaluate(ctx), is(true)); + when(ctx.getRuleIndex()).thenReturn(-1); + assertThat(objectUnderTest.shouldEvaluate(ctx), is(false)); } @Test @@ -94,6 +106,8 @@ void testGetSymbol() { assertThat(objectUnderTest.getSymbol(), is(DataPrepperExpressionParser.MULTIPLY)); objectUnderTest = createDivideOperatorUnderTest(); assertThat(objectUnderTest.getSymbol(), is(DataPrepperExpressionParser.DIVIDE)); + objectUnderTest = createModOperatorUnderTest(); + assertThat(objectUnderTest.getSymbol(), is(DataPrepperExpressionParser.MOD)); } @Test @@ -106,6 +120,10 @@ void testInvalid() { assertThrows(RuntimeException.class, () -> objectUnderTest.evaluate(2, 1, 2)); objectUnderTest = createDivideOperatorUnderTest(); assertThrows(RuntimeException.class, () -> objectUnderTest.evaluate(2, 1, 2)); + objectUnderTest = createModOperatorUnderTest(); + assertThrows(RuntimeException.class, () -> objectUnderTest.evaluate(2, 1, 2)); + assertThrows(RuntimeException.class, () -> objectUnderTest.evaluate(2.0, 1)); + assertThrows(RuntimeException.class, () -> objectUnderTest.evaluate(2, 1.0)); } @Test @@ -204,4 +222,11 @@ void testEvalValidArgsForDivide() { assertThat(objectUnderTest.evaluate(5.0, 2.0), is(2.5)); } + @Test + void testEvalValidArgsForMod() { + objectUnderTest = createModOperatorUnderTest(); + assertThat(objectUnderTest.evaluate(5, 5), is(0)); + assertThat(objectUnderTest.evaluate(5, 2), is(1)); + assertThat(objectUnderTest.evaluate(6, 3), is(0)); + } } diff --git a/data-prepper-expression/src/test/java/org/opensearch/dataprepper/expression/ParseTreeEvaluatorListenerTest.java b/data-prepper-expression/src/test/java/org/opensearch/dataprepper/expression/ParseTreeEvaluatorListenerTest.java index 047286f74a..99526bffef 100644 --- a/data-prepper-expression/src/test/java/org/opensearch/dataprepper/expression/ParseTreeEvaluatorListenerTest.java +++ b/data-prepper-expression/src/test/java/org/opensearch/dataprepper/expression/ParseTreeEvaluatorListenerTest.java @@ -56,6 +56,7 @@ class ParseTreeEvaluatorListenerTest { operatorConfiguration.subtractOperator(), operatorConfiguration.multiplyOperator(), operatorConfiguration.divideOperator(), + operatorConfiguration.modOperator(), new NotOperator() ); private final OperatorProvider operatorProvider = new OperatorProvider(operators);