Building a New Programming Language in Ruby: Wrapping Up

The conclusion to Alex Braha Stoll's 5-part series on how to build a toy programming language in Ruby.

Full Source on GitHub

A complete implementation of the Stoffle programming language is available on GitHub. Feel free to open an issue if you find bugs or have questions.

In this blog post, we will wrap up the series on Stoffle, a toy programming language built entirely in Ruby. You can read more about this project in the first part of this series.

In the last post, we finished implementing Stoffle's interpreter, completing what we intended for this toy language. Some of you may be interested in having resources available to continue your study of programming language design and implementation. Some of you may also be interested in some ideas to continue improving Stoffle on your own. These are the topics we will cover in this post.

In this section, I will make some recommendations for you to continue your path in learning programming language implementation and design. I personally found the resources below very helpful while going through my own journey studying the topic.

Crafting Interpreters, by Robert Nystrom

This is my number one recommendation for those interested in programming language design and implementation. The whole book can be read for free at For those that prefer, printed versions are also available for purchase.

Robert Nystrom presents the topic of language design and implementation in great detail without boring the reader. In the first part of the book, Nystrom teaches the reader how to build a tree-walk interpreter from scratch (in the same spirit as what we did in this series, although different in implementation). In the second part of the book, one learns how to evolve the project into a compiled language by building, also from zero, a bytecode virtual machine.

The only drawbacks I see are the languages used in each of these parts. In the first part, Nystrom uses the Java programming language. To build the virtual machine, C was chosen. Even if you particularly dislike one or both of these languages, I strongly suggest you put aside your distaste and at least try to see if the book works for you. The contents and style of the book, in my opinion, are reason enough to read it.

Writing an Interpreter in Go, by Thorsten Ball

When studying any topic, I find it very useful to use a variety of resources and experience the same subject of study from different perspectives. If you like doing the same, I would recommend Writing an Interpreter in Go as a nice complement to Robert Nystrom's Crafting Interpreters.

Sometimes, I personally found Writing an Interpreter in Go a bit harder to understand than my first recommendation. However, it is nonetheless a great book. As you may imagine, the language used to implement the toy interpreter is Go. This may or may not be a hindrance, depending on your interest and familiarity with typed languages and Go in particular.

If you happen to like this book, keep in mind that there is also a sequel available. Ball's Writing a Compiler in Go picks up where the first book left off and continues the project by transforming it into a compiled language with its own virtual machine.

A Compiler from Scratch, by Gary Bernhardt

This is a free screencast that is part of Gary Bernhardt's excellent From Scratch series. In this screencast, Bernhardt builds a compiler for a very simple programming language. The technique used here is to transpile the toy language into JavaScript.

This is a quick introduction to programming language design and implementation and not a comprehensive exploration, unlike the previous recommendations. Regardless, I still find this a very interesting resource. If you happened to land on this post before reading the previous ones where we implemented Stoffle, Bernhardt's screencast is a quick way to get you started and inspired about the topic. Even if you already read the previous posts, the screencast is a fun way to be exposed to a different technique: transpilation or source-to-source compilation. The language used to build the compiler is Ruby, which is another reason to take a look at this resource.

Continue Improving Stoffle On Your Own

If you followed along with this series and completed Stoffle's implementation, now you may be thinking about how to continue your programming language design and implementation journey. Besides checking out the resources recommended above, you can also keep learning by making Stoffle better on your own. In this section, I will offer some pointers on ways you can improve Stoffle and continue progressing in the study of language design and implementation.

Syntactic Sugar

As a toy language, Stoffle has a lot of room for improvement in its syntax. By comparing Stoffle to mature languages, you can probably think of a few constructs that the language misses that, if present, would improve its usage experience.

If you find adding a bit of syntactic sugar to Stoffle a compelling exercise, let me offer you a suggestion. The language currently supports nested conditionals, but it does not have a construct, such as Ruby's elsif. To express nested conditionals, here is what is currently necessary:

if false
  println("IF block evaluated.")
  if true
    println("ELSE IF block evaluated.")

A strategy that can be used in this case, as well as to add other types of syntactic sugar, is to make the parser identify the new construct and produce the same abstract syntax tree (AST) generated for code that is semantically equivalent and currently supported. In other words, change the parser in such a way that when it encounters an elsif, it creates the same AST generated for nested conditionals, such as the one in the code snippet above. To be a bit more specific, here are the two changes you have to make:

  1. Change the Lexer class, adding the new elsif keyword;
  2. Change Parser#parse_conditional, making it support the new elsif construct by following the strategy explained above.

