summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLucas Faria Mendes <lucas.oliveira1676@etec.sp.gov.br>2025-12-05 05:53:37 +0000
committerLucas Faria Mendes <lucas.oliveira1676@etec.sp.gov.br>2025-12-05 05:53:37 +0000
commit2515de92a5ae28edda56deba40c852f36928b294 (patch)
treecc048d51ba7dd6fc78376f02e1c12af1161c7d38
parent695b1fa1dc95d62016978a517b8e8544e486d9b2 (diff)
downloadshell-zig-2515de92a5ae28edda56deba40c852f36928b294.tar.gz
shell-zig-2515de92a5ae28edda56deba40c852f36928b294.zip
codecrafters submit [skip ci]
-rw-r--r--DOCS.md70
-rw-r--r--build.zig3
-rw-r--r--src/builtins.zig41
-rw-r--r--src/executor.zig29
-rw-r--r--src/main.zig97
-rw-r--r--src/parser.zig31
-rw-r--r--src/path.zig25
-rw-r--r--src/shell.zig37
8 files changed, 241 insertions, 92 deletions
diff --git a/DOCS.md b/DOCS.md
new file mode 100644
index 0000000..efa991d
--- /dev/null
+++ b/DOCS.md
@@ -0,0 +1,70 @@
+# Shell em Zig
+
+Um shell simples implementado em Zig que suporta comandos builtin e execução de programas externos.
+
+## Estrutura do Projeto
+
+```
+src/
+├── main.zig # Ponto de entrada e loop REPL
+├── parser.zig # Parsing de comandos e argumentos
+├── builtins.zig # Implementação de comandos builtin
+├── path.zig # Busca de executáveis no PATH
+├── executor.zig # Execução de programas externos
+└── shell.zig # Orquestração de comandos
+```
+
+## Módulos
+
+### main.zig
+- Ponto de entrada da aplicação
+- Implementa o REPL (Read-Eval-Print Loop)
+- Gerencia entrada/saída do shell
+
+### parser.zig
+- `parseCommand()`: Separa nome do comando dos argumentos
+- `parseArgs()`: Converte string de argumentos em array
+
+### builtins.zig
+- `isBuiltin()`: Verifica se comando é builtin
+- `executeExit()`: Implementa comando `exit`
+- `executeEcho()`: Implementa comando `echo`
+- `executeType()`: Implementa comando `type`
+
+### path.zig
+- `findInPath()`: Busca executáveis no PATH do sistema
+- Verifica permissões de execução
+
+### executor.zig
+- `runExternalProgram()`: Executa programas externos
+- Gerencia processos filhos
+
+### shell.zig
+- `executeCommand()`: Orquestra execução de comandos
+- Decide entre builtin ou programa externo
+
+## Comandos Suportados
+
+### Builtins
+- `exit` - Encerra o shell
+- `echo [args]` - Imprime argumentos
+- `type <command>` - Mostra tipo/localização do comando
+
+### Programas Externos
+Qualquer executável encontrado no PATH pode ser executado.
+
+## Compilar e Executar
+
+```bash
+# Compilar
+zig build
+
+# Executar
+./zig-out/bin/main
+```
+
+## Como Adicionar Novos Builtins
+
+1. Adicione o nome à lista `BUILTINS` em `builtins.zig`
+2. Implemente a função `executeNomeDoComando()` em `builtins.zig`
+3. Adicione o case no `executeCommand()` em `shell.zig`
diff --git a/build.zig b/build.zig
index 0eccf2a..9d8c383 100644
--- a/build.zig
+++ b/build.zig
@@ -10,6 +10,9 @@ pub fn build(b: *std.Build) void {
}),
});
+ // Habilita libc para usar environ
+ exe.linkLibC();
+
// This declares intent for the executable to be installed into the
// standard location when the user invokes the "install" step (the default
// step when running `zig build`).
diff --git a/src/builtins.zig b/src/builtins.zig
new file mode 100644
index 0000000..c989a35
--- /dev/null
+++ b/src/builtins.zig
@@ -0,0 +1,41 @@
+const std = @import("std");
+const path = @import("path.zig");
+
+const BUILTINS = [_][]const u8{ "exit", "echo", "type" };
+
+pub const CommandResult = enum {
+ continue_loop,
+ exit_shell,
+};
+
+pub fn isBuiltin(cmd_name: []const u8) bool {
+ for (BUILTINS) |builtin| {
+ if (std.mem.eql(u8, cmd_name, builtin)) return true;
+ }
+ return false;
+}
+
+pub fn executeExit() CommandResult {
+ return .exit_shell;
+}
+
+pub fn executeEcho(stdout: anytype, args: ?[]const u8) !void {
+ if (args) |a| {
+ try stdout.print("{s}\n", .{a});
+ } else {
+ try stdout.print("\n", .{});
+ }
+}
+
+pub fn executeType(allocator: std.mem.Allocator, stdout: anytype, args: ?[]const u8) !void {
+ if (args) |a| {
+ if (isBuiltin(a)) {
+ try stdout.print("{s} is a shell builtin\n", .{a});
+ } else if (try path.findInPath(allocator, a)) |cmd_path| {
+ defer allocator.free(cmd_path);
+ try stdout.print("{s} is {s}\n", .{ a, cmd_path });
+ } else {
+ try stdout.print("{s}: not found\n", .{a});
+ }
+ }
+}
diff --git a/src/executor.zig b/src/executor.zig
new file mode 100644
index 0000000..839fcda
--- /dev/null
+++ b/src/executor.zig
@@ -0,0 +1,29 @@
+const std = @import("std");
+
+pub fn runExternalProgram(allocator: std.mem.Allocator, program_path: []const u8, argv: []const []const u8) !void {
+ const argv_z = try allocator.allocSentinel(?[*:0]const u8, argv.len, null);
+ defer allocator.free(argv_z);
+
+ for (argv, 0..) |arg, i| {
+ argv_z[i] = (try allocator.dupeZ(u8, arg)).ptr;
+ }
+ defer {
+ for (argv_z[0..argv.len]) |arg_ptr| {
+ if (arg_ptr) |ptr| allocator.free(std.mem.span(ptr));
+ }
+ }
+
+ const program_path_z = try allocator.dupeZ(u8, program_path);
+ defer allocator.free(program_path_z);
+
+ const pid = try std.posix.fork();
+
+ if (pid == 0) {
+ _ = std.posix.execveZ(program_path_z, argv_z, std.c.environ) catch {
+ std.posix.exit(1);
+ };
+ unreachable;
+ } else {
+ _ = std.posix.waitpid(pid, 0);
+ }
+}
diff --git a/src/main.zig b/src/main.zig
index 10c5c1d..a0959ed 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -1,4 +1,6 @@
const std = @import("std");
+const parser = @import("parser.zig");
+const shell = @import("shell.zig");
var stdin_buffer: [4096]u8 = undefined;
var stdin_reader = std.fs.File.stdin().readerStreaming(&stdin_buffer);
@@ -7,93 +9,6 @@ const stdin = &stdin_reader.interface;
var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
const stdout = &stdout_writer.interface;
-const CommandResult = enum {
- continue_loop,
- exit_shell,
-};
-
-fn parseCommand(input: []const u8) struct { name: []const u8, args: ?[]const u8 } {
- const space_pos = std.mem.indexOfScalar(u8, input, ' ');
- if (space_pos) |pos| {
- return .{
- .name = input[0..pos],
- .args = input[pos + 1 ..],
- };
- }
- return .{
- .name = input,
- .args = null,
- };
-}
-
-fn isBuiltin(cmd_name: []const u8) bool {
- const builtins = [_][]const u8{ "exit", "echo", "type" };
- for (builtins) |builtin| {
- if (std.mem.eql(u8, cmd_name, builtin)) {
- return true;
- }
- }
- return false;
-}
-
-fn findInPath(allocator: std.mem.Allocator, cmd_name: []const u8) !?[]const u8 {
- const path_env = std.process.getEnvVarOwned(allocator, "PATH") catch return null;
- defer allocator.free(path_env);
-
- var it = std.mem.splitScalar(u8, path_env, ':');
- while (it.next()) |dir| {
- const full_path = std.fs.path.join(allocator, &[_][]const u8{ dir, cmd_name }) catch continue;
- defer allocator.free(full_path);
-
- const file = std.fs.openFileAbsolute(full_path, .{}) catch continue;
- const stat = file.stat() catch {
- file.close();
- continue;
- };
- file.close();
-
- const mode = stat.mode;
- const has_exec = (mode & 0o111) != 0;
- if (!has_exec) continue;
-
- return try allocator.dupe(u8, full_path);
- }
-
- return null;
-}
-
-fn executeCommand(allocator: std.mem.Allocator, cmd_name: []const u8, args: ?[]const u8) !CommandResult {
- if (std.mem.eql(u8, cmd_name, "exit")) {
- return .exit_shell;
- }
-
- if (std.mem.eql(u8, cmd_name, "echo")) {
- if (args) |a| {
- try stdout.print("{s}\n", .{a});
- } else {
- try stdout.print("\n", .{});
- }
- return .continue_loop;
- }
-
- if (std.mem.eql(u8, cmd_name, "type")) {
- if (args) |a| {
- if (isBuiltin(a)) {
- try stdout.print("{s} is a shell builtin\n", .{a});
- } else if (try findInPath(allocator, a)) |path| {
- defer allocator.free(path);
- try stdout.print("{s} is {s}\n", .{ a, path });
- } else {
- try stdout.print("{s}: not found\n", .{a});
- }
- }
- return .continue_loop;
- }
-
- try stdout.print("{s}: command not found\n", .{cmd_name});
- return .continue_loop;
-}
-
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
@@ -104,12 +19,10 @@ pub fn main() !void {
const command = try stdin.takeDelimiter('\n');
if (command) |cmd| {
- const parsed = parseCommand(cmd);
- const result = try executeCommand(allocator, parsed.name, parsed.args);
+ const parsed = parser.parseCommand(cmd);
+ const result = try shell.executeCommand(allocator, stdout, parsed.name, parsed.args);
- if (result == .exit_shell) {
- break;
- }
+ if (result == .exit_shell) break;
} else {
break;
}
diff --git a/src/parser.zig b/src/parser.zig
new file mode 100644
index 0000000..4909464
--- /dev/null
+++ b/src/parser.zig
@@ -0,0 +1,31 @@
+const std = @import("std");
+
+pub const ParsedCommand = struct {
+ name: []const u8,
+ args: ?[]const u8,
+};
+
+pub fn parseCommand(input: []const u8) ParsedCommand {
+ const space_pos = std.mem.indexOfScalar(u8, input, ' ');
+ if (space_pos) |pos| {
+ return .{ .name = input[0..pos], .args = input[pos + 1 ..] };
+ }
+ return .{ .name = input, .args = null };
+}
+
+pub fn parseArgs(allocator: std.mem.Allocator, cmd_name: []const u8, args_str: ?[]const u8) ![]const []const u8 {
+ var args_list = std.ArrayList([]const u8){};
+ try args_list.ensureTotalCapacity(allocator, 8);
+ errdefer args_list.deinit(allocator);
+
+ try args_list.append(allocator, cmd_name);
+
+ if (args_str) |args| {
+ var it = std.mem.tokenizeScalar(u8, args, ' ');
+ while (it.next()) |arg| {
+ try args_list.append(allocator, arg);
+ }
+ }
+
+ return args_list.toOwnedSlice(allocator);
+}
diff --git a/src/path.zig b/src/path.zig
new file mode 100644
index 0000000..2f52bc9
--- /dev/null
+++ b/src/path.zig
@@ -0,0 +1,25 @@
+const std = @import("std");
+
+pub fn findInPath(allocator: std.mem.Allocator, cmd_name: []const u8) !?[]const u8 {
+ const path_env = std.process.getEnvVarOwned(allocator, "PATH") catch return null;
+ defer allocator.free(path_env);
+
+ var it = std.mem.splitScalar(u8, path_env, ':');
+ while (it.next()) |dir| {
+ const full_path = std.fs.path.join(allocator, &[_][]const u8{ dir, cmd_name }) catch continue;
+ defer allocator.free(full_path);
+
+ const file = std.fs.openFileAbsolute(full_path, .{}) catch continue;
+ const stat = file.stat() catch {
+ file.close();
+ continue;
+ };
+ file.close();
+
+ if ((stat.mode & 0o111) == 0) continue;
+
+ return try allocator.dupe(u8, full_path);
+ }
+
+ return null;
+}
diff --git a/src/shell.zig b/src/shell.zig
new file mode 100644
index 0000000..c5aded1
--- /dev/null
+++ b/src/shell.zig
@@ -0,0 +1,37 @@
+const std = @import("std");
+const parser = @import("parser.zig");
+const builtins = @import("builtins.zig");
+const path = @import("path.zig");
+const executor = @import("executor.zig");
+
+pub fn executeCommand(
+ allocator: std.mem.Allocator,
+ stdout: anytype,
+ cmd_name: []const u8,
+ args: ?[]const u8,
+) !builtins.CommandResult {
+ if (std.mem.eql(u8, cmd_name, "exit")) return builtins.executeExit();
+
+ if (std.mem.eql(u8, cmd_name, "echo")) {
+ try builtins.executeEcho(stdout, args);
+ return .continue_loop;
+ }
+
+ if (std.mem.eql(u8, cmd_name, "type")) {
+ try builtins.executeType(allocator, stdout, args);
+ return .continue_loop;
+ }
+
+ if (try path.findInPath(allocator, cmd_name)) |program_path| {
+ defer allocator.free(program_path);
+
+ const argv = try parser.parseArgs(allocator, cmd_name, args);
+ defer allocator.free(argv);
+
+ try executor.runExternalProgram(allocator, program_path, argv);
+ return .continue_loop;
+ }
+
+ try stdout.print("{s}: command not found\n", .{cmd_name});
+ return .continue_loop;
+}