package com.acme.symbolic; import church.lang.operators.Relational.$$equal; import church.primitives.Objects; import static church.lang.Error.error; import static church.lang.operators.Streams.$$encode; import static com.acme.symbolic.Primitive.proc; @SuppressWarnings("unchecked") public abstract class Expr { private static final $$equal<String> $S0 = Objects::$equal; public interface Visitor<S> { default S get(Expr receiver) { S result = receiver.accept(this); return result == null ? defaultImplementation(receiver) : result; } default S getOrDefault(Expr receiver, S defaultValue) { S result = receiver.accept(this); return result == null ? defaultValue : result; } default S $const(Primitive value) { return null; } default S var(String name) { return null; } default S lambda(String var, Expr body) { return null; } default S app(Expr fun, Expr arg) { return null; } default S defaultImplementation(Expr e) { throw new UnsupportedOperationException(); } } public abstract <S> S accept(Visitor<S> visitor); public static Expr $const(Primitive value) { return new Expr() { public <S> S accept(Visitor<S> visitor) { return visitor.$const(value); } }; } public static Expr var(String name) { return new Expr() { public <S> S accept(Visitor<S> visitor) { return visitor.var(name); } }; } public static Expr lambda(String var, Expr body) { return new Expr() { public <S> S accept(Visitor<S> visitor) { return visitor.lambda(var, body); } }; } public static Expr app(Expr fun, Expr arg) { return new Expr() { public <S> S accept(Visitor<S> visitor) { return visitor.app(fun, arg); } }; } public static java.util.function.Function<Primitive, Primitive> toFunction(Primitive p) { return new Primitive.Visitor<java.util.function.Function<Primitive, Primitive>>() { public java.util.function.Function<Primitive, Primitive> proc(java.util.function.Function<Primitive, Primitive> fn) { return fn; } } .get(p); } public static Primitive evaluate(Expr exp, church.lang.Functions.Function1<String, Primitive> nameToValue) { return new Expr.Visitor<Primitive>() { public Primitive $const(Primitive value) { return value; } public Primitive var(String name) { return nameToValue.of(name); } public Primitive lambda(String param, Expr body) { return proc(arg -> Expr.evaluate(body, var -> $S0.$equal(var, param) ? arg : nameToValue.of(var))); } public Primitive app(Expr fun, Expr arg) { Primitive fun0 = Expr.evaluate(fun, nameToValue); Primitive arg0 = Expr.evaluate(arg, nameToValue); return toFunction(fun0).apply(arg0); } } .get(exp); } public static Primitive eval(Expr exp) { return evaluate(exp, name -> error("Undefined variable")); } public static <T> $$encode<T, Expr> $encode($$encode<T, String> $L0, $$encode<T, Primitive> $L1) { return new $$encode<T, Expr>() { public T $encode(T stream, Expr $0) { return new Expr.Visitor<T>() { public T $const(Primitive v) { return $L0.$encode($L1.$encode($L0.$encode(stream, "const("), v), ")"); } public T var(String name) { return $L0.$encode(stream, name); } public T lambda(String var, Expr exp) { return $L0.$encode($encode($L0.$encode($L0.$encode($L0.$encode(stream, "("), var), " -> "), exp), ")"); } public T app(Expr fun, Expr arg) { return $L0.$encode($encode($L0.$encode($encode($L0.$encode(stream, "("), fun), " "), arg), ")"); } } .get($0); } }; } }