summaryrefslogtreecommitdiff
path: root/src/main.zig
blob: 675b2590997b2247be03ed9f77ad2fb9ad11150a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
const std = @import("std");
const print = std.debug.print;
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const Thread = std.Thread;
const Mutex = std.Thread.Mutex;

const parser = @import("parser.zig");
const Command = parser.Command;

const eval = @import("eval.zig");
const CommandStatus = eval.CommandStatus;

const STDOUT_BUFFER_SIZE: usize = 1024;
const STDERR_BUFFER_SIZE: usize = 1024;

fn formatPath(allocator: Allocator, path: []const u8) ![]const u8 {
    var result = ArrayList(u8).init(allocator);
    defer result.deinit();

    // Simple DOS-style path formatting
    // Convert to uppercase and replace / with \
    for (path) |ch| {
        if (ch == '/') {
            try result.append('\\');
        } else {
            try result.append(std.ascii.toUpper(ch));
        }
    }

    // Add C: prefix if no drive letter
    if (result.items.len == 0 or result.items[1] != ':') {
        var prefixed = ArrayList(u8).init(allocator);
        defer prefixed.deinit();
        try prefixed.appendSlice("C:");
        try prefixed.appendSlice(result.items);
        return allocator.dupe(u8, prefixed.items);
    }

    return allocator.dupe(u8, result.items);
}

fn parseAndExecute(input: []const u8, allocator: Allocator) !CommandStatus {
    var command = parser.parse(input, allocator) catch |err| {
        switch (err) {
            error.ExpectedWord, error.UnexpectedToken => {
                return CommandStatus{ .Code = 1 };
            },
            else => return err,
        }
    };
    defer command.deinit(allocator);

    return try eval.executeCommand(command, allocator);
}

fn readLine(allocator: Allocator, prompt_text: []const u8) !?[]const u8 {
    const stdin = std.io.getStdIn().reader();
    print("{s}", .{prompt_text});

    if (try stdin.readUntilDelimiterOrEofAlloc(allocator, '\n', 4096)) |input| {
        // Remove trailing \r on Windows
        if (input.len > 0 and input[input.len - 1] == '\r') {
            return input[0 .. input.len - 1];
        }
        return input;
    }
    return null;
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    const prompt_spec = "$p$g ";

    while (true) {
        const cwd = std.fs.cwd().realpathAlloc(allocator, ".") catch |err| switch (err) {
            error.FileNotFound => "C:\\",
            else => return err,
        };
        defer allocator.free(cwd);

        const full_cwd = try formatPath(allocator, cwd);
        defer allocator.free(full_cwd);

        const interpolated_prompt = try std.mem.replaceOwned(u8, allocator, prompt_spec, "$p", full_cwd);
        defer allocator.free(interpolated_prompt);

        const final_prompt = try std.mem.replaceOwned(u8, allocator, interpolated_prompt, "$g", ">");
        defer allocator.free(final_prompt);

        if (try readLine(allocator, final_prompt)) |line| {
            defer allocator.free(line);

            const command_result = parseAndExecute(line, allocator) catch |err| {
                switch (err) {
                    error.ExpectedWord, error.UnexpectedToken => {
                        print("Bad command or file name\n", .{});
                        continue;
                    },
                    else => return err,
                }
            };

            switch (command_result) {
                .ExitShell => break,
                .Code => |_| {},
            }
        } else {
            break; // EOF
        }
    }
}