223 lines
6.4 KiB
Zig
223 lines
6.4 KiB
Zig
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);
|
|
}
|