Upload files to "java/expression/common"

This commit is contained in:
2026-04-13 10:53:30 +03:00
parent e82fdac1c7
commit 4bed5bbc53
5 changed files with 418 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View File

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