const { Glib } = require('../../lib'); const DEBUG = false; const OPCODES = { ADD: 1n, MULTIPLY: 2n, INPUT: 3n, OUTPUT: 4n, JUMP_IF_TRUE: 5n, JUMP_IF_FALSE: 6n, LESS_THAN: 7n, EQUALS: 8n, HALT: 99n, }; const MODES = { POSITION: 0n, IMMEDIATE: 1n, }; const CONDITIONS = { HALT: Symbol('HALT'), }; class AbstractParameter { read() { throw new Error("can't read"); } write() { throw new Error("can't write"); } position() { throw new Error("can't write"); } } class PositionParameter extends AbstractParameter { constructor(value) { super(); this._v = value; } read(memory) { return memory[this._v]; } write(memory, value) { return memory.safeSplice(Number(this._v), 1, value); } position() { return this._v; } } class DirectParameter extends AbstractParameter { constructor(value) { super(); this._v = value; } read() { return this._v; } position() { return -1n; } } const INSTRUCTIONS = new Map([ [ OPCODES.ADD, { arity: 3n, execute: ({ memory, parameters: [left, right, output] }) => { DEBUG && console.log( `ADD (${left.position()})->${left.read( memory, )} (${right.position()})->${right.read( memory, )} TO (${output.position()})`, ); return { memory: output.write(memory, left.read(memory) + right.read(memory)), }; }, }, ], [ OPCODES.MULTIPLY, { arity: 3n, execute: ({ memory, parameters: [left, right, output] }) => { DEBUG && console.log( `MULTIPLY (${left.position()})->${left.read( memory, )} (${right.position()})->${right.read( memory, )} TO (${output.position()})`, ); return { memory: output.write(memory, left.read(memory) * right.read(memory)), }; }, }, ], [ OPCODES.INPUT, { arity: 1n, execute: ({ memory, parameters: [target], inputs }) => { DEBUG && console.log(`INPUT TO (${target.position()})`); const [input, ...newInputs] = inputs; return { memory: target.write(memory, input), inputs: newInputs, }; }, }, ], [ OPCODES.OUTPUT, { arity: 1n, execute: ({ memory, parameters: [value], outputs }) => { DEBUG && console.log(`OUTPUT (${value.position()})->${value.read(memory)}`); return { outputs: outputs.safePush(value.read(memory)), }; }, }, ], [ OPCODES.JUMP_IF_TRUE, { arity: 2n, execute: ({ memory, parameters: [conditional, target] }) => { DEBUG && console.log( `JUMP_IF_TRUE (${conditional.position()})->${conditional.read( memory, )} TO (${target.position()})->${target.read(memory)}`, ); const diff = {}; if (conditional.read(memory) !== 0n) { diff.instructionPointer = target.read(memory); } return diff; }, }, ], [ OPCODES.JUMP_IF_FALSE, { arity: 2n, execute: ({ memory, parameters: [conditional, target] }) => { DEBUG && console.log( `JUMP_IF_FALSE (${conditional.position()})->${conditional.read( memory, )} TO (${target.position()})->${target.read(memory)}`, ); const diff = {}; if (conditional.read(memory) === 0n) { diff.instructionPointer = target.read(memory); } return diff; }, }, ], [ OPCODES.LESS_THAN, { arity: 3n, execute: ({ memory, parameters: [left, right, output] }) => { DEBUG && console.log( `LESS_THAN (${left.position()})->${left.read( memory, )} < (${right.position()})->${right.read( memory, )} TO (${output.position()})`, ); return { memory: output.write( memory, left.read(memory) < right.read(memory) ? 1n : 0n, ), }; }, }, ], [ OPCODES.EQUALS, { arity: 3n, execute: ({ memory, parameters: [left, right, output] }) => { DEBUG && console.log( `EQUALS (${left.position()})->${left.read( memory, )} === (${right.position()})->${right.read( memory, )} TO (${output.position()})`, ); return { memory: output.write( memory, left.read(memory) === right.read(memory) ? 1n : 0n, ), }; }, }, ], // JUMP_IF_TRUE: 5n, // JUMP_IF_FALSE: 6n, // LESS_THAN: 7n, // EQUALS: 8n, [ OPCODES.HALT, { arity: 0n, execute: () => { DEBUG && console.log(`HALT`); return { condition: CONDITIONS.HALT, }; }, }, ], ]); const PARAMETER_MODES = new Map([ [MODES.POSITION, (value) => new PositionParameter(value)], [MODES.IMMEDIATE, (value) => new DirectParameter(value)], ]); const parseInstruction = (instruction) => { const opcode = instruction % 100n; const modes = []; let modeInt = instruction / 100n; while (modeInt > 0) { modes.push(modeInt % 10n); modeInt /= 10n; } return { opcode, modes, }; }; const parseParameters = (modes, rawParameters) => { if (modes.length > rawParameters.length) { throw new Error('invalid mode arity'); } else if (modes.length < rawParameters.length) { modes = modes.concat(Array(rawParameters.length - modes.length).fill(0n)); } return rawParameters.map((value, i) => { const mode = modes[i]; if (!PARAMETER_MODES.has(mode)) { throw new Error(`No mode for '${mode}'`); } return PARAMETER_MODES.get(mode)(value); }); }; module.exports = { parse(text) { return Glib.fromSplit(text, ',').toInts().array; }, run(program, inputs = []) { let state = { memory: program.slice(), instructionPointer: 0n, inputs, outputs: [], }; while (state.condition !== CONDITIONS.HALT) { DEBUG && console.log(state); const { opcode, modes } = parseInstruction( state.memory[state.instructionPointer], ); if (!INSTRUCTIONS.has(opcode)) { throw new Error(`No instruction for opcode '${opcode}'`); } const instruction = INSTRUCTIONS.get(opcode); const instructionParametersPc = state.instructionPointer + 1n; const instructionEndPc = instructionParametersPc + instruction.arity; const parameters = parseParameters( modes, instruction.arity > 0n ? state.memory.slice( Number(instructionParametersPc), Number(instructionEndPc), ) : [], ); if (BigInt(parameters.length) !== instruction.arity) { throw new Error('Invalid number of parameters'); } const stateChanges = instruction.execute({ ...state, parameters }); if (!stateChanges.hasOwnProperty('instructionPointer')) { stateChanges.instructionPointer = instructionEndPc; } state = { ...state, ...stateChanges }; } DEBUG && console.log(state); return state; }, };