Compare commits
2 Commits
67357cf271
...
e458f6e51e
| Author | SHA1 | Date | |
|---|---|---|---|
| e458f6e51e | |||
| 3fbdccb52a |
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
*.xml
|
||||||
|
java/out/*
|
||||||
|
*.iml
|
||||||
10
java/.idea/.gitignore
generated
vendored
Normal file
10
java/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Ignored default folder with query files
|
||||||
|
/queries/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
@@ -1,202 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,192 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,288 +0,0 @@
|
|||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,211 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package expression;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
|
||||||
*/
|
|
||||||
public interface ToMiniString {
|
|
||||||
default String toMiniString() {
|
|
||||||
return toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,190 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
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)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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;
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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;
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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;
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
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> {}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,236 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
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]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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;
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,71 +0,0 @@
|
|||||||
package markup;
|
|
||||||
|
|
||||||
import base.Asserts;
|
|
||||||
import base.BaseChecker;
|
|
||||||
import base.TestCounter;
|
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandle;
|
|
||||||
import java.lang.invoke.MethodHandles;
|
|
||||||
import java.lang.invoke.MethodType;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.function.BiConsumer;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
|
||||||
*/
|
|
||||||
public final class MarkupTester {
|
|
||||||
private final Map<String, String> mapping;
|
|
||||||
private final String toString;
|
|
||||||
|
|
||||||
private MarkupTester(final Map<String, String> mapping, final String toString) {
|
|
||||||
this.mapping = mapping;
|
|
||||||
this.toString = toString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Consumer<TestCounter> variant(final Consumer<Checker> checker, final String name, final Map<String, String> mapping) {
|
|
||||||
return counter -> test(checker).accept(new MarkupTester(mapping, "to" + name), counter);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BiConsumer<MarkupTester, TestCounter> test(final Consumer<Checker> tester) {
|
|
||||||
return (checker, counter) -> tester.accept(checker.new Checker(counter));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return toString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Checker extends BaseChecker {
|
|
||||||
public Checker(final TestCounter counter) {
|
|
||||||
super(counter);
|
|
||||||
}
|
|
||||||
|
|
||||||
private <T> MethodHandle findMethod(final T value) {
|
|
||||||
try {
|
|
||||||
return MethodHandles.publicLookup().findVirtual(value.getClass(), toString, MethodType.methodType(void.class, StringBuilder.class));
|
|
||||||
} catch (final NoSuchMethodException | IllegalAccessException e) {
|
|
||||||
throw Asserts.error("Cannot find method 'void %s(StringBuilder)' for %s", toString, value.getClass());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> void test(final T value, String expectedTemplate) {
|
|
||||||
final MethodHandle method = findMethod(value);
|
|
||||||
for (final Map.Entry<String, String> entry : mapping.entrySet()) {
|
|
||||||
expectedTemplate = expectedTemplate.replace(entry.getKey(), entry.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
final String expected = expectedTemplate;
|
|
||||||
counter.println("Test " + counter.getTestNo());
|
|
||||||
counter.test(() -> {
|
|
||||||
final StringBuilder sb = new StringBuilder();
|
|
||||||
try {
|
|
||||||
method.invoke(value, sb);
|
|
||||||
} catch (final Throwable e) {
|
|
||||||
throw Asserts.error("%s(StringBuilder) for %s thrown exception: %s", toString, value.getClass(), e);
|
|
||||||
}
|
|
||||||
Asserts.assertEquals("Result", expected, sb.toString());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
/**
|
|
||||||
* Tests for <a href="https://www.kgeorgiy.info/courses/prog-intro/homeworks.html#markup">Markup</a> homework
|
|
||||||
* of <a href="https://www.kgeorgiy.info/courses/prog-intro/">Introduction to Programming</a> course.
|
|
||||||
*
|
|
||||||
* @author Georgiy Korneev (kgeorgiy@kgeorgiy.info)
|
|
||||||
*/
|
|
||||||
package markup;
|
|
||||||
10
java/sum/.idea/.gitignore
generated
vendored
Normal file
10
java/sum/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Ignored default folder with query files
|
||||||
|
/queries/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
6
java/sum/.idea/misc.xml
generated
Normal file
6
java/sum/.idea/misc.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="25" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
8
java/sum/.idea/modules.xml
generated
Normal file
8
java/sum/.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/sum.iml" filepath="$PROJECT_DIR$/sum.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
java/sum/.idea/vcs.xml
generated
Normal file
6
java/sum/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
20
java/sum/Sum.java
Normal file
20
java/sum/Sum.java
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package sum;
|
||||||
|
|
||||||
|
public class Sum {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
int res = 0;
|
||||||
|
for (String arg : args) {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
for (char c : arg.toCharArray()) {
|
||||||
|
if (!Character.isWhitespace(c)) {
|
||||||
|
builder.append(c);
|
||||||
|
} else {
|
||||||
|
res += (!builder.toString().isEmpty()) ? Integer.parseInt(builder.toString()) : 0;
|
||||||
|
builder = new StringBuilder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res += (!builder.toString().isEmpty()) ? Integer.parseInt(builder.toString()) : 0;
|
||||||
|
}
|
||||||
|
System.out.println(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user