diff options
author | Matthias Andreas Benkard <code@mail.matthias.benkard.de> | 2025-08-13 19:40:54 +0200 |
---|---|---|
committer | Matthias Andreas Benkard <code@mail.matthias.benkard.de> | 2025-08-13 19:40:54 +0200 |
commit | 01bb307ebe6e9379336b94729473007e8d1d9156 (patch) | |
tree | b03b0311fa263a58035dad63db0351b1d384b0df | |
parent | 7abe2622b2dac687558104630d10c0fa978a69e8 (diff) |
main: Split out eval.
-rw-r--r-- | src/eval.zig | 691 | ||||
-rw-r--r-- | src/main.zig | 684 |
2 files changed, 695 insertions, 680 deletions
diff --git a/src/eval.zig b/src/eval.zig new file mode 100644 index 0000000..e12fd9b --- /dev/null +++ b/src/eval.zig @@ -0,0 +1,691 @@ +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 BuiltinCommand = parser.BuiltinCommand; +const FileSpec = parser.FileSpec; +const RedirectType = parser.RedirectType; +const Redirect = parser.Redirect; + +const STDOUT_BUFFER_SIZE: usize = 1024; +const STDERR_BUFFER_SIZE: usize = 1024; + +pub const CommandStatus = union(enum) { + Code: u16, + ExitShell, +}; + +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); + } + } +}; + +pub fn executeCommand(command: Command, allocator: Allocator) !CommandStatus { + return executeCommandWithOutput(command, allocator, null, null); +} + +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 isLeapYear(year: u32) bool { + return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0); +} 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 { |