/..

#CONTENT

#TOP

shell.zig
RS
const std = @import("std");
const term = @import("term.zig");
const arch = @import("arch.zig");
const pio = @import("pio.zig");
const fs = @import("fs.zig");
const disk = @import("disk.zig");
const ascii = std.ascii;
const mem = std.mem;
const fmt = std.fmt;
const math = std.math;
const ArrayList = std.ArrayList;

pub const Error = error{
    MissingStartBlock,
    MissingEndBlock,
    UnexpectedCharacter,
    UndefinedVariable,
    InvalidChoice,
};

const Variables = std.StringHashMap(isize);
pub const Environment = struct {
    variables: Variables,
    chain: ?*Environment,
    depth: usize,

    const Self = @This();

    pub fn get(self: *Self, name: []const u8) ?isize {
        if (self.variables.get(name)) |v| {
            return v;
        }
        if (self.chain) |next| {
            return next.get(name);
        }
        return null;
    }

    fn put(self: *Self, name: []const u8, v: isize) !void {
        var env: *Environment = self;
        while (env.depth != 0) : (env = env.chain.?) {
            if (env.variables.getPtr(name)) |ptr| {
                ptr.* = v;
                return;
            }
        }
        try env.variables.put(name, v);
    }

    fn nest(self: *Self, allocator: anytype) Self {
        return .{
            .variables = Variables.init(allocator),
            .chain = self,
            .depth = self.depth + 1,
        };
    }

    fn deinit(self: *Self, allocator: anytype) void {
        var it = self.variables.keyIterator();
        while (it.next()) |key| {
            allocator.free(key.*);
        }
        self.variables.deinit();
    }
};

pub noinline fn start(allocator: anytype) void {
    var env = Environment{
        .variables = Variables.init(allocator),
        .chain = undefined,
        .depth = 0,
    };
    env.put("flag", 0xfaaf) catch |err| {
        term.printf("error: {s}\r\n", .{@errorName(err)});
        @panic("fatal error");
    };

    var stdin = Stream{
        .term = {},
    };

    while (true) {
        loop(stdin, &env, allocator) catch |err| {
            term.printf("error: {s}\n", .{@errorName(err)});
            term.flush();
        };
    }
}

var tmp: *fs.File = undefined;

fn loop(stream: anytype, env: *Environment, allocator: anytype) !void {
    term.printf(
        \\
        \\0. print flag variable
        \\1. input program
        \\2. echo on
        \\3. echo off
        \\4. exec file
        \\5. open file
        \\6. seek file
        \\7. make file
        \\8. write file
        \\
    , .{});
    stream.ignore(ascii.isWhitespace);
    const choice = try stream.readnum(allocator);
    term.printf("you chose option #{}\r\n", .{choice});
    switch (choice) {
        0 => term.printf("flag: {?}\r\n", .{env.get("flag")}),
        1 => {
            term.printf("gimme code now\r\n", .{});
            stream.ignore(ascii.isWhitespace);
            try stream.expect(char('{'));
            try run(stream, allocator, env);
        },
        2 => {
            term.echo(true);
            term.printf("echo on\r\n", .{});
        },
        3 => {
            term.echo(false);
            term.printf("echo off\r\n", .{});
        },
        4 => {
            var input = Stream{
                .file = tmp,
            };
            try input.expect(char('{'));
            try run(input, allocator, env);
        },
        5 => {
            term.printf("name: ", .{});
            var name = try stream.read(allocator, not(newline));
            defer name.deinit();
            errdefer name.deinit();
            try stream.expect(newline);
            tmp = try fs.open(name.items);
        },
        6 => {
            term.printf("offset: ", .{});
            const offset = try stream.readnum(allocator);
            try tmp.seek(offset);
        },
        7 => {
            term.printf("name: ", .{});
            var name = try stream.read(allocator, not(newline));
            defer name.deinit();
            errdefer name.deinit();
            try stream.expect(newline);
            term.printf("size: ", .{});
            var size = try stream.readnum(allocator);
            tmp = try fs.new(name.items, size);
        },
        8 => {
            term.printf("size: ", .{});
            var size = try stream.readnum(allocator);
            var data = try allocator.alloc(u8, size);
            defer allocator.free(data);
            errdefer allocator.free(data);
            term.printf("data: ", .{});
            for (0..size) |i| {
                data[i] = stream.getchar();
            }
            try stream.expect(newline);
            try tmp.write(data);
        },
        else => return Error.InvalidChoice,
    }
}

fn identifier(ch: u8) bool {
    return !ascii.isWhitespace(ch) and (ascii.isAlphanumeric(ch) or oneof(ch, "_"));
}

