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> 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; } } }