initial commit

This commit is contained in:
2026-01-29 23:20:12 +05:00
parent fb1ce36970
commit 67357cf271
76 changed files with 7115 additions and 0 deletions

View File

@@ -0,0 +1,202 @@
package expression;
import base.Asserts;
import base.Pair;
import base.TestCounter;
import expression.common.ExpressionKind;
import expression.common.Type;
import java.math.BigDecimal;
import java.util.List;
import java.util.stream.IntStream;
/**
* One-argument arithmetic expression over {@link BigDecimal}s.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
@FunctionalInterface
@SuppressWarnings("ClassReferencesSubclass")
public interface BigDecimalListExpression extends ToMiniString {
BigDecimal evaluateBd(List<BigDecimal> variables);
// Tests follow. You may temporarily remove everything til the end.
Add EXAMPLE = new Add(
new Subtract(new Variable(0), new Const(BigDecimal.ONE)),
new Multiply(new Variable(1), new Const(BigDecimal.TEN))
);
Type<BigDecimal> TYPE = new Type<>(
v -> new BigDecimal(v + ".000"),
random -> BigDecimal.valueOf(random.getRandom().nextGaussian()),
BigDecimal.class
);
ExpressionKind<BigDecimalListExpression, BigDecimal> KIND = new ExpressionKind<>(
TYPE,
BigDecimalListExpression.class,
(r, c) -> IntStream.range(0, c)
.mapToObj(name -> Pair.<String, BigDecimalListExpression>of("$" + name, new Variable(name)))
.toList(),
(expr, variables, values) -> expr.evaluateBd(values)
);
@SuppressWarnings("BigDecimalMethodWithoutRoundingCalled")
static ExpressionTester<?, ?> tester(final TestCounter counter) {
Asserts.assertEquals("Example toString()", "(($0 - 1) + ($1 * 10))", EXAMPLE.toString());
Asserts.assertEquals(
EXAMPLE + " at (2, 3)",
BigDecimal.valueOf(31),
EXAMPLE.evaluateBd(List.of(BigDecimal.valueOf(2), BigDecimal.valueOf(3)))
);
final Variable vx = new Variable(0);
final Variable vy = new Variable(1);
return new ExpressionTester<>(
counter, KIND, c -> v -> c,
(op, a, b) -> v -> op.apply(a.evaluateBd(v), b.evaluateBd(v)),
BigDecimal::add, BigDecimal::subtract, BigDecimal::multiply, BigDecimal::divide
)
.basic("10", "10", v -> v(10), c(10))
.basic("$x", "$x", BigDecimalListExpression::x, vx)
.basic("$y", "$y", BigDecimalListExpression::y, vy)
.basic("($x + $y)", "$x + $y", v -> x(v).add(y(v)), new Add(vx, vy))
.basic("($x + 2)", "$x + 2", v -> x(v).add(v(2)), new Add(vx, c(2)))
.basic("(2 - $x)", "2 - $x", v -> v(2).subtract(x(v)), new Subtract(c(2), vx))
.basic("(3 * $x)", "3 * $x", v -> v(3).multiply(x(v)), new Multiply(c(3), vx))
.basic("($x + $x)", "$x + $x", v -> x(v).add(x(v)), new Add(vx, vx))
.basic("($x / -2)", "$x / -2", v -> x(v).divide(v(-2)), new Divide(vx, c(-2)))
.basic("(2 + $x)", "2 + $x", v -> v(2).add(x(v)), new Add(c(2), vx))
.basic("((1 + 2) + 3)", "1 + 2 + 3", v -> v(6), new Add(new Add(c(1), c(2)), c(3)))
.basic("(1 + (2 * 3))", "1 + 2 * 3", v -> v(7), new Add(c(1), new Multiply(c(2), c(3))))
.basic("(1 - (2 * 3))", "1 - 2 * 3", v -> v(-5), new Subtract(c(1), new Multiply(c(2), c(3))))
.basic("(1 + (2 + 3))", "1 + 2 + 3", v -> v(6), new Add(c(1), new Add(c(2), c(3))))
.basic("((1 - 2) - 3)", "1 - 2 - 3", v -> v(-4), new Subtract(new Subtract(c(1), c(2)), c(3)))
.basic("(1 - (2 - 3))", "1 - (2 - 3)", v -> v(2), new Subtract(c(1), new Subtract(c(2), c(3))))
.basic("((1 * 2) * 3)", "1 * 2 * 3", v -> v(6), new Multiply(new Multiply(c(1), c(2)), c(3)))
.basic("(1 * (2 * 3))", "1 * 2 * 3", v -> v(6), new Multiply(c(1), new Multiply(c(2), c(3))))
.basic("((10 / 2) / 3)", "10 / 2 / 3", v -> v(10).divide(v(2)).divide(v(3)), new Divide(new Divide(c(10), c(2)), c(3)))
.basic("(10 / (3 / 2))", "10 / (3 / 2)", v -> v(10).divide(v(3).divide(v(2))), new Divide(c(10), new Divide(c(3), c(2))))
.basic("(($x * $x) + (($x - 1) / 10))",
"$x * $x + ($x - 1) / 10",
v -> x(v).multiply(x(v)).add(x(v).subtract(v(1)).divide(v(10))),
new Add(new Multiply(vx, vx), new Divide(new Subtract(vx, c(1)), c(10)))
)
.basic("($x * -1000000000)", "$x * -1000000000", v -> x(v).multiply(v(-1_000_000_000)), new Multiply(vx, c(-1_000_000_000)))
.basic("($x * -1000000000000000)", "$x * -1000000000000000", v -> x(v).multiply(v(-1_000_000_000_000_000L)), new Multiply(vx, c(-1_000_000_000_000_000L)))
.basic("(10 / $x)", "10 / $x", v -> v(10).divide(x(v)), new Divide(c(10), vx))
.basic("($x / $x)", "$x / $x", v -> x(v).divide(x(v)), new Divide(vx, vx))
.advanced("(2 + 1)", "2 + 1", v -> v(2 + 1), new Add(c(2), c(1)))
.advanced("($x - 1)", "$x - 1", v -> x(v).subtract(v(1)), new Subtract(vx, c(1)))
.advanced("(1 * 2)", "1 * 2", v -> v(1 * 2), new Multiply(c(1), c(2)))
.advanced("($x / 1)", "$x / 1", v -> x(v).divide(v(1)), new Divide(vx, c(1)))
.advanced("(1 + (2 + 1))", "1 + 2 + 1", v -> v(1 + 2 + 1), new Add(c(1), new Add(c(2), c(1))))
.advanced("($x - ($x - 1))", "$x - ($x - 1)", v -> x(v).subtract(x(v).subtract(v(1))), new Subtract(vx, new Subtract(vx, c(1))))
.advanced("(2 * ($x / 1))", "2 * ($x / 1)", v -> v(2).multiply(x(v).divide(v(1))), new Multiply(c(2), new Divide(vx, c(1))))
.advanced("(2 / ($x - 1))", "2 / ($x - 1)", v -> v(2).divide(x(v).subtract(v(1))), new Divide(c(2), new Subtract(vx, c(1))))
.advanced("((1 * 2) + $x)", "1 * 2 + $x", v -> v(1 * 2).add(x(v)), new Add(new Multiply(c(1), c(2)), vx))
.advanced("(($x - 1) - 2)", "$x - 1 - 2", v -> x(v).subtract(v(3)), new Subtract(new Subtract(vx, c(1)), c(2)))
.advanced("(($x / 1) * 2)", "$x / 1 * 2", v -> x(v).multiply(v(2)), new Multiply(new Divide(vx, c(1)), c(2)))
.advanced("((2 + 1) / 1)", "(2 + 1) / 1", v -> v(3), new Divide(new Add(c(2), c(1)), c(1)))
.advanced(
"(1 + (1 + (2 + 1)))",
"1 + 1 + 2 + 1",
v -> v(1 + 1 + 2 + 1),
new Add(c(1), new Add(c(1), new Add(c(2), c(1))))
)
.advanced(
"($x - ((1 * 2) + $x))",
"$x - (1 * 2 + $x)",
v -> x(v).subtract(v(1 * 2).add(x(v))),
new Subtract(vx, new Add(new Multiply(c(1), c(2)), vx))
)
.advanced(
"($x * (2 / ($x - 1)))",
"$x * (2 / ($x - 1))",
v -> x(v).multiply(v(2).divide(x(v).subtract(v(1)))),
new Multiply(vx, new Divide(c(2), new Subtract(vx, c(1))))
)
.advanced(
"($x / (1 + (2 + 1)))",
"$x / (1 + 2 + 1)",
v -> x(v).divide(v(1 + 2 + 1)),
new Divide(vx, new Add(c(1), new Add(c(2), c(1))))
)
.advanced(
"((1 * 2) + (2 + 1))",
"1 * 2 + 2 + 1",
v -> v(1 * 2 + 2 + 1),
new Add(new Multiply(c(1), c(2)), new Add(c(2), c(1)))
)
.advanced(
"((2 + 1) - (2 + 1))",
"2 + 1 - (2 + 1)",
v -> v(2 + 1 - (2 + 1)),
new Subtract(new Add(c(2), c(1)), new Add(c(2), c(1)))
)
.advanced(
"(($x - 1) * ($x / 1))",
"($x - 1) * ($x / 1)",
v -> x(v).subtract(v(1)).multiply(x(v).divide(v(1))),
new Multiply(new Subtract(vx, c(1)), new Divide(vx, c(1)))
)
.advanced(
"(($x - 1) / (1 * 2))",
"($x - 1) / (1 * 2)",
v -> x(v).subtract(v(1)).divide(v(2)),
new Divide(new Subtract(vx, c(1)), new Multiply(c(1), c(2)))
)
.advanced(
"((($x - 1) - 2) + $x)",
"$x - 1 - 2 + $x",
v -> x(v).subtract(v(3)).add(x(v)),
new Add(new Subtract(new Subtract(vx, c(1)), c(2)), vx)
)
.advanced(
"(((1 * 2) + $x) - 1)",
"1 * 2 + $x - 1",
v -> v(1).add(x(v)),
new Subtract(new Add(new Multiply(c(1), c(2)), vx), c(1))
)
.advanced(
"(((2 + 1) / 1) * $x)",
"(2 + 1) / 1 * $x",
v -> v(3).multiply(x(v)),
new Multiply(new Divide(new Add(c(2), c(1)), c(1)), vx)
)
.advanced(
"((2 / ($x - 1)) / 2)",
"2 / ($x - 1) / 2",
v -> v(2).divide(x(v).subtract(v(1))).divide(v(2)),
new Divide(new Divide(c(2), new Subtract(vx, c(1))), c(2))
);
}
private static BigDecimal x(final List<BigDecimal> vars) {
return vars.get(0);
}
private static BigDecimal y(final List<BigDecimal> vars) {
return vars.get(1);
}
private static Const c(final BigDecimal v) {
return TYPE.constant(v);
}
private static Const c(final long v) {
return TYPE.constant(v(v));
}
private static BigDecimal v(final long v) {
return BigDecimal.valueOf(v);
}
static void main(final String... args) {
TripleExpression.SELECTOR
.variant("BigDecimalList", ExpressionTest.v(BigDecimalListExpression::tester))
.main(args);
}
}

View File

@@ -0,0 +1,192 @@
package expression;
import base.Asserts;
import base.Pair;
import base.TestCounter;
import expression.common.ExpressionKind;
import expression.common.Type;
import java.math.BigInteger;
import java.util.List;
import java.util.stream.IntStream;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
@FunctionalInterface
@SuppressWarnings("ClassReferencesSubclass")
public interface BigIntegerListExpression extends ToMiniString {
BigInteger evaluateBi(List<BigInteger> variables);
// Tests follow. You may temporarily remove everything til the end.
Add EXAMPLE = new Add(
new Subtract(new Variable(0), new Const(BigInteger.ONE)),
new Multiply(new Variable(1), new Const(BigInteger.TEN))
);
Type<BigInteger> TYPE = new Type<>(BigInteger::valueOf, random -> v(random.getRandom().nextLong()), BigInteger.class);
ExpressionKind<BigIntegerListExpression, BigInteger> KIND = new ExpressionKind<>(
TYPE,
BigIntegerListExpression.class,
(r, c) -> IntStream.range(0, c)
.mapToObj(name -> Pair.<String, BigIntegerListExpression>of("$" + name, new Variable(name)))
.toList(),
(expr, variables, values) -> expr.evaluateBi(values)
);
@SuppressWarnings("PointlessArithmeticExpression")
static ExpressionTester<?, ?> tester(final TestCounter counter) {
Asserts.assertEquals("Example toString()", "(($0 - 1) + ($1 * 10))", EXAMPLE.toString());
Asserts.assertEquals(
EXAMPLE + " at (2, 3)",
BigInteger.valueOf(31),
EXAMPLE.evaluateBi(List.of(BigInteger.valueOf(2), BigInteger.valueOf(3)))
);
final Variable vx = new Variable(0);
final Variable vy = new Variable(1);
return new ExpressionTester<>(
counter, KIND, c -> v -> c,
(op, a, b) -> v -> op.apply(a.evaluateBi(v), b.evaluateBi(v)),
BigInteger::add, BigInteger::subtract, BigInteger::multiply, BigInteger::divide
)
.basic("10", "10", v -> v(10), c(10))
.basic("$x", "$x", BigIntegerListExpression::x, vx)
.basic("$y", "$y", BigIntegerListExpression::y, vy)
.basic("($x + $y)", "$x + $y", v -> x(v).add(y(v)), new Add(vx, vy))
.basic("($x + 2)", "$x + 2", v -> x(v).add(v(2)), new Add(vx, c(2)))
.basic("(2 - $x)", "2 - $x", v -> v(2).subtract(x(v)), new Subtract(c(2), vx))
.basic("(3 * $x)", "3 * $x", v -> v(3).multiply(x(v)), new Multiply(c(3), vx))
.basic("($x + $x)", "$x + $x", v -> x(v).add(x(v)), new Add(vx, vx))
.basic("($x / -2)", "$x / -2", v -> x(v).divide(v(-2)), new Divide(vx, c(-2)))
.basic("(2 + $x)", "2 + $x", v -> v(2).add(x(v)), new Add(c(2), vx))
.basic("((1 + 2) + 3)", "1 + 2 + 3", v -> v(6), new Add(new Add(c(1), c(2)), c(3)))
.basic("(1 + (2 * 3))", "1 + 2 * 3", v -> v(7), new Add(c(1), new Multiply(c(2), c(3))))
.basic("(1 - (2 * 3))", "1 - 2 * 3", v -> v(-5), new Subtract(c(1), new Multiply(c(2), c(3))))
.basic("(1 + (2 + 3))", "1 + 2 + 3", v -> v(6), new Add(c(1), new Add(c(2), c(3))))
.basic("((1 - 2) - 3)", "1 - 2 - 3", v -> v(-4), new Subtract(new Subtract(c(1), c(2)), c(3)))
.basic("(1 - (2 - 3))", "1 - (2 - 3)", v -> v(2), new Subtract(c(1), new Subtract(c(2), c(3))))
.basic("((1 * 2) * 3)", "1 * 2 * 3", v -> v(6), new Multiply(new Multiply(c(1), c(2)), c(3)))
.basic("(1 * (2 * 3))", "1 * 2 * 3", v -> v(6), new Multiply(c(1), new Multiply(c(2), c(3))))
.basic("((10 / 2) / 3)", "10 / 2 / 3", v -> v(10 / 2 / 3), new Divide(new Divide(c(10), c(2)), c(3)))
.basic("(10 / (3 / 2))", "10 / (3 / 2)", v -> v(10 / (3 / 2)), new Divide(c(10), new Divide(c(3), c(2))))
.basic("(($x * $x) + (($x - 1) / 10))",
"$x * $x + ($x - 1) / 10",
v -> x(v).multiply(x(v)).add(x(v).subtract(v(1)).divide(v(10))),
new Add(new Multiply(vx, vx), new Divide(new Subtract(vx, c(1)), c(10)))
)
.basic("($x * -1000000000)", "$x * -1000000000", v -> x(v).multiply(v(-1_000_000_000)), new Multiply(vx, c(-1_000_000_000)))
.basic("($x * -1000000000000000)", "$x * -1000000000000000", v -> x(v).multiply(v(-1_000_000_000_000_000L)), new Multiply(vx, c(-1_000_000_000_000_000L)))
.basic("(10 / $x)", "10 / $x", v -> v(10).divide(x(v)), new Divide(c(10), vx))
.basic("($x / $x)", "$x / $x", v -> x(v).divide(x(v)), new Divide(vx, vx))
.advanced("(2 + 1)", "2 + 1", v -> v(2 + 1), new Add(c(2), c(1)))
.advanced("($x - 1)", "$x - 1", v -> x(v).subtract(v(1)), new Subtract(vx, c(1)))
.advanced("(1 * 2)", "1 * 2", v -> v(1 * 2), new Multiply(c(1), c(2)))
.advanced("($x / 1)", "$x / 1", v -> x(v).divide(v(1)), new Divide(vx, c(1)))
.advanced("(1 + (2 + 1))", "1 + 2 + 1", v -> v(1 + 2 + 1), new Add(c(1), new Add(c(2), c(1))))
.advanced("($x - ($x - 1))", "$x - ($x - 1)", v -> x(v).subtract(x(v).subtract(v(1))), new Subtract(vx, new Subtract(vx, c(1))))
.advanced("(2 * ($x / 1))", "2 * ($x / 1)", v -> v(2).multiply(x(v).divide(v(1))), new Multiply(c(2), new Divide(vx, c(1))))
.advanced("(2 / ($x - 1))", "2 / ($x - 1)", v -> v(2).divide(x(v).subtract(v(1))), new Divide(c(2), new Subtract(vx, c(1))))
.advanced("((1 * 2) + $x)", "1 * 2 + $x", v -> v(1 * 2).add(x(v)), new Add(new Multiply(c(1), c(2)), vx))
.advanced("(($x - 1) - 2)", "$x - 1 - 2", v -> x(v).subtract(v(3)), new Subtract(new Subtract(vx, c(1)), c(2)))
.advanced("(($x / 1) * 2)", "$x / 1 * 2", v -> x(v).multiply(v(2)), new Multiply(new Divide(vx, c(1)), c(2)))
.advanced("((2 + 1) / 1)", "(2 + 1) / 1", v -> v(3), new Divide(new Add(c(2), c(1)), c(1)))
.advanced(
"(1 + (1 + (2 + 1)))",
"1 + 1 + 2 + 1",
v -> v(1 + 1 + 2 + 1),
new Add(c(1), new Add(c(1), new Add(c(2), c(1))))
)
.advanced(
"($x - ((1 * 2) + $x))",
"$x - (1 * 2 + $x)",
v -> x(v).subtract(v(1 * 2).add(x(v))),
new Subtract(vx, new Add(new Multiply(c(1), c(2)), vx))
)
.advanced(
"($x * (2 / ($x - 1)))",
"$x * (2 / ($x - 1))",
v -> x(v).multiply(v(2).divide(x(v).subtract(v(1)))),
new Multiply(vx, new Divide(c(2), new Subtract(vx, c(1))))
)
.advanced(
"($x / (1 + (2 + 1)))",
"$x / (1 + 2 + 1)",
v -> x(v).divide(v(1 + 2 + 1)),
new Divide(vx, new Add(c(1), new Add(c(2), c(1))))
)
.advanced(
"((1 * 2) + (2 + 1))",
"1 * 2 + 2 + 1",
v -> v(1 * 2 + 2 + 1),
new Add(new Multiply(c(1), c(2)), new Add(c(2), c(1)))
)
.advanced(
"((2 + 1) - (2 + 1))",
"2 + 1 - (2 + 1)",
v -> v(2 + 1 - (2 + 1)),
new Subtract(new Add(c(2), c(1)), new Add(c(2), c(1)))
)
.advanced(
"(($x - 1) * ($x / 1))",
"($x - 1) * ($x / 1)",
v -> x(v).subtract(v(1)).multiply(x(v).divide(v(1))),
new Multiply(new Subtract(vx, c(1)), new Divide(vx, c(1)))
)
.advanced(
"(($x - 1) / (1 * 2))",
"($x - 1) / (1 * 2)",
v -> x(v).subtract(v(1)).divide(v(2)),
new Divide(new Subtract(vx, c(1)), new Multiply(c(1), c(2)))
)
.advanced(
"((($x - 1) - 2) + $x)",
"$x - 1 - 2 + $x",
v -> x(v).subtract(v(3)).add(x(v)),
new Add(new Subtract(new Subtract(vx, c(1)), c(2)), vx)
)
.advanced(
"(((1 * 2) + $x) - 1)",
"1 * 2 + $x - 1",
v -> v(1).add(x(v)),
new Subtract(new Add(new Multiply(c(1), c(2)), vx), c(1))
)
.advanced(
"(((2 + 1) / 1) * $x)",
"(2 + 1) / 1 * $x",
v -> v(3).multiply(x(v)),
new Multiply(new Divide(new Add(c(2), c(1)), c(1)), vx)
)
.advanced(
"((2 / ($x - 1)) / 2)",
"2 / ($x - 1) / 2",
v -> v(2).divide(x(v).subtract(v(1))).divide(v(2)),
new Divide(new Divide(c(2), new Subtract(vx, c(1))), c(2))
);
}
private static BigInteger x(final List<BigInteger> vars) {
return vars.get(0);
}
private static BigInteger y(final List<BigInteger> vars) {
return vars.get(1);
}
private static Const c(final long v) {
return TYPE.constant(v(v));
}
private static BigInteger v(final long v) {
return BigInteger.valueOf(v);
}
static void main(final String... args) {
TripleExpression.SELECTOR
.variant("BigIntegerList", ExpressionTest.v(BigIntegerListExpression::tester))
.main(args);
}
}

View File

@@ -0,0 +1,112 @@
package expression;
import base.Asserts;
import base.ExtendedRandom;
import base.Pair;
import base.TestCounter;
import expression.common.ExpressionKind;
import expression.common.Type;
import java.util.List;
/**
* One-argument arithmetic expression over integers.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
@FunctionalInterface
@SuppressWarnings("ClassReferencesSubclass")
public interface Expression extends ToMiniString {
int evaluate(int x);
// Tests follow. You may temporarily remove everything til the end.
Subtract EXAMPLE = new Subtract(
new Multiply(new Const(2), new Variable("x")),
new Const(3)
);
Type<Integer> TYPE = new Type<>(a -> a, ExtendedRandom::nextInt, int.class);
ExpressionKind<Expression, Integer> KIND = new ExpressionKind<>(
TYPE,
Expression.class,
List.of(Pair.of("x", new Variable("x"))),
(expr, variables, values) -> expr.evaluate(values.get(0))
);
private static Const c(final int c) {
return new Const(c);
}
@SuppressWarnings({"PointlessArithmeticExpression", "Convert2MethodRef"})
static ExpressionTester<?, ?> tester(final TestCounter counter) {
Asserts.assertEquals("Example toString()", "((2 * x) - 3)", EXAMPLE.toString());
Asserts.assertEquals("Example at 5", 7, EXAMPLE.evaluate(5));
Asserts.assertTrue("Example equals 1",
new Multiply(new Const(2), new Variable("x"))
.equals(new Multiply(new Const(2), new Variable("x"))));
Asserts.assertTrue("Example equals 2",
!new Multiply(new Const(2), new Variable("x"))
.equals(new Multiply(new Variable("x"), new Const(2))));
final Variable vx = new Variable("x");
final Const c1 = c(1);
final Const c2 = c(2);
return new ExpressionTester<>(
counter, KIND, c -> x -> c,
(op, a, b) -> x -> op.apply(a.evaluate(x), b.evaluate(x)),
(a, b) -> a + b, (a, b) -> a - b, (a, b) -> a * b, (a, b) -> a / b
)
.basic("10", "10", x -> 10, c(10))
.basic("x", "x", x -> x, vx)
.basic("(x + 2)", "x + 2", x -> x + 2, new Add(vx, c(2)))
.basic("(2 - x)", "2 - x", x -> 2 - x, new Subtract(c(2), vx))
.basic("(3 * x)", "3 * x", x -> 3*x, new Multiply(c(3), vx))
.basic("(x + x)", "x + x", x -> x + x, new Add(vx, vx))
.basic("(x / -2)", "x / -2", x -> -x / 2, new Divide(vx, c(-2)))
.basic("(2 + x)", "2 + x", x -> 2 + x, new Add(c(2), vx))
.basic("((1 + 2) + 3)", "1 + 2 + 3", x -> 6, new Add(new Add(c(1), c(2)), c(3)))
.basic("(1 + (2 + 3))", "1 + 2 + 3", x -> 6, new Add(c(1), new Add(c(2), c(3))))
.basic("((1 - 2) - 3)", "1 - 2 - 3", x -> -4, new Subtract(new Subtract(c(1), c(2)), c(3)))
.basic("(1 - (2 - 3))", "1 - (2 - 3)", x -> 2, new Subtract(c(1), new Subtract(c(2), c(3))))
.basic("((1 * 2) * 3)", "1 * 2 * 3", x -> 6, new Multiply(new Multiply(c(1), c(2)), c(3)))
.basic("(1 * (2 * 3))", "1 * 2 * 3", x -> 6, new Multiply(c(1), new Multiply(c(2), c(3))))
.basic("((10 / 2) / 3)", "10 / 2 / 3", x -> 10 / 2 / 3, new Divide(new Divide(c(10), c(2)), c(3)))
.basic("(10 / (3 / 2))", "10 / (3 / 2)", x -> 10 / (3 / 2), new Divide(c(10), new Divide(c(3), c(2))))
.basic("(10 * (3 / 2))", "10 * (3 / 2)", x -> 10 * (3 / 2), new Multiply(c(10), new Divide(c(3), c(2))))
.basic("(10 + (3 - 2))", "10 + 3 - 2", x -> 10 + (3 - 2), new Add(c(10), new Subtract(c(3), c(2))))
.basic("((x * x) + ((x - 1) / 10))", "x * x + (x - 1) / 10", x -> x * x + (x - 1) / 10, new Add(
new Multiply(vx, vx),
new Divide(new Subtract(vx, c(1)), c(10))
))
.basic("(x * -1000000000)", "x * -1000000000", x -> x * -1_000_000_000, new Multiply(vx, c(-1_000_000_000)))
.basic("(10 / x)", "10 / x", x -> 10 / x, new Divide(c(10), vx))
.basic("(x / x)", "x / x", x -> x / x, new Divide(vx, vx))
.advanced("(2 + 1)", "2 + 1", x -> 2 + 1, new Add(c2, c1))
.advanced("(x - 1)", "x - 1", x -> x - 1, new Subtract(vx, c1))
.advanced("(1 * 2)", "1 * 2", x -> 1 * 2, new Multiply(c1, c2))
.advanced("(x / 1)", "x / 1", x -> x / 1, new Divide(vx, c1))
.advanced("(1 + (2 + 1))", "1 + 2 + 1", x -> 1 + 2 + 1, new Add(c1, new Add(c2, c1)))
.advanced("(x - (x - 1))", "x - (x - 1)", x -> x - (x - 1), new Subtract(vx, new Subtract(vx, c1)))
.advanced("(2 * (x / 1))", "2 * (x / 1)", x -> 2 * (x / 1), new Multiply(c2, new Divide(vx, c1)))
.advanced("(2 / (x - 1))", "2 / (x - 1)", x -> 2 / (x - 1), new Divide(c2, new Subtract(vx, c1)))
.advanced("((1 * 2) + x)", "1 * 2 + x", x -> 1 * 2 + x, new Add(new Multiply(c1, c2), vx))
.advanced("((x - 1) - 2)", "x - 1 - 2", x -> x - 1 - 2, new Subtract(new Subtract(vx, c1), c2))
.advanced("((x / 1) * 2)", "x / 1 * 2", x -> x / 1 * 2, new Multiply(new Divide(vx, c1), c2))
.advanced("((2 + 1) / 1)", "(2 + 1) / 1", x -> (2 + 1) / 1, new Divide(new Add(c2, c1), c1))
.advanced("(1 + (1 + (2 + 1)))", "1 + 1 + 2 + 1", x -> 1 + 1 + 2 + 1, new Add(c1, new Add(c1, new Add(c2, c1))))
.advanced("(x - ((1 * 2) + x))", "x - (1 * 2 + x)", x -> x - (1 * 2 + x), new Subtract(vx, new Add(new Multiply(c1, c2), vx)))
.advanced("(x * (2 / (x - 1)))", "x * (2 / (x - 1))", x -> x * (2 / (x - 1)), new Multiply(vx, new Divide(c2, new Subtract(vx, c1))))
.advanced("(x / (1 + (2 + 1)))", "x / (1 + 2 + 1)", x -> x / (1 + 2 + 1), new Divide(vx, new Add(c1, new Add(c2, c1))))
.advanced("((1 * 2) + (2 + 1))", "1 * 2 + 2 + 1", x -> 1 * 2 + 2 + 1, new Add(new Multiply(c1, c2), new Add(c2, c1)))
.advanced("((2 + 1) - (2 + 1))", "2 + 1 - (2 + 1)", x -> 2 + 1 - (2 + 1), new Subtract(new Add(c2, c1), new Add(c2, c1)))
.advanced("((x - 1) * (x / 1))", "(x - 1) * (x / 1)", x -> (x - 1) * (x / 1), new Multiply(new Subtract(vx, c1), new Divide(vx, c1)))
.advanced("((x - 1) / (1 * 2))", "(x - 1) / (1 * 2)", x -> (x - 1) / (1 * 2), new Divide(new Subtract(vx, c1), new Multiply(c1, c2)))
.advanced("(((x - 1) - 2) + x)", "x - 1 - 2 + x", x -> x - 1 - 2 + x, new Add(new Subtract(new Subtract(vx, c1), c2), vx))
.advanced("(((1 * 2) + x) - 1)", "1 * 2 + x - 1", x -> 1 * 2 + x - 1, new Subtract(new Add(new Multiply(c1, c2), vx), c1))
.advanced("(((2 + 1) / 1) * x)", "(2 + 1) / 1 * x", x -> (2 + 1) / 1 * x, new Multiply(new Divide(new Add(c2, c1), c1), vx))
.advanced("((2 / (x - 1)) / 2)", "2 / (x - 1) / 2", x -> 2 / (x - 1) / 2, new Divide(new Divide(c2, new Subtract(vx, c1)), c2));
}
}

View File

@@ -0,0 +1,26 @@
package expression;
import base.Selector;
import base.TestCounter;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class ExpressionTest {
public static final Selector SELECTOR = new Selector(ExpressionTest.class, "easy", "hard")
.variant("Base", v(Expression::tester));
private ExpressionTest() {
}
public static Consumer<TestCounter> v(final Function<TestCounter, ? extends ExpressionTester<?, ?>> tester) {
return t -> tester.apply(t).test();
}
public static void main(final String... args) {
SELECTOR.main(args);
}
}

View File

@@ -0,0 +1,288 @@
package expression;
import base.*;
import expression.common.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import static base.Asserts.assertTrue;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public class ExpressionTester<E extends ToMiniString, C> extends Tester {
private final List<Integer> VALUES = IntStream.rangeClosed(-10, 10).boxed().toList();
private final ExpressionKind<E, C> kind;
private final List<Test> basic = new ArrayList<>();
private final List<Test> advanced = new ArrayList<>();
private final Set<String> used = new HashSet<>();
private final GeneratorBuilder generator;
private final List<Pair<ToMiniString, String>> prev = new ArrayList<>();
private final Map<String, C> mappings;
protected ExpressionTester(
final TestCounter counter,
final ExpressionKind<E, C> kind,
final Function<C, E> expectedConstant,
final Binary<C, E> binary,
final BinaryOperator<C> add,
final BinaryOperator<C> sub,
final BinaryOperator<C> mul,
final BinaryOperator<C> div,
final Map<String, C> mappings
) {
super(counter);
this.kind = kind;
this.mappings = mappings;
generator = new GeneratorBuilder(expectedConstant, kind::constant, binary, kind::randomValue);
generator.binary("+", 1600, add, Add.class);
generator.binary("-", 1602, sub, Subtract.class);
generator.binary("*", 2001, mul, Multiply.class);
generator.binary("/", 2002, div, Divide.class);
}
protected ExpressionTester(
final TestCounter counter,
final ExpressionKind<E, C> kind,
final Function<C, E> expectedConstant,
final Binary<C, E> binary,
final BinaryOperator<C> add,
final BinaryOperator<C> sub,
final BinaryOperator<C> mul,
final BinaryOperator<C> div
) {
this(counter, kind, expectedConstant, binary, add, sub, mul, div, Map.of());
}
@Override
public String toString() {
return kind.getName();
}
@Override
public void test() {
counter.scope("Basic tests", () -> basic.forEach(Test::test));
counter.scope("Advanced tests", () -> advanced.forEach(Test::test));
counter.scope("Random tests", generator::testRandom);
}
@SuppressWarnings({"ConstantValue", "EqualsWithItself"})
private void checkEqualsAndToString(final String full, final String mini, final ToMiniString expression, final ToMiniString copy) {
checkToString("toString", full, expression.toString());
if (mode() > 0) {
checkToString("toMiniString", mini, expression.toMiniString());
}
counter.test(() -> {
assertTrue("Equals to this", expression.equals(expression));
assertTrue("Equals to copy", expression.equals(copy));
assertTrue("Equals to null", !expression.equals(null));
assertTrue("Copy equals to null", !copy.equals(null));
});
final String expressionToString = Objects.requireNonNull(expression.toString());
for (final Pair<ToMiniString, String> pair : prev) {
counter.test(() -> {
final ToMiniString prev = pair.first();
final String prevToString = pair.second();
final boolean equals = prevToString.equals(expressionToString);
assertTrue("Equals to " + prevToString, prev.equals(expression) == equals);
assertTrue("Equals to " + prevToString, expression.equals(prev) == equals);
assertTrue("Inconsistent hashCode for " + prev + " and " + expression, (prev.hashCode() == expression.hashCode()) == equals);
});
}
}
private void checkToString(final String method, final String expected, final String actual) {
counter.test(() -> assertTrue(String.format("Invalid %s\n expected: %s\n actual: %s", method, expected, actual), expected.equals(actual)));
}
private void check(
final String full,
final E expected,
final E actual,
final List<String> variables,
final List<C> values
) {
final String vars = IntStream.range(0, variables.size())
.mapToObj(i -> variables.get(i) + "=" + values.get(i))
.collect(Collectors.joining(","));
counter.test(() -> {
final Object expectedResult = evaluate(expected, variables, values);
final Object actualResult = evaluate(actual, variables, values);
final String reason = String.format(
"%s:%n expected `%s`,%n actual `%s`",
String.format("f(%s)\nwhere f is %s", vars, full),
Asserts.toString(expectedResult),
Asserts.toString(actualResult)
);
if (
expectedResult != null && actualResult != null &&
expectedResult.getClass() == actualResult.getClass()
&& (expectedResult.getClass() == Double.class || expectedResult.getClass() == Float.class)
) {
final double expectedValue = ((Number) expectedResult).doubleValue();
final double actualValue = ((Number) actualResult).doubleValue();
Asserts.assertEquals(reason, expectedValue, actualValue, 1e-6);
} else {
assertTrue(reason, Objects.deepEquals(expectedResult, actualResult));
}
});
}
private Object evaluate(final E expression, final List<String> variables, final List<C> values) {
try {
return kind.evaluate(expression, variables, values);
} catch (final Exception e) {
return e.getClass().getName();
}
}
protected ExpressionTester<E, C> basic(final String full, final String mini, final E expected, final E actual) {
return basicF(full, mini, expected, vars -> actual);
}
protected ExpressionTester<E, C> basicF(final String full, final String mini, final E expected, final Function<List<String>, E> actual) {
return basic(new Test(full, mini, expected, actual));
}
private ExpressionTester<E, C> basic(final Test test) {
Asserts.assertTrue(test.full, used.add(test.full));
basic.add(test);
return this;
}
protected ExpressionTester<E, C> advanced(final String full, final String mini, final E expected, final E actual) {
return advancedF(full, mini, expected, vars -> actual);
}
protected ExpressionTester<E, C> advancedF(final String full, final String mini, final E expected, final Function<List<String>, E> actual) {
Asserts.assertTrue(full, used.add(full));
advanced.add(new Test(full, mini, expected, actual));
return this;
}
protected static <E> Named<E> variable(final String name, final E expected) {
return Named.of(name, expected);
}
@FunctionalInterface
public interface Binary<C, E> {
E apply(BinaryOperator<C> op, E a, E b);
}
private final class Test {
private final String full;
private final String mini;
private final E expected;
private final Function<List<String>, E> actual;
private Test(final String full, final String mini, final E expected, final Function<List<String>, E> actual) {
this.full = full;
this.mini = mini;
this.expected = expected;
this.actual = actual;
}
private void test() {
final List<Pair<String, E>> variables = kind.variables().generate(random(), 3);
final List<String> names = Functional.map(variables, Pair::first);
final E actual = kind.cast(this.actual.apply(names));
final String full = mangle(this.full, names);
final String mini = mangle(this.mini, names);
counter.test(() -> {
kind.allValues(variables.size(), VALUES).forEach(values -> check(mini, expected, actual, names, values));
checkEqualsAndToString(full, mini, actual, actual);
prev.add(Pair.of(actual, full));
});
}
private String mangle(String string, final List<String> names) {
for (int i = 0; i < names.size(); i++) {
string = string.replace("$" + (char) ('x' + i), names.get(i));
}
for (final Map.Entry<String, C> mapping : mappings.entrySet()) {
string = string.replace(mapping.getKey(), mapping.getValue().toString());
}
return string;
}
}
private final class GeneratorBuilder {
private final Generator.Builder<C> generator;
private final NodeRendererBuilder<C> renderer = new NodeRendererBuilder<>(random());
private final Renderer.Builder<C, Unit, E> expected;
private final Renderer.Builder<C, Unit, E> actual;
private final Renderer.Builder<C, Unit, E> copy;
private final Binary<C, E> binary;
private GeneratorBuilder(
final Function<C, E> expectedConstant,
final Function<? super C, E> actualConstant,
final Binary<C, E> binary,
final Function<ExtendedRandom, C> randomValue
) {
generator = Generator.builder(() -> randomValue.apply(random()), random());
expected = Renderer.builder(expectedConstant::apply);
actual = Renderer.builder(actualConstant::apply);
copy = Renderer.builder(actualConstant::apply);
this.binary = binary;
}
private void binary(final String name, final int priority, final BinaryOperator<C> op, final Class<?> type) {
generator.add(name, 2);
renderer.binary(name, priority);
expected.binary(name, (unit, a, b) -> binary.apply(op, a, b));
@SuppressWarnings("unchecked") final Constructor<? extends E> constructor = (Constructor<? extends E>) Arrays.stream(type.getConstructors())
.filter(cons -> Modifier.isPublic(cons.getModifiers()))
.filter(cons -> cons.getParameterCount() == 2)
.findFirst()
.orElseGet(() -> counter.fail("%s(..., ...) constructor not found", type.getSimpleName()));
final Renderer.BinaryOperator<Unit, E> actual = (unit, a, b) -> {
try {
return constructor.newInstance(a, b);
} catch (final Exception e) {
return counter.fail(e);
}
};
this.actual.binary(name, actual);
copy.binary(name, actual);
}
private void testRandom() {
final NodeRenderer<C> renderer = this.renderer.build();
final Renderer<C, Unit, E> expectedRenderer = this.expected.build();
final Renderer<C, Unit, E> actualRenderer = this.actual.build();
final expression.common.Generator<C, E> generator = this.generator.build(kind.variables(), List.of());
generator.testRandom(counter, 1, expr -> {
final String full = renderer.render(expr, NodeRenderer.FULL);
final String mini = renderer.render(expr, NodeRenderer.MINI);
final E expected = expectedRenderer.render(expr, Unit.INSTANCE);
final E actual = actualRenderer.render(expr, Unit.INSTANCE);
final List<Pair<String, E>> variables = expr.variables();
final List<String> names = Functional.map(variables, Pair::first);
final List<C> values = Stream.generate(() -> kind.randomValue(random()))
.limit(variables.size())
.toList();
checkEqualsAndToString(full, mini, actual, copy.build().render(expr, Unit.INSTANCE));
check(full, expected, actual, names, values);
});
}
}
}

View File

@@ -0,0 +1,211 @@
package expression;
import base.Asserts;
import base.ExtendedRandom;
import base.Pair;
import base.TestCounter;
import expression.common.ExpressionKind;
import expression.common.Type;
import java.util.List;
import java.util.stream.IntStream;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
@SuppressWarnings("ClassReferencesSubclass")
@FunctionalInterface
public interface ListExpression extends ToMiniString {
int evaluate(List<Integer> variables);
// Tests follow. You may temporarily remove everything til the end.
Add EXAMPLE = new Add(
new Subtract(new Variable(0), new Const(1)),
new Multiply(new Variable(1), new Const(10))
);
Type<Integer> TYPE = new Type<>(a -> a, ExtendedRandom::nextInt, int.class);
ExpressionKind<ListExpression, Integer> KIND = new ExpressionKind<>(
TYPE,
ListExpression.class,
(r, c) -> IntStream.range(0, c)
.mapToObj(name -> Pair.<String, ListExpression>of("$" + name, new Variable(name)))
.toList(),
(expr, variables, values) -> expr.evaluate(values)
);
private static Const c(final Integer c) {
return TYPE.constant(c);
}
@SuppressWarnings({"PointlessArithmeticExpression", "Convert2MethodRef"})
static ExpressionTester<?, ?> tester(final TestCounter counter) {
Asserts.assertEquals("Example toString()", "(($0 - 1) + ($1 * 10))", EXAMPLE.toString());
Asserts.assertEquals(EXAMPLE + " at (2, 3)", 31, EXAMPLE.evaluate(List.of(2, 3)));
final Variable vx = new Variable(0);
return new ExpressionTester<>(
counter, KIND, c -> vars -> c,
(op, a, b) -> vars -> op.apply(a.evaluate(vars), b.evaluate(vars)),
(a, b) -> a + b, (a, b) -> a - b, (a, b) -> a * b, (a, b) -> a / b
)
.basic("10", "10", vars -> 10, c(10))
.basic("$x", "$x", ListExpression::x, vx)
.basic("($x + 2)", "$x + 2", vars -> x(vars) + 2, new Add(vx, c(2)))
.basic("(2 - $x)", "2 - $x", vars -> 2 - x(vars), new Subtract(c(2), vx))
.basic("(3 * $x)", "3 * $x", vars -> 3 * x(vars), new Multiply(c(3), vx))
.basic("($x + $x)", "$x + $x", vars -> x(vars) + x(vars), new Add(vx, vx))
.basic("($x / -2)", "$x / -2", vars -> -x(vars) / 2, new Divide(vx, c(-2)))
.basic("(2 + $x)", "2 + $x", vars -> 2 + x(vars), new Add(c(2), vx))
.basic("((1 + 2) + 3)", "1 + 2 + 3", vars -> 6, new Add(new Add(c(1), c(2)), c(3)))
.basic("(1 + (2 + 3))", "1 + 2 + 3", vars -> 6, new Add(c(1), new Add(c(2), c(3))))
.basic("((1 - 2) - 3)", "1 - 2 - 3", vars -> -4, new Subtract(new Subtract(c(1), c(2)), c(3)))
.basic("(1 - (2 - 3))", "1 - (2 - 3)", vars -> 2, new Subtract(c(1), new Subtract(c(2), c(3))))
.basic("((1 * 2) * 3)", "1 * 2 * 3", vars -> 6, new Multiply(new Multiply(c(1), c(2)), c(3)))
.basic("(1 * (2 * 3))", "1 * 2 * 3", vars -> 6, new Multiply(c(1), new Multiply(c(2), c(3))))
.basic("((10 / 2) / 3)", "10 / 2 / 3", vars -> 10 / 2 / 3, new Divide(new Divide(c(10), c(2)), c(3)))
.basic("(10 / (3 / 2))", "10 / (3 / 2)", vars -> 10 / (3 / 2), new Divide(c(10), new Divide(c(3), c(2))))
.basic("(10 * (3 / 2))", "10 * (3 / 2)", vars -> 10 * (3 / 2), new Multiply(c(10), new Divide(c(3), c(2))))
.basic("(10 + (3 - 2))", "10 + 3 - 2", vars -> 10 + (3 - 2), new Add(c(10), new Subtract(c(3), c(2))))
.basic(
"(($x * $x) + (($x - 1) / 10))",
"$x * $x + ($x - 1) / 10",
vars -> x(vars) * x(vars) + (x(vars) - 1) / 10,
new Add(new Multiply(vx, vx), new Divide(new Subtract(vx, c(1)), c(10)))
)
.basic(
"($x * -1000000000)",
"$x * -1000000000",
vars -> x(vars) * -1_000_000_000,
new Multiply(vx, c(-1_000_000_000))
)
.basic("(10 / $x)", "10 / $x", vars -> 10 / x(vars), new Divide(c(10), vx))
.basic("($x / $x)", "$x / $x", vars -> x(vars) / x(vars), new Divide(vx, vx))
.advanced("(2 + 1)", "2 + 1", vars -> 2 + 1, new Add(c(2), c(1)))
.advanced("($x - 1)", "$x - 1", vars -> x(vars) - 1, new Subtract(vx, c(1)))
.advanced("(1 * 2)", "1 * 2", vars -> 1 * 2, new Multiply(c(1), c(2)))
.advanced("($x / 1)", "$x / 1", vars -> x(vars) / 1, new Divide(vx, c(1)))
.advanced("(1 + (2 + 1))", "1 + 2 + 1", vars -> 1 + 2 + 1, new Add(c(1), new Add(c(2), c(1))))
.advanced(
"($x - ($x - 1))",
"$x - ($x - 1)",
vars -> x(vars) - (x(vars) - 1),
new Subtract(vx, new Subtract(vx, c(1)))
)
.advanced(
"(2 * ($x / 1))",
"2 * ($x / 1)",
vars -> 2 * (x(vars) / 1),
new Multiply(c(2), new Divide(vx, c(1)))
)
.advanced(
"(2 / ($x - 1))",
"2 / ($x - 1)",
vars -> 2 / (x(vars) - 1),
new Divide(c(2), new Subtract(vx, c(1)))
)
.advanced(
"((1 * 2) + $x)",
"1 * 2 + $x",
vars -> 1 * 2 + x(vars),
new Add(new Multiply(c(1), c(2)), vx)
)
.advanced(
"(($x - 1) - 2)",
"$x - 1 - 2",
vars -> x(vars) - 1 - 2,
new Subtract(new Subtract(vx, c(1)), c(2))
)
.advanced(
"(($x / 1) * 2)",
"$x / 1 * 2",
vars -> x(vars) / 1 * 2,
new Multiply(new Divide(vx, c(1)), c(2))
)
.advanced("((2 + 1) / 1)", "(2 + 1) / 1", vars -> (2 + 1) / 1, new Divide(new Add(c(2), c(1)), c(1)))
.advanced(
"(1 + (1 + (2 + 1)))",
"1 + 1 + 2 + 1",
vars -> 1 + 1 + 2 + 1,
new Add(c(1), new Add(c(1), new Add(c(2), c(1))))
)
.advanced(
"($x - ((1 * 2) + $x))",
"$x - (1 * 2 + $x)",
vars -> x(vars) - (1 * 2 + x(vars)),
new Subtract(vx, new Add(new Multiply(c(1), c(2)), vx))
)
.advanced(
"($x * (2 / ($x - 1)))",
"$x * (2 / ($x - 1))",
vars -> x(vars) * (2 / (x(vars) - 1)),
new Multiply(vx, new Divide(c(2), new Subtract(vx, c(1))))
)
.advanced(
"($x / (1 + (2 + 1)))",
"$x / (1 + 2 + 1)",
vars -> x(vars) / (1 + 2 + 1),
new Divide(vx, new Add(c(1), new Add(c(2), c(1))))
)
.advanced(
"((1 * 2) + (2 + 1))",
"1 * 2 + 2 + 1",
vars -> 1 * 2 + 2 + 1,
new Add(new Multiply(c(1), c(2)), new Add(c(2), c(1)))
)
.advanced(
"((2 + 1) - (2 + 1))",
"2 + 1 - (2 + 1)",
vars -> 2 + 1 - (2 + 1),
new Subtract(new Add(c(2), c(1)), new Add(c(2), c(1)))
)
.advanced(
"(($x - 1) * ($x / 1))",
"($x - 1) * ($x / 1)",
vars -> (x(vars) - 1) * (x(vars) / 1),
new Multiply(new Subtract(vx, c(1)), new Divide(vx, c(1)))
)
.advanced(
"(($x - 1) / (1 * 2))",
"($x - 1) / (1 * 2)",
vars -> (x(vars) - 1) / (1 * 2),
new Divide(new Subtract(vx, c(1)), new Multiply(c(1), c(2)))
)
.advanced(
"((($x - 1) - 2) + $x)",
"$x - 1 - 2 + $x",
vars -> x(vars) - 1 - 2 + x(vars),
new Add(new Subtract(new Subtract(vx, c(1)), c(2)), vx)
)
.advanced(
"(((1 * 2) + $x) - 1)",
"1 * 2 + $x - 1",
vars -> 1 * 2 + x(vars) - 1,
new Subtract(new Add(new Multiply(c(1), c(2)), vx), c(1))
)
.advanced(
"(((2 + 1) / 1) * $x)",
"(2 + 1) / 1 * $x",
vars -> (2 + 1) / 1 * x(vars),
new Multiply(new Divide(new Add(c(2), c(1)), c(1)), vx)
)
.advanced(
"((2 / ($x - 1)) / 2)",
"2 / ($x - 1) / 2",
vars -> 2 / (x(vars) - 1) / 2,
new Divide(new Divide(c(2), new Subtract(vx, c(1))), c(2))
);
}
private static Integer x(final List<Integer> vars) {
return vars.get(0);
}
static void main(final String... args) {
TripleExpression.SELECTOR
.variant("List", ExpressionTest.v(ListExpression::tester))
.main(args);
}
}

View File

@@ -0,0 +1,10 @@
package expression;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public interface ToMiniString {
default String toMiniString() {
return toString();
}
}

View File

@@ -0,0 +1,190 @@
package expression;
import base.ExtendedRandom;
import base.Pair;
import base.Selector;
import base.TestCounter;
import expression.common.ExpressionKind;
import expression.common.Type;
import java.util.List;
/**
* Three-argument arithmetic expression over integers.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
@FunctionalInterface
@SuppressWarnings("ClassReferencesSubclass")
public interface TripleExpression extends ToMiniString {
int evaluate(int x, int y, int z);
// Tests follow. You may temporarily remove everything til the end.
Type<Integer> TYPE = new Type<>(a -> a, ExtendedRandom::nextInt, int.class);
ExpressionKind<TripleExpression, Integer> KIND = new ExpressionKind<>(
TYPE,
TripleExpression.class,
List.of(
Pair.of("x", new Variable("x")),
Pair.of("y", new Variable("y")),
Pair.of("z", new Variable("z"))
),
(expr, variables, values) -> expr.evaluate(values.get(0), values.get(1), values.get(2))
);
@SuppressWarnings("PointlessArithmeticExpression")
static ExpressionTester<?, ?> tester(final TestCounter counter) {
final Variable vx = new Variable("x");
final Variable vy = new Variable("y");
final Variable vz = new Variable("z");
return new ExpressionTester<>(
counter, KIND, c -> (x, y, z) -> c,
(op, a, b) -> (x, y, z) -> op.apply(a.evaluate(x, y, z), b.evaluate(x, y, z)),
Integer::sum, (a, b) -> a - b, (a, b) -> a * b, (a, b) -> a / b
)
.basic("10", "10", (x, y, z) -> 10, c(10))
.basic("x", "x", (x, y, z) -> x, vx)
.basic("y", "y", (x, y, z) -> y, vy)
.basic("z", "z", (x, y, z) -> z, vz)
.basic("(x + 2)", "x + 2", (x, y, z) -> x + 2, new Add(vx, c(2)))
.basic("(2 - y)", "2 - y", (x, y, z) -> 2 - y, new Subtract(c(2), vy))
.basic("(3 * z)", "3 * z", (x, y, z) -> 3 * z, new Multiply(c(3), vz))
.basic("(x / -2)", "x / -2", (x, y, z) -> -x / 2, new Divide(vx, c(-2)))
.basic("((1 + 2) + 3)", "1 + 2 + 3", (x, y, z) -> 6, new Add(new Add(c(1), c(2)), c(3)))
.basic("(1 + (2 + 3))", "1 + 2 + 3", (x, y, z) -> 6, new Add(c(1), new Add(c(2), c(3))))
.basic("((1 - 2) - 3)", "1 - 2 - 3", (x, y, z) -> -4, new Subtract(new Subtract(c(1), c(2)), c(3)))
.basic("(1 - (2 - 3))", "1 - (2 - 3)", (x, y, z) -> 2, new Subtract(c(1), new Subtract(c(2), c(3))))
.basic("((1 * 2) * 3)", "1 * 2 * 3", (x, y, z) -> 6, new Multiply(new Multiply(c(1), c(2)), c(3)))
.basic("(1 * (2 * 3))", "1 * 2 * 3", (x, y, z) -> 6, new Multiply(c(1), new Multiply(c(2), c(3))))
.basic("((10 / 2) / 3)", "10 / 2 / 3", (x, y, z) -> 10 / 2 / 3, new Divide(new Divide(c(10), c(2)), c(3)))
.basic("(10 / (3 / 2))", "10 / (3 / 2)", (x, y, z) -> 10, new Divide(c(10), new Divide(c(3), c(2))))
.basic("((x * y) + ((z - 1) / 10))", "x * y + (z - 1) / 10", (x, y, z) -> x * y + (z - 1) / 10, new Add(
new Multiply(vx, vy),
new Divide(new Subtract(vz, c(1)), c(10))
))
.basic("(x + y)", "x + y", (x, y, z) -> x + y, new Add(vx, vy))
.basic("(y + x)", "y + x", (x, y, z) -> y + x, new Add(vy, vx))
.advanced("(1 + 1)", "1 + 1", (x, y, z) -> 1 + 1, new Add(c(1), c(1)))
.advanced("(y - x)", "y - x", (x, y, z) -> y - x, new Subtract(vy, vx))
.advanced("(2 * x)", "2 * x", (x, y, z) -> 2 * x, new Multiply(c(2), vx))
.advanced("(2 / x)", "2 / x", (x, y, z) -> 2 / x, new Divide(c(2), vx))
.advanced("(z + (1 + 1))", "z + 1 + 1", (x, y, z) -> z + 1 + 1, new Add(vz, new Add(c(1), c(1))))
.advanced(
"(2 - (y - x))",
"2 - (y - x)",
(x, y, z) -> 2 - (y - x),
new Subtract(c(2), new Subtract(vy, vx))
)
.advanced(
"(z * (2 / x))",
"z * (2 / x)",
(x, y, z) -> z * (2 / x),
new Multiply(vz, new Divide(c(2), vx))
)
.advanced(
"(z / (y - x))",
"z / (y - x)",
(x, y, z) -> z / (y - x),
new Divide(vz, new Subtract(vy, vx))
)
.advanced("((2 * x) + y)", "2 * x + y", (x, y, z) -> 2 * x + y, new Add(new Multiply(c(2), vx), vy))
.advanced(
"((y - x) - 2)",
"y - x - 2",
(x, y, z) -> y - x - 2,
new Subtract(new Subtract(vy, vx), c(2))
)
.advanced("((2 / x) * y)", "2 / x * y", (x, y, z) -> 2 / x * y, new Multiply(new Divide(c(2), vx), vy))
.advanced("((1 + 1) / x)", "(1 + 1) / x", (x, y, z) -> (1 + 1) / x, new Divide(new Add(c(1), c(1)), vx))
.advanced("(1 + (2 * 3))", "1 + 2 * 3", (x, y, z) -> 7, new Add(c(1), new Multiply(c(2), c(3))))
.advanced("(1 - (2 * 3))", "1 - 2 * 3", (x, y, z) -> -5, new Subtract(c(1), new Multiply(c(2), c(3))))
.advanced("(1 + (2 / 3))", "1 + 2 / 3", (x, y, z) -> 1, new Add(c(1), new Divide(c(2), c(3))))
.advanced("(1 - (2 / 3))", "1 - 2 / 3", (x, y, z) -> 1, new Subtract(c(1), new Divide(c(2), c(3))))
.advanced(
"(2 + (z + (1 + 1)))",
"2 + z + 1 + 1",
(x, y, z) -> 2 + z + 1 + 1,
new Add(c(2), new Add(vz, new Add(c(1), c(1))))
)
.advanced(
"(1 - ((2 * x) + y))",
"1 - (2 * x + y)",
(x, y, z) -> 1 - (2 * x + y),
new Subtract(c(1), new Add(new Multiply(c(2), vx), vy))
)
.advanced(
"(1 * (z / (y - x)))",
"1 * (z / (y - x))",
(x, y, z) -> 1 * (z / (y - x)),
new Multiply(c(1), new Divide(vz, new Subtract(vy, vx)))
)
.advanced(
"(z / (z + (1 + 1)))",
"z / (z + 1 + 1)",
(x, y, z) -> z / (z + 1 + 1),
new Divide(vz, new Add(vz, new Add(c(1), c(1))))
)
.advanced(
"((2 * x) + (1 + 1))",
"2 * x + 1 + 1",
(x, y, z) -> 2 * x + 1 + 1,
new Add(new Multiply(c(2), vx), new Add(c(1), c(1)))
)
.advanced(
"((1 + 1) - (1 + 1))",
"1 + 1 - (1 + 1)",
(x, y, z) -> 1 + 1 - (1 + 1),
new Subtract(new Add(c(1), c(1)), new Add(c(1), c(1)))
)
.advanced(
"((y - x) * (2 / x))",
"(y - x) * (2 / x)",
(x, y, z) -> (y - x) * (2 / x),
new Multiply(new Subtract(vy, vx), new Divide(c(2), vx))
)
.advanced(
"((y - x) / (2 * x))",
"(y - x) / (2 * x)",
(x, y, z) -> (y - x) / (2 * x),
new Divide(new Subtract(vy, vx), new Multiply(c(2), vx))
)
.advanced(
"(((y - x) - 2) + 1)",
"y - x - 2 + 1",
(x, y, z) -> y - x - 2 + 1,
new Add(new Subtract(new Subtract(vy, vx), c(2)), c(1))
)
.advanced(
"(((2 * x) + y) - z)",
"2 * x + y - z",
(x, y, z) -> 2 * x + y - z,
new Subtract(new Add(new Multiply(c(2), vx), vy), vz)
)
.advanced(
"(((1 + 1) / x) * 2)",
"(1 + 1) / x * 2",
(x, y, z) -> (1 + 1) / x * 2,
new Multiply(new Divide(new Add(c(1), c(1)), vx), c(2))
)
.advanced(
"((z / (y - x)) / x)",
"z / (y - x) / x",
(x, y, z) -> z / (y - x) / x,
new Divide(new Divide(vz, new Subtract(vy, vx)), vx)
);
}
private static Const c(final Integer c) {
return TYPE.constant(c);
}
Selector SELECTOR = ExpressionTest.SELECTOR
.variant("Triple", ExpressionTest.v(TripleExpression::tester));
static void main(final String... args) {
TripleExpression.SELECTOR.main(args);
}
}

View File

@@ -0,0 +1,32 @@
package expression.common;
import base.Functional;
import base.Pair;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public record Expr<C, V>(Node<C> node, List<Pair<String, V>> variables) {
public <T> List<Pair<String, T>> variables(final BiFunction<String, V, T> f) {
return Functional.map(
variables,
variable -> variable.second(f.apply(variable.first(), variable.second()))
);
}
public <T> Expr<C, T> convert(final BiFunction<String, V, T> f) {
return of(node, variables(f));
}
public Expr<C, V> node(final Function<Node<C>, Node<C>> f) {
return of(f.apply(node), variables);
}
public static <C, V> Expr<C, V> of(final Node<C> node, final List<Pair<String, V>> variables) {
return new Expr<>(node, variables);
}
}

View File

@@ -0,0 +1,94 @@
package expression.common;
import base.ExtendedRandom;
import base.Functional;
import base.Pair;
import expression.ToMiniString;
import java.util.List;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public class ExpressionKind<E extends ToMiniString, C> {
private final Type<C> type;
private final Class<E> kind;
private final Variables<E> variables;
private final Evaluator<E, C> evaluator;
public ExpressionKind(
final Type<C> type,
final Class<E> kind,
final Variables<E> variables,
final Evaluator<E, C> evaluator
) {
this.type = type;
this.kind = kind;
this.variables = variables;
this.evaluator = evaluator;
}
public ExpressionKind(
final Type<C> type,
final Class<E> kind,
final List<Pair<String, E>> variables,
final Evaluator<E, C> evaluator
) {
this(type, kind, (r, c) -> variables, evaluator);
}
public C evaluate(final E expression, final List<String> variables, final List<C> values) throws Exception {
return evaluator.evaluate(expression, variables, values);
}
public E cast(final Object expression) {
return kind.cast(expression);
}
public String getName() {
return kind.getSimpleName();
}
public E constant(final C value) {
return cast(type.constant(value));
}
public C randomValue(final ExtendedRandom random) {
return type.randomValue(random);
}
public List<List<C>> allValues(final int length, final List<Integer> values) {
return Functional.allValues(fromInts(values), length);
}
public List<C> fromInts(final List<Integer> values) {
return Functional.map(values, this::fromInt);
}
public C fromInt(final int value) {
return type.fromInt(value);
}
@Override
public String toString() {
return kind.getName();
}
public ExpressionKind<E, C> withVariables(final Variables<E> variables) {
return new ExpressionKind<>(type, kind, variables, evaluator);
}
public Variables<E> variables() {
return variables;
}
@FunctionalInterface
public interface Variables<E> {
List<Pair<String, E>> generate(final ExtendedRandom random, final int count);
}
@FunctionalInterface
public interface Evaluator<E, R> {
R evaluate(final E expression, final List<String> vars, final List<R> values) throws Exception;
}
}

View File

@@ -0,0 +1,173 @@
package expression.common;
import base.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
public class Generator<C, E> {
private final Supplier<C> constant;
private final List<Named<Integer>> ops;
private final ExpressionKind.Variables<E> variables;
private final Set<String> forbidden;
private final ExtendedRandom random;
private final List<Function<List<Node<C>>, Stream<Node<C>>>> basicTests;
public Generator(
final Supplier<C> constant,
final List<Named<Integer>> ops,
final ExpressionKind.Variables<E> variables,
final Set<String> forbidden,
final ExtendedRandom random,
final List<Function<List<Node<C>>, Stream<Node<C>>>> basicTests
) {
this.constant = constant;
this.ops = List.copyOf(ops);
this.variables = variables;
this.forbidden = Set.copyOf(forbidden);
this.random = random;
this.basicTests = List.copyOf(basicTests);
}
public static <C> Builder<C> builder(final Supplier<C> constant, final ExtendedRandom random) {
return new Builder<>(random, constant);
}
public void testRandom(
final TestCounter counter,
final int denominator,
final Consumer<Expr<C, E>> consumer
) {
final int d = Math.max(TestCounter.DENOMINATOR, denominator);
testRandom(counter, consumer, 1, 100, 100 / d, (vars, depth) -> generateFullDepth(vars, Math.min(depth, 3)));
testRandom(counter, consumer, 2, 1000 / d, 1, this::generateSize);
testRandom(counter, consumer, 3, 12, 100 / d, this::generateFullDepth);
testRandom(counter, consumer, 4, 777 / d, 1, this::generatePartialDepth);
}
private void testRandom(
final TestCounter counter,
final Consumer<Expr<C, E>> consumer,
final int seq,
final int levels,
final int perLevel,
final BiFunction<List<Node<C>>, Integer, Node<C>> generator
) {
counter.scope("Random tests #" + seq, () -> {
final int total = levels * perLevel;
int generated = 0;
for (int level = 0; level < levels; level++) {
for (int j = 0; j < perLevel; j++) {
if (generated % 100 == 0) {
progress(counter, total, generated);
}
generated++;
final List<Pair<String, E>> vars = variables(random.nextInt(10) + 1);
consumer.accept(Expr.of(generator.apply(Functional.map(vars, v -> Node.op(v.first())), level), vars));
}
}
progress(counter, generated, total);
});
}
private static void progress(final TestCounter counter, final int total, final int generated) {
counter.format("Completed %4d out of %d%n", generated, total);
}
private Node<C> generate(
final List<Node<C>> variables,
final boolean nullary,
final Supplier<Node<C>> unary,
final Supplier<Pair<Node<C>, Node<C>>> binary
) {
if (nullary || ops.isEmpty()) {
return random.nextBoolean() ? random.randomItem(variables) : Node.constant(constant.get());
} else {
final Named<Integer> op = random.randomItem(ops);
if (Math.abs(op.value()) == 1) {
return Node.op(op.name(), (op.value() + 1) >> 1, unary.get());
} else {
final Pair<Node<C>, Node<C>> pair = binary.get();
return Node.op(op.name(), pair.first(), pair.second());
}
}
}
private Node<C> generate(final List<Node<C>> variables, final boolean nullary, final Supplier<Node<C>> child) {
return generate(variables, nullary, child, () -> Pair.of(child.get(), child.get()));
}
private Node<C> generateFullDepth(final List<Node<C>> variables, final int depth) {
return generate(variables, depth == 0, () -> generateFullDepth(variables, depth - 1));
}
private Node<C> generatePartialDepth(final List<Node<C>> variables, final int depth) {
return generate(variables, depth == 0, () -> generatePartialDepth(variables, random.nextInt(depth)));
}
private Node<C> generateSize(final List<Node<C>> variables, final int size) {
final int first = size <= 1 ? 0 : random.nextInt(size);
return generate(
variables,
size == 0,
() -> generateSize(variables, size - 1),
() -> Pair.of(
generateSize(variables, first),
generateSize(variables, size - 1 - first)
)
);
}
public void testBasic(final Consumer<Expr<C, E>> consumer) {
basicTests.forEach(test -> {
final List<Pair<String, E>> vars = variables(random.nextInt(5) + 3);
test.apply(Functional.map(vars, v -> Node.op(v.first())))
.map(node -> Expr.of(node, vars))
.forEachOrdered(consumer);
});
}
public List<Pair<String, E>> variables(final int count) {
List<Pair<String, E>> vars;
do {
vars = variables.generate(random, count);
} while (vars.stream().map(Pair::first).anyMatch(forbidden::contains));
return vars;
}
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public static final class Builder<C> {
private final ExtendedRandom random;
private final Supplier<C> constant;
private final List<Named<Integer>> ops = new ArrayList<>();
private final Set<String> forbidden = new HashSet<>();
private Builder(final ExtendedRandom random, final Supplier<C> constant) {
this.random = random;
this.constant = constant;
}
public void add(final String name, final int arity) {
ops.add(Named.of(name, arity));
forbidden.add(name);
}
public <E> Generator<C, E> build(
final ExpressionKind.Variables<E> variables,
final List<Function<List<Node<C>>, Stream<Node<C>>>> basicTests
) {
return new Generator<>(constant, ops, variables, forbidden, random, basicTests);
}
}
}

View File

@@ -0,0 +1,106 @@
package expression.common;
import java.util.function.Function;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public abstract class Node<T> {
private Node() {
}
public abstract <R> R get(Const<T, R> con, Nullary<R> nul, Unary<Node<T>, R> un, Binary<Node<T>, R> bin);
public abstract <R> R cata(Const<T, R> con, Nullary<R> nul, Unary<R, R> un, Binary<R, R> bin);
public final String toPolish() {
return cata(
T::toString,
name -> name,
(name, priority, a) -> a + " " + name + ":1",
(name, a1, a2) -> a1 + " " + a2 + " " + name + ":2"
);
}
@Override
public final String toString() {
return cata(
T::toString,
name -> name,
(name, priority, a) -> name.equals("[") ? "[" + a + "]" :
(priority & 1) == 1 ? "(" + name + " " + a + ")" : "(" + a + " " + name + ")",
(name, a1, a2) -> "(" + a1 + " " + name + " " + a2 + ")"
);
}
public static <T> Node<T> constant(final T value) {
return new Node<>() {
@Override
public <R> R get(final Const<T, R> con, final Nullary<R> nul, final Unary<Node<T>, R> un, final Binary<Node<T>, R> bin) {
return con.apply(value);
}
@Override
public <R> R cata(final Const<T, R> con, final Nullary<R> nul, final Unary<R, R> un, final Binary<R, R> bin) {
return con.apply(value);
}
};
}
public static <T> Node<T> op(final String name) {
return new Node<>() {
@Override
public <R> R get(final Const<T, R> con, final Nullary<R> nul, final Unary<Node<T>, R> un, final Binary<Node<T>, R> bin) {
return nul.apply(name);
}
@Override
public <R> R cata(final Const<T, R> con, final Nullary<R> nul, final Unary<R, R> un, final Binary<R, R> bin) {
return nul.apply(name);
}
};
}
public static <T> Node<T> op(final String name, final int priority, final Node<T> arg) {
return new Node<>() {
@Override
public <R> R get(final Const<T, R> con, final Nullary<R> nul, final Unary<Node<T>, R> un, final Binary<Node<T>, R> bin) {
return un.apply(name, priority, arg);
}
@Override
public <R> R cata(final Const<T, R> con, final Nullary<R> nul, final Unary<R, R> un, final Binary<R, R> bin) {
return un.apply(name, priority, arg.cata(con, nul, un, bin));
}
};
}
public static <T> Node<T> op(final String name, final Node<T> arg1, final Node<T> arg2) {
return new Node<>() {
@Override
public <R> R get(final Const<T, R> con, final Nullary<R> nul, final Unary<Node<T>, R> un, final Binary<Node<T>, R> bin) {
return bin.apply(name, arg1, arg2);
}
@Override
public <R> R cata(final Const<T, R> con, final Nullary<R> nul, final Unary<R, R> un, final Binary<R, R> bin) {
return bin.apply(name, arg1.cata(con, nul, un, bin), arg2.cata(con, nul, un, bin));
}
};
}
@FunctionalInterface
public interface Const<T, R> extends Function<T, R> {}
@FunctionalInterface
public interface Nullary<R> extends Function<String, R> {}
@FunctionalInterface
public interface Unary<T, R> {
R apply(String name, int priority, T arg);
}
@FunctionalInterface
public interface Binary<T, R> {
R apply(String name, T arg1, T arg2);
}
}

View File

@@ -0,0 +1,96 @@
package expression.common;
import base.ExtendedRandom;
import java.util.List;
import java.util.Map;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public class NodeRenderer<C> {
public static final String PAREN = "[";
public static final List<Paren> DEFAULT_PARENS = List.of(paren("(", ")"));
public static final Mode MINI_MODE = Mode.SIMPLE_MINI; // Replace by TRUE_MINI for some challenge;
public static final Settings FULL = Mode.FULL.settings(0);
public static final Settings FULL_EXTRA = Mode.FULL.settings(Integer.MAX_VALUE / 4);
public static final Settings SAME = Mode.SAME.settings(0);
public static final Settings MINI = MINI_MODE.settings(0);
public static final Settings TRUE_MINI = Mode.TRUE_MINI.settings(0);
private final Renderer<C, Settings, Node<C>> renderer;
private final Map<String, String> brackets;
private final ExtendedRandom random;
public NodeRenderer(
final Renderer<C, Settings, Node<C>> renderer,
final Map<String, String> brackets,
final ExtendedRandom random
) {
this.renderer = renderer;
this.brackets = Map.copyOf(brackets);
this.random = random;
}
public static <C> Node<C> paren(final boolean condition, final Node<C> node) {
return condition ? Node.op(PAREN, 1, node) : node;
}
public static Paren paren(final String open, final String close) {
return new Paren(open, close);
}
public Node<C> renderToNode(final Settings settings, final Expr<C, ?> expr) {
final Expr<C, Node<C>> convert = expr.convert((name, variable) -> Node.op(name));
return renderer.render(convert, settings);
}
public String render(final Node<C> node, final List<Paren> parens) {
return node.cata(
String::valueOf,
name -> name,
(name, priority, arg) ->
name == PAREN ? random.randomItem(parens).apply(arg) :
priority == Integer.MAX_VALUE ? name + arg + brackets.get(name) :
(priority & 1) == 1 ? name + arg :
arg + name,
(name, a, b) -> a + " " + name + " " + b
);
}
public String render(final Expr<C, ?> expr, final Settings settings) {
return render(renderToNode(settings, expr), settings.parens());
}
public enum Mode {
FULL, SAME, TRUE_MINI, SIMPLE_MINI;
public Settings settings(final int limit) {
return new Settings(this, limit);
}
}
public record Paren(String open, String close) {
String apply(final String expression) {
return open() + expression + close();
}
}
public record Settings(Mode mode, int limit, List<Paren> parens) {
public Settings(final Mode mode, final int limit) {
this(mode, limit, DEFAULT_PARENS);
}
public <C> Node<C> extra(Node<C> node, final ExtendedRandom random) {
while (random.nextInt(Integer.MAX_VALUE) < limit) {
node = paren(true, node);
}
return node;
}
public Settings withParens(final List<Paren> parens) {
return this.parens.equals(parens) ? this : new Settings(mode, limit, List.copyOf(parens));
}
}
}

View File

@@ -0,0 +1,145 @@
package expression.common;
import base.ExtendedRandom;
import base.Functional;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
@SuppressWarnings("StaticMethodOnlyUsedInOneClass")
public class NodeRendererBuilder<C> {
private final Renderer.Builder<C, NodeRenderer.Settings, Node<C>> nodeRenderer = Renderer.builder(Node::constant);
private final Map<String, Priority> priorities = new HashMap<>();
private final Map<String, String> brackets = new HashMap<>();
private final ExtendedRandom random;
public NodeRendererBuilder(final ExtendedRandom random) {
this.random = random;
nodeRenderer.unary(NodeRenderer.PAREN, (mode, arg) -> NodeRenderer.paren(true, arg));
}
public void unary(final String name, final int priority) {
final String space = name.equals("-") || Character.isLetter(name.charAt(0)) ? " " : "";
nodeRenderer.unary(
name,
(settings, arg) -> settings.extra(Node.op(name, priority, inner(settings, priority, arg, space)), random)
);
}
public void unary(final String left, final String right) {
brackets.put(left, right);
nodeRenderer.unary(
left,
(settings, arg) -> settings.extra(Node.op(left, Integer.MAX_VALUE, arg), random)
);
}
private Node<C> inner(final NodeRenderer.Settings settings, final int priority, final Node<C> arg, final String space) {
if (settings.mode() == NodeRenderer.Mode.FULL) {
return NodeRenderer.paren(true, arg);
} else {
final String op = arg.get(
c -> space,
n -> space,
(n, p, a) ->
priority > unaryPriority(arg) ? NodeRenderer.PAREN :
NodeRenderer.PAREN.equals(n) ? "" :
space,
(n, a, b) -> NodeRenderer.PAREN
);
return op.isEmpty() ? arg : Node.op(op, Priority.MAX.priority | 1, arg);
}
}
private static <C> Integer unaryPriority(final Node<C> node) {
return node.get(c -> Integer.MAX_VALUE, n -> Integer.MAX_VALUE, (n, p, a) -> p, (n, a, b) -> Integer.MIN_VALUE);
}
public void binary(final String name, final int priority) {
final Priority mp = new Priority(name, priority);
priorities.put(name, mp);
nodeRenderer.binary(name, (settings, l, r) -> settings.extra(process(settings, mp, l, r), random));
}
private Node<C> process(final NodeRenderer.Settings settings, final Priority mp, final Node<C> l, final Node<C> r) {
if (settings.mode() == NodeRenderer.Mode.FULL) {
return NodeRenderer.paren(true, op(mp, l, r));
}
final Priority lp = priority(l);
final Priority rp = priority(r);
final int rc = rp.compareLevels(mp);
// :NOTE: Especially ugly code, do not replicate
final boolean advanced = settings.mode() == NodeRenderer.Mode.SAME
|| mp.has(2)
|| mp.has(1) && (mp != rp || (settings.mode() == NodeRenderer.Mode.TRUE_MINI && hasOther(r, rp)));
final Node<C> al = NodeRenderer.paren(lp.compareLevels(mp) < 0, l);
if (rc == 0 && !advanced) {
return get(r, null, (n, a, b) -> rp.op(mp.op(al, a), b));
} else {
return mp.op(al, NodeRenderer.paren(rc == 0 && advanced || rc < 0, r));
}
}
private boolean hasOther(final Node<C> node, final Priority priority) {
return get(node, () -> false, (name, l, r) -> {
final Priority p = Functional.get(priorities, name);
if (p.compareLevels(priority) != 0) {
return false;
}
return p != priority || hasOther(l, priority);
});
}
private Node<C> op(final Priority mp, final Node<C> l, final Node<C> r) {
return mp.op(l, r);
}
private Priority priority(final Node<C> node) {
return get(node, () -> Priority.MAX, (n, a, b) -> Functional.get(priorities, n));
}
private <R> R get(final Node<C> node, final Supplier<R> common, final Node.Binary<Node<C>, R> binary) {
return node.get(
c -> common.get(),
n -> common.get(),
(n, p, a) -> common.get(),
binary
);
}
public NodeRenderer<C> build() {
return new NodeRenderer<>(nodeRenderer.build(), brackets, random);
}
// :NOTE: Especially ugly bit-fiddling, do not replicate
private record Priority(String op, int priority) {
private static final int Q = 3;
private static final Priority MAX = new Priority("MAX", Integer.MAX_VALUE - Q);
private int compareLevels(final Priority that) {
return (priority | Q) - (that.priority | Q);
}
@Override
public String toString() {
return String.format("Priority(%s, %d, %d)", op, priority | Q, priority & Q);
}
public <C> Node<C> op(final Node<C> l, final Node<C> r) {
return Node.op(op, l, r);
}
private boolean has(final int value) {
return (priority & Q) == value;
}
}
}

View File

@@ -0,0 +1,60 @@
package expression.common;
import base.Either;
import java.util.function.LongUnaryOperator;
import java.util.function.Supplier;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class Reason {
public static final Reason OVERFLOW = new Reason("Overflow");
public static final Reason DBZ = new Reason("Division by zero");
private final String description;
public Reason(final String description) {
this.description = description;
}
public static <T> Either<Reason, T> eval(final Supplier<T> action) {
try {
return Either.right(action.get());
} catch (final ReasonException e) {
return Either.left(e.reason);
}
}
public static int overflow(final long value) {
return value < Integer.MIN_VALUE || Integer.MAX_VALUE < value
? OVERFLOW.error()
: (int) value;
}
public <T> T error() {
throw new ReasonException(this);
}
public LongUnaryOperator less(final long limit, final LongUnaryOperator op) {
return a -> a < limit ? error() : op.applyAsLong(a);
}
public LongUnaryOperator greater(final int limit, final LongUnaryOperator op) {
return a -> a > limit ? error() : op.applyAsLong(a);
}
private static class ReasonException extends RuntimeException {
private final Reason reason;
public ReasonException(final Reason reason) {
super(reason.description);
this.reason = reason;
}
}
@Override
public String toString() {
return String.format("Reason(%s)", description);
}
}

View File

@@ -0,0 +1,60 @@
package expression.common;
import base.Functional;
import base.Pair;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public interface Renderer<C, S, R> {
static <C, S, R> Builder<C, S, R> builder(final Node.Const<C, R> constant) {
return new Builder<>(constant);
}
R render(final Expr<C, R> expr, final S settings);
@FunctionalInterface
interface UnaryOperator<S, R> {
R apply(S settings, R arg);
}
@FunctionalInterface
interface BinaryOperator<S, R> {
R apply(S settings, R arg1, R arg2);
}
final class Builder<C, S, R> {
private final Node.Const<C, R> constant;
private final Map<String, UnaryOperator<S, R>> unary = new HashMap<>();
private final Map<String, BinaryOperator<S, R>> binary = new HashMap<>();
private Builder(final Node.Const<C, R> constant) {
this.constant = constant;
}
public void unary(final String name, final UnaryOperator<S, R> op) {
unary.put(name, op);
}
public void binary(final String name, final BinaryOperator<S, R> op) {
binary.put(name, op);
}
public Renderer<C, S, R> build() {
return (expr, settings) -> {
final Map<String, R> vars = expr.variables().stream()
.collect(Collectors.toMap(Pair::first, Pair::second));
return expr.node().cata(
constant,
name -> Functional.get(vars, name),
(name, p, arg) -> Functional.get(unary, name).apply(settings, arg),
(name, arg1, arg2) -> Functional.get(binary, name).apply(settings, arg1, arg2)
);
};
}
}
}

View File

@@ -0,0 +1,56 @@
package expression.common;
import base.Pair;
import base.TestCounter;
import expression.ToMiniString;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
public class TestGenerator<C, E extends ToMiniString> {
private final Generator<C, E> generator;
private final NodeRenderer<C> renderer;
public TestGenerator(final Generator<C, E> generator, final NodeRenderer<C> renderer) {
this.generator = generator;
this.renderer = renderer;
}
public void testBasic(final Consumer<Test<C, E>> test) {
generator.testBasic(consumer(test));
}
public void testRandom(final TestCounter counter, final int denominator, final Consumer<Test<C, E>> test) {
generator.testRandom(counter, denominator, consumer(test));
}
private Consumer<Expr<C, E>> consumer(final Consumer<TestGenerator.Test<C, E>> consumer) {
return expr -> consumer.accept(new TestGenerator.Test<>(expr, renderer));
}
public List<Pair<String, E>> variables(final int count) {
return generator.variables(count);
}
public String render(final Expr<C, ?> expr, final NodeRenderer.Settings settings) {
return renderer.render(expr, settings);
}
public static class Test<C, E> {
public final Expr<C, E> expr;
private final Map<NodeRenderer.Settings, String> rendered = new HashMap<>();
private final NodeRenderer<C> renderer;
public Test(final Expr<C, E> expr, final NodeRenderer<C> renderer) {
this.expr = expr;
this.renderer = renderer;
}
public String render(final NodeRenderer.Settings settings) {
return rendered.computeIfAbsent(settings, s -> renderer.render(expr, s));
}
}
}

View File

@@ -0,0 +1,145 @@
package expression.common;
import base.ExtendedRandom;
import base.Functional;
import expression.ToMiniString;
import expression.common.ExpressionKind.Variables;
import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public class TestGeneratorBuilder<C> {
private final ExtendedRandom random;
private final Generator.Builder<C> generator;
private final NodeRendererBuilder<C> renderer;
private final List<Function<List<Node<C>>, Stream<Node<C>>>> basicTests = new ArrayList<>();
private final List<Node<C>> consts;
private final boolean verbose;
public TestGeneratorBuilder(
final ExtendedRandom random,
final Supplier<C> constant,
final List<C> constants,
final boolean verbose
) {
this.random = random;
this.verbose = verbose;
generator = Generator.builder(constant, random);
renderer = new NodeRendererBuilder<>(random);
consts = Functional.map(constants, Node::constant);
basicTests.add(vars -> consts.stream());
basicTests.add(List::stream);
}
private Node<C> c() {
return random.randomItem(consts);
}
private Node<C> v(final List<Node<C>> variables) {
return random.randomItem(variables);
}
private static <C> Node<C> f(final String name, final int priority, final Node<C> arg) {
return Node.op(name, priority, arg);
}
private static <C> Node<C> f(final String left, final Node<C> arg) {
return Node.op(left, Integer.MAX_VALUE, arg);
}
private static <C> Node<C> f(final String name, final Node<C> arg1, final Node<C> arg2) {
return Node.op(name, arg1, arg2);
}
@SafeVarargs
private void basicTests(final Function<List<Node<C>>, Node<C>>... tests) {
Arrays.stream(tests).map(test -> test.andThen(Stream::of)).forEachOrdered(basicTests::add);
}
public void unary(final String name, final int priority) {
generator.add(name, (priority & 1) * 2 - 1);
renderer.unary(name, priority);
if (verbose) {
basicTests.add(vars -> Stream.concat(consts.stream(), vars.stream()).map(a -> f(name, priority, a)));
} else {
basicTests(vars -> f(name, priority, c()), vars -> f(name, priority, v(vars)));
}
final Function<List<Node<C>>, Node<C>> p1 = vars -> f(name, priority, f(name, priority, f("+", v(vars), c())));
final Function<List<Node<C>>, Node<C>> p2 = vars -> f("*", v(vars), f("*", v(vars), f(name, priority, c())));
basicTests(
vars -> f(name, priority, f("+", v(vars), v(vars))),
vars -> f(name, priority, f(name, priority, v(vars))),
vars -> f(name, priority, f("/", f(name, priority, v(vars)), f("+", v(vars), v(vars)))),
p1,
p2,
vars -> f("+", p1.apply(vars), p2.apply(vars))
);
}
public void unary(final String left, final String right) {
generator.add(left, 1);
renderer.unary(left, right);
if (verbose) {
basicTests.add(vars -> Stream.concat(consts.stream(), vars.stream()).map(a -> f(left, a)));
} else {
basicTests(vars -> f(left, c()), vars -> f(left, v(vars)));
}
final Function<List<Node<C>>, Node<C>> p1 = vars -> f(left, f(left, f("+", v(vars), c())));
final Function<List<Node<C>>, Node<C>> p2 = vars -> f("*", v(vars), f("*", v(vars), f(left, c())));
basicTests(
vars -> f(left, f("+", v(vars), v(vars))),
vars -> f(left, f(left, v(vars))),
vars -> f(left, f("/", f(left, v(vars)), f("+", v(vars), v(vars)))),
p1,
p2,
vars -> f("+", p1.apply(vars), p2.apply(vars))
);
}
public void binary(final String name, final int priority) {
generator.add(name, 2);
renderer.binary(name, priority);
if (verbose) {
basicTests.add(vars -> Stream.concat(consts.stream(), vars.stream().limit(3))
.flatMap(a -> consts.stream().map(b -> f(name, a, b))));
} else {
basicTests(
vars -> f(name, c(), c()),
vars -> f(name, v(vars), c()),
vars -> f(name, c(), v(vars)),
vars -> f(name, v(vars), v(vars))
);
}
final Function<List<Node<C>>, Node<C>> p1 = vars -> f(name, f(name, f("+", v(vars), c()), v(vars)), v(vars));
final Function<List<Node<C>>, Node<C>> p2 = vars -> f("*", v(vars), f("*", v(vars), f(name, c(), v(vars))));
basicTests(
vars -> f(name, f(name, v(vars), v(vars)), v(vars)),
vars -> f(name, v(vars), f(name, v(vars), v(vars))),
vars -> f(name, f(name, v(vars), v(vars)), f(name, v(vars), v(vars))),
vars -> f(name, f("-", f(name, v(vars), v(vars)), c()), f("+", v(vars), v(vars))),
p1,
p2,
vars -> f("+", p1.apply(vars), p2.apply(vars))
);
}
public <E extends ToMiniString> TestGenerator<C,E> build(final Variables<E> variables) {
return new TestGenerator<>(generator.build(variables, basicTests), renderer.build());
}
}

View File

@@ -0,0 +1,51 @@
package expression.common;
import base.Asserts;
import base.ExtendedRandom;
import expression.Const;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.function.Function;
import java.util.function.IntFunction;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public class Type<C> {
private final IntFunction<C> fromInt;
private final Function<ExtendedRandom, C> random;
private final Function<C, Const> constant;
public Type(final IntFunction<C> fromInt, final Function<ExtendedRandom, C> random, final Class<?> type) {
this.fromInt = fromInt;
this.random = random;
try {
final MethodHandle constructor = MethodHandles.publicLookup()
.findConstructor(Const.class, MethodType.methodType(void.class, type));
constant = c -> {
try {
return (Const) constructor.invoke(c);
} catch (final Throwable e) {
throw Asserts.error("Cannot create new Const(%s): %s", c, e);
}
};
} catch (final IllegalAccessException | NoSuchMethodException e) {
throw Asserts.error("Cannot find constructor Const(%s): %s", type, e);
}
}
public Const constant(final C value) {
return constant.apply(value);
}
public C fromInt(final int value) {
return fromInt.apply(value);
}
public C randomValue(final ExtendedRandom random) {
return this.random.apply(random);
}
}

View File

@@ -0,0 +1,7 @@
/**
* Expressions generators for expression-based homeworks
* of <a href="https://www.kgeorgiy.info/courses/prog-intro/">Introduction to Programming</a> course.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
package expression.common;

View File

@@ -0,0 +1,30 @@
package expression.exceptions;
import base.Selector;
import expression.ListExpression;
import expression.parser.Operations;
import static expression.parser.Operations.*;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class ExceptionsTest {
private static final ExpressionParser PARSER = new ExpressionParser();
private static final Operations.Operation LIST = kind(ListExpression.KIND, PARSER::parse);
public static final Selector SELECTOR = Selector.composite(ExceptionsTest.class, ExceptionsTester::new, "easy", "hard")
.variant("Base", LIST, ADD, SUBTRACT, MULTIPLY, DIVIDE, NEGATE)
.variant("3637", POW, LOG)
.variant("3839", POW, LOG, POW_2, LOG_2)
.variant("3435", POW_2, LOG_2)
.variant("3233", HIGH, LOW)
.selector();
private ExceptionsTest() {
}
public static void main(final String... args) {
SELECTOR.main(args);
}
}

View File

@@ -0,0 +1,162 @@
package expression.exceptions;
import base.Functional;
import base.Named;
import base.Pair;
import expression.ToMiniString;
import expression.Variable;
import expression.common.ExpressionKind;
import expression.parser.ParserTestSet;
import java.util.ArrayList;
import java.util.List;
import java.util.function.LongBinaryOperator;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public class ExceptionsTestSet<E extends ToMiniString, C> extends ParserTestSet<E, C> {
private static final int D = 5;
private static final List<Integer> OVERFLOW_VALUES = new ArrayList<>();
private final char[] CHARS = "AZ+-*%()[]<>".toCharArray();
static {
Functional.addRange(OVERFLOW_VALUES, D, Integer.MIN_VALUE + D);
Functional.addRange(OVERFLOW_VALUES, D, Integer.MIN_VALUE / 2);
Functional.addRange(OVERFLOW_VALUES, D, (int) -Math.sqrt(Integer.MAX_VALUE));
Functional.addRange(OVERFLOW_VALUES, D, 0);
Functional.addRange(OVERFLOW_VALUES, D, (int) Math.sqrt(Integer.MAX_VALUE));
Functional.addRange(OVERFLOW_VALUES, D, Integer.MAX_VALUE / 2);
Functional.addRange(OVERFLOW_VALUES, D, Integer.MAX_VALUE - D);
}
private final List<Named<String>> parsingTest;
public ExceptionsTestSet(final ExceptionsTester tester, final ParsedKind<E, C> kind) {
super(tester, kind, false);
parsingTest = tester.parsingTest;
}
private void testParsingErrors() {
counter.testForEach(parsingTest, op -> {
final List<String> names = Functional.map(kind.kind().variables().generate(counter.random(), 3), Pair::first);
final String expr = mangle(op.value(), names);
try {
kind.parse(expr, names);
counter.fail("Successfully parsed '%s'", op.value());
} catch (final Exception e) {
counter.format("%-30s %s%n", op.name(), e.getClass().getSimpleName() + ": " + e.getMessage());
}
});
}
private void testOverflow() {
final List<Pair<String, E>> variables = kind.kind().variables().generate(counter.random(), 3);
final List<String> names = Functional.map(variables, Pair::first);
final Variable vx = (Variable) variables.get(0).second();
final Variable vy = (Variable) variables.get(1).second();
//noinspection Convert2MethodRef
testOverflow(names, (a, b) -> a + b, "+", new CheckedAdd(vx, vy));
testOverflow(names, (a, b) -> a - b, "-", new CheckedSubtract(vx, vy));
testOverflow(names, (a, b) -> a * b, "*", new CheckedMultiply(vx, vy));
testOverflow(names, (a, b) -> b == 0 ? Long.MAX_VALUE : a / b, "/", new CheckedDivide(vx, vy));
testOverflow(names, (a, b) -> -b, "<- ignore first argument, unary -", new CheckedNegate(vy));
}
private void testOverflow(final List<String> names, final LongBinaryOperator f, final String op, final Object expression) {
final ExpressionKind<E, C> kind = this.kind.kind();
for (final int a : OVERFLOW_VALUES) {
for (final int b : OVERFLOW_VALUES) {
final long expected = f.applyAsLong(a, b);
final boolean isInt = Integer.MIN_VALUE <= expected && expected <= Integer.MAX_VALUE;
try {
final C actual = kind.evaluate(
kind.cast(expression),
names,
kind.fromInts(List.of(a, b, 0))
);
counter.checkTrue(
isInt && kind.fromInt((int) expected).equals(actual),
"%d %s %d == %d", a, op, b, actual
);
} catch (final Exception e) {
if (isInt) {
counter.fail(e, "Unexpected error in %d %s %d", a, op, b);
}
}
}
}
}
@Override
protected void test() {
counter.scope("Overflow tests", (Runnable) this::testOverflow);
super.test();
counter.scope("Parsing error tests", this::testParsingErrors);
}
@Override
protected E parse(final String expression, final List<String> variables, final boolean reparse) {
final String expr = expression.strip();
if (expr.length() > 10) {
for (final char ch : CHARS) {
for (int i = 0; i < 10; i++) {
final int index = 1 + tester.random().nextInt(expr.length() - 2);
int pi = index - 1;
while (Character.isWhitespace(expr.charAt(pi))) {
pi--;
}
int ni = index;
while (Character.isWhitespace(expr.charAt(ni))) {
ni++;
}
final char pc = expr.charAt(pi);
final char nc = expr.charAt(ni);
if (
"-([{*∛√²³₂₃!‖⎵⎴⌊⌈=?".indexOf(nc) < 0 &&
(!Character.isLetterOrDigit(pc) || !Character.isLetterOrDigit(ch)) &&
nc != ch && pc != ch &&
!Character.isLetterOrDigit(nc) && nc != '$'
) {
shouldFail(
variables,
"Parsing error expected for " + insert(expr, index, "<ERROR_INSERTED -->" + ch + "<-- ERROR_INSERTED>"),
insert(expr, index, String.valueOf(ch))
);
break;
}
}
}
parens(variables, expr, '[', ']');
parens(variables, expr, '{', '}');
}
return counter.testV(() -> counter.call("parse", () -> kind.parse(expr, variables)));
}
private static String insert(final String expr, final int index, final String value) {
return expr.substring(0, index) + value + expr.substring(index);
}
private void parens(final List<String> variables, final String expr, final char open, final char close) {
if (expr.indexOf(open) >= 0) {
replaces(variables, expr, open, '(');
replaces(variables, expr, close, ')');
if (expr.indexOf('(') >= 0) {
replaces(variables, expr, '(', open);
replaces(variables, expr, ')', close);
}
}
}
private void replaces(final List<String> variables, final String expr, final char what, final char by) {
final String input = expr.replace(what, by);
shouldFail(variables, "Unmatched parentheses: " + input, input);
}
private void shouldFail(final List<String> variables, final String message, final String input) {
counter.shouldFail(message, () -> kind.parse(input, variables));
}
}

View File

@@ -0,0 +1,100 @@
package expression.exceptions;
import base.Named;
import base.TestCounter;
import expression.common.Reason;
import expression.parser.ParserTestSet;
import expression.parser.ParserTester;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.LongBinaryOperator;
import java.util.function.LongToIntFunction;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public class ExceptionsTester extends ParserTester {
/* package-private */ final List<Named<String>> parsingTest = new ArrayList<>(List.of(
Named.of("No first argument", "* $y * $z"),
Named.of("No middle argument", "$x * * $z"),
Named.of("No last argument", "$x * $y * "),
Named.of("No first argument'", "1 + (* $y * $z) + 2"),
Named.of("No middle argument'", "1 + ($x * / 9) + 3"),
Named.of("No last argument'", "1 + ($x * $y - ) + 3"),
Named.of("No opening parenthesis", "$x * $y)"),
Named.of("No closing parenthesis", "($x * $y"),
Named.of("Mismatched closing parenthesis", "($x * $y]"),
Named.of("Mismatched open parenthesis", "[$x * $y)"),
Named.of("Start symbol", "@$x * $y"),
Named.of("Middle symbol", "$x @ * $y"),
Named.of("End symbol", "$x * $y@"),
Named.of("Constant overflow 1", Integer.MIN_VALUE - 1L + ""),
Named.of("Constant overflow 2", Integer.MAX_VALUE + 1L + ""),
Named.of("Bare +", "+"),
Named.of("Bare -", "-"),
Named.of("Bare a", "a"),
Named.of("(())", "(())"),
Named.of("Spaces in numbers", "10 20")
));
public ExceptionsTester(final TestCounter counter) {
super(counter);
}
private void parsingTests(final String... tests) {
for (final String test : tests) {
parsingTest.add(Named.of(test, test));
}
}
@Override
public void unary(final String name, final int priority, final BiFunction<Long, LongToIntFunction, Long> op) {
if (allowed(name)) {
parsingTests(name, "1 * " + name, name + " * 1");
}
parsingTests(name + "()", name + "(1, 2)");
if (name.length() > 1) {
parsingTests(name + "q");
}
if (allLetterAndDigit(name)) {
parsingTests(name + "1", name + "q");
}
super.unary(name, priority, op);
}
private static boolean allowed(final String name) {
return !"xyz".contains(name.substring(0, 1)) && !"xyz".contains(name.substring(name.length() - 1));
}
@Override
public void binary(final String name, final int priority, final LongBinaryOperator op) {
if (allowed(name)) {
parsingTests(name);
}
parsingTests("1 " + name, "1 " + name + " * 3");
if (!"-".equals(name)) {
parsingTests(name + " 1", "1 * " + name + " 2");
}
if (allLetterAndDigit(name)) {
parsingTests("5" + name + "5", "5 " + name + "5", "5 " + name + "5 5", "1" + name + "x 1", "1 " + name + "x 1");
}
super.binary(name, priority, op);
}
private static boolean allLetterAndDigit(final String name) {
return name.chars().allMatch(Character::isLetterOrDigit);
}
@Override
protected void test(final ParserTestSet.ParsedKind<?, ?> kind) {
new ExceptionsTestSet<>(this, kind).test();
}
@Override
protected int cast(final long value) {
return Reason.overflow(value);
}
}

