Advent of Code 2020 solutions in Zig
const std = @import("std");

const PATH = "input/day08.txt";

const Str = []const u8;

pub fn first(allocator: ?std.mem.Allocator) anyerror!usize {
    const lines = try parseInstructions(allocator.?);
    defer allocator.?.free(lines);

    var visited = try std.DynamicBitSet.initEmpty(allocator.?, lines.len);
    defer visited.deinit();

    var accumulator: isize = 0;

    var idx: isize = 0;
    while (idx < lines.len) : (idx += 1) {
        if (visited.isSet(@intCast(usize, idx))) break;
        visited.set(@intCast(usize, idx));

        const line = lines[@intCast(usize, idx)];
        switch (line[0]) {
            'n' => {},
            'a' => {
                var parts = std.mem.tokenize(u8, line, " ");
                _ = parts.next();
                const diff = try std.fmt.parseInt(isize, parts.next().?, 0);
                accumulator += diff;
            },
            'j' => {
                var parts = std.mem.tokenize(u8, line, " ");
                _ = parts.next();
                const diff = try std.fmt.parseInt(isize, parts.next().?, 0);
                idx += diff - 1;
            },
            else => unreachable,
        }
    }

    return @intCast(usize, accumulator);
}

fn parseInstructions(allocator: std.mem.Allocator) ![]Str {
    const file = @embedFile(PATH);
    var lines = std.mem.split(u8, file, "\n");

    var instr = std.ArrayList(Str).init(allocator);

    while (lines.next()) |line| {
        if (line.len == 0) break;
        try instr.append(line);
    }

    return instr.toOwnedSlice();
}

pub fn second(allocator: ?std.mem.Allocator) anyerror!usize {
    const lines = try parseInstructions(allocator.?);
    defer allocator.?.free(lines);

    for (lines) |l, i| {
        const changed_line = switch (l[0]) {
            'j', 'n' => @intCast(isize, i),
            else => continue,
        };

        var accumulator: isize = 0;

        var visited = try std.DynamicBitSet.initEmpty(allocator.?, lines.len);
        defer visited.deinit();

        var idx: isize = 0;
        while (idx < lines.len) : (idx += 1) {
            if (visited.isSet(@intCast(usize, idx))) break;
            visited.set(@intCast(usize, idx));

            const line = lines[@intCast(usize, idx)];
            var op: u8 = line[0];
            if (idx == changed_line) {
                // flip op on changed_line, so the next switch part can be unchanged
                if (op == 'j') op = 'n' else op = 'j';
            }

            switch (op) {
                'n' => {},
                'a' => {
                    var parts = std.mem.tokenize(u8, line, " ");
                    _ = parts.next();
                    const diff = try std.fmt.parseInt(isize, parts.next().?, 0);
                    accumulator += diff;
                },
                'j' => {
                    var parts = std.mem.tokenize(u8, line, " ");
                    _ = parts.next();
                    const diff = try std.fmt.parseInt(isize, parts.next().?, 0);
                    idx += diff - 1;
                },
                else => unreachable,
            }
        }

        // If all instructions called, then the program is finished.
        if (idx == lines.len) return @intCast(usize, accumulator);
    }

    unreachable;
}

test "day08a" {
    try std.testing.expectEqual(@as(usize, 1727), try first(std.testing.allocator));
}

test "day08b" {
    try std.testing.expectEqual(@as(usize, 552), try second(std.testing.allocator));
}