314 lines
7.2 KiB
JavaScript
314 lines
7.2 KiB
JavaScript
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;
|
|
},
|
|
};
|