Expression.java
package com.acme.symbolic;

import church.lang.operators.Relational.$$equal;
import church.primitives.Objects;

import static church.lang.operators.Arithmetic.*;
import static church.lang.operators.Streams.$$encode;

@SuppressWarnings("unchecked")
public abstract class Expression<T> {
    private static final $$equal<String> $S0 = Objects::$equal;

    private static interface $I0<T> {
        public T eval(Expression<T> exp);
    }

    public interface Visitor<T, S> {
        default S get(Expression<T> receiver) {
            S result = receiver.accept(this);
            return result == null ? defaultImplementation(receiver) : result;
        }

        default S getOrDefault(Expression<T> receiver, S defaultValue) {
            S result = receiver.accept(this);
            return result == null ? defaultValue : result;
        }

        default S $const(T value) {
            return null;
        }

        default S var(String name) {
            return null;
        }

        default S app(String fun, Expression<T> arg) {
            return null;
        }

        default S sum(Expression<T> a, Expression<T> b) {
            return null;
        }

        default S neg(Expression<T> a) {
            return null;
        }

        default S sub(Expression<T> a, Expression<T> b) {
            return null;
        }

        default S prd(Expression<T> a, Expression<T> b) {
            return null;
        }

        default S div(Expression<T> a, Expression<T> b) {
            return null;
        }

        default S pow(Expression<T> a, T n) {
            return null;
        }

        default S defaultImplementation(Expression<T> e) {
            throw new UnsupportedOperationException();
        }
    }

    public abstract <S> S accept(Visitor<T, S> visitor);

    public static <T> Expression<T> $const(T value) {
        return new Expression<T>() {
            public <S> S accept(Visitor<T, S> visitor) {
                return visitor.$const(value);
            }
        };
    }

    public static <T> Expression<T> var(String name) {
        return new Expression<T>() {
            public <S> S accept(Visitor<T, S> visitor) {
                return visitor.var(name);
            }
        };
    }

    public static <T> Expression<T> app(String fun, Expression<T> arg) {
        return new Expression<T>() {
            public <S> S accept(Visitor<T, S> visitor) {
                return visitor.app(fun, arg);
            }
        };
    }

    public static <T> Expression<T> sum(Expression<T> a, Expression<T> b) {
        return new Expression<T>() {
            public <S> S accept(Visitor<T, S> visitor) {
                return visitor.sum(a, b);
            }
        };
    }

    public static <T> Expression<T> neg(Expression<T> a) {
        return new Expression<T>() {
            public <S> S accept(Visitor<T, S> visitor) {
                return visitor.neg(a);
            }
        };
    }

    public static <T> Expression<T> sub(Expression<T> a, Expression<T> b) {
        return new Expression<T>() {
            public <S> S accept(Visitor<T, S> visitor) {
                return visitor.sub(a, b);
            }
        };
    }

    public static <T> Expression<T> prd(Expression<T> a, Expression<T> b) {
        return new Expression<T>() {
            public <S> S accept(Visitor<T, S> visitor) {
                return visitor.prd(a, b);
            }
        };
    }

    public static <T> Expression<T> div(Expression<T> a, Expression<T> b) {
        return new Expression<T>() {
            public <S> S accept(Visitor<T, S> visitor) {
                return visitor.div(a, b);
            }
        };
    }

    public static <T> Expression<T> pow(Expression<T> a, T n) {
        return new Expression<T>() {
            public <S> S accept(Visitor<T, S> visitor) {
                return visitor.pow(a, n);
            }
        };
    }

    public static interface $Evaluate<T> {
        public T evaluate(church.lang.Functions.Function1<String, church.lang.Functions.Function1<T, T>> funNameToFunction, Expression<T> exp0, church.lang.Functions.Function1<String, T> varNameToValue);
    }

    public static <T> $Evaluate<T> evaluate($$sum<T> $L0, $$neg<T> $L1, $$sub<T> $L2, $$prd<T, T> $L3, $$div<T, T> $L4, $$pow<T, T> $L5) {
        return (funNameToFunction, exp0, varNameToValue) -> {
            $I0<T> eval = new $I0<T>() {
                public T eval(Expression<T> exp) {
                    return new Visitor<T, T>() {
                        public T $const(T value) {
                            return value;
                        }

                        public T var(String name) {
                            return varNameToValue.of(name);
                        }

                        public T app(String funName, Expression<T> a) {
                            return funNameToFunction.of(funName).of(eval(a));
                        }

                        public T sum(Expression<T> f, Expression<T> g) {
                            return $L0.$sum(eval(f), eval(g));
                        }

                        public T neg(Expression<T> f) {
                            return $L1.$neg(eval(f));
                        }

                        public T sub(Expression<T> f, Expression<T> g) {
                            return $L2.$sub(eval(f), eval(g));
                        }

                        public T prd(Expression<T> f, Expression<T> g) {
                            return $L3.$prd(eval(f), eval(g));
                        }

                        public T div(Expression<T> f, Expression<T> g) {
                            return $L4.$div(eval(f), eval(g));
                        }

                        public T pow(Expression<T> f, T n) {
                            return $L5.$pow(eval(f), n);
                        }

                    }
                            .get(exp);
                }
            };
            return eval.eval(exp0);
        };
    }

