/** * Copyright (C) 2019, 2021, 2024, 2025 Adrien Hopkins * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package sevenUnits.utils; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.List; import java.util.stream.IntStream; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; /** * A test for the {@code ExpressionParser} class. This is NOT part of this * program's public API. * * @author Adrien Hopkins * @since 2019-03-22 * @since v0.2.0 */ class ExpressionParserTest { private static final ExpressionParser numberParser = new ExpressionParser.Builder<>( Integer::parseInt).addBinaryOperator("+", (o1, o2) -> o1 + o2, 0) .addBinaryOperator("-", (o1, o2) -> o1 - o2, 0) .addUnaryOperator("neg", o1 -> -o1, 1) .addBinaryOperator("*", (o1, o2) -> o1 * o2, 2) .addBinaryOperator("/", (o1, o2) -> o1 / o2, 2) .addUnaryOperator("recip", o1 -> 1 / o1, 3) .addBinaryOperator("^", (o1, o2) -> (int) Math.pow(o1, o2), 4).build(); /** The expressions used in the expression parsing tests */ private static final List TEST_EXPRESSIONS = List.of( // test parsing of expressions "1 + 2 ^ 5 * 3", "(1 + 2) ^ 5 * 3", "12 * 5 + (3 ^ (2 * 3) - 72) / (3 + 3 * 2)", // ensure it normally goes from left to right "1 + 2 + 3 + 4", "12 - 4 - 3", "12 - (4 - 3)", "1 / 2 + 3"); /** The expected results for evaluating these expressions */ private static final int[] RESULTS = { 97, 729, 133, 10, 5, 11, 3 }; private static final Stream testConvertExpressionToRPN() { return Stream.of(Arguments.of("1 + 2 ^ 5 * 3", "1 2 5 ^ 3 * +"), Arguments.of("(1 + 2) ^ 5 * 3", "1 2 + 5 ^ 3 *"), Arguments.of("12 * 5 + (3 ^ (2 * 3) - 72) / (3 + 3 * 2)", "12 5 * 3 2 3 * ^ 72 - 3 3 2 * + / +"), Arguments.of("1 + 2 + 3 + 4", "1 2 + 3 + 4 +"), Arguments.of("12 - 4 - 3", "12 4 - 3 -"), Arguments.of("12 - (4 - 3)", "12 4 3 - -"), Arguments.of("1 / 2 + 3", "1 2 / 3 +"), Arguments.of("12", "12"), Arguments.of("2 * 3 + 4", "2 3 * 4 +"), Arguments.of("(2 * 3) + 4", "2 3 * 4 +"), Arguments.of("2 * 3 - 4", "2 3 * 4 -"), Arguments.of("(2 * 3) - 4", "2 3 * 4 -"), Arguments.of("2 * (3 + 4)", "2 3 4 + *"), Arguments.of("2 * (3 - 4)", "2 3 4 - *"), Arguments.of("neg 2", "2 neg"), Arguments.of("1 + neg 2", "1 2 neg +")); } private static final Stream testInvalidExpression() { return Stream.of("+", "1 +", "1 + * 2", "1 (+ 1)", "neg"); } private static final Stream testInvalidRPN() { return Stream.of("+", "1 +", "1 + * 2", "1 * 2", "1 2", "neg"); } /** * @return A stream of objects, where each one is an expression and the * expected result * @since 2021-09-27 * @since v0.3.2 */ private static final Stream testParseExpressionData() { return IntStream.range(0, TEST_EXPRESSIONS.size()) .mapToObj(i -> Arguments.of(TEST_EXPRESSIONS.get(i), RESULTS[i])); } private static final Stream testParseRPN() { return Stream.of(Arguments.of("1 2 5 ^ 3 * +", 97), Arguments.of("1 2 + 5 ^ 3 *", 729), Arguments.of("12 5 * 3 2 3 * ^ 72 - 3 3 2 * + / +", 133), Arguments.of("1 2 + 3 + 4 +", 10), Arguments.of("12 4 - 3 -", 5), Arguments.of("12 4 3 - -", 11), Arguments.of("1 2 / 3 +", 3), Arguments.of("12", 12), Arguments.of("2 3 * 4 +", 10), Arguments.of("2 3 * 4 -", 2), Arguments.of("2 3 4 + *", 14), Arguments.of("2 3 4 - *", -2), Arguments.of("2 neg", -2), Arguments.of("1 2 neg +", -1)); } @ParameterizedTest @MethodSource public void testConvertExpressionToRPN(String expression, String expectedRPN) { assertEquals(expectedRPN, numberParser.convertExpressionToReversePolish(expression)); } @ParameterizedTest @MethodSource public void testInvalidExpression(String expression) { assertThrows(RuntimeException.class, () -> numberParser.convertExpressionToReversePolish(expression)); } @ParameterizedTest @MethodSource public void testInvalidRPN(String expressionRPN) { assertThrows(RuntimeException.class, () -> numberParser.parseReversePolishExpression(expressionRPN)); } /** * Test method for * {@link sevenUnits.utils.ExpressionParser#parseExpression(java.lang.String)}. */ @ParameterizedTest @MethodSource("testParseExpressionData") public void testParseExpression(String expression, int value) { assertEquals(value, numberParser.parseExpression(expression)); } @ParameterizedTest @MethodSource public void testParseRPN(String expressionRPN, int value) { assertEquals(value, numberParser.parseReversePolishExpression(expressionRPN)); } }