summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Andreas Benkard <code@mail.matthias.benkard.de>2025-08-13 19:40:54 +0200
committerMatthias Andreas Benkard <code@mail.matthias.benkard.de>2025-08-13 19:40:54 +0200
commit01bb307ebe6e9379336b94729473007e8d1d9156 (patch)
treeb03b0311fa263a58035dad63db0351b1d384b0df
parent7abe2622b2dac687558104630d10c0fa978a69e8 (diff)
main: Split out eval.
-rw-r--r--src/eval.zig691
-rw-r--r--src/main.zig684
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 {