const std = @import("std"); const CharDev = std.ArrayList(i32); const CharDevLimit = 32; pub fn loadFromStream(alloc: *std.mem.Allocator, stream: *std.fs.File.InStream.Stream) anyerror![]i32 { var program = try alloc.alloc(i32, 1024); var buf = [_]u8{0} ** 255; var i: u32 = 0; while (try stream.readUntilDelimiterOrEof(&buf, ',')) |num| { var trimmed = std.mem.trimRight(u8, num, "\r\n"); program[i] = try std.fmt.parseInt(i32, trimmed, 10); // std.debug.warn("{},", program[i]); i += 1; } std.debug.warn("\n"); return program[0..program.len]; } pub fn loadFromStdIn(alloc: *std.mem.Allocator) anyerror![]i32 { const file = std.io.getStdIn(); const stream = &file.inStream().stream; return loadFromStream(alloc, stream); } const Opcode = enum(u8) { ADD = 1, MULT = 2, IN = 3, OUT = 4, JNZ = 5, JZ = 6, LT = 7, EQ = 8, END = 99, }; const Mode = enum(u8) { Position = 0, Immediate = 1, }; const Instruction = struct { op: Opcode, // Modes for each parameter of the opcode. Parameter 0 is in position 0, etc modes: [3]Mode, pub fn decode(value: i32) !Instruction { assert(value > 0 and value < 99999); var buf: [6]u8 = undefined; const str = try std.fmt.bufPrint(&buf, "{d:0<6}", value); const op = ((str[4] - '0') * 10) + (str[5] - '0'); return Instruction{ .op = @intToEnum(Opcode, op), .modes = [3]Mode{ @intToEnum(Mode, buf[3] - '0'), @intToEnum(Mode, buf[2] - '0'), @intToEnum(Mode, buf[1] - '0'), }, }; } /// Number of bytes taken up by a particular instruction pub fn size(self: Instruction) usize { return switch (self.op) { .ADD => 4, .MULT => 4, .IN => 2, .OUT => 2, .JNZ => 3, .JZ => 3, .LT => 4, .EQ => 4, .END => 1, }; } pub fn halt(self: Instruction) bool { return self.op == .END; } }; pub const Machine = struct { memory: []i32, ip: usize = 0, input: CharDev, output: CharDev, pub fn Run(alloc: *std.mem.Allocator, program: []i32) anyerror!Machine { var machine = try init(alloc, program); try machine.run(); return machine; } pub fn init(alloc: *std.mem.Allocator, program: []i32) anyerror!Machine { return Machine{ .memory = program, .input = try CharDev.initCapacity(alloc, CharDevLimit), .output = try CharDev.initCapacity(alloc, CharDevLimit), }; } pub fn run(self: *Machine) anyerror!void { while (try self.step()) {} } pub fn deinit(self: *Machine) void { self.input.deinit(); self.output.deinit(); } /// Read an immediate or position value from memory. Parameter determines /// which field following the current instruction to read from inline fn __read(self: *Machine, parameter: usize, mode: Mode) i32 { const immediate = self.memory[self.ip + parameter + 1]; return switch (mode) { .Immediate => immediate, .Position => self.memory[@intCast(usize, immediate)], }; } inline fn __write(self: *Machine, parameter: usize, value: i32) void { const dest = self.__read(parameter, .Immediate); self.memory[@intCast(usize, dest)] = value; } pub fn step(self: *Machine) anyerror!bool { const insn = try Instruction.decode(self.memory[self.ip]); const start_ip = self.ip; const opcode = switch (insn.op) { .ADD => { const a = self.__read(0, insn.modes[0]); const b = self.__read(1, insn.modes[1]); self.__write(2, a + b); }, .MULT => { const a = self.__read(0, insn.modes[0]); const b = self.__read(1, insn.modes[1]); self.__write(2, a * b); }, .IN => { self.__write(0, self.input.orderedRemove(0)); }, .OUT => { try self.output.append(self.__read(0, insn.modes[0])); }, .JNZ => { if (self.__read(0, insn.modes[0]) != 0) self.ip = @intCast(usize, self.__read(1, insn.modes[1])); }, .JZ => { if (self.__read(0, insn.modes[0]) == 0) self.ip = @intCast(usize, self.__read(1, insn.modes[1])); }, .LT => { const a = self.__read(0, insn.modes[0]); const b = self.__read(1, insn.modes[1]); if (a < b) { self.__write(2, 1); } else { self.__write(2, 0); } }, .EQ => { const a = self.__read(0, insn.modes[0]); const b = self.__read(1, insn.modes[1]); if (a == b) { self.__write(2, 1); } else { self.__write(2, 0); } }, .END => {}, }; // Only modify IP if the instruction itself has not if (self.ip == start_ip) self.ip += insn.size(); return !insn.halt(); } }; const assert = std.debug.assert; const test_allocator = std.heap.page_allocator; test "day 2 example 1" { var before: [12]i32 = .{ 1, 9, 10, 3, 2, 3, 11, 0, 99, 30, 40, 50 }; var after: [12]i32 = .{ 3500, 9, 10, 70, 2, 3, 11, 0, 99, 30, 40, 50 }; var machine = try Machine.Run(test_allocator, before[0..before.len]); assert(std.mem.eql(i32, before[0..before.len], after[0..after.len])); } test "day 2 example 2" { var before: [5]i32 = .{ 1, 0, 0, 0, 99 }; var after: [5]i32 = .{ 2, 0, 0, 0, 99 }; var machine = try Machine.Run(test_allocator, before[0..before.len]); assert(std.mem.eql(i32, before[0..before.len], after[0..after.len])); } test "day 2 example 3" { var before: [5]i32 = .{ 2, 3, 0, 3, 99 }; var after: [5]i32 = .{ 2, 3, 0, 6, 99 }; var machine = try Machine.Run(test_allocator, before[0..before.len]); assert(std.mem.eql(i32, before[0..before.len], after[0..after.len])); } test "day 2 example 4" { var before: [6]i32 = .{ 2, 4, 4, 5, 99, 0 }; var after: [6]i32 = .{ 2, 4, 4, 5, 99, 9801 }; var machine = try Machine.Run(test_allocator, before[0..before.len]); assert(std.mem.eql(i32, before[0..before.len], after[0..after.len])); } test "day 2 example 5" { var before: [9]i32 = .{ 1, 1, 1, 4, 99, 5, 6, 0, 99 }; var after: [9]i32 = .{ 30, 1, 1, 4, 2, 5, 6, 0, 99 }; var machine = try Machine.Run(test_allocator, before[0..before.len]); assert(std.mem.eql(i32, before[0..before.len], after[0..after.len])); } test "day 5 example 1" { var before: [5]i32 = .{ 3, 0, 4, 0, 99 }; var after: [5]i32 = .{ 666, 0, 4, 0, 99 }; var machine = try Machine.init(test_allocator, before[0..before.len]); try machine.input.append(666); try machine.run(); assert(std.mem.eql(i32, before[0..before.len], after[0..after.len])); assert(machine.output.len == 1); assert(machine.output.at(0) == 666); } test "Instruction#decode ADD" { const insn = try Instruction.decode(1); assert(insn.op == Opcode.ADD); assert(insn.modes[0] == .Position); assert(insn.modes[1] == .Position); assert(insn.modes[2] == .Position); } test "Instruction#decode MULT" { const insn = try Instruction.decode(2); assert(insn.op == Opcode.MULT); assert(insn.modes[0] == .Position); assert(insn.modes[1] == .Position); assert(insn.modes[2] == .Position); } test "Instruction#decode IN" { const insn = try Instruction.decode(3); assert(insn.op == Opcode.IN); assert(insn.modes[0] == .Position); assert(insn.modes[1] == .Position); assert(insn.modes[2] == .Position); } test "Instruction#decode OUT" { const insn = try Instruction.decode(4); assert(insn.op == Opcode.OUT); assert(insn.modes[0] == .Position); assert(insn.modes[1] == .Position); assert(insn.modes[2] == .Position); } test "Instruction#decode END Position" { const insn = try Instruction.decode(99); assert(insn.op == Opcode.END); assert(insn.modes[0] == .Position); assert(insn.modes[1] == .Position); assert(insn.modes[2] == .Position); } test "Instruction#decode END Immediate" { const insn = try Instruction.decode(11199); assert(insn.op == Opcode.END); assert(insn.modes[0] == .Immediate); assert(insn.modes[1] == .Immediate); assert(insn.modes[2] == .Immediate); } test "Instruction#decode END mixed" { const insn = try Instruction.decode(10099); assert(insn.op == Opcode.END); assert(insn.modes[0] == .Position); assert(insn.modes[1] == .Position); assert(insn.modes[2] == .Immediate); }