I have been having fun with Advent of Code recently. I only started playing in 2019 (and didn’t finish back then), so I decided to go back to previous years to solve old puzzles for fun. And while powering through year 2017, I’ve ended up using the `with` JavaScript statement for the very first time. Worth a few lines!

## The problem

Day 8 of 2017 has a very straightforward problem statement. Given a set of instructions like the ones below, figure out the maximum value reached by any variable (called “registers”). Quoting directly from the manual:

Each instruction consists of several parts: the register to modify, whether to increase or decrease that register's value, the amount by which to increase or decrease it, and a condition. If the condition fails, skip the instruction without modifying the register. The registers all start at 0. The instructions look like this:

``````b inc 5 if a > 1
a inc 1 if b < 5
c dec -10 if a >= 1
c inc -20 if c == 10
``````

As we can see, all lines are constructed the same way:

1. The name of a register.
2. `inc` or `dec` to indicate whether to increment or decrement the register.
3. A numeric value by which to update the register.
4. A condition in the form of:
1. The `if` keyword.
2. The name of a register (could be the same as #1).
3. An operator amongst `<`, `>`, `<=`, `>=` and `==`.
4. A numeric value to compare the register’s value to.

I guess the safe and healthy way to approach this problem is to break down each line into its components as listed above, but this is Advent of Code and it’s the one time we don’t have to be safe and healthy… 😈

## Good ol’ eval

Inspecting my input (which is 1,000 expressions, not just 4), the thing that striked me is that it looks kinda like JavaScript. What if — and hear me out — we did the least amount of work to be able to just evaluate the lines as pieces of code?

It would look something like this:

``````lines.forEach(line => {
const [action, condition] = line.split(' if ')

eval(`if (\${condition}) \${action}`)
})``````

This first tries to execute `if (a > 1) b inc 5`, which is not valid JavaScript. We need to change these `inc` and `dec` for actual operators.

``````lines.forEach(line => {
const [action, condition] = line.split(' if ')
const operation = action.replace('inc', '+=').replace('dec', '-=')

eval(`if (\${condition}) \${operation}`)
})``````

It now tries to execute `if (a > 1) b += 5` at this stage, which is good! We unfortunately have a new error:

`a` is not defined

Hard to argue with that — it is not defined. One way to solve the problem would be to manually define the variable `a` (and all others) at the top of our function, but that’s a tad too cumbersome, especially when there are 1,000 instructions with many many different registers.

What if instead of using individual variables, we used an object with dynamic keys? So we would have a single `registers` object, and then we would read and write keys in it.

``const registers = {}``

That’s getting us one step closer, but that’s still not enough because `a` (and other variables) remains undefined. We could prefix variable names with `registers.` in our expression. This way, we would run `if (registers.a > 1) registers.b += 5`, which is what we want, but it’s still a little annoying having to do that.

## The `with` statement

Enters `with`. If you’ve never heard of it, don’t worry because it’s a discouraged feature which happens to be forbidden in strict mode. 😅 What it does is “extending the scope chain for a statement.”

When doing `b += 5`, JavaScript looks for the variable `b` in the current scope (like the current block, or the condition, or the function) then goes up the scopes until reaching the global object, looking for the variable called `b`. What `with` does is inject the given object in the scope chain, so the lookup also happens there. MDN has a good snippet to illustrate how it works:

``````// From: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with
let a, x, y
const r = 10

with (Math) {
a = PI * r * r
x = r * cos(PI)
y = r * sin(PI / 2)
}``````

In this case, using `PI`, `cos` and `sin` — which would typically fail because there are no variables named as such — end up working. That’s because the `Math` object was added to the lookup chain and therefore `PI`, `cos` and `sin` were all found there.

You might see where we’re going with that. If we inject our `registers` object to our evaluation context, variables like `a`, `b` and `c` will be read in the `registers` object.

``````const registers = {}

lines.forEach(line => {
const [action, condition] = line.split(' if ')
const operation = action.replace('inc', '+=').replace('dec', '-=')

with (registers) eval(`if (\${condition}) \${operation}`)
})``````

Wait but, it still doesn’t work. Injecting `registers` into the scope chain doesn’t do magic though, and `a`, `b` and `c` are still not defined. And even if the interpreter didn’t crash on this, it would try to increment or decrement `undefined` which would result in `NaN`.

So we also need to initialize these values to 0. Are we back to square one? Not exactly. We could just capture everything that looks like a variable name in each line and instantiate them to 0 if they’re not already on the `registers` object.

``````line.match(/\w+/g).forEach(variable => {
registers[variable] = registers[variable] || 0
})``````

Keen observers among you might have noticed that this will also capture `inc` or `dec` as well as `if` to which I say: it doesn’t matter? But if we were precious about it, we could look in the condition and the operation exclusively instead:

``````;(condition + ' ' + operation).match(/\w+/g).forEach(variable => {
registers[variable] = registers[variable] || 0
})``````

Once the logical nullish assignment operator gets more widely adopted, we can do `registers[variable] ??= 0` to define only if not yet present.

And we’re basically done. Now all at once for good measure:

``````const run = lines => {
const registers = {}

lines.forEach(line => {
const [action, condition] = line.split(' if ')
const operation = action.replace('inc', '+=').replace('dec', '-=')

line.match(/\w+/g).forEach(variable => {
registers[variable] = registers[variable] || 0
})

with (registers) eval(`if (\${condition}) \${operation}`)
})

return Math.max(...Object.values(registers))
}``````

That’s it! 9 lines of JavaScript for the whole puzzle. Not bad I say.