335 lines
9.8 KiB
Zig
335 lines
9.8 KiB
Zig
//brainf
|
|
const std = @import("std");
|
|
const io = std.io;
|
|
const fs = std.fs;
|
|
const mem = std.mem;
|
|
const process = std.process;
|
|
const testing = std.testing;
|
|
const ArrayList = std.ArrayList;
|
|
|
|
const Level1 = union(enum) {
|
|
// think about it... i hope
|
|
inc: i8,
|
|
shift: i16,
|
|
// u8 because why would we be reading to/writing from the same cell 256 times in a row
|
|
// well, might as well use the extra space (union is bigger than its biggest member)
|
|
in: u16,
|
|
out: u16,
|
|
|
|
// u24s because i arbitrarily chose 2^24 as the limit
|
|
// maybe u32 would be more efficient idk this union should fit in a u32
|
|
// hm we can probably go way lower bc level1 is so much more compact than prog_unfiltered
|
|
// shouldn't be necessary
|
|
loop_start: u24,
|
|
loop_end: u24,
|
|
|
|
zero,
|
|
find_zero_left,
|
|
find_zero_right,
|
|
|
|
const Self = @This();
|
|
|
|
fn pointless(self: Self) bool {
|
|
return switch (self) {
|
|
.inc, .shift => |n| n == 0,
|
|
.in, .out => |n| n == 0,
|
|
else => false,
|
|
};
|
|
}
|
|
|
|
fn nothing() Self {
|
|
return .{ .inc = 0 };
|
|
}
|
|
|
|
// the default fmt is human and machine readable..... but idc xd lol pog
|
|
pub fn format(
|
|
self: Self,
|
|
comptime fmt: []const u8,
|
|
options: std.fmt.FormatOptions,
|
|
writer: anytype,
|
|
) !void {
|
|
_ = fmt;
|
|
_ = options;
|
|
|
|
switch (self) {
|
|
.inc => |n| try writer.print("INC {d: >4}", .{n}),
|
|
.shift => |n| try writer.print("SHF {d: >4}", .{n}),
|
|
.in => |n| try writer.print("INP {d: >4}", .{n}),
|
|
.out => |n| try writer.print("OUT {d: >4}", .{n}),
|
|
.loop_start => |n| try writer.print("JEZ {d:0>4}", .{n}),
|
|
.loop_end => |n| try writer.print("JNZ {d:0>4}", .{n}),
|
|
.zero => try writer.print("ZER ", .{}),
|
|
.find_zero_left => try writer.print("FZL ", .{}),
|
|
.find_zero_right => try writer.print("FZR ", .{}),
|
|
}
|
|
}
|
|
};
|
|
|
|
// mandelbrot.b has 120 instances of [-] which can be replaced with a memory[mp] = 0
|
|
// dang it! gcc -O3 with code generated by dbf2c.b finishes in like 1.4s
|
|
|
|
// we'll do the discarding of comment chars here
|
|
fn compileLevel1(allocator: mem.Allocator, src: []const u8) !ArrayList(Level1) {
|
|
var cur = Level1.nothing();
|
|
var out = ArrayList(Level1).init(allocator);
|
|
var openers = ArrayList(u24).init(allocator);
|
|
errdefer {
|
|
out.deinit();
|
|
openers.deinit();
|
|
}
|
|
var skip: u8 = 0;
|
|
|
|
for (src, 0..) |b, i| {
|
|
if (skip > 0) {
|
|
skip -= 1;
|
|
continue;
|
|
}
|
|
|
|
switch (b) {
|
|
'+', '-' => {
|
|
const n: i8 = if (b == '+') 1 else -1;
|
|
switch (cur) {
|
|
.inc => |*ns| ns.* +%= n,
|
|
else => {
|
|
if (!cur.pointless()) try out.append(cur);
|
|
cur = .{ .inc = n };
|
|
},
|
|
}
|
|
},
|
|
'>', '<' => {
|
|
const n: i16 = if (b == '>') 1 else -1;
|
|
switch (cur) {
|
|
.shift => |*ns| ns.* +%= n,
|
|
else => {
|
|
if (!cur.pointless()) try out.append(cur);
|
|
cur = .{ .shift = n };
|
|
},
|
|
}
|
|
},
|
|
'[' => {
|
|
if (!cur.pointless()) try out.append(cur);
|
|
// bad (what if space yk)
|
|
if ((src[i + 1] == '-' or src[i + 1] == '+') and src[i + 2] == ']') {
|
|
try out.append(.zero);
|
|
skip = 2;
|
|
cur = Level1.nothing();
|
|
} else if ((src[i + 1] == '>' or src[i + 1] == '<') and src[i + 2] == ']') {
|
|
try out.append(if (src[i + 1] == '>') .find_zero_right else .find_zero_left);
|
|
skip = 2;
|
|
cur = Level1.nothing();
|
|
} else {
|
|
cur = .{ .loop_start = 0 };
|
|
try openers.append(@as(u24, @truncate(out.items.len)));
|
|
}
|
|
},
|
|
']' => {
|
|
if (!cur.pointless()) try out.append(cur);
|
|
const opener_idx = openers.popOrNull() orelse return error.UnopenedCloser;
|
|
out.items[opener_idx].loop_start = @as(u24, @truncate(out.items.len));
|
|
cur = .{ .loop_end = opener_idx };
|
|
},
|
|
',' => {
|
|
switch (cur) {
|
|
.in => |*ns| ns.* += 1,
|
|
else => {
|
|
if (!cur.pointless()) try out.append(cur);
|
|
cur = .{ .in = 1 };
|
|
},
|
|
}
|
|
},
|
|
'.' => {
|
|
switch (cur) {
|
|
.out => |*ns| ns.* += 1,
|
|
else => {
|
|
if (!cur.pointless()) try out.append(cur);
|
|
cur = .{ .out = 1 };
|
|
},
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
if (!cur.pointless()) try out.append(cur);
|
|
|
|
const unclosed_openers = openers.items.len;
|
|
if (unclosed_openers > 0) return error.UnclosedOpener;
|
|
openers.deinit();
|
|
|
|
return out;
|
|
}
|
|
|
|
fn testLevel1(src: []const u8, expected: []const Level1) !void {
|
|
const level1 = try compileLevel1(testing.allocator, src);
|
|
defer level1.deinit();
|
|
|
|
errdefer {
|
|
std.debug.print("{s}\n", .{src});
|
|
std.debug.print(" OBTAINED EXPECTED\n", .{});
|
|
if (level1.items.len < expected.len) {
|
|
for (level1.items, expected[0..level1.items.len], 0..) |a, b, i| {
|
|
std.debug.print("{:0>4}: {} {}\n", .{ i, a, b });
|
|
}
|
|
for (expected[level1.items.len..], level1.items.len..) |b, i| {
|
|
std.debug.print("{:0>4}: -------- {}\n", .{ i, b });
|
|
}
|
|
} else {
|
|
for (level1.items[0..expected.len], expected, 0..) |a, b, i| {
|
|
std.debug.print("{:0>4}: {} {}\n", .{ i, a, b });
|
|
}
|
|
for (level1.items[expected.len..], expected.len..) |a, i| {
|
|
std.debug.print("{:0>4}: {} --------\n", .{ i, a });
|
|
}
|
|
}
|
|
}
|
|
|
|
try testing.expectEqual(expected.len, level1.items.len);
|
|
|
|
for (
|
|
level1.items,
|
|
expected,
|
|
) |a, b| {
|
|
try testing.expectEqual(a, b);
|
|
}
|
|
}
|
|
|
|
test "level 1" {
|
|
try testLevel1(
|
|
"+++-- [ >><<< +- ] ...,,,",
|
|
&[_]Level1{
|
|
.{ .inc = 1 },
|
|
.{ .loop_start = 3 },
|
|
.{ .shift = -1 },
|
|
.{ .loop_end = 1 },
|
|
.{ .out = 3 },
|
|
.{ .in = 3 },
|
|
},
|
|
);
|
|
try testLevel1(
|
|
\\ 0 [
|
|
\\ 1 [
|
|
\\ 2 ]
|
|
\\ 3 [
|
|
\\ 4 [
|
|
\\ 5 ]
|
|
\\ 6 [
|
|
\\ 7 ]
|
|
\\ 8 ]
|
|
\\ 9 ]
|
|
,
|
|
&[_]Level1{
|
|
.{ .loop_start = 9 },
|
|
.{ .loop_start = 2 },
|
|
.{ .loop_end = 1 },
|
|
.{ .loop_start = 8 },
|
|
.{ .loop_start = 5 },
|
|
.{ .loop_end = 4 },
|
|
.{ .loop_start = 7 },
|
|
.{ .loop_end = 6 },
|
|
.{ .loop_end = 3 },
|
|
.{ .loop_end = 0 },
|
|
},
|
|
);
|
|
|
|
testLevel1(
|
|
"bla bla - [ ++ ",
|
|
&[_]Level1{},
|
|
) catch |err| try testing.expectEqual(err, error.UnclosedOpener);
|
|
testLevel1(
|
|
"wow wow += ]",
|
|
&[_]Level1{},
|
|
) catch |err| try testing.expectEqual(err, error.UnopenedCloser);
|
|
|
|
try testLevel1(
|
|
"+++[>>[-]-+]",
|
|
&[_]Level1{
|
|
.{ .inc = 3 },
|
|
.{ .loop_start = 4 },
|
|
.{ .shift = 2 },
|
|
.zero,
|
|
.{ .loop_end = 1 },
|
|
},
|
|
);
|
|
|
|
try testLevel1(
|
|
"[>][<][-]",
|
|
&[_]Level1{
|
|
.find_zero_right,
|
|
.find_zero_left,
|
|
.zero,
|
|
},
|
|
);
|
|
}
|
|
|
|
pub fn main() !void {
|
|
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
|
var allocator = arena.allocator();
|
|
defer arena.deinit();
|
|
|
|
var out = io.getStdOut().writer();
|
|
var in = io.getStdIn().reader();
|
|
|
|
var args = process.args();
|
|
defer args.deinit();
|
|
_ = args.skip();
|
|
|
|
const prog_path = args.next() orelse {
|
|
try io.getStdErr().writer().writeAll("i need a program path :(\n");
|
|
process.exit(1);
|
|
};
|
|
|
|
var prog_file = try fs.cwd().openFile(prog_path, .{});
|
|
const src = try prog_file.readToEndAlloc(allocator, 1 << 24);
|
|
const prog_arr = try compileLevel1(allocator, src);
|
|
const prog = prog_arr.items;
|
|
const prog_len = prog.len;
|
|
|
|
var memory = try allocator.alloc(u8, 1 << 16);
|
|
for (memory) |*m| {
|
|
m.* = 0;
|
|
}
|
|
var mp: u16 = 0;
|
|
var ip: u32 = 0;
|
|
|
|
// should definitely add a command to print the cool asm
|
|
// std.debug.print("{s}\n", .{src});
|
|
// for (prog, 0..) |ins, i| {
|
|
// std.debug.print("{:0>4}: {}\n", .{ i, ins });
|
|
// }
|
|
|
|
// inc shift in out loop_start loop_end
|
|
while (ip < prog_len) {
|
|
switch (prog[ip]) {
|
|
.inc => |n| memory[mp] +%= @bitCast(n),
|
|
.shift => |n| mp +%= @bitCast(n),
|
|
.in => |n| {
|
|
for (0..(n - 1)) |_| {
|
|
_ = in.readByte() catch 0;
|
|
}
|
|
memory[mp] = in.readByte() catch 0;
|
|
},
|
|
.out => |n| {
|
|
try out.writeByteNTimes(memory[mp], n);
|
|
},
|
|
.loop_start => |new_ip| {
|
|
if (memory[mp] == 0) ip = new_ip;
|
|
},
|
|
.loop_end => |new_ip| {
|
|
if (memory[mp] != 0) ip = new_ip;
|
|
},
|
|
.zero => memory[mp] = 0,
|
|
.find_zero_right => {
|
|
while (memory[mp] != 0) {
|
|
mp +%= 1;
|
|
}
|
|
},
|
|
.find_zero_left => {
|
|
while (memory[mp] != 0) {
|
|
mp -%= 1;
|
|
}
|
|
},
|
|
}
|
|
ip += 1;
|
|
}
|
|
}
|