DEV Community

Alex Esoposting
Alex Esoposting

Posted on

Quick minilanguage to steer a submarine with Quackery

Every year I try to solve Advent of Code with some esolang for as long as possible. This year's language of choice was Quackery: a cute concatenative language based on Lisp and Forth, and when I saw the second puzzle I knew that language was perfect.

The problem

Puzzle for day 2 of this year's AoC is about steering a submarine and figuring up what coordinates you end up on. Input is a set of simple commands like this:

forward 5
down 5
forward 8
up 3
down 8
forward 2
Enter fullscreen mode Exit fullscreen mode

Each command consists of one of three directions: forward, up or down, and a number indicating how far in this direction to swim. The important note is that we care about depth and horizontal distance, so down would increase depth, and up would decrease it.

The idea

A standard approach to such problem would be to iterate over the input string word by word and changing some state based on what words are read. For anyone familiar with concatenative languages this should sound familiar: it's exactly what the interpreter does to execute code! Given that one of the rules of concatenative programming is to never write the same code twice we should use the interpreter to run interpret our input for us.

About Quackery

Quackery is a stack based concatenative language. Every word executed interacts with the main Data Stack: numbers put themselves on the stack and operators and user defined words act on top values of the stack. There are plenty of stack shuffling words to arrange the top of the stack for other operators. Most notable are:

  • dup - duplicate the top of the stack
  • drop - remove the top of the stack
  • swap - swap top two values on the stack The rest will be explained when used.

The implementation

The Quackery inerpreter already recognises numbers so the only thing we need to define are direction words: forward, up and down. In addition to that we need some state that these words would update, which I chose to be two numbers on the stack indicating depth and horizontal distance.

To make things nicer I will define a state constructor word which just puts two zeroes on the stack:

( state: depth, horizontal-distance )
[ 0 0 ] is new-state ( -- state )
Enter fullscreen mode Exit fullscreen mode

Parenthesis in Quackery indicate comments, just as in Forth.

up and down should take a number that follows them and add or subtract from the second item on the stack:

[ swap ]'[ + swap ] is down ( state 'n -- state )

[ swap ]'[ - swap ] is up ( state 'n -- state )
Enter fullscreen mode Exit fullscreen mode

]'[ is used to take the item following the word currently executed, so it would be the number after up or down.

forward is a bit tricky because it's already defined as a builder word in Quackery, and redefining builders requires some trickery. I need to first define a normal word with the behaviour I want forward to have and then redefine the builder forward to build that behaviour:

[ ]'[ + ] is forward-behaviour ( state 'n -- state )

[ dip [ ' forward-behaviour
        nested join ] ]      builds forward ( [ $ -- [ $ )
Enter fullscreen mode Exit fullscreen mode

Now that the interpreter can understand our input as source code we just need to run it on said source code:

[ dip new-state
  quackery * ]   is solve-puzzle ( input -- n )
Enter fullscreen mode Exit fullscreen mode

dip takes the top item off the stack, runs the operator that follows it (in this case new-state) and puts the item back on the stack. Coordinates are multiplied at the end to produce the answer that AoC will accept.

Full code, along with the second part of the puzzle, can be found in my AoC github repo.

Conclusion

This solution is a very minimalistic way of showing how easy and convenient it is to implement new languages in stack languages. This is a very useful feature because you don't need to implement a whole new interpreter, just define what each command does and tell the user what commands you support.

If you want to fully understand the code I wrote there check out The Book of Quackery, a 100 page tutorial and documentation for the Quackery language written by it's author and available for free as a PDF. It's very newbie-friendly.

Discussion (0)