From 4bed5bbc539967d7c5a0af2a02c98a525913a412 Mon Sep 17 00:00:00 2001 From: codejava Date: Mon, 13 Apr 2026 10:53:30 +0300 Subject: [PATCH] Upload files to "java/expression/common" --- .../common/NodeRendererBuilder.java | 201 ++++++++++++++++++ java/expression/common/Reason.java | 67 ++++++ java/expression/common/Renderer.java | 71 +++++++ java/expression/common/TestGenerator.java | 72 +++++++ java/expression/common/package-info.java | 7 + 5 files changed, 418 insertions(+) create mode 100644 java/expression/common/NodeRendererBuilder.java create mode 100644 java/expression/common/Reason.java create mode 100644 java/expression/common/Renderer.java create mode 100644 java/expression/common/TestGenerator.java create mode 100644 java/expression/common/package-info.java diff --git a/java/expression/common/NodeRendererBuilder.java b/java/expression/common/NodeRendererBuilder.java new file mode 100644 index 0000000..086e5f3 --- /dev/null +++ b/java/expression/common/NodeRendererBuilder.java @@ -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 { + + private final Renderer.Builder< + C, + NodeRenderer.Settings, + Node + > nodeRenderer = Renderer.builder(Node::constant); + private final Map priorities = new HashMap<>(); + private final Map 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 inner( + final NodeRenderer.Settings settings, + final int priority, + final Node 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 Integer unaryPriority(final Node 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 process( + final NodeRenderer.Settings settings, + final Priority mp, + final Node l, + final Node 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 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 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 op(final Priority mp, final Node l, final Node r) { + return mp.op(l, r); + } + + private Priority priority(final Node node) { + return get( + node, + () -> Priority.MAX, + (n, a, b) -> Functional.get(priorities, n) + ); + } + + private R get( + final Node node, + final Supplier common, + final Node.Binary, R> binary + ) { + return node.get( + c -> common.get(), + n -> common.get(), + (n, p, a) -> common.get(), + binary + ); + } + + public NodeRenderer 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 Node op(final Node l, final Node r) { + return Node.op(op, l, r); + } + + private boolean has(final int value) { + return (priority & Q) == value; + } + } +} diff --git a/java/expression/common/Reason.java b/java/expression/common/Reason.java new file mode 100644 index 0000000..e343ba7 --- /dev/null +++ b/java/expression/common/Reason.java @@ -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 Either eval(final Supplier 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 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); + } +} diff --git a/java/expression/common/Renderer.java b/java/expression/common/Renderer.java new file mode 100644 index 0000000..21a8fff --- /dev/null +++ b/java/expression/common/Renderer.java @@ -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 { + static Builder builder(final Node.Const constant) { + return new Builder<>(constant); + } + + R render(final Expr expr, final S settings); + + @FunctionalInterface + interface UnaryOperator { + R apply(S settings, R arg); + } + + @FunctionalInterface + interface BinaryOperator { + R apply(S settings, R arg1, R arg2); + } + + final class Builder { + + private final Node.Const constant; + private final Map> unary = new HashMap<>(); + private final Map> binary = + new HashMap<>(); + + private Builder(final Node.Const constant) { + this.constant = constant; + } + + public void unary(final String name, final UnaryOperator op) { + unary.put(name, op); + } + + public void binary(final String name, final BinaryOperator op) { + binary.put(name, op); + } + + public Renderer build() { + return (expr, settings) -> { + final Map 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 + ) + ); + }; + } + } +} diff --git a/java/expression/common/TestGenerator.java b/java/expression/common/TestGenerator.java new file mode 100644 index 0000000..d6b1e51 --- /dev/null +++ b/java/expression/common/TestGenerator.java @@ -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 { + + private final Generator generator; + private final NodeRenderer renderer; + + public TestGenerator( + final Generator generator, + final NodeRenderer renderer + ) { + this.generator = generator; + this.renderer = renderer; + } + + public void testBasic(final Consumer> test) { + generator.testBasic(consumer(test)); + } + + public void testRandom( + final TestCounter counter, + final int denominator, + final Consumer> test + ) { + generator.testRandom(counter, denominator, consumer(test)); + } + + private Consumer> consumer( + final Consumer> consumer + ) { + return expr -> + consumer.accept(new TestGenerator.Test<>(expr, renderer)); + } + + public List> variables(final int count) { + return generator.variables(count); + } + + public String render( + final Expr expr, + final NodeRenderer.Settings settings + ) { + return renderer.render(expr, settings); + } + + public static class Test { + + public final Expr expr; + private final Map rendered = + new HashMap<>(); + private final NodeRenderer renderer; + + public Test(final Expr expr, final NodeRenderer renderer) { + this.expr = expr; + this.renderer = renderer; + } + + public String render(final NodeRenderer.Settings settings) { + return rendered.computeIfAbsent(settings, s -> + renderer.render(expr, s) + ); + } + } +} diff --git a/java/expression/common/package-info.java b/java/expression/common/package-info.java new file mode 100644 index 0000000..80392dc --- /dev/null +++ b/java/expression/common/package-info.java @@ -0,0 +1,7 @@ +/** + * Expressions generators for expression-based homeworks + * of Introduction to Programming course. + * + * @author Georgiy Korneev (kgeorgiy@kgeorgiy.info) + */ +package expression.common;