~ntgg/PGLang

562ef424a2f3d30619d6e3f49121ea0e33b1218a — Noah Graff 5 months ago 97fab4c master
Cleaned up parsing and vm
2 files changed, 176 insertions(+), 188 deletions(-)

M src/parse.zig
M src/vm.zig
M src/parse.zig => src/parse.zig +156 -170
@@ 2,187 2,173 @@ const std = @import("std");
const jsLog = @import("root").jsLog;
const Allocator = std.mem.Allocator;

pub const Instruction = union(enum) {
    Incr: u8,
    Decr: u8,
    Next: usize,
    Prev: usize,
    Output,
    Input,
    LoopStart: ?usize,
    LoopEnd: usize,
pub const Instruction = struct {
    id: Id,
    // on everything but Loop*, data is repetitions, on LoopStart and LoopEnd
    // it is the matching loop to jump to.
    data: usize,

    pub const Id = enum {
        Incr,
        Decr,
        Next,
        Prev,
        Output,
        Input,
        LoopStart,
        LoopEnd,
    };
};

const State = union(enum) {
    Incr: u8,
    Decr: u8,
    Next: usize,
    Prev: usize,
    Scanning,
const InstructionIterator = struct {
    bytes: []const u8,
    idx: usize,

    pub fn init(bytes: []const u8) InstructionIterator {
        return InstructionIterator{
            .bytes = bytes,
            .idx = 0,
        };
    }

    pub fn peek(iter: *InstructionIterator) ?Instruction.Id {
        while (iter.idx < iter.bytes.len) : (iter.idx += 1) {
            switch (iter.bytes[iter.idx]) {
                '+' => return .Incr,
                '-' => return .Decr,
                '<' => return .Prev,
                '>' => return .Next,
                '.' => return .Output,
                ',' => return .Input,
                '[' => return .LoopStart,
                ']' => return .LoopEnd,
                else => {},
            }
        }
        return null;
    }

    pub fn next(iter: *InstructionIterator) ?Instruction.Id {
        defer iter.idx += 1;
        return iter.peek();
    }
};

pub fn parse(allocator: *Allocator, source: []const u8) !std.ArrayList(Instruction) {
    var state: State = State{ .Scanning = {} };
pub fn parse(allocator: *Allocator, bytes: []const u8) ![]Instruction {
    var iter = InstructionIterator.init(bytes);
    var instructions = std.ArrayList(Instruction).init(allocator);
    var loop_stack = std.ArrayList(usize).init(allocator);

    for (source) |char| {
        switch (char) {
            '+' => {
                var switch_state = true;
                switch (state) {
                    .Incr => |*value| {
                        value.* += 1;
                        switch_state = false;
                    },
                    .Decr => |value| try instructions.append(Instruction{ .Decr = value }),
                    .Next => |value| try instructions.append(Instruction{ .Next = value }),
                    .Prev => |value| try instructions.append(Instruction{ .Prev = value }),
                    .Scanning => {},
                }
                if (switch_state) {
                    state = State{ .Incr = 1 };
                }
            },
            '-' => {
                var switch_state = true;
                switch (state) {
                    .Incr => |value| try instructions.append(Instruction{ .Incr = value }),
                    .Decr => |*value| {
                        value.* += 1;
                        switch_state = false;
                    },
                    .Next => |value| try instructions.append(Instruction{ .Next = value }),
                    .Prev => |value| try instructions.append(Instruction{ .Prev = value }),
                    .Scanning => {},
                }
                if (switch_state) {
                    state = State{ .Decr = 1 };
                }
            },
            '>' => {
                var switch_state = true;
                switch (state) {
                    .Incr => |value| try instructions.append(Instruction{ .Incr = value }),
                    .Decr => |value| try instructions.append(Instruction{ .Decr = value }),
                    .Next => |*value| {
                        value.* += 1;
                        switch_state = false;
                    },
                    .Prev => |value| try instructions.append(Instruction{ .Prev = value }),
                    .Scanning => {},
                }
                if (switch_state) {
                    state = State{ .Next = 1 };
                }
            },
            '<' => {
                var switch_state = true;
                switch (state) {
                    .Incr => |value| try instructions.append(Instruction{ .Incr = value }),
                    .Decr => |value| try instructions.append(Instruction{ .Decr = value }),
                    .Next => |value| try instructions.append(Instruction{ .Next = value }),
                    .Prev => |*value| {
                        value.* += 1;
                        switch_state = false;
                    },
                    .Scanning => {},
                }
                if (switch_state) {
                    state = State{ .Prev = 1 };
                }
            },
            '.' => {
                switch (state) {
                    .Incr => |value| try instructions.append(Instruction{ .Incr = value }),
                    .Decr => |value| try instructions.append(Instruction{ .Decr = value }),
                    .Next => |value| try instructions.append(Instruction{ .Next = value }),
                    .Prev => |value| try instructions.append(Instruction{ .Prev = value }),
                    .Scanning => {},
                }
                state = State{ .Scanning = {} };
                try instructions.append(Instruction{ .Output = {} });
            },
            ',' => {
                switch (state) {
                    .Incr => |value| try instructions.append(Instruction{ .Incr = value }),
                    .Decr => |value| try instructions.append(Instruction{ .Decr = value }),
                    .Next => |value| try instructions.append(Instruction{ .Next = value }),
                    .Prev => |value| try instructions.append(Instruction{ .Prev = value }),
                    .Scanning => {},
                }
                state = State{ .Scanning = {} };
                try instructions.append(Instruction{ .Input = {} });
            },
            '[' => {
                switch (state) {
                    .Incr => |value| try instructions.append(Instruction{ .Incr = value }),
                    .Decr => |value| try instructions.append(Instruction{ .Decr = value }),
                    .Next => |value| try instructions.append(Instruction{ .Next = value }),
                    .Prev => |value| try instructions.append(Instruction{ .Prev = value }),
                    .Scanning => {},
                }
                state = State{ .Scanning = {} };
                try loop_stack.append(instructions.len);
                try instructions.append(Instruction{ .LoopStart = null });
            },
            ']' => {
                switch (state) {
                    .Incr => |value| try instructions.append(Instruction{ .Incr = value }),
                    .Decr => |value| try instructions.append(Instruction{ .Decr = value }),
                    .Next => |value| try instructions.append(Instruction{ .Next = value }),
                    .Prev => |value| try instructions.append(Instruction{ .Prev = value }),
                    .Scanning => {},
                }
                state = State{ .Scanning = {} };
                const loop_start_idx = loop_stack.popOrNull() orelse return error.MismatchedLoop;
                instructions.set(loop_start_idx, Instruction{ .LoopStart = instructions.len });
                try instructions.append(Instruction{ .LoopEnd = loop_start_idx });
            },
            else => {},
        }
    errdefer instructions.deinit();
    while (iter.peek()) |_| {
        try parseIter(&iter, &instructions);
    }
    return instructions.toOwnedSlice();
}

    for (instructions.toSliceConst()) |instr| switch (instr) {
        .LoopStart => |maybe| if (maybe == null) return error.MismatchedLoop,
        else => {},
fn parseIter(
    iter: *InstructionIterator,
    instructions: *std.ArrayList(Instruction),
) !void {
    if (iter.peek()) |id| switch (id) {
        .LoopStart => try parseLoop(iter, instructions),
        .LoopEnd => return error.LoopFormat,
        else => try parseCumulative(iter, id, instructions),
    };
}

fn parseCumulative(
    iter: *InstructionIterator,
    target: Instruction.Id,
    instructions: *std.ArrayList(Instruction),
) !void {
    var amount: usize = 0;
    while (iter.peek()) |id| {
        if (id != target) break;
        _ = iter.next();
        amount += 1;
    }
    try instructions.append(Instruction{ .id = target, .data = amount });
}

    return instructions;
fn parseLoop(
    iter: *InstructionIterator,
    instructions: *std.ArrayList(Instruction),
) error{LoopFormat, OutOfMemory}!void {
    const start = instructions.len;
    std.debug.assert(iter.peek().? == .LoopStart);
    try instructions.append(Instruction{ .id = .LoopStart, .data = undefined });
    _ = iter.next();
    while (iter.peek()) |id| {
        if (id == .LoopEnd) break;
        try parseIter(iter, instructions);
    }
    const end = instructions.len;
    instructions.toSlice()[start].data = end;
    try instructions.append(Instruction{ .id = iter.next() orelse return error.LoopFormat, .data = start });
}

test "parse basic" {
    const program = "++++++++++[>+++++++++>+<<-]>+++++++.>.";
    const expected = [_]Instruction{
        Instruction{ .Incr = 10 },
        Instruction{ .LoopStart = 8 },
        Instruction{ .Next = 1 },
        Instruction{ .Incr = 9 },
        Instruction{ .Next = 1 },
        Instruction{ .Incr = 1 },
        Instruction{ .Prev = 2 },
        Instruction{ .Decr = 1 },
        Instruction{ .LoopEnd = 1 },
        Instruction{ .Next = 1 },
        Instruction{ .Incr = 7 },
        Instruction{ .Output = {} },
        Instruction{ .Next = 1 },
        Instruction{ .Output = {} },
    };
    try testParse("+++--", &[_]Instruction{
        Instruction{ .id = .Incr, .data = 3 },
        Instruction{ .id = .Decr, .data = 2 },
    });
    try testParse("+this+is+some-junk-", &[_]Instruction{
        Instruction{ .id = .Incr, .data = 3 },
        Instruction{ .id = .Decr, .data = 2 },
    });
}

test "parse loop" {
    try testParse("[]", &[_]Instruction{
        Instruction{ .id = .LoopStart, .data = 1 },
        Instruction{ .id = .LoopEnd, .data = 0 },
    });

    try testParse("+[]", &[_]Instruction{
        Instruction{ .id = .Incr, .data = 1 },
        Instruction{ .id = .LoopStart, .data = 2 },
        Instruction{ .id = .LoopEnd, .data = 1 },
    });

    const parsed = (try parse(std.heap.direct_allocator, program)).toOwnedSlice();
    std.testing.expectEqual(parsed[0].Incr, expected[0].Incr);
    std.testing.expectEqual(parsed[1].LoopStart, expected[1].LoopStart);
    std.testing.expectEqual(parsed[2].Next, expected[2].Next);
    std.testing.expectEqual(parsed[3].Incr, expected[3].Incr);
    std.testing.expectEqual(parsed[4].Next, expected[4].Next);
    std.testing.expectEqual(parsed[5].Incr, expected[5].Incr);
    std.testing.expectEqual(parsed[6].Prev, expected[6].Prev);
    std.testing.expectEqual(parsed[7].Decr, expected[7].Decr);
    std.testing.expectEqual(parsed[8].LoopEnd, expected[8].LoopEnd);
    std.testing.expectEqual(parsed[9].Next, expected[9].Next);
    std.testing.expectEqual(parsed[10].Incr, expected[10].Incr);
    std.testing.expectEqual(parsed[11].Output, expected[11].Output);
    std.testing.expectEqual(parsed[12].Next, expected[12].Next);
    std.testing.expectEqual(parsed[13].Output, expected[13].Output);
    try testParse("[>]", &[_]Instruction{
        Instruction{ .id = .LoopStart, .data = 2 },
        Instruction{ .id = .Next, .data = 1 },
        Instruction{ .id = .LoopEnd, .data = 0 },
    });

    try testParse("[[[[]]]]", &[_]Instruction{
        Instruction{ .id = .LoopStart, .data = 7 },
        Instruction{ .id = .LoopStart, .data = 6 },
        Instruction{ .id = .LoopStart, .data = 5 },
        Instruction{ .id = .LoopStart, .data = 4 },
        Instruction{ .id = .LoopEnd, .data = 3 },
        Instruction{ .id = .LoopEnd, .data = 2 },
        Instruction{ .id = .LoopEnd, .data = 1 },
        Instruction{ .id = .LoopEnd, .data = 0 },
    });
    
    try testParse("++[-]", &[_]Instruction{
        Instruction{ .id = .Incr, .data = 2 },
        Instruction{ .id = .LoopStart, .data = 3 },
        Instruction{ .id = .Decr, .data = 1 },
        Instruction{ .id = .LoopEnd, .data = 1 },
    });

    try testParse("+[[-]]+", &[_]Instruction{
        Instruction{ .id = .Incr, .data = 1 },
        Instruction{ .id = .LoopStart, .data = 5 },
        Instruction{ .id = .LoopStart, .data = 4 },
        Instruction{ .id = .Decr, .data = 1 },
        Instruction{ .id = .LoopEnd, .data = 2 },
        Instruction{ .id = .LoopEnd, .data = 1 },
        Instruction{ .id = .Incr, .data = 1 },
    });
}

fn testParse(program: []const u8, expected: []const Instruction) !void {
    const parsed = try parse(std.heap.page_allocator, program);
    if (expected.len != parsed.len) @panic("not the same length");
    for (expected) |item, i| {
        std.testing.expectEqual(item, parsed[i]);
    }
}


M src/vm.zig => src/vm.zig +20 -18
@@ 11,7 11,7 @@ pub fn VM(comptime ReadError: type, comptime WriteError: type) type {
        const OutStream = std.io.OutStream(WriteError);

        allocator: *Allocator,
        instructions: std.ArrayList(Instruction),
        instructions: []const Instruction,
        current_instr: usize,
        memory: []u8,
        current_cell: usize,


@@ 33,7 33,7 @@ pub fn VM(comptime ReadError: type, comptime WriteError: type) type {
        }

        pub fn deinit(vm: *Self) void {
            vm.instructions.deinit();
            vm.allocator.free(vm.instructions);
            vm.allocator.free(vm.memory);
        }



@@ 49,30 49,32 @@ pub fn VM(comptime ReadError: type, comptime WriteError: type) type {
                vm.memory = try vm.allocator.realloc(vm.memory, std.math.max(vm.memory.len * 2, vm.current_cell));
            }

            const instr = vm.instructions.toSliceConst()[vm.current_instr];
            const instr = vm.instructions[vm.current_instr];
            const cell = &vm.memory[vm.current_cell];
            switch (instr) {
                .Incr => |by| {
                    cell.* +%= by;
                },
                .Decr => |by| cell.* -%= by,
                .Next => |cells| vm.current_cell += cells,
                .Prev => |cells| {
                    if (vm.current_cell < cells) return error.InvalidPointer;
                    vm.current_cell -= cells;
            switch (instr.id) {
                .Incr => cell.* +%= @intCast(u8, instr.data),
                .Decr => cell.* -%= @intCast(u8, instr.data),
                .Next => vm.current_cell += instr.data,
                .Prev => {
                    if (vm.current_cell < instr.data) return error.InvalidPointer;
                    vm.current_cell -= instr.data;
                },
                .Output => {
                    try vm.out_stream.print("{}", .{&[_]u8{cell.*}});
                    var output = try vm.allocator.alloc(u8, instr.data);
                    defer vm.allocator.free(output);
                    for (output) |*item| item.* = cell.*;
                    try vm.out_stream.print("{}", .{output});
                },
                .Input => {
                    // we can ignore the sequential input, because it would be
                    // reading into the same cell.
                    cell.* = try vm.in_stream.readByte();
                },
                .LoopStart => |end| if (cell.* == 0) {
                    // parse returns only vaild code, so .? is safe
                    vm.current_instr = end.?;
                .LoopStart => if (cell.* == 0) {
                    vm.current_instr = instr.data;
                },
                .LoopEnd => |start| if (cell.* != 0) {
                    vm.current_instr = start;
                .LoopEnd => if (cell.* != 0) {
                    vm.current_instr = instr.data;
                },
            }