const std = @import("std"); pub fn main() anyerror!void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); var gpa = &arena.allocator; var program = try Program.init(gpa); defer program.*.deinit(); var f = try std.fs.cwd().openFile("input", .{}); var contents = try f.readToEndAlloc(gpa, std.math.maxInt(u32)); var it = std.mem.tokenize(contents, "\n"); while (it.next()) |line| { var s = try Statement.from_string(line); try program.code.append(s); } std.log.debug("Loaded program with {} statements", .{program.code.items.len}); _ = try program.run(); // Part 2 // Exactly one nop should be a jmp or a jmp a noop var i : usize = 0; while (i < program.code.items.len) : (i += 1) { if (program.code.items[i].op == .acc) { continue; } // Mutate program.code.items[i].op = switch(program.code.items[i].op) { .acc => Operation.acc, .jmp => Operation.nop, .nop => Operation.jmp, }; // Test var run_ok = program.run() catch false; // Undo mutation program.code.items[i].op = switch(program.code.items[i].op) { .acc => Operation.acc, .jmp => Operation.nop, .nop => Operation.jmp, }; if (run_ok) { std.log.info("By changing statement {}, the program exited okay", .{i}); break; } } } pub const Operation = enum { nop, acc, jmp }; pub const ParseError = error { UnknownStatement, }; pub const Statement = struct { op: Operation, arg: i32, /// Modifies acc and returns the offset for the next operation pub fn execute(self: *Statement, acc: *i32) i32 { var delta : i32 = switch (self.op) { .nop => 1, .jmp => self.arg, .acc => 1, }; if (self.op == .acc) { acc.* += self.arg; } return delta; } pub fn from_string(line: []const u8) !Statement { var arg : i32 = 0; var op : Operation = undefined; var it = std.mem.tokenize(line, " "); if (it.next()) |l| { if (std.mem.eql(u8, l, "nop")) { op = Operation.nop; } else if (std.mem.eql(u8, l, "acc")) { op = Operation.acc; } else if (std.mem.eql(u8, l, "jmp")) { op = Operation.jmp; } else { std.log.err("Unknown statement: '{}'", .{l}); return ParseError.UnknownStatement; } } else { // Error, and operator is required unreachable; } if (it.next()) |l| { arg = atoi(l); } else { // Error, we need an arg unreachable; } while(it.next()) |l| { // Error, there shouldn't be anything else on the line unreachable; } return Statement { .op = op, .arg = arg, }; } }; pub const Program = struct { code: std.ArrayList(Statement), allocator: *std.mem.Allocator, accumulator: i32 = 0, pub fn init(a: *std.mem.Allocator) !*Program { const self = try a.create(Program); errdefer a.destroy(self); self.* = Program { .code = std.ArrayList(Statement).init(a), .allocator = a, }; return self; } pub fn deinit(self: *Program) void { self.code.deinit(); self.allocator.destroy(self); } pub fn run(self: *Program) !bool { self.accumulator = 0; var next_statement_index : usize = 0; // Statement counter var map = std.hash_map.AutoHashMap(usize, u32).init(self.allocator); defer map.deinit(); var ended_ok = false; while (true) { // Check if we have already exectured this index var n_accumulator : i32 = self.accumulator; if (map.contains(next_statement_index)) { std.log.warn("Infinite loop detected. Statement {} would have run twice", .{next_statement_index}); std.log.warn("Acc value at break: {}", .{self.accumulator}); break; } else { try map.put(next_statement_index, 1); } var delta = self.code.items[next_statement_index].execute(&n_accumulator); //std.log.debug("Before {}, After {} [statement {} new offset {}] {}", // .{self.accumulator, n_accumulator, next_statement_index, // delta, self.code.items[next_statement_index]}); if (delta < 0) { next_statement_index -= @intCast(usize, try std.math.absInt(delta)); } else { next_statement_index += @intCast(usize, delta); } std.debug.assert(next_statement_index >= 0); if (next_statement_index >= self.code.items.len) { std.log.warn("Reached end of code, or jumping past end ({}, {} statements).", .{next_statement_index, self.code.items.len}); std.log.warn("Acc value at end: {}", .{self.accumulator}); ended_ok = true; // maybe... break; } self.accumulator = n_accumulator; } return ended_ok; } }; fn atoi(a: []const u8) i32 { var i : i32 = 0; var mul : i32 = 1; var start : usize = 0; if (a[0] == '-' or a[0] == '+') { start = 1; if (a[0] == '-') { mul *= -1; } } for(a[start..]) |v, k| { if (! std.ascii.isDigit(v)) { std.log.warn("Byte {x} is not a digit", .{v}); continue; } // 48 is '0' in ascii std.debug.assert(v >= 48 and v < 58); i += @as(i32, @as(i32, (v - 48)) * std.math.pow(i32, 10, @intCast(i32, a.len - k - 1 - start))); } //std.log.debug("{x} --> {}", .{a, i}); return i * mul; } test "atoi_regular" { var i = atoi("1234"); std.testing.expectEqual(i, 1234); } test "atoi_pos" { var i = atoi("+1234"); std.testing.expectEqual(i, 1234); } test "atoi_neg" { var i = atoi("-1234"); std.testing.expectEqual(i, -1234); }