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.
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
Each command consists of one of three directions:
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.
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.
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 Quackery inerpreter already recognises numbers so the only thing we need to define are direction words:
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 )
Parenthesis in Quackery indicate comments, just as in Forth.
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 )
]'[ is used to take the item following the word currently executed, so it would be the number after
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 ( [ $ -- [ $ )
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 )
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.
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.