const std = @import("std"); 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 paths = @import("paths.zig"); const convertTo83 = paths.convertTo83; const STDOUT_BUFFER_SIZE: usize = 1024; const STDERR_BUFFER_SIZE: usize = 1024; fn formatPath(allocator: Allocator, path: []const u8) ![]const u8 { // Normalize separators and case first var normalized = ArrayList(u8){}; defer normalized.deinit(allocator); for (path) |ch| { if (ch == '/') { try normalized.append(allocator, '\\'); } else { try normalized.append(allocator, std.ascii.toUpper(ch)); } } // Extract drive letter if present (e.g., "C:") var idx: usize = 0; var has_drive = false; var drive_letter: u8 = 'C'; if (normalized.items.len >= 2 and normalized.items[1] == ':') { has_drive = true; drive_letter = normalized.items[0]; idx = 2; } // Remember if the path is absolute after the drive (leading backslash) var starts_with_backslash = false; if (idx < normalized.items.len and normalized.items[idx] == '\\') { starts_with_backslash = true; // Skip the first leading backslash to avoid creating an empty component idx += 1; } // Build result with components converted to 8.3 var result = ArrayList(u8){}; defer result.deinit(allocator); // If absolute, start with a backslash (after potential drive prefix) if (starts_with_backslash) { try result.append(allocator, '\\'); } var it = std.mem.splitScalar(u8, normalized.items[idx..], '\\'); var first_component = true; while (it.next()) |component| { if (component.len == 0) continue; if (!first_component and result.items.len > 0 and result.items[result.items.len - 1] != '\\') { try result.append(allocator, '\\'); } first_component = false; const short_name = try convertTo83(allocator, component); defer allocator.free(short_name.name); defer allocator.free(short_name.ext); try result.appendSlice(allocator, short_name.name); if (short_name.ext.len > 0) { try result.append(allocator, '.'); try result.appendSlice(allocator, short_name.ext); } } // Prepend drive if present or add default C: var final_buf = ArrayList(u8){}; defer final_buf.deinit(allocator); if (has_drive) { try final_buf.append(allocator, drive_letter); try final_buf.append(allocator, ':'); if (result.items.len > 0 and result.items[0] != '\\') { try final_buf.append(allocator, '\\'); } try final_buf.appendSlice(allocator, result.items); } else { // No drive: default to C: try final_buf.appendSlice(allocator, "C:"); if (result.items.len > 0 and result.items[0] != '\\') { try final_buf.append(allocator, '\\'); } try final_buf.appendSlice(allocator, result.items); } return allocator.dupe(u8, final_buf.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(buf: []u8, prompt_text: []const u8) !?[]const u8 { const stdin = std.fs.File.stdin(); var stdout_buffer: [1024]u8 = undefined; var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); const stdout = &stdout_writer.interface; try stdout.writeAll(prompt_text); try stdout.flush(); var reader = stdin.readerStreaming(buf); const input = std.io.Reader.takeDelimiterExclusive(&reader.interface, '\n') catch |err| { switch (err) { error.EndOfStream => return null, else => return err, } }; if (input.len > 0 and input[input.len - 1] == '\r') { return input[0 .. input.len - 1]; } return input; } pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); try printWelcomeMessage(); 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); var buf: [4096]u8 = undefined; if (try readLine(&buf, final_prompt)) |line| { const command_result = parseAndExecute(line, allocator) catch |err| { switch (err) { error.ExpectedWord, error.UnexpectedToken => { var stdout_buffer: [1024]u8 = undefined; var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); const stdout = &stdout_writer.interface; try stdout.writeAll("Bad command or file name\n"); try stdout.flush(); continue; }, else => return err, } }; switch (command_result) { .ExitShell => break, .Code => |_| {}, } } else { break; // EOF } } } fn printWelcomeMessage() !void { const stdout_file = std.fs.File.stdout(); if (!stdout_file.isTty()) return; var stdout_buffer: [1024]u8 = undefined; var stdout_writer = stdout_file.writer(&stdout_buffer); const stdout = &stdout_writer.interface; try stdout.writeAll( \\Starting MB-DOS... \\ \\HIMEM is testing extended memory...done. \\ \\MULKROSOFT Expanded Memory Manager 386 Version 4.48 \\EMM386 active. \\ \\64K High Memory Area available. \\ \\ ); try stdout.flush(); }