This article is my attempt of putting together most of the ideas I learned in CPSC 311: Definition of Programming Languages - the most influential course of my undergraduate degree so far. I will attempt to take you through specifying a programming language from the ground up as well as building an interpreter for it in Racket.
My experience with CPSC 311
I thoroughly enjoyed taking CPSC 311 (with Prof. Ron Garcia). Working with Racket was amazing - throughout the course, we implemented interpreters for numerous languages, each building upon the other with more complex features. The template driven approach to development (HtDP), where the structure of our code followed directly from our data type definitions, made it a lot easier to reason about and debug programs. This coupled with the monadic design patterns we used to implement our interpreters lead to really delightful software that I thouroughly enjoyed working with. Ron emphasized how APIs and libraries are really just micro-DSLs in disguise, which allows us to use the learnings from this course in our development endevours.
Cooking Khichdi - Step 1
Alright, let’s build a programming language. Meet “Khichdi”, a functional language which supports
- Mutable variables (by-reference and by-value)
- First-class (recursive) functions
- Arrays, dictionaries and pairs
- Backstops (simplified Scala Implicits)
- Generalized search
- Exceptions
- First-class Continuations
You can check out the full code at the repository here
Let’s start with the basics and define the surface-level syntax for conditionals, functions, identifiers and arithmetic.
Writing a parser in Racket is straighforward, but before we get started with parsing we need to define a data type for Khichdi. Our parser will transform the input to a Racket expression of this type.
Using this definition, we can write the parser. This turns out to very easy due to Racket’s pattern matching capabilities. For instance, the following code sample shows you to parse an addition expression of Khichdi.
The interpreter looks like this.
Monadifying the interpreter
Before moving on and adding new features, we should prepare our interpreter to handle effectful computations, such as handling exceptions and mutable variables, by “monadifying” its structure. I talk more about monads in this article.
@todo