summaryrefslogtreecommitdiff
path: root/src/main.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.zig')
-rw-r--r--src/main.zig684
1 files changed, 4 insertions, 680 deletions
diff --git a/src/main.zig b/src/main.zig
index 3faa829..675b259 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -7,19 +7,13 @@ const Mutex = std.Thread.Mutex;
const parser = @import("parser.zig");
const Command = parser.Command;
-const BuiltinCommand = parser.BuiltinCommand;
-const FileSpec = parser.FileSpec;
-const RedirectType = parser.RedirectType;
-const Redirect = parser.Redirect;
+
+const eval = @import("eval.zig");
+const CommandStatus = eval.CommandStatus;
const STDOUT_BUFFER_SIZE: usize = 1024;
const STDERR_BUFFER_SIZE: usize = 1024;
-const CommandStatus = union(enum) {
- Code: u16,
- ExitShell,
-};
-
fn formatPath(allocator: Allocator, path: []const u8) ![]const u8 {
var result = ArrayList(u8).init(allocator);
defer result.deinit();
@@ -46,10 +40,6 @@ fn formatPath(allocator: Allocator, path: []const u8) ![]const u8 {
return allocator.dupe(u8, result.items);
}
-fn isLeapYear(year: u32) bool {
- return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0);
-}
-
fn parseAndExecute(input: []const u8, allocator: Allocator) !CommandStatus {
var command = parser.parse(input, allocator) catch |err| {
switch (err) {
@@ -61,673 +51,7 @@ fn parseAndExecute(input: []const u8, allocator: Allocator) !CommandStatus {
};
defer command.deinit(allocator);
- return try executeCommand(command, allocator);
-}
-
-const OutputCapture = struct {
- buffer: ArrayList(u8),
-
- pub fn init(allocator: Allocator) OutputCapture {
- return OutputCapture{
- .buffer = ArrayList(u8).init(allocator),
- };
- }
-
- pub fn deinit(self: *OutputCapture) void {
- self.buffer.deinit();
- }
-
- pub fn write(self: *OutputCapture, data: []const u8) !void {
- try self.buffer.appendSlice(data);
- }
-
- pub fn getContents(self: *const OutputCapture) []const u8 {
- return self.buffer.items;
- }
-};
-
-const InputSource = struct {
- data: []const u8,
- position: usize,
-
- pub fn init(data: []const u8) InputSource {
- return InputSource{
- .data = data,
- .position = 0,
- };
- }
-
- pub fn readLine(self: *InputSource, allocator: Allocator) !?[]const u8 {
- if (self.position >= self.data.len) {
- return null; // EOF
- }
-
- var line_end = self.position;
- while (line_end < self.data.len and self.data[line_end] != '\n') {
- line_end += 1;
- }
-
- const line = self.data[self.position..line_end];
- self.position = if (line_end < self.data.len) line_end + 1 else self.data.len;
-
- // Remove trailing \r if present (DOS line endings)
- if (line.len > 0 and line[line.len - 1] == '\r') {
- return try allocator.dupe(u8, line[0 .. line.len - 1]);
- } else {
- return try allocator.dupe(u8, line);
- }
- }
-};
-
-fn executeCommandWithOutput(command: Command, allocator: Allocator, output_capture: ?*OutputCapture, input_source: ?*InputSource) !CommandStatus {
- switch (command) {
- .Empty => return CommandStatus{ .Code = 0 },
-
- .Builtin => |builtin_cmd| {
- switch (builtin_cmd) {
- .EchoText => |echo| {
- const output = try std.fmt.allocPrint(allocator, "{s}\n", .{echo.message});
- defer allocator.free(output);
- if (output_capture) |capture| {
- try capture.write(output);
- } else {
- print("{s}", .{output});
- }
- return CommandStatus{ .Code = 0 };
- },
- .Cls => {
- if (output_capture == null) {
- // Clear screen - only works when not redirected
- print("\x1B[2J\x1B[H", .{});
- }
- return CommandStatus{ .Code = 0 };
- },
- .Exit => {
- return CommandStatus.ExitShell;
- },
- .EchoPlain => {
- const output = "ECHO is on\n";
- if (output_capture) |capture| {
- try capture.write(output);
- } else {
- print("{s}", .{output});
- }
- return CommandStatus{ .Code = 0 };
- },
- .EchoOn => {
- const output = "ECHO is on\n";
- if (output_capture) |capture| {
- try capture.write(output);
- } else {
- print("{s}", .{output});
- }
- return CommandStatus{ .Code = 0 };
- },
- .EchoOff => {
- return CommandStatus{ .Code = 0 };
- },
- .Ver => {
- const output = "MS-DOS Version 6.22 (Zig Implementation)\n";
- if (output_capture) |capture| {
- try capture.write(output);
- } else {
- print("{s}", .{output});
- }
- return CommandStatus{ .Code = 0 };
- },
- .Date => {
- const timestamp = std.time.timestamp();
- const epoch_seconds = @as(u64, @intCast(timestamp));
- const epoch_day = @divFloor(epoch_seconds, std.time.s_per_day);
-
- // Calculate days since Unix epoch (1970-01-01)
- // Unix epoch is 719163 days since year 1 AD
- const days_since_year_1 = epoch_day + 719163;
-
- // Simple algorithm to convert days to year/month/day
- var year: u32 = 1;
- var remaining_days = days_since_year_1;
-
- // Find the year
- while (true) {
- const days_in_year: u64 = if (isLeapYear(year)) 366 else 365;
- if (remaining_days < days_in_year) break;
- remaining_days -= days_in_year;
- year += 1;
- }
-
- // Days in each month (non-leap year)
- const days_in_month = [_]u32{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
-
- var month: u32 = 1;
- for (days_in_month, 1..) |days, m| {
- var month_days = days;
- // Adjust February for leap years
- if (m == 2 and isLeapYear(year)) {
- month_days = 29;
- }
-
- if (remaining_days < month_days) {
- month = @intCast(m);
- break;
- }
- remaining_days -= month_days;
- }
-
- const day = remaining_days + 1; // Days are 1-indexed
-
- const output = try std.fmt.allocPrint(allocator, "Current date is {d:0>2}/{d:0>2}/{d}\n", .{ month, day, year });
- defer allocator.free(output);
- if (output_capture) |capture| {
- try capture.write(output);
- } else {
- print("{s}", .{output});
- }
- return CommandStatus{ .Code = 0 };
- },
- .Time => {
- const timestamp = std.time.timestamp();
- const epoch_seconds = @as(u64, @intCast(timestamp));
- const day_seconds = epoch_seconds % std.time.s_per_day;
- const hours = day_seconds / std.time.s_per_hour;
- const minutes = (day_seconds % std.time.s_per_hour) / std.time.s_per_min;
- const seconds = day_seconds % std.time.s_per_min;
-
- const output = try std.fmt.allocPrint(allocator, "Current time is {d:0>2}:{d:0>2}:{d:0>2}\n", .{ hours, minutes, seconds });
- defer allocator.free(output);
- if (output_capture) |capture| {
- try capture.write(output);
- } else {
- print("{s}", .{output});
- }
- return CommandStatus{ .Code = 0 };
- },
- .Dir => |dir| {
- var output_buffer = ArrayList(u8).init(allocator);
- defer output_buffer.deinit();
-
- try output_buffer.writer().print("Directory of {s}\n\n", .{dir.path});
-
- var dir_iterator = std.fs.cwd().openDir(dir.path, .{ .iterate = true }) catch {
- const error_msg = "File not found\n";
- if (output_capture) |capture| {
- try capture.write(error_msg);
- } else {
- print("{s}", .{error_msg});
- }
- return CommandStatus{ .Code = 1 };
- };
- defer dir_iterator.close();
-
- var iterator = dir_iterator.iterate();
- var file_count: u32 = 0;
- var dir_count: u32 = 0;
-
- while (try iterator.next()) |entry| {
- switch (entry.kind) {
- .directory => {
- try output_buffer.writer().print("<DIR> {s}\n", .{entry.name});
- dir_count += 1;
- },
- .file => {
- const stat = dir_iterator.statFile(entry.name) catch continue;
- try output_buffer.writer().print("{d:>14} {s}\n", .{ stat.size, entry.name });
- file_count += 1;
- },
- else => {},
- }
- }
-
- try output_buffer.writer().print("\n{d} File(s)\n", .{file_count});
- try output_buffer.writer().print("{d} Dir(s)\n", .{dir_count});
-
- if (output_capture) |capture| {
- try capture.write(output_buffer.items);
- } else {
- print("{s}", .{output_buffer.items});
- }
- return CommandStatus{ .Code = 0 };
- },
- .Type => |type_cmd| {
- const file_path = switch (type_cmd.file) {
- .Con => {
- const error_msg = "Cannot TYPE from CON\n";
- if (output_capture) |capture| {
- try capture.write(error_msg);
- } else {
- print("{s}", .{error_msg});
- }
- return CommandStatus{ .Code = 1 };
- },
- .Lpt1, .Lpt2, .Lpt3, .Prn => {
- const error_msg = "Cannot TYPE from device\n";
- if (output_capture) |capture| {
- try capture.write(error_msg);
- } else {
- print("{s}", .{error_msg});
- }
- return CommandStatus{ .Code = 1 };
- },
- .Path => |path| path,
- };
-
- const file = std.fs.cwd().openFile(file_path, .{}) catch |err| {
- const error_msg = switch (err) {
- error.FileNotFound => "The system cannot find the file specified.\n",
- error.IsDir => "Access is denied.\n",
- error.AccessDenied => "Access is denied.\n",
- else => "Cannot access file.\n",
- };
- if (output_capture) |capture| {
- try capture.write(error_msg);
- } else {
- print("{s}", .{error_msg});
- }
- return CommandStatus{ .Code = 1 };
- };
- defer file.close();
-
- // Read and display file contents
- var buffer: [4096]u8 = undefined;
- while (true) {
- const bytes_read = file.readAll(&buffer) catch |err| {
- const error_msg = switch (err) {
- error.AccessDenied => "Access is denied.\n",
- else => "Error reading file.\n",
- };
- if (output_capture) |capture| {
- try capture.write(error_msg);
- } else {
- print("{s}", .{error_msg});
- }
- return CommandStatus{ .Code = 1 };
- };
-
- if (bytes_read == 0) break;
-
- // Process buffer contents for output
- var processed_output = ArrayList(u8).init(allocator);
- defer processed_output.deinit();
-
- for (buffer[0..bytes_read]) |byte| {
- // Convert to printable characters, similar to DOS TYPE behavior
- if (byte >= 32 and byte <= 126) {
- try processed_output.append(byte);
- } else if (byte == '\n') {
- try processed_output.append('\n');
- } else if (byte == '\r') {
- // Skip carriage return in DOS-style line endings
- continue;
- } else if (byte == '\t') {
- try processed_output.append('\t');
- } else {
- // Replace non-printable characters with '?'
- try processed_output.append('?');
- }
- }
-
- if (output_capture) |capture| {
- try capture.write(processed_output.items);
- } else {
- print("{s}", .{processed_output.items});
- }
-
- // If we read less than the buffer size, we're done
- if (bytes_read < buffer.len) break;
- }
-
- return CommandStatus{ .Code = 0 };
- },
- .Sort => {
- var lines = ArrayList([]const u8).init(allocator);
- defer {
- for (lines.items) |line| {
- allocator.free(line);
- }
- lines.deinit();
- }
-
- // Read input lines
- if (input_source) |source| {
- // Read from input redirection
- while (try source.readLine(allocator)) |line| {
- try lines.append(line);
- }
- } else {
- // Read from stdin (simplified - just show message)
- const msg = "SORT: Use input redirection (< file.txt) to sort file contents\n";
- if (output_capture) |capture| {
- try capture.write(msg);
- } else {
- print("{s}", .{msg});
- }
- return CommandStatus{ .Code = 0 };
- }
-
- // Sort the lines
- std.mem.sort([]const u8, lines.items, {}, struct {
- fn lessThan(_: void, lhs: []const u8, rhs: []const u8) bool {
- return std.mem.order(u8, lhs, rhs) == .lt;
- }
- }.lessThan);
-
- // Output sorted lines
- var output_buffer = ArrayList(u8).init(allocator);
- defer output_buffer.deinit();
-
- for (lines.items) |line| {
- try output_buffer.writer().print("{s}\n", .{line});
- }
-
- if (output_capture) |capture| {
- try capture.write(output_buffer.items);
- } else {
- print("{s}", .{output_buffer.items});
- }
-
- return CommandStatus{ .Code = 0 };
- },
- else => {
- const error_msg = try std.fmt.allocPrint(allocator, "Command not implemented: {any}\n", .{builtin_cmd});
- defer allocator.free(error_msg);
- if (output_capture) |capture| {
- try capture.write(error_msg);
- } else {
- print("{s}", .{error_msg});
- }
- return CommandStatus{ .Code = 1 };
- },
- }
- },
-
- .External => |external| {
- // Try to execute external command
- var child_args = ArrayList([]const u8).init(allocator);
- defer child_args.deinit();
-
- try child_args.append(external.program);
- for (external.args.items) |arg| {
- try child_args.append(arg);
- }
-
- var child = std.process.Child.init(child_args.items, allocator);
-
- // Set up pipes for capturing output
- child.stdin_behavior = if (input_source != null) .Pipe else .Inherit;
- child.stdout_behavior = if (output_capture != null) .Pipe else .Inherit;
- child.stderr_behavior = if (output_capture != null) .Pipe else .Inherit;
-
- const spawn_result = child.spawn();
- if (spawn_result) |_| {
- // Spawn succeeded, continue with execution
- } else |err| switch (err) {
- error.FileNotFound => {
- const error_msg = try std.fmt.allocPrint(allocator, "'{s}' is not recognized as an internal or external command,\noperable program or batch file.\n", .{external.program});
- defer allocator.free(error_msg);
-
- if (output_capture) |capture| {
- try capture.write(error_msg);
- } else {
- print("{s}", .{error_msg});
- }
- return CommandStatus{ .Code = 1 };
- },
- error.AccessDenied => {
- const error_msg = "Access is denied.\n";
- if (output_capture) |capture| {
- try capture.write(error_msg);
- } else {
- print("{s}", .{error_msg});
- }
- return CommandStatus{ .Code = 1 };
- },
- else => {
- const error_msg = try std.fmt.allocPrint(allocator, "Cannot execute '{s}': {}\n", .{ external.program, err });
- defer allocator.free(error_msg);
-
- if (output_capture) |capture| {
- try capture.write(error_msg);
- } else {
- print("{s}", .{error_msg});
- }
- return CommandStatus{ .Code = 1 };
- },
- }
-
- // Handle input redirection
- if (input_source) |source| {
- if (child.stdin) |stdin| {
- const writer = stdin.writer();
-
- // Reset source position for reading
- var temp_source = source.*;
- temp_source.position = 0;
-
- while (try temp_source.readLine(allocator)) |line| {
- defer allocator.free(line);
- try writer.print("{s}\n", .{line});
- }
- child.stdin.?.close();
- child.stdin = null;
- }
- }
-
- // Handle output capture
- if (output_capture) |capture| {
- // Read stdout
- if (child.stdout) |stdout| {
- var buffer: [4096]u8 = undefined;
- while (true) {
- const bytes_read = stdout.read(&buffer) catch break;
- if (bytes_read == 0) break;
- try capture.write(buffer[0..bytes_read]);
- }
- }
-
- // Read stderr
- if (child.stderr) |stderr| {
- var buffer: [4096]u8 = undefined;
- while (true) {
- const bytes_read = stderr.read(&buffer) catch break;
- if (bytes_read == 0) break;
- try capture.write(buffer[0..bytes_read]);
- }
- }
- }
-
- // Wait for process to complete
- const term = child.wait() catch |err| {
- const error_msg = switch (err) {
- error.FileNotFound => try std.fmt.allocPrint(allocator, "'{s}' is not recognized as an internal or external command,\noperable program or batch file.\n", .{external.program}),
- else => try std.fmt.allocPrint(allocator, "Error waiting for command: {}\n", .{err}),
- };
- defer allocator.free(error_msg);
-
- if (output_capture) |capture| {
- try capture.write(error_msg);
- } else {
- print("{s}", .{error_msg});
- }
- return CommandStatus{ .Code = 1 };
- };
-
- // Return exit code
- switch (term) {
- .Exited => |code| return CommandStatus{ .Code = @intCast(code) },
- .Signal => |_| return CommandStatus{ .Code = 1 },
- .Stopped => |_| return CommandStatus{ .Code = 1 },
- .Unknown => |_| return CommandStatus{ .Code = 1 },
- }
- },
-
- .Redirect => |redirect| {
- // Check if we have any output redirections
- var has_output_redirect = false;
- for (redirect.redirects.items) |redir| {
- if (redir.redirect_type == .OutputOverwrite or redir.redirect_type == .OutputAppend) {
- has_output_redirect = true;
- break;
- }
- }
-
- var captured_output = OutputCapture.init(allocator);
- defer captured_output.deinit();
-
- // Prepare input redirection if needed
- var input_data: ?[]const u8 = null;
- var redirect_input_source: ?InputSource = null;
- defer if (input_data) |data| allocator.free(data);
-
- // Process input redirections first
- for (redirect.redirects.items) |redir| {
- if (redir.redirect_type == .InputFrom) {
- const file_path = switch (redir.target) {
- .Con => {
- print("Input redirection from CON not supported\n", .{});
- return CommandStatus{ .Code = 1 };
- },
- .Lpt1, .Lpt2, .Lpt3, .Prn => {
- print("Cannot redirect input from device\n", .{});
- return CommandStatus{ .Code = 1 };
- },
- .Path => |path| path,
- };
-
- // Read input file
- const file = std.fs.cwd().openFile(file_path, .{}) catch |err| {
- switch (err) {
- error.FileNotFound => print("The system cannot find the file specified.\n", .{}),
- error.AccessDenied => print("Access is denied.\n", .{}),
- else => print("Cannot open input file.\n", .{}),
- }
- return CommandStatus{ .Code = 1 };
- };
- defer file.close();
-
- input_data = file.readToEndAlloc(allocator, std.math.maxInt(usize)) catch |err| {
- switch (err) {
- error.AccessDenied => print("Access is denied.\n", .{}),
- else => print("Cannot read input file.\n", .{}),
- }
- return CommandStatus{ .Code = 1 };
- };
-
- redirect_input_source = InputSource.init(input_data.?);
- break; // Only handle first input redirection
- }
- }
-
- // Execute the command with input and output capture (only capture output if needed)
- const status = try executeCommandWithOutput(redirect.command.*, allocator, if (has_output_redirect) &captured_output else null, if (redirect_input_source) |*source| source else null);
-
- // Handle output redirections
- for (redirect.redirects.items) |redir| {
- if (redir.redirect_type == .InputFrom) continue; // Already handled
- const file_path = switch (redir.target) {
- .Con => {
- // Redirect to console - just print normally
- print("{s}", .{captured_output.getContents()});
- continue;
- },
- .Lpt1, .Lpt2, .Lpt3, .Prn => {
- print("Cannot redirect to device\n", .{});
- return CommandStatus{ .Code = 1 };
- },
- .Path => |path| path,
- };
-
- // Handle different redirect types
- switch (redir.redirect_type) {
- .OutputOverwrite => {
- // Write to file, overwriting existing content
- const file = std.fs.cwd().createFile(file_path, .{}) catch |err| {
- switch (err) {
- error.AccessDenied => print("Access is denied.\n", .{}),
- else => print("Cannot create file.\n", .{}),
- }
- return CommandStatus{ .Code = 1 };
- };
- defer file.close();
-
- file.writeAll(captured_output.getContents()) catch |err| {
- switch (err) {
- error.AccessDenied => print("Access is denied.\n", .{}),
- else => print("Cannot write to file.\n", .{}),
- }
- return CommandStatus{ .Code = 1 };
- };
- },
- .OutputAppend => {
- // Append to file
- const file = std.fs.cwd().openFile(file_path, .{ .mode = .write_only }) catch |err| {
- switch (err) {
- error.FileNotFound => {
- // Create new file if it doesn't exist
- const new_file = std.fs.cwd().createFile(file_path, .{}) catch |create_err| {
- switch (create_err) {
- error.AccessDenied => print("Access is denied.\n", .{}),
- else => print("Cannot create file.\n", .{}),
- }
- return CommandStatus{ .Code = 1 };
- };
- defer new_file.close();
-
- new_file.writeAll(captured_output.getContents()) catch {
- print("Cannot write to file.\n", .{});
- return CommandStatus{ .Code = 1 };
- };
- continue;
- },
- error.AccessDenied => {
- print("Access is denied.\n", .{});
- return CommandStatus{ .Code = 1 };
- },
- else => {
- print("Cannot open file.\n", .{});
- return CommandStatus{ .Code = 1 };
- },
- }
- };
- defer file.close();
-
- // Seek to end for append
- file.seekFromEnd(0) catch {
- print("Cannot seek to end of file.\n", .{});
- return CommandStatus{ .Code = 1 };
- };
-
- file.writeAll(captured_output.getContents()) catch |err| {
- switch (err) {
- error.AccessDenied => print("Access is denied.\n", .{}),
- else => print("Cannot write to file.\n", .{}),
- }
- return CommandStatus{ .Code = 1 };
- };
- },
- .InputFrom => {
- // Input redirection already handled above
- continue;
- },
- }
- }
-
- return status;
- },
-
- else => {
- const error_msg = "Command type not implemented\n";
- if (output_capture) |capture| {
- try capture.write(error_msg);
- } else {
- print("{s}", .{error_msg});
- }
- return CommandStatus{ .Code = 1 };
- },
- }
-}
-
-fn executeCommand(command: Command, allocator: Allocator) !CommandStatus {
- return executeCommandWithOutput(command, allocator, null, null);
+ return try eval.executeCommand(command, allocator);
}
fn readLine(allocator: Allocator, prompt_text: []const u8) !?[]const u8 {