public interface Env {
    Env nullEnv();
    Env extend(String var, Val value);
    Val lookup(String var);
    String toString();
}

/* **************************************************************** */

public interface Val {
    String toString();
}

public class IntVal implements Val {
    int value;
    IntVal(int value) {
        this.value = value;
    }
    public String toString() {
        return "" + value;
    }
}

public class Closure implements Val {
    Env env;
    LamForm<Val> fun;
    
    Closure(LamForm<Val> fun, Env env) {
        this.fun = fun;
        this.env = env;
    }

    public String toString() {
        return "<CLOSURE: " + env + " | " + fun + ">";
    }
}

/* **************************************************************** */

public interface LangProcessor<Result> {
    Result constCase(ConstForm<Result> cf);
    Result varCase(VarForm<Result> vf);
    Result lamCase(LamForm<Result> lf);
    Result appCase(AppForm<Result> af);
}

public class Interp implements LangProcessor<Val> {
    Env env;
    
    Interp(Env env) {
        this.env = env;
    }

    public Val constCase(ConstForm<Val> cf) {
        return new IntVal(cf.value);
    }

    public Val varCase(VarForm<Val> vf) {
        return env.lookup(vf.name);
    }

    public Val lamCase(LamForm<Val> lf) {
        return new Closure<LangProcessor<Val>>(lf, env);
    }

    public Val appCase(AppForm<Val> af) {
        Val rator = af.fun.process(this);
        Val rand = af.arg.process(this);
        
        if (rator instanceof Closure) {
            Closure clos = (Closure) rator;
            LamForm<Val> lf = clos.fun;
            Env newEnv = clos.env.extend(lf.var, rand);
            LangProcessor<Val> newProc = new Interp(newEnv);
            return lf.body.process(newProc);
        } else {
            throw new ApplicationException("not a function: " + rator);
        }
    }
}

/* **************************************************************** */

public interface Form<Result> {
    Result process(LangProcessor<Result> lp);
    String toString();
}

public class ConstForm<R> implements Form<R> {
    int value;
    ConstForm(int value) {
        this.value = value;
    }
    public R process(LangProcessor<R> lp) {
        return lp.constCase(this);
    }
    public String toString() {
        return "" + value;
    }
}

public class VarForm<R> implements Form<R> {
    String name;

    VarForm(String name) {
        this.name = name;
    }
    public R process(LangProcessor<R> lp) {
        return lp.varCase(this);
    }
    
    public String toString() {
        return name;
    }
}

public class LamForm<R> implements Form<R> {
    String var;
    Form<R> body;
    LamForm(String var, Form<R> body) {
        this.var = var; this.body = body;
    }
    public R process(LangProcessor<R> lp) {
        return lp.lamCase(this);
    }
    public String toString() { return "\\" + var + ".(" + body + ")"; }
}

public class AppForm<R> implements Form<R> {
    Form<R> fun, arg;
    
    AppForm(Form<R> fun, Form<R> arg) {
        this.fun = fun; this.arg = arg;
    }
    
    public R  process(LangProcessor<R> lp) {
        return lp.appCase(this);
    }
    
    public String toString() {
        return "(" + fun + " " + arg + ")";
    }
}