Introducing UnoScript

I imagine it’s a fairly common item on a programmer’s bucket-list: design a new programming language. For people that spend all day working with languages of other people’s design, there’s a romantic appeal to building a language of your own, a chance to build something abstract yet capable of improving your day-to-day experience.

UnoScript is not that language.

At the start of 2020, I finished a several month project to design and implement my own programming language. This project began with buying a book on Lex and Yacc and brainstorming what new language I could build. I decided to take inspiration from the UNO card game and see where it would go. I had grand visions: a strongly-typed languaged with UNO colors as types, with control flow inspired by UNO action cards, full I/O constructs, the works. A couple months in I realized I was just building a clone of BASIC.

Choosing better contraints

My interest in the project plummeted immediately. I had wanted to build something new, not an even-more-basic BASIC! Looking back on my design process, I realized that by following examples from my Lex/Yacc book, I had followed the authors’ design choices by default, making the original inspiration an afterthought. So, I went back to my notebook and started thinking about the language UNO-first. What renewed my interest in the project was an intriguing constraint: how useable could a language be, when not merely inspired by UNO but implemented in UNO?

From this perspective, there’s an existing alphabet (set of symbols in the language) given by the cards in an UNO deck. It’s the designer’s responsibility to build a useable language out of these primitives.

This constraint made several things clear.

  1. First, a stack would need to be central to the language. This translates the mechanics of an actual card game, with draw and discard piles, into native language constructs.

  2. Second, the language would need to be more procedural and less structured. Thinking of a game of UNO, where a series of cards are played one-at-a-time, translates to a language where nested scopes and a local/global distinction feel foreign.

What emerged was UnoScript, a language somewhere between Forth and a Turing machine.

Writing an interpreter

I implemented the interpreter in C++, which meshed well with the output of Lex. Interestingly, since the language has no concept of scope, parsing by Yacc/Bison was uneeded.

Looking back on my design choices, there’s a few things I would reconsider:

Final thoughts

Writing UnoScript was a rewarding experience. Producing an unstructured language saved headaches sorting through parsing and building a syntax tree, making this less technically challenging than I had expected. However, this project provided good insight on the impact of choosing good constraints, which make a project enjoyable and encourage thinking design-first instead of implementation-first.

For example, after I scrapped the BASIC-clone, I started by writing the language specification first and then implementing the interpreter. This is much more disciplined than I usually am when programming, and I enjoyed it.

Overall, it just felt good to see a project through from start to finish. I’m also interested to see how usable this language is – it was pretty difficult writing a simple hello world!