fn oneof(needle: u8, haystack: []const u8) bool {
    if (mem.indexOfScalar(u8, haystack, needle)) |_| {
        return true;
    } else {
        return false;
    }
}

fn newline(ch: u8) bool {
    return ch == '\n' or ch == '\r';
}

fn not(comptime rule: Rule) Rule {
    const Inverse = struct {
        fn fun(ch: u8) bool {
            return !rule(ch);
        }
    };
    return Inverse.fun;
}

fn char(comptime ch: u8) Rule {
    const Char = struct {
        fn fun(a: u8) bool {
            return ch == a;
        }
    };
    return Char.fun;
}

pub const Rule = *const fn (ch: u8) bool;

pub const Stream = union(enum) {
    term: void,
    file: *fs.File,

    const Self = @This();

    pub fn readnum(self: Self, allocator: mem.Allocator) !usize {
        var buf = try self.read(allocator, not(newline));
        defer buf.deinit();
        errdefer buf.deinit();
        const res = try fmt.parseInt(usize, buf.items, 10);
        try self.expect(newline);
        return res;
    }

    pub fn getchar(self: Self) u8 {
        return switch (self) {
            .term => term.getchar(),
            .file => |f| f.getchar(),
        };
    }

    pub fn putback(self: Self, ch: u8) void {
        switch (self) {
            .term => term.putback(ch),
            .file => |f| f.putback(ch),
        }
    }

    pub fn peek(self: Self) u8 {
        return switch (self) {
            .term => term.peek(),
            .file => |f| f.peek(),
        };
    }

    pub fn expect(self: Self, rule: Rule) Error!void {
        const ch = self.getchar();
        if (!rule(ch)) {
            term.printf("unexpected character, found `{c}`\r\n", .{ch});
            return Error.UnexpectedCharacter;
        }
    }

    const String = ArrayList(u8);

    pub fn read(self: Self, allocator: mem.Allocator, rule: Rule) !String {
        var string = String.init(allocator);
        var ch = self.getchar();
        while (rule(ch)) : (ch = self.getchar()) {
            try string.append(ch);
        }
        self.putback(ch);
        return string;
    }

    pub fn ignore(self: Self, rule: Rule) void {
        var ch = self.getchar();
        while (rule(ch)) : (ch = self.getchar()) {}
        self.putback(ch);
    }

    pub fn optional(self: Self, haystack: []const u8) ?u8 {
        const needle = self.peek();
        if (oneof(needle, haystack)) {
            _ = self.getchar();
            return needle;
        } else {
            return null;
        }
    }

    pub fn value(self: Self, allocator: anytype, env: *Environment) !isize {
        const next = self.peek();
        if (ascii.isDigit(next) or next == '-') {
            var negative: isize = 1;
            if (next == '-') {
                negative = -1;
                _ = self.getchar();
            }
            const number = try self.read(allocator, ascii.isDigit);
            defer number.deinit();
            return negative * try fmt.parseInt(isize, number.items, 10);
        } else {
            const ident = try self.read(allocator, identifier);
            defer ident.deinit();
            if (env.get(ident.items)) |v| {
                return v;
            }
            term.printf("could not find variable `{s}`\r\n", .{ident.items});
            return Error.UndefinedVariable;
        }
    }
};

fn run(stream: Stream, allocator: mem.Allocator, env: *Environment) !void {
    var nest = env.nest(allocator);
    defer nest.deinit(allocator);
    errdefer nest.deinit(allocator);

    while (true) {
        const next = stream.getchar();
        switch (next) {
            '\r', '\n', ' ', '\t' => continue,
            '}' => break,
            '{' => {
                try run(stream, allocator, &nest);
            },
            else => {
                stream.putback(next);
                const ident = try stream.read(allocator, identifier);
                stream.ignore(ascii.isWhitespace);
                try stream.expect(char('='));
                stream.ignore(ascii.isWhitespace);
                var num = try stream.value(allocator, &nest);
                stream.ignore(ascii.isWhitespace);
                if (stream.optional("-+/*")) |op| {
                    stream.ignore(ascii.isWhitespace);
                    var rhs = try stream.value(allocator, &nest);
                    switch (op) {
                        '-' => num -= rhs,
                        '+' => num += rhs,
                        '/' => {
                            num = try math.divTrunc(isize, num, rhs);
                        },
                        '*' => num *= rhs,
                        else => unreachable,
                    }
                }
                try stream.expect(char(';'));
                try nest.put(ident.items, num);
            },
        }
    }
}