Essentials of Programming Languages by Friedman and Wand
A great book introducing the concepts of programming languages, including such things call-by-value/call-by-reference/call-by-name/call-by-need, type checking, type interface and modularization, all by means of interpreters for the various languages which are implemented in Scheme. The book covers various translations on the interpreters to make them more efficient, techniques like registerization (converting a series of tail recursion functions into a set of blocks connected via gotos with the arguments passed in global variables), CPS transformations and the use of trampoline techniques to move computation off the control stack into the heap. This latter technique was recently covered on the B# blog and is used in Clojure to prevent stack overflow.
I also think that you don’t understand a language until you implement it, and the idea of using Scheme interpretersto do the implementation is a powerful idea. Plenty of really good exercises too.