Today's challenge was another staple of Advent of Code that is probably setting us up for later challenges.
The gist was to parse a series of directional commands for the submarine and figure out the position after applying them all.
Here's the example input:
forward 5
down 5
forward 8
up 3
down 8
forward 2
The interesting thing I noticed here is that these commands are actually already valid Nim syntax.
That makes this a great candidate for an irresponsible solution using macros.
macro part1(expr: static string): int =
let code = parsestmt(expr)
quote do:
var x, y = 0 proc up(n: int) = y -= n
proc down(n: int) = y += n
proc forward(n: int) = x += n
`code`
x * y
The static string
type means that this macro takes a string that must be known at compile time.
Then the parseStmt
procedure from the macros
package parses a string and returns the equivalent syntax nodes.
Finally, the quote do:
statement describes the code that we want to generate when this macro is invoked. This is a much nicer way to write macros than manually arranging syntax nodes, but I suppose it's only really viable with simpler macros.
When invoked with the example instructions, you can imagine that this macro expands to the following code.
var x, y = 0
proc up(n: int) = y -= n
proc down(n: int) = y += n
proc forward(n: int) = x += n
forward 5
down 5
forward 8
up 3
down 8
forward 2
x * y
After adding a similar solution for part 2, I discovered that the variables names clash between the two macros. I was expecting Nim's genSym
to generate a unique symbol for each variable at runtime, this didn't happen, but I was able to solve by wrapping both macro bodies in blocks.
macro part1(expr: static string): int =
let code = parsestmt(expr)
quote do:
block:
var x, y = 0 proc up(n: int) = y -= n
proc down(n: int) = y += n
proc forward(n: int) = x += n
`code`
x * y
Macros are evaluated at compile time, using a VM and a subset of the language called NimScript. This VM can also run evaluate expressions marked with static
, so long as their code can be evaluated at compile time.
Because I'm using slurp
(an alias for staticRead
) to embed my input file into the resulting binary, it's actually possible to run the whole solution at compile time.
static:
const input = slurp("input.txt")
echo "Part 1: ", part1(input)
echo "Part 2: ", part2(input)
It's surprisingly novel to see the puzzle output show up before the compiler hints finish.