View File

@@ -0,0 +1,13 @@
package expression.exceptions;
import expression.ListExpression;
import java.util.List;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
@FunctionalInterface
public interface ListParser {
ListExpression parse(String expression, final List<String> variables) throws Exception;
}

View File

@@ -0,0 +1,8 @@
/**
* Tests for <a href="https://www.kgeorgiy.info/courses/prog-intro/homeworks.html#expressions-exceptions">Expression Error Handling</a> homework
* of <a href="https://www.kgeorgiy.info/courses/prog-intro/">Introduction to Programming</a> course.
*
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
package expression.exceptions;

View File

@@ -0,0 +1,7 @@
/**
* Tests for <a href="https://www.kgeorgiy.info/courses/prog-intro/homeworks.html#expressions">Expressions</a> homework
* of <a href="https://www.kgeorgiy.info/courses/prog-intro/">Introduction to Programming</a> course.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
package expression;

View File

@@ -0,0 +1,13 @@
package expression.parser;
import expression.ListExpression;
import java.util.List;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
@FunctionalInterface
public interface ListParser {
ListExpression parse(String expression, List<String> variables);
}

View File

@@ -0,0 +1,148 @@
package expression.parser;
import expression.ToMiniString;
import expression.common.ExpressionKind;
import expression.common.Reason;
import java.math.BigInteger;
import java.util.function.*;
import java.util.stream.LongStream;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class Operations {
// === Base
public static final Operation NEGATE = unary("-", 1, a -> -a);
@SuppressWarnings("Convert2MethodRef")
public static final Operation ADD = binary("+", 1600, (a, b) -> a + b);
public static final Operation SUBTRACT = binary("-", 1602, (a, b) -> a - b);
public static final Operation MULTIPLY = binary("*", 2001, (a, b) -> a * b);
public static final Operation DIVIDE = binary("/", 2002, (a, b) -> b == 0 ? Reason.DBZ.error() : a / b);
// === MinMax
public static final Operation MIN = binary("min", 401, Math::min);
public static final Operation MAX = binary("max", 401, Math::max);
// === Reverse
private static Operation digits(final String name, final boolean mask, final int r, final LongBinaryOperator q) {
return unary(name, 1, v -> LongStream.iterate(mask ? v & 0xffff_ffffL : v, n -> n != 0, n -> n / r)
.map(n -> n % r)
.reduce(0, q));
}
public static final Operation REVERSE = digits("reverse", false, 10, (a, b) -> a * 10 + b);
// === Digits
public static final Operation DIGITS = digits("digits", false, 10, Long::sum);
// === Floor and Ceiling
private static long floor(final long a) {
return (a >= 0 ? a : a - FLOOR_CEILING_STEP + 1) / FLOOR_CEILING_STEP * FLOOR_CEILING_STEP;
}
private static long ceiling(final long a) {
return (a >= 0 ? a + FLOOR_CEILING_STEP - 1: a) / FLOOR_CEILING_STEP * FLOOR_CEILING_STEP;
}
public static final int FLOOR_CEILING_STEP = 1000;
public static final Operation FLOOR = unary("floor", 1, Operations::floor);
public static final Operation CEILING = unary("ceiling", 1, Operations::ceiling);
// === Set, Clear
@SuppressWarnings("IntegerMultiplicationImplicitCastToLong")
public static final Operation SET = binary("set", 202, (a, b) -> a | (1 << b));
@SuppressWarnings("IntegerMultiplicationImplicitCastToLong")
public static final Operation CLEAR = binary("clear", 202, (a, b) -> a & ~(1 << b));
// === Pow, Log
public static final Operation POW_O = binary("**", 2402, (a, b) ->
b < 0 ? 1 : BigInteger.valueOf(a).modPow(BigInteger.valueOf(b), BigInteger.valueOf(1L << 32)).intValue());
public static final Operation LOG_O = binary("//", 2402, (a, b) ->
a == 0 && b > 0 ? Integer.MIN_VALUE :
a <= 0 || b <= 0 || a == 1 && b == 1 ? 0 :
a > 1 && b == 1 ? Integer.MAX_VALUE
: LongStream.iterate(b, v -> v <= a, v -> v * b).count()
);
private static final Reason INVALID_POW = new Reason("Invalid power");
public static final Operation POW = binary("**", 2402, Operations::powC);
private static long powC(final long a, final long b) {
if (b < 0 || a == 0 && b == 0) {
return INVALID_POW.error();
}
if (Math.abs(a) > 1 && b > 32) {
return Reason.OVERFLOW.error();
}
final BigInteger result = BigInteger.valueOf(a).pow((int) b);
if (result.compareTo(BigInteger.valueOf(Integer.MIN_VALUE)) < 0 || BigInteger.valueOf(Integer.MAX_VALUE).compareTo(result) < 0) {
return Reason.OVERFLOW.error();
}
return result.intValue();
}
private static final Reason INVALID_LOG = new Reason("Invalid log");
public static final Operation LOG = binary("//", 2402, (a, b) ->
a <= 0 || b <= 1 ? INVALID_LOG.error() : (int) (Math.log(a) / Math.log(b)));
// Pow2, Log2
private static final Reason NEG_LOG = new Reason("Logarithm of negative value");
public static final Operation LOG_2
= unary("log₂", 1, NEG_LOG.less(1, a-> (long) (Math.log(a) / Math.log(2))));
private static final Reason NEG_POW = new Reason("Exponentiation to negative power");
public static final Operation POW_2
= unary("pow₂", 1, NEG_POW.less(0, Reason.OVERFLOW.greater(31, a -> (long) Math.pow(2, a))));
// === High, Low
public static final Operation HIGH = unary("high", 1, v -> Integer.highestOneBit((int) v));
public static final Operation LOW = unary("low", 1, v -> Integer.lowestOneBit((int) v));
// === Common
private Operations() {
}
public static Operation unary(final String name, final int priority, final LongUnaryOperator op) {
return unary(name, priority, (a, c) -> op.applyAsLong(a));
}
public static Operation unary(final String left, final String right, final LongUnaryOperator op) {
return unary(left, right, (a, c) -> op.applyAsLong(a));
}
public static Operation unary(final String name, final int priority, final BiFunction<Long, LongToIntFunction, Long> op) {
return tests -> tests.unary(name, priority, op);
}
public static Operation unary(final String left, final String right, final BiFunction<Long, LongToIntFunction, Long> op) {
return tests -> tests.unary(left, right, op);
}
public static Operation binary(final String name, final int priority, final LongBinaryOperator op) {
return tests -> tests.binary(name, priority, op);
}
public static <E extends ToMiniString, C> Operation kind(
final ExpressionKind<E, C> kind,
final ParserTestSet.Parser<E> parser
) {
return factory -> factory.kind(kind, parser);
}
@FunctionalInterface
public interface Operation extends Consumer<ParserTester> {}
}

View File

@@ -0,0 +1,31 @@
package expression.parser;
import base.Selector;
import expression.ListExpression;
import static expression.parser.Operations.*;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public final class ParserTest {
private static final ExpressionParser PARSER = new ExpressionParser();
private static final Operations.Operation LIST = kind(ListExpression.KIND, PARSER::parse);
// === Common
public static final Selector SELECTOR = Selector.composite(ParserTest.class, ParserTester::new, "easy", "hard")
.variant("Base", LIST, ADD, SUBTRACT, MULTIPLY, DIVIDE, NEGATE)
.variant("3637", MIN, MAX, REVERSE)
.variant("3839", MIN, MAX, REVERSE, DIGITS)
.variant("3435", FLOOR, CEILING, SET, CLEAR)
.variant("3233", FLOOR, CEILING)
.selector();
private ParserTest() {
}
public static void main(final String... args) {
SELECTOR.main(args);
}
}

View File

@@ -0,0 +1,236 @@
package expression.parser;
import base.*;
import expression.ToMiniString;
import expression.common.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public class ParserTestSet<E extends ToMiniString, C> {
private static final int D = 5;
private static final List<Integer> TEST_VALUES = new ArrayList<>();
static {
Functional.addRange(TEST_VALUES, D, D);
Functional.addRange(TEST_VALUES, D, -D);
}
public static final List<Integer> CONSTS
= List.of(0, 1, -1, 4, -4, 10, -10, 30, -30, 100, -100, Integer.MAX_VALUE, Integer.MIN_VALUE);
protected final ParserTester tester;
protected final ParsedKind<E, C> kind;
private final boolean safe;
protected final TestCounter counter;
public ParserTestSet(final ParserTester tester, final ParsedKind<E, C> kind) {
this(tester, kind, true);
}
protected ParserTestSet(final ParserTester tester, final ParsedKind<E, C> kind, final boolean safe) {
this.tester = tester;
this.kind = kind;
this.safe = safe;
counter = tester.getCounter();
}
private void examples(final TestGenerator<Integer, E> generator) {
example(generator, "$x+2", (x, y, z) -> x + 2);
example(generator, "2-$y", (x, y, z) -> 2 - y);
example(generator, " 3* $z ", (x, y, z) -> 3 * z);
example(generator, "$x/ - 2", (x, y, z) -> -x / 2);
example(generator, "$x*$y+($z-1 )/10", (x, y, z) -> x * y + (int) (z - 1) / 10);
example(generator, "-(-(-\t\t-5 + 16 *$x*$y) + 1 * $z) -(((-11)))", (x, y, z) -> -(-(5 + 16 * x * y) + z) + 11);
example(generator, "" + Integer.MAX_VALUE, (x, y, z) -> (long) Integer.MAX_VALUE);
example(generator, "" + Integer.MIN_VALUE, (x, y, z) -> (long) Integer.MIN_VALUE);
example(generator, "$x--$y--$z", (x, y, z) -> x + y + z);
example(generator, "((2+2))-0/(--2)*555", (x, y, z) -> 4L);
example(generator, "$x-$x+$y-$y+$z-($z)", (x, y, z) -> 0L);
example(generator, "(".repeat(300) + "$x + $y + (-10*-$z)" + ")".repeat(300), (x, y, z) -> x + y + 10 * z);
example(generator, "$x / $y / $z", (x, y, z) -> y == 0 || z == 0 ? Reason.DBZ.error() : (int) x / (int) y / z);
}
private void example(final TestGenerator<Integer, E> generator, final String expr, final ExampleExpression expression) {
final List<String> names = Functional.map(generator.variables(3), Pair::first);
final TExpression expected = vars -> expression.evaluate(vars.get(0), vars.get(1), vars.get(2));
counter.test(() -> {
final String mangled = mangle(expr, names);
final E parsed = parse(mangled, names, true);
Functional.allValues(TEST_VALUES, 3).forEach(values -> check(expected, parsed, names, values, mangled));
});
}
protected static String mangle(final String expr, final List<String> names) {
return expr
.replace("$x", names.get(0))
.replace("$y", names.get(1))
.replace("$z", names.get(2));
}
protected void test() {
final TestGenerator<Integer, E> generator = tester.generator.build(kind.kind.variables());
final Renderer<Integer, Unit, TExpression> renderer = tester.renderer.build();
final Consumer<TestGenerator.Test<Integer, E>> consumer = test -> test(renderer, test);
counter.scope("Basic tests", () -> generator.testBasic(consumer));
counter.scope("Handmade tests", () -> examples(generator));
counter.scope("Random tests", () -> generator.testRandom(counter, 1, consumer));
}
private void test(final Renderer<Integer, Unit, TExpression> renderer, final TestGenerator.Test<Integer, E> test) {
final Expr<Integer, E> expr = test.expr;
final List<Pair<String, E>> vars = expr.variables();
final List<String> variables = Functional.map(vars, Pair::first);
final String full = test.render(NodeRenderer.FULL);
final String mini = test.render(NodeRenderer.MINI);
final E fullParsed = parse(test, variables, NodeRenderer.FULL);
final E miniParsed = parse(test, variables, NodeRenderer.MINI);
final E safeParsed = parse(test, variables, NodeRenderer.SAME);
checkToString(full, mini, "base", fullParsed);
if (tester.mode() > 0) {
counter.test(() -> Asserts.assertEquals("mini.toMiniString", mini, miniParsed.toMiniString()));
counter.test(() -> Asserts.assertEquals("safe.toMiniString", mini, safeParsed.toMiniString()));
}
checkToString(full, mini, "extraParentheses", parse(test, variables, NodeRenderer.FULL_EXTRA));
checkToString(full, mini, "noSpaces", parse(removeSpaces(full), variables, false));
checkToString(full, mini, "extraSpaces", parse(extraSpaces(full), variables, false));
final TExpression expected = renderer.render(
Expr.of(
expr.node(),
Functional.map(vars, (i, var) -> Pair.of(var.first(), args -> args.get(i)))
),
Unit.INSTANCE
);
check(expected, fullParsed, variables, tester.random().random(variables.size(), ExtendedRandom::nextInt), full);
if (this.safe) {
final String safe = test.render(NodeRenderer.SAME);
check(expected, safeParsed, variables, tester.random().random(variables.size(), ExtendedRandom::nextInt), safe);
}
}
private E parse(
final TestGenerator.Test<Integer, E> test,
final List<String> variables,
final NodeRenderer.Settings settings
) {
return parse(test.render(settings.withParens(tester.parens)), variables, false);
}
private static final String LOOKBEHIND = "(?<![a-zA-Z0-9<>*/+=!-])";
private static final String LOOKAHEAD = "(?![a-zA-Z0-9<>*/])";
private static final Pattern SPACES = Pattern.compile(LOOKBEHIND + " | " + LOOKAHEAD + "|" + LOOKAHEAD + LOOKBEHIND);
private String extraSpaces(final String expression) {
return SPACES.matcher(expression).replaceAll(r -> tester.random().randomString(
ExtendedRandom.SPACES,
tester.random().nextInt(5)
));
}
private static String removeSpaces(final String expression) {
return SPACES.matcher(expression).replaceAll("");
}
private void checkToString(final String full, final String mini, final String context, final ToMiniString parsed) {
counter.test(() -> {
assertEquals(context + ".toString", full, full, parsed.toString());
if (tester.mode() > 0) {
assertEquals(context + ".toMiniString", full, mini, parsed.toMiniString());
}
});
}
private static void assertEquals(
final String context,
final String original,
final String expected,
final String actual
) {
final String message = String.format("%s:%n original `%s`,%n expected `%s`,%n actual `%s`",
context, original, expected, actual);
Asserts.assertTrue(message, Objects.equals(expected, actual));
}
private Either<Reason, Integer> eval(final TExpression expression, final List<Integer> vars) {
return Reason.eval(() -> tester.cast(expression.evaluate(vars)));
}
protected E parse(final String expression, final List<String> variables, final boolean reparse) {
return counter.testV(() -> {
final E parsed = counter.testV(() -> counter.call("parse",
() -> kind.parse(expression, variables)));
if (reparse) {
counter.testV(() -> counter.call("parse", () -> kind.parse(parsed.toString(), variables)));
}
return parsed;
});
}
private void check(
final TExpression expectedExpression,
final E expression,
final List<String> variables,
final List<Integer> values,
final String unparsed
) {
counter.test(() -> {
final Either<Reason, Integer> answer = eval(expectedExpression, values);
final String args = IntStream.range(0, variables.size())
.mapToObj(i -> variables.get(i) + "=" + values.get(i))
.collect(Collectors.joining(", "));
final String message = String.format("f(%s)%n\twhere f=%s%n\tyour f=%s", args, unparsed, expression);
try {
final C actual = kind.kind.evaluate(expression, variables, kind.kind.fromInts(values));
counter.checkTrue(answer.isRight(), "Error expected for f(%s)%n\twhere f=%s%n\tyour f=%s", args, unparsed, expression);
Asserts.assertEquals(message, answer.getRight(), actual);
} catch (final Exception e) {
if (answer.isRight()) {
counter.fail(e, "No error expected for %s", message);
}
}
});
}
@FunctionalInterface
public interface TExpression {
long evaluate(List<Integer> vars);
}
@FunctionalInterface
protected interface ExampleExpression {
long evaluate(long x, long y, long z);
}
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public record ParsedKind<E extends ToMiniString, C>(ExpressionKind<E, C> kind, Parser<E> parser) {
public E parse(final String expression, final List<String> variables) throws Exception {
return parser.parse(expression, variables);
}
@Override
public String toString() {
return kind.toString();
}
}
@FunctionalInterface
public interface Parser<E> {
E parse(final String expression, final List<String> variables) throws Exception;
}
}

