Though stacker was simple, we covered many of the foundational ideas of implementing languages in Racket. Our next tutorials will build on these:
A language in Racket can be thought of as a source-to-source compiler that converts the source code of a new language into ordinary Racket code, then runs it normally. This means that all Racket-implemented languages can use Racket’s libraries and tools.
The reader converts the source code of the new language into Racket-style parenthesized forms called S-expressions.
After converting the source to S-expressions, the function must return Racket code describing a module (packaged as a syntax object).
Back at the source file, Racket replaces the source code with this module code.
The expander determines how the S-expressions generated by the reader correspond to real Racket expressions, by ensuring that every identifier has a binding.
This macro must return new Racket code, again packaged as a syntax object. Racket replaces the module expression generated by the reader with this new code.
Once the expander finishes its work, the new language has been translated into ordinary Racket code. It’s evaluated as a Racket program to produce a result.
Racket supports a special class of functions called macros. Macros can copy & rewrite code at compile time. All macros take a code fragment as input (packaged as a syntax object) and return a new code fragment as output (also packaged as a syntax object).
A syntax object can also hold a reference to the lexical context at a certain point in the program, so that code inside the syntax object can see all the variables defined at that point. We can use the #' prefix to turn any code into a syntax object, by converting it to a datum and capturing its lexical context.
Try funstacker, a rewrite of stacker in a functional style (highly recommended for those new to Racket)
Make another quick language—stackerizer, a Racket-to-stacker compiler
Move ahead to the next big tutorial: bf