Source: Leonardo’s Monorail
Part 1: Create a virtual machine that has four registers (
a
,b
,c
, andd
) and can process the following instructions:
cpy x y
- copiesx
intoy
(x
can be an integer or a register)inc x
- increases registerx
by onedec x
- decreases registerx
by onejnz x y
- jumps overy
instructions ifx
is not zero (x
can be an integer or a register)
What is the final value in register
a
?
Let’s make a virtual machine!
class APC(object):
def __init__(self, code):
self._code = code
self._pc = 0
self._registers = {k: 0 for k in 'abcd'}
def __repr__(self):
return 'APC<{}, {}, {}>'.format(
id(self),
self._pc,
self._registers,
)
def run(self):
def val(x):
try:
return int(x)
except:
return self._registers[x]
try:
while True:
cmd, *args = self._code[self._pc]
logging.info('{} running {}({})'.format(self, cmd, args))
if cmd == 'cpy':
self._registers[args[1]] = val(args[0])
elif cmd == 'inc':
self._registers[args[0]] += 1
elif cmd == 'dec':
self._registers[args[0]] -= 1
elif cmd == 'jnz':
if val(args[0]) != 0:
self._pc += val(args[1]) - 1
self._pc += 1
except Exception as ex:
logging.info('Exception: {}'.format(ex))
self.exception = ex
In true EAFP
style, I’m using the out of bounds exception to halt execution. Other than that, the translations are pretty direct.
instructions = [
line.strip().split()
for line in fileinput.input(args.files)
if line.strip()
]
apc = APC(instructions)
print('Initial:', apc)
apc.run()
print('Final:', apc)
Part 2: If register
c
starts with the value1
, what is the final value left ina
?
Let’s modify the __init__
function to take in initial values for any registers and then read those in with argparse
:
class APC(object):
def __init__(self, code, registers):
self._code = code
self._pc = 0
self._registers = {k: registers.get(k, 0) for k in 'abcd'}
...
registers = {
arg.split('=')[0].strip(): int(arg.split('=')[1].strip())
for arg in args.registers
}
apc = APC(instructions, registers)
Nice when part 1 is flexible enough to solve part 2 with minimal changes.