const std = @import("std"); const CharDev = std.atomic.Queue(i32); 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 { alloc: *std.mem.Allocator, 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{ .alloc = alloc, .memory = program, .input = CharDev.init(), .output = CharDev.init(), }; } pub fn run(self: *Machine) anyerror!void { while (try self.step()) {} } pub fn deinit(self: *Machine) void { // TODO } pub fn readFromInput(self: *Machine, blocking: bool) !i32 { return self.__in(&self.input, blocking); } pub fn writeToInput(self: *Machine, val: i32) !void { return self.__out(&self.input, val); } pub fn readFromOutput(self: *Machine, blocking: bool) !i32 { return self.__in(&self.output, blocking); } pub fn writeToOutput(self: *Machine, val: i32) !void { return self.__out(&self.output, val); } inline fn __in(self: *Machine, dev: *CharDev, blocking: bool) !i32 { var node = dev.get(); if (node == null) { if (!blocking) return std.os.ReadError.WouldBlock; // Just busy-wait for now while (node == null) node = dev.get(); } if (node) |n| { defer self.alloc.destroy(n); return n.data; } unreachable; } inline fn __out(self: *Machine, dev: *CharDev, val: i32) !void { var node = try self.alloc.create(CharDev.Node); node.data = val; dev.put(node); } /// 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, try self.readFromInput(true)); // blocking read }, .OUT => { try self.writeToOutput(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.writeToInput(666); try machine.run(); assert(std.mem.eql(i32, before[0..before.len], after[0..after.len])); assert(666 == try machine.readFromOutput(false)); } 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); }