class LetExpr extends Expr { IdExpr id; Expr rhs; Expr body; static final String TOKEN = "say"; public String toString( /* LetExpr this */ ) { return TOKEN + " " + this.id.toString() + " be " + this.rhs.toString() + " in " + this.body.toString() + " mates"; } public Value eval(/* LetExpr this */) { Value v = this.rhs.eval(); Expr ePrime = this.body.subst( this.id, v ); return ePrime.eval(); } public Expr subst( /* LetExpr this, */ IdExpr id, Value v ) { return new LetExpr( this.id, // keep this an id, not a Value this.rhs.subst(id,v), this.body.subst(id,v) ); } public LetExpr( IdExpr _id, Expr _rhs, Expr _body ) { this.id = _id; this.rhs = _rhs; this.body = _body; } @Override public boolean equals( /* BinExpr this, */ Object that) { if (this==that) { return true; } else if (that==null) { return false; } else if (this.getClass() != that.getClass()) { return false; } else { LetExpr thatt = (LetExpr) that; return this.id.equals(thatt.id) && this.rhs.equals(thatt.rhs) && this.body.equals(thatt.body); } } @Override public int hashCode() { if (cachedHash == null) { int hashSoFar = 0x815F58D4; // fingerprint hashSoFar += this.id.hashCode(); hashSoFar *= 31; hashSoFar += this.rhs.hashCode(); hashSoFar *= 31; hashSoFar += this.body.hashCode(); cachedHash = hashSoFar; } return cachedHash; } private Integer cachedHash = null; }