summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMatthias Andreas Benkard <code@mail.matthias.benkard.de>2025-08-13 21:07:01 +0200
committerMatthias Andreas Benkard <code@mail.matthias.benkard.de>2025-08-13 21:07:01 +0200
commit4a5f0120b68e752a0c61597eec6daf55f01c2582 (patch)
treed5e87e3d27aa199b3c57244d91c85e403cc5cd85 /src
parentbf1dd870c839a447d6a1f7035fac2f347185925a (diff)
Add CD, COPY, DEL, MD.
Diffstat (limited to 'src')
-rw-r--r--src/eval.zig247
-rw-r--r--src/syntax.zig72
2 files changed, 313 insertions, 6 deletions
diff --git a/src/eval.zig b/src/eval.zig
index ba7cff0..e320713 100644
--- a/src/eval.zig
+++ b/src/eval.zig
@@ -469,6 +469,253 @@ fn executeCommandWithOutput(command: Command, allocator: Allocator, output_captu
return CommandStatus{ .Code = 0 };
},
+ .Chdir => |chdir| {
+ if (chdir.path.len == 0) {
+ // No arguments - display current directory
+ const cwd = std.fs.cwd().realpathAlloc(allocator, ".") catch {
+ const error_msg = "Unable to determine current directory\n";
+ if (output_capture) |capture| {
+ try capture.write(error_msg);
+ } else {
+ print("{s}", .{error_msg});
+ }
+ return CommandStatus{ .Code = 1 };
+ };
+ defer allocator.free(cwd);
+
+ const formatted_path = try formatDosPath(allocator, cwd);
+ defer allocator.free(formatted_path);
+
+ const output = try std.fmt.allocPrint(allocator, "{s}\n", .{formatted_path});
+ defer allocator.free(output);
+
+ if (output_capture) |capture| {
+ try capture.write(output);
+ } else {
+ print("{s}", .{output});
+ }
+ return CommandStatus{ .Code = 0 };
+ } else {
+ // Change directory
+ const target_path = chdir.path;
+
+ // Handle special cases
+ if (std.mem.eql(u8, target_path, "..")) {
+ // Go to parent directory
+ std.process.changeCurDir("..") catch {
+ const error_msg = "The system cannot find the path specified.\n";
+ if (output_capture) |capture| {
+ try capture.write(error_msg);
+ } else {
+ print("{s}", .{error_msg});
+ }
+ return CommandStatus{ .Code = 1 };
+ };
+ } else if (std.mem.eql(u8, target_path, "\\") or std.mem.eql(u8, target_path, "/")) {
+ // Go to root directory - simplified to just go to "/"
+ std.process.changeCurDir("/") catch {
+ const error_msg = "The system cannot find the path specified.\n";
+ if (output_capture) |capture| {
+ try capture.write(error_msg);
+ } else {
+ print("{s}", .{error_msg});
+ }
+ return CommandStatus{ .Code = 1 };
+ };
+ } else {
+ // Regular directory change
+ // Make sure the path doesn't contain null bytes
+ for (target_path) |ch| {
+ if (ch == 0) {
+ const error_msg = "Invalid path: contains null character\n";
+ if (output_capture) |capture| {
+ try capture.write(error_msg);
+ } else {
+ print("{s}", .{error_msg});
+ }
+ return CommandStatus{ .Code = 1 };
+ }
+ }
+
+ std.process.changeCurDir(target_path) catch {
+ const error_msg = "The system cannot find the path specified.\n";
+ if (output_capture) |capture| {
+ try capture.write(error_msg);
+ } else {
+ print("{s}", .{error_msg});
+ }
+ return CommandStatus{ .Code = 1 };
+ };
+ }
+ return CommandStatus{ .Code = 0 };
+ }
+ },
+ .Copy => |copy| {
+ // Handle source file
+ const source_path = switch (copy.from) {
+ .Con => {
+ const error_msg = "Cannot copy 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 copy from device\n";
+ if (output_capture) |capture| {
+ try capture.write(error_msg);
+ } else {
+ print("{s}", .{error_msg});
+ }
+ return CommandStatus{ .Code = 1 };
+ },
+ .Path => |path| path,
+ };
+
+ // Handle destination
+ const dest_path = switch (copy.to) {
+ .Con => {
+ // Copy to console (display file contents)
+ const source_file = std.fs.cwd().openFile(source_path, .{}) catch |err| {
+ const error_msg = switch (err) {
+ error.FileNotFound => "File not found\n",
+ error.AccessDenied => "Access denied\n",
+ else => "Cannot access source file\n",
+ };
+ if (output_capture) |capture| {
+ try capture.write(error_msg);
+ } else {
+ print("{s}", .{error_msg});
+ }
+ return CommandStatus{ .Code = 1 };
+ };
+ defer source_file.close();
+
+ // Read and display file contents
+ var buffer: [4096]u8 = undefined;
+ while (true) {
+ const bytes_read = source_file.readAll(&buffer) catch {
+ const error_msg = "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;
+
+ if (output_capture) |capture| {
+ try capture.write(buffer[0..bytes_read]);
+ } else {
+ print("{s}", .{buffer[0..bytes_read]});
+ }
+
+ if (bytes_read < buffer.len) break;
+ }
+
+ const msg = " 1 File(s) copied\n";
+ if (output_capture) |capture| {
+ try capture.write(msg);
+ } else {
+ print("{s}", .{msg});
+ }
+ return CommandStatus{ .Code = 0 };
+ },
+ .Lpt1, .Lpt2, .Lpt3, .Prn => {
+ const error_msg = "Cannot copy to device\n";
+ if (output_capture) |capture| {
+ try capture.write(error_msg);
+ } else {
+ print("{s}", .{error_msg});
+ }
+ return CommandStatus{ .Code = 1 };
+ },
+ .Path => |path| path,
+ };
+
+ // Regular file-to-file copy
+ std.fs.cwd().copyFile(source_path, std.fs.cwd(), dest_path, .{}) catch |err| {
+ const error_msg = switch (err) {
+ error.FileNotFound => "File not found\n",
+ error.AccessDenied => "Access denied\n",
+ error.PathAlreadyExists => "File already exists\n",
+ else => "Cannot copy file\n",
+ };
+ if (output_capture) |capture| {
+ try capture.write(error_msg);
+ } else {
+ print("{s}", .{error_msg});
+ }
+ return CommandStatus{ .Code = 1 };
+ };
+
+ const msg = " 1 File(s) copied\n";
+ if (output_capture) |capture| {
+ try capture.write(msg);
+ } else {
+ print("{s}", .{msg});
+ }
+ return CommandStatus{ .Code = 0 };
+ },
+ .Remove => |remove| {
+ const file_path = remove.path;
+
+ // Check for wildcards (basic support)
+ if (std.mem.indexOf(u8, file_path, "*") != null or std.mem.indexOf(u8, file_path, "?") != null) {
+ // Simple wildcard deletion - just show error for now
+ const error_msg = "Wildcard deletion not yet implemented\n";
+ if (output_capture) |capture| {
+ try capture.write(error_msg);
+ } else {
+ print("{s}", .{error_msg});
+ }
+ return CommandStatus{ .Code = 1 };
+ }
+
+ // Delete single file
+ std.fs.cwd().deleteFile(file_path) catch |err| {
+ const error_msg = switch (err) {
+ error.FileNotFound => "File not found\n",
+ error.AccessDenied => "Access denied\n",
+ error.IsDir => "Access denied - cannot delete directory\n",
+ else => "Cannot delete file\n",
+ };
+ if (output_capture) |capture| {
+ try capture.write(error_msg);
+ } else {
+ print("{s}", .{error_msg});
+ }
+ return CommandStatus{ .Code = 1 };
+ };
+
+ // No output for successful deletion (DOS style)
+ return CommandStatus{ .Code = 0 };
+ },
+ .Mkdir => |mkdir| {
+ const dir_path = mkdir.path;
+
+ std.fs.cwd().makeDir(dir_path) catch |err| {
+ const error_msg = switch (err) {
+ error.PathAlreadyExists => "A subdirectory or file already exists\n",
+ error.AccessDenied => "Access denied\n",
+ error.FileNotFound => "The system cannot find the path specified\n",
+ error.NotDir => "The system cannot find the path specified\n",
+ else => "Unable to create directory\n",
+ };
+ if (output_capture) |capture| {
+ try capture.write(error_msg);
+ } else {
+ print("{s}", .{error_msg});
+ }
+ return CommandStatus{ .Code = 1 };
+ };
+
+ // No output for successful creation (DOS style)
+ 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);
diff --git a/src/syntax.zig b/src/syntax.zig
index 6d5ae2e..dde6dba 100644
--- a/src/syntax.zig
+++ b/src/syntax.zig
@@ -190,11 +190,43 @@ pub const Command = union(enum) {
.Redirect => |*redirect| {
redirect.command.deinit(allocator);
allocator.destroy(redirect.command);
+ for (redirect.redirects.items) |redir| {
+ switch (redir.target) {
+ .Path => |path| allocator.free(path),
+ else => {},
+ }
+ }
redirect.redirects.deinit();
},
.External => |*external| {
external.args.deinit();
},
+ .Builtin => |builtin| {
+ switch (builtin) {
+ .Dir => |dir| allocator.free(dir.path),
+ .Chdir => |chdir| allocator.free(chdir.path),
+ .EchoText => |echo| allocator.free(echo.message),
+ .Type => |type_cmd| {
+ switch (type_cmd.file) {
+ .Path => |path| allocator.free(path),
+ else => {},
+ }
+ },
+ .Copy => |copy| {
+ switch (copy.from) {
+ .Path => |path| allocator.free(path),
+ else => {},
+ }
+ switch (copy.to) {
+ .Path => |path| allocator.free(path),
+ else => {},
+ }
+ },
+ .Remove => |remove| allocator.free(remove.path),
+ .Mkdir => |mkdir| allocator.free(mkdir.path),
+ else => {},
+ }
+ },
else => {},
}
}
@@ -417,7 +449,7 @@ const Parser = struct {
self.advance(); // consume redirect token
const target_str = try self.expectWord();
- const target = parseFilespec(target_str);
+ const target = try parseFilespec(self.allocator, target_str);
try redirects.append(Redirect{
.redirect_type = redirect_type,
@@ -488,7 +520,10 @@ const Parser = struct {
} else if (std.mem.eql(u8, cmd_upper, "VERIFY")) {
return Command{ .Builtin = BuiltinCommand.Verify };
} else if (std.mem.eql(u8, cmd_upper, "DIR")) {
- const path = if (args.items.len == 0) "." else args.items[0];
+ const path = if (args.items.len == 0)
+ try self.allocator.dupe(u8, ".")
+ else
+ try self.allocator.dupe(u8, args.items[0]);
return Command{ .Builtin = BuiltinCommand{ .Dir = .{ .path = path } } };
} else if (std.mem.eql(u8, cmd_upper, "VER")) {
return Command{ .Builtin = BuiltinCommand.Ver };
@@ -500,10 +535,35 @@ const Parser = struct {
if (args.items.len == 0) {
return error.ExpectedWord; // Will be caught and show "Bad command or file name"
}
- const file_spec = parseFilespec(args.items[0]);
+ const file_spec = try parseFilespec(self.allocator, args.items[0]);
return Command{ .Builtin = BuiltinCommand{ .Type = .{ .file = file_spec } } };
} else if (std.mem.eql(u8, cmd_upper, "SORT")) {
return Command{ .Builtin = BuiltinCommand.Sort };
+ } else if (std.mem.eql(u8, cmd_upper, "CD") or std.mem.eql(u8, cmd_upper, "CHDIR")) {
+ const path = if (args.items.len == 0)
+ try self.allocator.dupe(u8, "")
+ else
+ try self.allocator.dupe(u8, args.items[0]);
+ return Command{ .Builtin = BuiltinCommand{ .Chdir = .{ .path = path } } };
+ } else if (std.mem.eql(u8, cmd_upper, "COPY")) {
+ if (args.items.len < 2) {
+ return error.ExpectedWord; // Will show "Bad command or file name"
+ }
+ const from_spec = try parseFilespec(self.allocator, args.items[0]);
+ const to_spec = try parseFilespec(self.allocator, args.items[1]);
+ return Command{ .Builtin = BuiltinCommand{ .Copy = .{ .from = from_spec, .to = to_spec } } };
+ } else if (std.mem.eql(u8, cmd_upper, "DEL") or std.mem.eql(u8, cmd_upper, "ERASE")) {
+ if (args.items.len == 0) {
+ return error.ExpectedWord; // Will show "Bad command or file name"
+ }
+ const path = try self.allocator.dupe(u8, args.items[0]);
+ return Command{ .Builtin = BuiltinCommand{ .Remove = .{ .path = path } } };
+ } else if (std.mem.eql(u8, cmd_upper, "MD") or std.mem.eql(u8, cmd_upper, "MKDIR")) {
+ if (args.items.len == 0) {
+ return error.ExpectedWord; // Will show "Bad command or file name"
+ }
+ const path = try self.allocator.dupe(u8, args.items[0]);
+ return Command{ .Builtin = BuiltinCommand{ .Mkdir = .{ .path = path } } };
} else {
// External command
return Command{ .External = .{ .program = command_name, .args = args } };
@@ -511,9 +571,9 @@ const Parser = struct {
}
};
-fn parseFilespec(path_str: []const u8) FileSpec {
+fn parseFilespec(allocator: Allocator, path_str: []const u8) !FileSpec {
var upper_buf: [256]u8 = undefined;
- if (path_str.len >= upper_buf.len) return FileSpec{ .Path = path_str };
+ if (path_str.len >= upper_buf.len) return FileSpec{ .Path = try allocator.dupe(u8, path_str) };
const upper_str = std.ascii.upperString(upper_buf[0..path_str.len], path_str);
if (std.mem.eql(u8, upper_str, "CON")) return FileSpec.Con;
@@ -521,7 +581,7 @@ fn parseFilespec(path_str: []const u8) FileSpec {
if (std.mem.eql(u8, upper_str, "LPT2")) return FileSpec.Lpt2;
if (std.mem.eql(u8, upper_str, "LPT3")) return FileSpec.Lpt3;
if (std.mem.eql(u8, upper_str, "PRN")) return FileSpec.Prn;
- return FileSpec{ .Path = path_str };
+ return FileSpec{ .Path = try allocator.dupe(u8, path_str) };
}
pub fn parse(input: []const u8, allocator: Allocator) !Command {