zig-bf/bf.zig

317 lines
9.1 KiB
Zig
Raw Permalink Normal View History

2024-06-24 13:23:11 -04:00
//brainf
const std = @import("std");
const io = std.io;
const fs = std.fs;
const mem = std.mem;
2024-06-24 13:23:11 -04:00
const process = std.process;
const testing = std.testing;
const ArrayList = std.ArrayList;
const Level1 = union(enum) {
// think about it... i hope
2024-06-24 23:27:09 -04:00
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,
2024-06-24 22:54:53 -04:00
// 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,
const Self = @This();
2024-06-24 22:54:53 -04:00
fn pointless(self: Self) bool {
return switch (self) {
.inc, .shift => |n| n == 0,
.in, .out => |n| n == 0,
else => false,
};
}
2024-06-24 22:54:53 -04:00
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 ", .{}),
2024-06-24 22:54:53 -04:00
}
}
};
// 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) {
2024-06-24 22:54:53 -04:00
var cur = Level1.nothing();
var out = ArrayList(Level1).init(allocator);
2024-06-24 22:54:53 -04:00
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) {
'+', '-' => {
2024-06-24 23:27:09 -04:00
const n: i8 = if (b == '+') 1 else -1;
2024-06-24 22:54:53 -04:00
switch (cur) {
2024-06-24 23:27:09 -04:00
.inc => |*ns| ns.* +%= n,
2024-06-24 22:54:53 -04:00
else => {
if (!cur.pointless()) try out.append(cur);
cur = .{ .inc = n };
},
}
},
'>', '<' => {
const n: i16 = if (b == '>') 1 else -1;
2024-06-24 22:54:53 -04:00
switch (cur) {
2024-06-24 23:27:09 -04:00
.shift => |*ns| ns.* +%= n,
2024-06-24 22:54:53 -04:00
else => {
if (!cur.pointless()) try out.append(cur);
cur = .{ .shift = n };
},
}
},
2024-06-24 22:54:53 -04:00
'[' => {
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 {
cur = .{ .loop_start = 0 };
try openers.append(@as(u24, @truncate(out.items.len)));
}
2024-06-24 22:54:53 -04:00
},
']' => {
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 };
},
',' => {
2024-06-24 22:54:53 -04:00
switch (cur) {
.in => |*ns| ns.* += 1,
else => {
if (!cur.pointless()) try out.append(cur);
cur = .{ .in = 1 };
},
}
},
'.' => {
2024-06-24 22:54:53 -04:00
switch (cur) {
.out => |*ns| ns.* += 1,
else => {
if (!cur.pointless()) try out.append(cur);
cur = .{ .out = 1 };
},
}
},
else => {},
}
}
2024-06-24 22:54:53 -04:00
if (!cur.pointless()) try out.append(cur);
const unclosed_openers = openers.items.len;
if (unclosed_openers > 0) return error.UnclosedOpener;
openers.deinit();
return out;
}
2024-06-24 22:54:53 -04:00
fn testLevel1(src: []const u8, expected: []const Level1) !void {
const level1 = try compileLevel1(testing.allocator, src);
defer level1.deinit();
2024-06-24 22:54:53 -04:00
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 });
}
}
}
2024-06-24 22:54:53 -04:00
try testing.expectEqual(expected.len, level1.items.len);
for (
level1.items,
2024-06-24 22:54:53 -04:00
expected,
) |a, b| {
try testing.expectEqual(a, b);
}
}
2024-06-24 13:23:11 -04:00
2024-06-24 22:54:53 -04:00
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,
},
);
2024-06-24 22:54:53 -04:00
}
2024-06-24 13:23:11 -04:00
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, .{});
2024-06-24 23:27:09 -04:00
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;
2024-06-24 13:23:11 -04:00
var memory = try allocator.alloc(u8, 1 << 16);
for (memory) |*m| {
2024-06-24 13:23:11 -04:00
m.* = 0;
}
var mp: u16 = 0;
var ip: u32 = 0;
2024-06-24 23:27:09 -04:00
// 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
2024-06-24 13:23:11 -04:00
while (ip < prog_len) {
switch (prog[ip]) {
2024-06-24 23:27:09 -04:00
.inc => |n| memory[mp] +%= @bitCast(n),
.shift => |n| mp +%= @bitCast(n),
.in => |n| {
for (0..(n - 1)) |_| {
_ = in.readByte() catch 0;
2024-06-24 13:23:11 -04:00
}
2024-06-24 23:27:09 -04:00
memory[mp] = in.readByte() catch 0;
2024-06-24 13:23:11 -04:00
},
2024-06-24 23:27:09 -04:00
.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;
2024-06-24 13:23:11 -04:00
},
.zero => memory[mp] = 0,
2024-06-24 13:23:11 -04:00
}
ip += 1;
}
}