    public static interface $ExprEqual<T> {
        public boolean exprEqual(Expression<T> a, Expression<T> b);
    }

    public static <T> $ExprEqual<T> exprEqual($$equal<T> $L0) {
        return (a, b) -> new Visitor<T, Boolean>() {
            public Boolean $const(T v1) {
                return new Visitor<T, Boolean>() {
                    public Boolean $const(T v2) {
                        return $L0.$equal(v1, v2);
                    }

                }
                        .getOrDefault(b, false);
            }

            public Boolean var(String n1) {
                return new Visitor<T, Boolean>() {
                    public Boolean var(String n2) {
                        return $S0.$equal(n1, n2);
                    }

                }
                        .getOrDefault(b, false);
            }

        }
                .getOrDefault(a, false);
    }

    public static <T, U> $$encode<T, Expression<U>> $encode($$encode<T, U> $L0, $$encode<T, String> $L1) {
        return new $$encode<T, Expression<U>>() {
            public T $encode(T stream, Expression<U> $0) {
                return new Visitor<U, T>() {
                    public T $const(U v) {
                        return $L0.$encode(stream, v);
                    }

                    public T var(String name) {
                        return $L1.$encode(stream, name);
                    }

                    public T app(String f, Expression<U> a) {
                        return $L1.$encode($encode($L1.$encode($L1.$encode(stream, f), "("), a), ")");
                    }

                    public T sum(Expression<U> a, Expression<U> b) {
                        return $encode($L1.$encode($encode(stream, a), " + "), b);
                    }

                    public T neg(Expression<U> a) {
                        return $encode($L1.$encode(stream, "-"), a);
                    }

                    public T sub(Expression<U> a, Expression<U> b) {
                        return $encode($L1.$encode($encode(stream, a), " - "), b);
                    }

                    public T prd(Expression<U> a, Expression<U> b) {
                        return $encode($L1.$encode($encode(stream, a), " * "), b);
                    }

                    public T div(Expression<U> a, Expression<U> b) {
                        return $encode($L1.$encode($encode(stream, a), " / "), b);
                    }

                    public T pow(Expression<U> a, U b) {
                        return $L0.$encode($L1.$encode($encode(stream, a), "^"), b);
                    }

                }
                        .get($0);
            }
        };
    }

    public static <T> $$equal<Expression<T>> $equal($ExprEqual<T> $L0) {
        return (a, b) -> $L0.exprEqual(a, b);
    }

    public static <T> $Additive_identity<Expression<T>> additive_identity($Additive_identity<T> $L0) {
        return () -> $const($L0.additive_identity());
    }

    public static <T> $Multiplicative_identity<Expression<T>> multiplicative_identity($Multiplicative_identity<T> $L0) {
        return () -> $const($L0.multiplicative_identity());
    }

    public static <T> $$sum<Expression<T>> $sum($$equal<Expression<T>> $L0, $Additive_identity<Expression<T>> $L1) {
        return (a, b) -> $L0.$equal(a, $L1.additive_identity()) ? b : $L0.$equal(b, $L1.additive_identity()) ? a : sum(a, b);
    }

    public static <T> Expression<T> $neg(Expression<T> a) {
        return neg(a);
    }

    public static <T> $$sub<Expression<T>> $sub($$equal<Expression<T>> $L0, $Additive_identity<Expression<T>> $L1, $$neg<Expression<T>> $L2) {
        return (a, b) -> $L0.$equal(a, $L1.additive_identity()) ? $L2.$neg(b) : $L0.$equal(b, $L1.additive_identity()) ? a : sub(a, b);
    }

    public static <T> $$prd<Expression<T>, Expression<T>> $prd($$equal<Expression<T>> $L0, $Additive_identity<Expression<T>> $L1, $Multiplicative_identity<Expression<T>> $L2) {
        return (a, b) -> $L0.$equal(a, $L1.additive_identity()) ? $L1.additive_identity() : $L0.$equal(b, $L1.additive_identity()) ? $L1.additive_identity() : $L0.$equal(a, $L2.multiplicative_identity()) ? b : $L0.$equal(b, $L2.multiplicative_identity()) ? a : prd(a, b);
    }

    public static <T> $$div<Expression<T>, Expression<T>> $div($$equal<Expression<T>> $L0, $Additive_identity<Expression<T>> $L1, $Multiplicative_identity<Expression<T>> $L2) {
        return (a, b) -> $L0.$equal(a, $L1.additive_identity()) ? $L1.additive_identity() : $L0.$equal(b, $L2.multiplicative_identity()) ? a : div(a, b);
    }

    public static <T> $$pow<Expression<T>, T> $pow($$equal<Expression<T>> $L0, $Additive_identity<Expression<T>> $L1, $$equal<T> $L2, $Additive_identity<T> $L3, $Multiplicative_identity<Expression<T>> $L4, $Multiplicative_identity<T> $L5) {
        return (a, n) -> $L0.$equal(a, $L1.additive_identity()) ? $L1.additive_identity() : $L2.$equal(n, $L3.additive_identity()) ? $L4.multiplicative_identity() : $L2.$equal(n, $L5.multiplicative_identity()) ? a : pow(a, n);
    }

}