Type Verification for Arithmetic Operators

To make Stoffle a bit more robust, one interesting exercise is to start adding some basic safeguards to the language. One that comes to mind is adding type verification to arithmetic operators. Currently, there is no implemented mechanism to allow the interpreter to gracefully exit and show the user a helpful error message in cases where an illegal operation is performed. Multiplying two strings together, for example, causes a hard crash in the current version of the interpreter. Want to see for yourself? Try running the following Stoffle snippet:

result = "A string " * "and another string"

One straightforward way to implement such a check is to change Interpreter#interpret_binary_operator. There, you can add code that verifies the types of the operands before executing the operation and aborts the interpretation of the program when illegal operations are detected. Keep in mind, however, that this exercise is a bit trickier than you might be imagining. Remember that the operands will not always be primitive types, and in some programs, they may be complex expressions. This means that you will first need to evaluate the operands before being able to figure out their types.

Transpile Stoffle to Another High-Level Language

This is a much larger and challenging project than the previous two. After watching Bernhardt's screencast mentioned above, you may be catching yourself asking whether it would be possible to compile Stoffle to another high-level language (i.e., transpilation) instead of interpreting it. The answer is yes. It is out of the scope of this post to provide detailed instructions on how one could do this, but let me at least give you some pointers to help you if you happen to find yourself enamored with the idea.

You probably will not need to touch either the lexer or the parser. The component that needs to be replaced is the interpreter. Instead of traversing and interpreting the AST generated by the parser, you will need to implement a transpiler. Its job will be to traverse the AST and generate a source file in the programming language that you are targeting. Let's say, for example, that you want to target JavaScript. In this case, your transpiler will need to traverse the AST and generate a JS source file that is semantically equivalent (i.e., have the same meaning) to the original Stoffle program. After doing that, you should be able to run the generated JS file using an appropriate tool of your choice (such as Node.js).


In this post, we wrapped up the series on Stoffle, a toy programming language implemented from scratch entirely in Ruby. This was a long and challenging journey, but we were able to make it to the other side!

If you want to tackle implementation challenges before studying deeper and learning about new concepts, the exercises I proposed may be a good start. If you feel you are ready to dive deeper and want to start exploring new and more complex concepts, the resources I recommended in the beginning of this post are definitely a great bet. I really hope this series ignited a passion for programming language implementation and design and that you continue on this exhilarating adventure!

What to do next:
  1. Try Honeybadger for FREE
    Honeybadger helps you find and fix errors before your users can even report them. Get set up in minutes and check monitoring off your to-do list.
    Start free trial
    Easy 5-minute setup — No credit card required
  2. Get the Honeybadger newsletter
    Each month we share news, best practices, and stories from the DevOps & monitoring community—exclusively for developers like you.
    author photo

    Alex Braha Stoll

    Alex is a software developer that cannot get tired of attempting to write the next line of code at least a little better than the one before it. In his free time, he likes to study and practice Photography.

    More articles by Alex Braha Stoll
    Stop wasting time manually checking logs for errors!

    Try the only application health monitoring tool that allows you to track application errors, uptime, and cron jobs in one simple platform.

    • Know when critical errors occur, and which customers are affected.
    • Respond instantly when your systems go down.
    • Improve the health of your systems over time.
    • Fix problems before your customers can report them!

    As developers ourselves, we hated wasting time tracking down errors—so we built the system we always wanted.

    Honeybadger tracks everything you need and nothing you don't, creating one simple solution to keep your application running and error free so you can do what you do best—release new code. Try it free and see for yourself.

    Start free trial
    Simple 5-minute setup — No credit card required

    Learn more

    "We've looked at a lot of error management systems. Honeybadger is head and shoulders above the rest and somehow gets better with every new release."
    — Michael Smith, Cofounder & CTO of YvesBlue

    Honeybadger is trusted by top companies like:

    “Everyone is in love with Honeybadger ... the UI is spot on.”
    Molly Struve, Sr. Site Reliability Engineer, Netflix
    Start free trial
    Are you using Sentry, Rollbar, Bugsnag, or Airbrake for your monitoring? Honeybadger includes error tracking with a whole suite of amazing monitoring tools — all for probably less than you're paying now. Discover why so many companies are switching to Honeybadger here.
    Start free trial
    Stop digging through chat logs to find the bug-fix someone mentioned last month. Honeybadger's built-in issue tracker keeps discussion central to each error, so that if it pops up again you'll be able to pick up right where you left off.
    Start free trial