View File

@@ -0,0 +1,76 @@
package expression.parser;
import base.ExtendedRandom;
import base.TestCounter;
import base.Tester;
import base.Unit;
import expression.ToMiniString;
import expression.common.*;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.LongBinaryOperator;
import java.util.function.LongToIntFunction;
/**
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
public class ParserTester extends Tester {
/* package-private */ final TestGeneratorBuilder<Integer> generator;
/* package-private */ final Renderer.Builder<Integer, Unit, ParserTestSet.TExpression> renderer;
private final List<ParserTestSet.ParsedKind<?, ?>> kinds = new ArrayList<>();
/* package-private */ final List<NodeRenderer.Paren> parens = new ArrayList<>(List.of(NodeRenderer.paren("(", ")")));
public ParserTester(final TestCounter counter) {
super(counter);
renderer = Renderer.builder(c -> vars -> c);
final ExtendedRandom random = counter.random();
generator = new TestGeneratorBuilder<>(random, random::nextInt, ParserTestSet.CONSTS, true);
}
public void unary(final String name, final int priority, final BiFunction<Long, LongToIntFunction, Long> op) {
generator.unary(name, priority);
renderer.unary(name, (unit, a) -> vars -> cast(op.apply(a.evaluate(vars), this::cast)));
}
public void unary(final String left, final String right, final BiFunction<Long, LongToIntFunction, Long> op) {
generator.unary(left, right);
renderer.unary(left, (unit, a) -> vars -> cast(op.apply(a.evaluate(vars), this::cast)));
}
public void binary(final String name, final int priority, final LongBinaryOperator op) {
generator.binary(name, priority);
renderer.binary(name, (unit, a, b) -> vars -> cast(op.applyAsLong(a.evaluate(vars), b.evaluate(vars))));
}
<E extends ToMiniString, C> void kind(final ExpressionKind<E, C> kind, final ParserTestSet.Parser<E> parser) {
kinds.add(new ParserTestSet.ParsedKind<>(kind, parser));
}
@Override
public void test() {
for (final ParserTestSet.ParsedKind<?, ?> kind : kinds) {
counter.scope(kind.toString(), () -> test(kind));
}
}
protected void test(final ParserTestSet.ParsedKind<?, ?> kind) {
new ParserTestSet<>(this, kind).test();
}
public TestCounter getCounter() {
return counter;
}
protected int cast(final long value) {
return (int) value;
}
public void parens(final String... parens) {
assert parens.length % 2 == 0 : "Parens should come in pairs";
for (int i = 0; i < parens.length; i += 2) {
this.parens.add(NodeRenderer.paren(parens[i], parens[i + 1]));
}
}
}

View File

@@ -0,0 +1,7 @@
/**
* Tests for <a href="https://www.kgeorgiy.info/courses/prog-intro/homeworks.html#expressions-parsing">Expressions Parsing</a> homework
* of <a href="https://www.kgeorgiy.info/courses/prog-intro/">Introduction to Programming</a> course.
*
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
*/
package expression.parser;