diff options
| -rw-r--r-- | DOCS.md | 70 | ||||
| -rw-r--r-- | build.zig | 3 | ||||
| -rw-r--r-- | src/builtins.zig | 41 | ||||
| -rw-r--r-- | src/executor.zig | 29 | ||||
| -rw-r--r-- | src/main.zig | 97 | ||||
| -rw-r--r-- | src/parser.zig | 31 | ||||
| -rw-r--r-- | src/path.zig | 25 | ||||
| -rw-r--r-- | src/shell.zig | 37 |
8 files changed, 241 insertions, 92 deletions
@@ -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` @@ -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; +} |