Implement shadowing
...I think I know how to do it.
In the sub function for let-exprs, check if the id being substituted is the same as the one in the let expr. If they are the same, then you should do the substitution in the value expression, but not in the body expression (those are shadowed by the inner declaration).
The inner, shadowing variable will be bound later, when the inner let-expr is evaluated. And at that point, since we replaced the variable occurrences in the value expression, we can eval that and bind like "normal".
Of course if the identifiers are not the same, just do the substitution recursively in both sub-expressions, as previously.
Not 100% sure it will work, but easy enough to be worth trying!