~ntgg/zosh

34b566469eacf9f2436bb7b336f62e207730ae5b — Noah Graff 4 years ago 3ff7564
added basic interactive input.

currently it prints out usage, and gets input from standard in. It's
not all that useful at the moment, but it is one step closer to running
a command.
2 files changed, 94 insertions(+), 2 deletions(-)

M src/main.zig
A src/util.zig
M src/main.zig => src/main.zig +88 -2
@@ 1,6 1,92 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const util = @import("util.zig");
const State = @import("state.zig").State;
const TextBuffer = @import("text_buffer.zig").TextBuffer;

pub fn main() void {
    std.debug.warn("All your base are belong to us.\n");
const usage_fmt =
    \\Usage:
    \\  {0} [option]...
    \\  {0} [option]... [script_file [argument...]]
    \\
    \\Options:
    \\  -c command_string [command_name [argument...]]
    \\    Read commands from command_string.
    \\
    \\  -s [argument...]
    \\    Read commands from the standard input.
    \\
    \\  -i
    \\    Specify that the shell is interactive. If the user or group id of the
    \\    calling process is not effective this is an error.
    \\
    \\  [-abCefmnuvx] [-o option] [+abCefmnuvx] [+o option]
    \\    Part of the 'set' builtin. to see more info run {0} -c 'help set'.
    \\
;

pub fn main() !u8 {
    const allocator = std.heap.direct_allocator;

    var args_iter = std.process.ArgIteratorPosix.init();
    const exe_path = args_iter.next() orelse {
        // args_iter.next() here should always return the exe path, as far as I know.
        util.unknownError();
    };

    @setEvalBranchQuota(2000); // usage_fmt is too long for the default limit
    std.debug.warn(usage_fmt, exe_path);

    var state = State.init(allocator);
    defer state.deinit();

    var text_buffer = TextBuffer.initEmpty(allocator);
    defer text_buffer.deinit();

    // TODO: move this to a better place. not all shells need stdin/out.
    var stdout = std.io.getStdOut() catch util.unknownError();
    var stdout_stream = stdout.outStream();
    var stdin = std.io.getStdIn() catch util.unknownError();
    var stdin_stream = stdin.inStream();

    while (state.exit == null) {
        if (state.is_interactive) {
            const prompt = state.getPs1();
            try stdout_stream.stream.write(prompt);

            var line = (try readLineAlloc(allocator, &stdin_stream.stream)) orelse {
                state.exit = state.last_status;
                continue;
            };

            try text_buffer.append(line);
            allocator.free(line);
        }
    }

    return state.exit.?;
}

/// Read from stream up to and including the first '\n', or the end of the
/// stream. Allocates the required space. null if the stream is already empty.
fn readLineAlloc(allocator: *Allocator, stream: var) !?[]u8 {
    var line_buffer = try std.Buffer.initSize(allocator, 0);
    errdefer line_buffer.deinit();

    while (true) {
        const byte = stream.readByte() catch |err| switch (err) {
            error.EndOfStream => if (line_buffer.len() == 0) {
                return null;
            } else {
                break;
            },
            else => |e| return e,
        };

        try line_buffer.appendByte(byte);
        if (byte == '\n') break;
    }

    return line_buffer.toOwnedSlice();
}


A src/util.zig => src/util.zig +6 -0
@@ 0,0 1,6 @@
const std = @import("std");

pub fn unknownError() noreturn {
    std.debug.warn("An unknown error occured! please submit a ticket on https://todo.sr.ht/~ntgg/zosh\n");
    std.process.abort();
}