From a787f65b8f97e3d94edf852e74bdd7db0beb7f53 Mon Sep 17 00:00:00 2001 From: Paze SH Date: Sun, 16 Aug 2026 12:25:26 +0800 Subject: [PATCH] Initial commit: psh - minimal bash-like shell with && || operators and tab completion --- Makefile | 27 ++ README.md | 115 +++++ psh.c | 1121 ++++++++++++++++++++++++++++++++++++++++++++++++ psh.exe | Bin 0 -> 75445 bytes test.c | 84 ++++ test.exe | Bin 0 -> 56281 bytes test_input.txt | 8 + 7 files changed, 1355 insertions(+) create mode 100644 Makefile create mode 100644 README.md create mode 100644 psh.c create mode 100644 psh.exe create mode 100644 test.c create mode 100644 test.exe create mode 100644 test_input.txt diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..86b8294 --- /dev/null +++ b/Makefile @@ -0,0 +1,27 @@ +CC = gcc +CFLAGS = -Wall -Wextra -std=c99 -O2 +LDFLAGS = + +TARGET = psh.exe +SRC = psh.c + +.PHONY: all clean test + +all: $(TARGET) + +$(TARGET): $(SRC) + $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) + +test: $(TARGET) + @echo "Testing psh..." + ./$(TARGET) -c "echo hello" + ./$(TARGET) -c "pwd" + ./$(TARGET) -c "dir | findstr .exe" + @echo "Tests passed!" + +clean: + rm -f $(TARGET) *.o + +# Build with readline support (if available) +readline: $(SRC) + $(CC) $(CFLAGS) -DHAVE_READLINE -lreadline -o $(TARGET) $(SRC) diff --git a/README.md b/README.md new file mode 100644 index 0000000..3d36a05 --- /dev/null +++ b/README.md @@ -0,0 +1,115 @@ +# psh - A minimal bash-like shell in pure C + +A lightweight POSIX shell implementation featuring: + +- Command execution via fork/exec (Windows: CreateProcess) +- Built-in commands: `cd`, `pwd`, `echo`, `exit`, `help`, `env` +- Input/output redirection (`<`, `>`, `>>`) +- Piping (`|`) +- Background execution (`&`) +- Command history with up/down arrows +- Tab completion +- `&&` and `||` operators +- Basic environment variable support +- Tilde expansion (`~`) + +## Building + +```bash +make +``` + +Requires: +- GCC or compatible C compiler +- GNU Readline library (for line editing) + +## Running + +```bash +./psh +``` + +## Features + +### Built-ins +- `cd [dir]` - Change directory +- `pwd` - Print working directory +- `echo [args]` - Print text (supports `$VAR` expansion) +- `exit` - Exit shell +- `help` - Show help +- `env [var=val]` - Show or set environment variables +- `history` - Show command history + +### Redirection +```bash +ls > output.txt # Redirect output +cat < input.txt # Redirect input +echo hi >> log.txt # Append output +``` + +### Piping +```bash +ps aux | grep python | wc -l +``` + +### Background +```bash +sleep 100 & +``` + +### Logical Operators +```bash +cmd1 && cmd2 # Execute cmd2 only if cmd1 succeeds +cmd1 || cmd2 # Execute cmd2 only if cmd1 fails +``` + +### Tab Completion +Press `Tab` to complete commands: +- Built-in commands (cd, pwd, echo, etc.) +- Executable files in PATH and current directory +- Environment variables + +### Command History +- ↑/↓ arrows: Navigate history +- Tab: Try completion +- Backspace: Delete character + +## Example Session + +``` +user@localhost:F:\Paze SH> echo Hello World +Hello World + +user@localhost:F:\Paze SH> ls && echo "List completed" +psh.c README.md Makefile +List completed + +user@localhost:F:\Paze SH> false || echo "Fallback executed" +Fallback executed + +user@localhost:F:\Paze SH> help +psh - minimal bash-like shell (Windows) + +Built-in commands: + cd [dir] Change directory + pwd Print working directory + echo [args] Print arguments + exit Exit shell + env [var=val] Show/set environment + help Show this help + +Features: + cmd1 | cmd2 Pipe output + cmd > file Redirect output + cmd >> file Append output + cmd < file Redirect input + cmd & Run in background + cmd1 && cmd2 Execute cmd2 if cmd1 succeeds + cmd1 || cmd2 Execute cmd2 if cmd1 fails + history View command history + Tab Try command completion +``` + +## License + +MIT diff --git a/psh.c b/psh.c new file mode 100644 index 0000000..c3075ae --- /dev/null +++ b/psh.c @@ -0,0 +1,1121 @@ +/* + * psh - A minimal bash-like shell implemented in pure C (Windows version) + * + * Features: + * - Command execution with CreateProcess + * - Built-in commands: cd, pwd, echo, exit, help, env, history + * - Input piping (|) + * - Output redirection (>, >>) + * - Background execution (&) + * - Command history with up/down arrows + * - Tab completion (basic) + * - && and || operators + * - Simple environment variable expansion + */ + +#define _WIN32_WINNT 0x0501 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __GNUC__ +#ifdef HAVE_READLINE +#include +#include +#endif +#else +/* Minimal readline replacement for MSVC */ +#define rl_on_new_line() +#define rl_replace_line(text, clear) strcpy(rl_line_buffer, text); rl_point = strlen(text) +#define add_history(s) /* not implemented */ +extern char *readline(const char *prompt); +extern int rl_done; +#endif + +#define MAX_ARGS 256 +#define MAX_LINE 8192 +#define MAX_HIST 512 +#define MAX_CMDS 64 +#define MAX_ENV_VARS 256 +#define MAX_PATH_LEN 260 + +/* Token types for the parser */ +typedef enum { + TOKEN_WORD, + TOKEN_PIPE, + TOKEN_REDIR_OUT, /* > */ + TOKEN_REDIR_APP, /* >> */ + TOKEN_REDIR_IN, /* < */ + TOKEN_BG, /* & */ + TOKEN_AND, /* && */ + TOKEN_OR, /* || */ + TOKEN_SUBSTITUTE /* ; */ +} TokenType_t; + +typedef struct { + char *text; + TokenType_t type; +} Token; + +typedef struct { + char **argv; + int argc; + int bg; /* background flag */ + char *input_redir; /* for < */ + char *output_redir; /* for > */ + char *append_redir; /* for >> */ + int exit_code; /* exit code of the command */ +} SimpleCmd; + +typedef struct { + SimpleCmd cmds[MAX_CMDS]; + int cmd_count; +} Pipeline; + +/* Global state */ +static char *history[MAX_HIST]; +static int hist_size = 0; + +/* Tab completion state */ +static const char *completion_dirs[64]; +static int completion_dir_count = 0; + +/* Command line state for custom reader */ +static char cmd_line_buf[MAX_LINE]; +static int cmd_line_pos = 0; +static int cmd_line_len = 0; +static int cmd_hist_idx = 0; +static int cmd_hist_pos = 0; + +/* Environment variables */ +static char *env_table[256]; +static int env_count = 0; + +/* Forward declarations */ +static char *get_env_var(const char *name); +static void set_env_var(const char *name, const char *value); +static void free_argv(char **argv); +static void free_pipeline(Pipeline *p); +static int tokenize_line(const char *line, Token *tokens, int max_tokens); +static int parse_pipeline(Token *tokens, int token_count, Pipeline *pipeline); +static int execute_simple(SimpleCmd *cmd); +static int execute_pipeline(Pipeline *pipeline); +static int execute_command_list(Token *tokens, int token_count); +static void free_tokens(Token *tokens, int count); +static void handle_exit(int argc, char **argv); +static void handle_cd(int argc, char **argv); +static void handle_pwd(char **argv); +static void handle_echo(int argc, char **argv); +static void handle_env(int argc, char **argv); +static void handle_help(); +static int is_builtin(const char *cmd); +static int run_builtin(int argc, char **argv); +static void add_history_entry(const char *line); +static void print_prompt(); +static void cleanup_history(); +static void init_command_line(); +static char *read_command_line(const char *prompt); +static void free_command_line(); +static void build_completion_list(const char *prefix); +static void try_tab_completion(char *line, int *pos); + +/* Get environment variable value */ +static char *get_env_var(const char *name) { + char prefix[512]; + snprintf(prefix, sizeof(prefix), "%s=", name); + + for (int i = 0; i < env_count; i++) { + if (strncmp(env_table[i], prefix, strlen(prefix)) == 0) { + return env_table[i] + strlen(prefix); + } + } + return getenv(name); +} + +/* Set environment variable */ +static void set_env_var(const char *name, const char *value) { + char assignment[1024]; + snprintf(assignment, sizeof(assignment), "%s=%s", name, value); + + /* Check if already exists */ + char prefix[512]; + snprintf(prefix, sizeof(prefix), "%s=", name); + + for (int i = 0; i < env_count; i++) { + if (strncmp(env_table[i], prefix, strlen(prefix)) == 0) { + free(env_table[i]); + env_table[i] = strdup(assignment); + return; + } + } + + if (env_count < MAX_ENV_VARS) { + env_table[env_count++] = strdup(assignment); + } +} + +/* Free an argv array */ +static void free_argv(char **argv) { + if (!argv) return; + for (int i = 0; argv[i]; i++) { + free(argv[i]); + } + free(argv); +} + +/* Tokenize input line */ +static int tokenize_line(const char *line, Token *tokens, int max_tokens) { + int token_count = 0; + const char *p = line; + int in_single = 0, in_double = 0; + char *word_buf = NULL; + int word_len = 0; + int word_cap = 64; + + word_buf = malloc(word_cap); + if (!word_buf) return 0; + word_buf[0] = '\0'; + + while (*p && token_count < max_tokens) { + /* Skip whitespace */ + if (isspace((unsigned char)*p) && !in_single && !in_double) { + p++; + continue; + } + + /* Handle quotes */ + if (*p == '\'' && !in_double) { + in_single = !in_single; + p++; + continue; + } + if (*p == '"' && !in_single) { + in_double = !in_double; + p++; + continue; + } + + /* Handle special tokens */ + if (!in_single && !in_double) { + if (strncmp(p, "&&", 2) == 0) { + tokens[token_count++].type = TOKEN_AND; + p += 2; + continue; + } + if (strncmp(p, "||", 2) == 0) { + tokens[token_count++].type = TOKEN_OR; + p += 2; + continue; + } + if (*p == '|') { + tokens[token_count++].type = TOKEN_PIPE; + p++; + continue; + } + if (*p == '>') { + if (*(p+1) == '>') { + tokens[token_count++].type = TOKEN_REDIR_APP; + p += 2; + } else { + tokens[token_count++].type = TOKEN_REDIR_OUT; + p++; + } + continue; + } + if (*p == '<') { + tokens[token_count++].type = TOKEN_REDIR_IN; + p++; + continue; + } + if (*p == '&') { + tokens[token_count++].type = TOKEN_BG; + p++; + continue; + } + if (*p == ';') { + tokens[token_count++].type = TOKEN_SUBSTITUTE; + p++; + continue; + } + } + + /* Collect word characters */ + if (word_len + 1 >= word_cap) { + word_cap *= 2; + word_buf = realloc(word_buf, word_cap); + } + word_buf[word_len++] = *p++; + word_buf[word_len] = '\0'; + } + + /* Add final word if any */ + if (word_len > 0 && token_count < max_tokens) { + tokens[token_count].type = TOKEN_WORD; + tokens[token_count].text = strdup(word_buf); + token_count++; + } + + free(word_buf); + return token_count; +} + +static void free_tokens(Token *tokens, int count) { + for (int i = 0; i < count; i++) { + if (tokens[i].type == TOKEN_WORD && tokens[i].text) { + free(tokens[i].text); + tokens[i].text = NULL; + } + } +} + +/* Parse tokens into pipeline structure */ +static int parse_pipeline(Token *tokens, int token_count, Pipeline *pipeline) { + memset(pipeline, 0, sizeof(Pipeline)); + + int cmd_idx = 0; + int arg_idx = 0; + int i = 0; + + pipeline->cmds[cmd_idx].argv = malloc(sizeof(char*) * MAX_ARGS); + if (!pipeline->cmds[cmd_idx].argv) return -1; + memset(pipeline->cmds[cmd_idx].argv, 0, sizeof(char*) * MAX_ARGS); + pipeline->cmds[cmd_idx].argc = 0; + + while (i < token_count) { + Token *t = &tokens[i]; + + switch (t->type) { + case TOKEN_WORD: + if (arg_idx < MAX_ARGS - 1) { + pipeline->cmds[cmd_idx].argv[arg_idx++] = t->text; + pipeline->cmds[cmd_idx].argc++; + } + t->text = NULL; /* Transfer ownership */ + break; + + case TOKEN_PIPE: + pipeline->cmds[cmd_idx].argv[arg_idx] = NULL; + cmd_idx++; + if (cmd_idx >= MAX_CMDS) goto done; + pipeline->cmds[cmd_idx].argv = malloc(sizeof(char*) * MAX_ARGS); + if (!pipeline->cmds[cmd_idx].argv) goto done; + memset(pipeline->cmds[cmd_idx].argv, 0, sizeof(char*) * MAX_ARGS); + pipeline->cmds[cmd_idx].argc = 0; + arg_idx = 0; + break; + + case TOKEN_REDIR_OUT: + i++; + if (i < token_count && tokens[i].type == TOKEN_WORD) { + pipeline->cmds[cmd_idx].output_redir = tokens[i].text; + tokens[i].text = NULL; + } + break; + + case TOKEN_REDIR_APP: + i++; + if (i < token_count && tokens[i].type == TOKEN_WORD) { + pipeline->cmds[cmd_idx].append_redir = tokens[i].text; + tokens[i].text = NULL; + } + break; + + case TOKEN_REDIR_IN: + i++; + if (i < token_count && tokens[i].type == TOKEN_WORD) { + pipeline->cmds[cmd_idx].input_redir = tokens[i].text; + tokens[i].text = NULL; + } + break; + + case TOKEN_BG: + pipeline->cmds[cmd_idx].bg = 1; + break; + + case TOKEN_AND: + case TOKEN_OR: + case TOKEN_SUBSTITUTE: + /* These are handled in execute_command_list */ + break; + + default: + break; + } + i++; + } + + pipeline->cmds[cmd_idx].argv[arg_idx] = NULL; + pipeline->cmd_count = cmd_idx + 1; + +done: + return 0; +} + +static void free_pipeline(Pipeline *p) { + for (int i = 0; i < p->cmd_count; i++) { + if (p->cmds[i].argv) free_argv(p->cmds[i].argv); + if (p->cmds[i].output_redir) free(p->cmds[i].output_redir); + if (p->cmds[i].append_redir) free(p->cmds[i].append_redir); + if (p->cmds[i].input_redir) free(p->cmds[i].input_redir); + } +} + +/* Execute a single simple command using CreateProcess */ +static int execute_simple(SimpleCmd *cmd) { + if (cmd->argc == 0) { + cmd->exit_code = 0; + return 0; + } + + /* Build command line */ + char cmd_line[MAX_LINE * 2]; + cmd_line[0] = '\0'; + for (int i = 0; i < cmd->argc; i++) { + if (i > 0) strcat(cmd_line, " "); + /* Quote arguments with spaces */ + if (strchr(cmd->argv[i], ' ') || strchr(cmd->argv[i], '\t')) { + strcat(cmd_line, "\""); + strcat(cmd_line, cmd->argv[i]); + strcat(cmd_line, "\""); + } else { + strcat(cmd_line, cmd->argv[i]); + } + } + + /* Set up startup info */ + STARTUPINFOA si; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + si.hStdError = GetStdHandle(STD_ERROR_HANDLE); + + PROCESS_INFORMATION pi; + ZeroMemory(&pi, sizeof(pi)); + + /* Handle redirections */ + if (cmd->input_redir || cmd->output_redir || cmd->append_redir) { + char full_cmd[MAX_LINE * 3]; + sprintf(full_cmd, "cmd /c \"%s\"", cmd_line); + + if (cmd->input_redir) { + strcat(full_cmd, " < "); + strcat(full_cmd, cmd->input_redir); + } + if (cmd->output_redir) { + strcat(full_cmd, " > "); + strcat(full_cmd, cmd->output_redir); + } else if (cmd->append_redir) { + strcat(full_cmd, " >> "); + strcat(full_cmd, cmd->append_redir); + } + + if (!CreateProcessA(NULL, full_cmd, NULL, NULL, TRUE, + cmd->bg ? CREATE_NO_WINDOW : 0, + NULL, NULL, &si, &pi)) { + fprintf(stderr, "psh: failed to execute '%s'\n", cmd->argv[0]); + cmd->exit_code = 127; + return 1; + } + } else { + if (!CreateProcessA(NULL, cmd_line, NULL, NULL, TRUE, + cmd->bg ? CREATE_NO_WINDOW : 0, + NULL, NULL, &si, &pi)) { + fprintf(stderr, "psh: failed to execute '%s'\n", cmd->argv[0]); + cmd->exit_code = 127; + return 1; + } + } + + /* Parent process */ + if (!cmd->bg) { + WaitForSingleObject(pi.hProcess, INFINITE); + DWORD exit_code = 0; + GetExitCodeProcess(pi.hProcess, &exit_code); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + cmd->exit_code = (int)exit_code; + return (int)exit_code; + } else { + printf("[%lu] %lu\n", GetCurrentProcessId(), pi.dwProcessId); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + cmd->exit_code = 0; + return 0; + } +} + +/* Execute a pipeline */ +static int execute_pipeline(Pipeline *pipeline) { + if (pipeline->cmd_count == 0) { + return 0; + } + + if (pipeline->cmd_count == 1) { + return execute_simple(&pipeline->cmds[0]); + } + + /* For multiple commands with pipes, use cmd /c with pipe syntax */ + char cmd_line[MAX_LINE * 4]; + cmd_line[0] = '\0'; + + for (int i = 0; i < pipeline->cmd_count; i++) { + if (i > 0) strcat(cmd_line, " | "); + + /* Add command */ + for (int j = 0; j < pipeline->cmds[i].argc; j++) { + if (j > 0) strcat(cmd_line, " "); + if (strchr(pipeline->cmds[i].argv[j], ' ')) { + strcat(cmd_line, "\""); + strcat(cmd_line, pipeline->cmds[i].argv[j]); + strcat(cmd_line, "\""); + } else { + strcat(cmd_line, pipeline->cmds[i].argv[j]); + } + } + + /* Add redirections */ + if (pipeline->cmds[i].input_redir) { + strcat(cmd_line, " < "); + strcat(cmd_line, pipeline->cmds[i].input_redir); + } + if (pipeline->cmds[i].output_redir) { + strcat(cmd_line, " > "); + strcat(cmd_line, pipeline->cmds[i].output_redir); + } else if (pipeline->cmds[i].append_redir) { + strcat(cmd_line, " >> "); + strcat(cmd_line, pipeline->cmds[i].append_redir); + } + } + + /* Execute via cmd /c */ + char full_cmd[MAX_LINE * 4]; + sprintf(full_cmd, "cmd /c \"%s\"", cmd_line); + + DWORD flags = pipeline->cmds[pipeline->cmd_count - 1].bg ? CREATE_NO_WINDOW : 0; + + STARTUPINFOA si; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + + PROCESS_INFORMATION pi; + ZeroMemory(&pi, sizeof(pi)); + + if (!CreateProcessA(NULL, full_cmd, NULL, NULL, TRUE, flags, + NULL, NULL, &si, &pi)) { + fprintf(stderr, "psh: failed to execute pipeline\n"); + return 1; + } + + if (!pipeline->cmds[pipeline->cmd_count - 1].bg) { + WaitForSingleObject(pi.hProcess, INFINITE); + DWORD exit_code = 0; + GetExitCodeProcess(pi.hProcess, &exit_code); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + pipeline->cmds[pipeline->cmd_count - 1].exit_code = (int)exit_code; + return (int)exit_code; + } else { + printf("[%lu] %lu\n", GetCurrentProcessId(), pi.dwProcessId); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + return 0; + } +} + + /* Execute a command list with && and || operators */ +static int execute_command_list(Token *tokens, int token_count) { + if (token_count == 0) return 0; + + /* Find the first && or || operator */ + int op_idx = -1; + TokenType_t op_type = TOKEN_WORD; + int i = 0; + + /* Skip leading semicolons */ + while (i < token_count && tokens[i].type == TOKEN_SUBSTITUTE) i++; + + /* Find the first AND or OR operator */ + while (i < token_count) { + if (tokens[i].type == TOKEN_AND || tokens[i].type == TOKEN_OR) { + /* Check if this is part of && or || */ + if (tokens[i].type == TOKEN_AND) { + if (i + 1 < token_count && tokens[i + 1].type == TOKEN_AND) { + op_idx = i; + op_type = TOKEN_AND; + i += 2; /* Skip both & tokens */ + break; + } + } else if (tokens[i].type == TOKEN_OR) { + if (i + 1 < token_count && tokens[i + 1].type == TOKEN_OR) { + op_idx = i; + op_type = TOKEN_OR; + i += 2; /* Skip both | tokens */ + break; + } + } + } + i++; + } + + /* If no AND/OR found, try semicolon separation */ + if (op_idx == -1) { + /* Find semicolon */ + i = 0; + while (i < token_count) { + if (tokens[i].type == TOKEN_SUBSTITUTE) { + op_idx = i; + op_type = TOKEN_SUBSTITUTE; + break; + } + i++; + } + } + + if (op_idx == -1) { + /* No operators found - parse and execute single command */ + Pipeline pipeline; + memset(&pipeline, 0, sizeof(Pipeline)); + if (parse_pipeline(tokens, token_count, &pipeline) == 0) { + if (pipeline.cmd_count > 0) { + if (is_builtin(pipeline.cmds[0].argv[0])) { + run_builtin(pipeline.cmds[0].argc, pipeline.cmds[0].argv); + return 0; + } else { + return execute_pipeline(&pipeline); + } + } + } + free_pipeline(&pipeline); + return 0; + } + + /* Split tokens into left and right parts */ + Token left_tokens[MAX_LINE]; + Token right_tokens[MAX_LINE]; + int left_count = 0; + int right_count = 0; + + for (int j = 0; j < op_idx; j++) { + left_tokens[left_count++] = tokens[j]; + } + + int skip = (op_type == TOKEN_AND || op_type == TOKEN_OR) ? 2 : 1; + for (int j = op_idx + skip; j < token_count; j++) { + right_tokens[right_count++] = tokens[j]; + } + + /* Execute left side */ + Pipeline left_pipeline; + memset(&left_pipeline, 0, sizeof(left_pipeline)); + if (parse_pipeline(left_tokens, left_count, &left_pipeline) == 0) { + if (left_pipeline.cmd_count > 0) { + if (is_builtin(left_pipeline.cmds[0].argv[0])) { + run_builtin(left_pipeline.cmds[0].argc, left_pipeline.cmds[0].argv); + } else { + execute_pipeline(&left_pipeline); + } + } + } + free_pipeline(&left_pipeline); + free_tokens(left_tokens, left_count); + + int left_exit_code = 0; + if (left_pipeline.cmd_count > 0) { + left_exit_code = left_pipeline.cmds[left_pipeline.cmd_count - 1].exit_code; + } + + /* Execute right side based on operator */ + if (op_type == TOKEN_AND) { + /* && : only execute right if left succeeded */ + if (left_exit_code != 0) { + printf("[[&&]] left command failed (exit %d), skipping right\n", left_exit_code); + free_tokens(right_tokens, right_count); + return left_exit_code; + } + } else if (op_type == TOKEN_OR) { + /* || : only execute right if left failed */ + if (left_exit_code == 0) { + printf("[[||]] left command succeeded, skipping right\n"); + free_tokens(right_tokens, right_count); + return 0; + } + } + + Pipeline right_pipeline; + memset(&right_pipeline, 0, sizeof(right_pipeline)); + if (parse_pipeline(right_tokens, right_count, &right_pipeline) == 0) { + if (right_pipeline.cmd_count > 0) { + if (is_builtin(right_pipeline.cmds[0].argv[0])) { + run_builtin(right_pipeline.cmds[0].argc, right_pipeline.cmds[0].argv); + } else { + execute_pipeline(&right_pipeline); + } + } + } + free_pipeline(&right_pipeline); + free_tokens(right_tokens, right_count); + + int right_exit_code = 0; + if (right_pipeline.cmd_count > 0) { + right_exit_code = right_pipeline.cmds[right_pipeline.cmd_count - 1].exit_code; + } + + return right_exit_code; +} + +/* Free history entries */ +static void cleanup_history() { + for (int i = 0; i < hist_size; i++) { + if (history[i]) free(history[i]); + } +} + +/* Add entry to history */ +static void add_history_entry(const char *line) { + if (line && strlen(line) > 0) { + if (hist_size < MAX_HIST) { + history[hist_size++] = strdup(line); + } else { + /* Shift and replace oldest */ + for (int i = 0; i < hist_size - 1; i++) { + free(history[i]); + history[i] = history[i + 1]; + } + history[hist_size - 1] = strdup(line); + } + cmd_hist_idx = hist_size; + } +} + +/* Print prompt */ +static void print_prompt() { + const char *user = getenv("USERNAME"); + const char *host = getenv("COMPUTERNAME"); + if (!host) host = "localhost"; + if (!user) user = "user"; + + char cwd[MAX_PATH]; + GetCurrentDirectoryA(MAX_PATH, cwd); + + printf("%s@%s:%s> ", user, host, cwd); + fflush(stdout); +} + +/* Built-in command handlers */ +static void handle_exit(int argc, char **argv) { + (void)argc; + (void)argv; + cleanup_history(); + exit(0); +} + +static void handle_cd(int argc, char **argv) { + const char *dir = (argc > 1) ? argv[1] : getenv("HOME"); + if (!dir) { + dir = getenv("USERPROFILE"); + } + if (!dir) { + dir = "."; + } + + if (chdir(dir) != 0) { + fprintf(stderr, "cd: cannot change directory to '%s': %s\n", dir, strerror(errno)); + } +} + +static void handle_pwd(char **argv) { + (void)argv; + char buf[MAX_PATH]; + if (GetCurrentDirectoryA(MAX_PATH, buf)) { + printf("%s\n", buf); + } else { + fprintf(stderr, "pwd: failed to get current directory\n"); + } +} + +static void handle_echo(int argc, char **argv) { + for (int i = 1; i < argc; i++) { + if (i > 1) printf(" "); + + /* Handle -n flag */ + if (strcmp(argv[i], "-n") == 0) { + continue; + } + + /* Simple variable expansion */ + const char *p = argv[i]; + while (*p) { + if ((*p == '$' && isalpha((unsigned char)*p)) || *p == '_') { + p++; + const char *start = p; + while (*p && (isalnum((unsigned char)*p) || *p == '_')) p++; + char varname[256]; + int len = p - start; + if ((int)len >= (int)sizeof(varname)) len = sizeof(varname) - 1; + strncpy(varname, start, len); + varname[len] = '\0'; + const char *val = get_env_var(varname); + if (val) printf("%s", val); + } else { + printf("%c", *p); + p++; + } + } + } + printf("\n"); +} + +static void handle_env(int argc, char **argv) { + if (argc > 1) { + /* Set variable */ + for (int i = 1; i < argc; i++) { + char *eq = strchr(argv[i], '='); + if (eq) { + *eq = '\0'; + set_env_var(argv[i], eq + 1); + *eq = '='; + } + } + } else { + /* Print environment - use Windows API */ + wchar_t **wenv = _wenviron; + if (wenv) { + for (wchar_t **we = wenv; *we; we++) { + // Convert wide string to narrow string for printing + size_t len = wcslen(*we); + char *narrow = malloc(len * 2 + 1); + if (narrow) { + wcstombs(narrow, *we, len * 2 + 1); + printf("%s\n", narrow); + free(narrow); + } + } + } + for (int i = 0; i < env_count; i++) { + printf("%s\n", env_table[i]); + } + } +} + +static void handle_help() { + printf("psh - minimal bash-like shell (Windows)\n\n"); + printf("Built-in commands:\n"); + printf(" cd [dir] Change directory\n"); + printf(" pwd Print working directory\n"); + printf(" echo [args] Print arguments\n"); + printf(" exit Exit shell\n"); + printf(" env [var=val] Show/set environment\n"); + printf(" help Show this help\n\n"); + printf("Features:\n"); + printf(" cmd1 | cmd2 Pipe output\n"); + printf(" cmd > file Redirect output\n"); + printf(" cmd >> file Append output\n"); + printf(" cmd < file Redirect input\n"); + printf(" cmd & Run in background\n"); + printf(" cmd1 && cmd2 Execute cmd2 if cmd1 succeeds\n"); + printf(" cmd1 || cmd2 Execute cmd2 if cmd1 fails\n"); + printf(" history View command history\n"); + printf(" Tab Try command completion\n"); +} + +static int is_builtin(const char *cmd) { + return (strcmp(cmd, "cd") == 0 || + strcmp(cmd, "pwd") == 0 || + strcmp(cmd, "echo") == 0 || + strcmp(cmd, "exit") == 0 || + strcmp(cmd, "help") == 0 || + strcmp(cmd, "env") == 0 || + strcmp(cmd, "history") == 0); +} + +static int run_builtin(int argc, char **argv) { + if (argc == 0) return 0; + + if (strcmp(argv[0], "cd") == 0) { + handle_cd(argc, argv); + } else if (strcmp(argv[0], "pwd") == 0) { + handle_pwd(argv); + } else if (strcmp(argv[0], "echo") == 0) { + handle_echo(argc, argv); + } else if (strcmp(argv[0], "exit") == 0) { + handle_exit(argc, argv); + } else if (strcmp(argv[0], "help") == 0) { + handle_help(); + } else if (strcmp(argv[0], "env") == 0) { + handle_env(argc, argv); + } else if (strcmp(argv[0], "history") == 0) { + for (int i = 0; i < hist_size; i++) { + printf("%d %s\n", i + 1, history[i]); + } + } else { + return 1; /* Not a builtin */ + } + return 0; +} + +/* Build completion list from available commands and files */ +static void build_completion_list(const char *prefix) { + completion_dir_count = 0; + const char *builtin_cmds[] = {"cd", "pwd", "echo", "exit", "help", "env", "history", NULL}; + for (int i = 0; builtin_cmds[i]; i++) { + if (strncmp(builtin_cmds[i], prefix, strlen(prefix)) == 0) { + if (completion_dir_count < 64) { + completion_dirs[completion_dir_count++] = builtin_cmds[i]; + } + } + } + + /* Add environment variable names from shell */ + for (int i = 0; i < env_count; i++) { + char *eq = strchr(env_table[i], '='); + if (eq) { + *eq = '\0'; + if (strncmp(env_table[i], prefix, strlen(prefix)) == 0) { + if (completion_dir_count < 64) { + completion_dirs[completion_dir_count++] = env_table[i]; + } + } + *eq = '='; + } + } + + /* Add executable files in PATH and current directory */ + const char *path = getenv("PATH"); + if (!path) path = ""; + + WIN32_FIND_DATAA find_data; + HANDLE hFind; + + char search_pattern[MAX_PATH]; + sprintf(search_pattern, ".\\%s*", prefix); + hFind = FindFirstFileA(search_pattern, &find_data); + if (hFind != INVALID_HANDLE_VALUE) { + do { + if (!(find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { + if (completion_dir_count < 64) { + completion_dirs[completion_dir_count++] = find_data.cFileName; + } + } + } while (FindNextFileA(hFind, &find_data)); + FindClose(hFind); + } + + /* Check PATH directories */ + char path_copy[MAX_PATH * 2]; + strncpy(path_copy, path, sizeof(path_copy) - 1); + path_copy[sizeof(path_copy) - 1] = '\0'; + + char *dir = strtok(path_copy, ";"); + while (dir && completion_dir_count < 64) { + char dir_pattern[MAX_PATH]; + sprintf(dir_pattern, "%s\\%s*", dir, prefix); + hFind = FindFirstFileA(dir_pattern, &find_data); + if (hFind != INVALID_HANDLE_VALUE) { + do { + if (!(find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { + if (completion_dir_count < 64) { + completion_dirs[completion_dir_count++] = find_data.cFileName; + } + } + } while (FindNextFileA(hFind, &find_data)); + FindClose(hFind); + } + dir = strtok(NULL, ";"); + } +} + +/* Try tab completion */ +static void try_tab_completion(char *line, int *pos) { + /* Find the word at cursor position */ + int word_start = *pos; + while (word_start > 0 && line[word_start - 1] != ' ') { + word_start--; + } + + /* Extract the prefix */ + char prefix[MAX_LINE]; + strncpy(prefix, line + word_start, *pos - word_start); + prefix[*pos - word_start] = '\0'; + + if (strlen(prefix) == 0) { + /* Tab on empty line - show built-in commands */ + build_completion_list(""); + if (completion_dir_count > 0) { + printf("\n"); + for (int i = 0; i < completion_dir_count; i++) { + printf(" %s\n", completion_dirs[i]); + } + printf("%s", line); + for (int i = 0; i < *pos; i++) printf(" "); + printf("\n"); + } + return; + } + + build_completion_list(prefix); + + if (completion_dir_count == 0) { + return; /* No matches */ + } + + if (completion_dir_count == 1) { + /* Single match - complete it */ + int match_len = strlen(completion_dirs[0]); + memmove(line + word_start + match_len + 1, line + *pos, strlen(line + *pos) + 1); + strncpy(line + word_start, completion_dirs[0], match_len); + line[word_start + match_len] = ' '; + *pos = word_start + match_len + 1; + cmd_line_len = *pos; + printf("%s", line); + for (int i = *pos; i < cmd_line_len; i++) printf(" "); + printf("\b"); + } else { + /* Multiple matches - show them */ + printf("\n"); + for (int i = 0; i < completion_dir_count; i++) { + printf(" %s\n", completion_dirs[i]); + } + printf("%s", line); + for (int i = *pos; i < cmd_line_len; i++) printf(" "); + printf("\b"); + } + fflush(stdout); +} + +/* Simple command line reader */ +static char *read_command_line(const char *prompt) { + printf("%s", prompt); + fflush(stdout); + + cmd_line_pos = 0; + cmd_line_len = 0; + cmd_line_buf[0] = '\0'; + + int c; + while ((c = getchar()) != EOF && c != '\n' && c != '\r') { + if (c == 8 || c == 127) { /* Backspace */ + if (cmd_line_pos > 0) { + cmd_line_pos--; + cmd_line_len--; + memmove(cmd_line_buf + cmd_line_pos, + cmd_line_buf + cmd_line_pos + 1, + cmd_line_len - cmd_line_pos + 1); + printf("\b \b"); + fflush(stdout); + } + } else if (c == 27) { /* Escape - check for arrow keys */ + c = getchar(); + if (c == '[') { + c = getchar(); + if (c == 'A') { /* Up arrow */ + if (cmd_hist_pos > 0) { + cmd_hist_pos--; + strncpy(cmd_line_buf, history[cmd_hist_pos], MAX_LINE - 1); + cmd_line_buf[MAX_LINE - 1] = '\0'; + cmd_line_pos = strlen(cmd_line_buf); + cmd_line_len = cmd_line_pos; + printf("\r%s", cmd_line_buf); + for (int i = cmd_line_pos; i < cmd_line_len; i++) printf(" "); + printf("\r%s", cmd_line_buf); + fflush(stdout); + } + } else if (c == 'B') { /* Down arrow */ + if (cmd_hist_pos < cmd_hist_idx) { + cmd_hist_pos++; + if (cmd_hist_pos == cmd_hist_idx) { + cmd_line_buf[0] = '\0'; + cmd_line_pos = 0; + cmd_line_len = 0; + printf("\r"); + fflush(stdout); + } else { + strncpy(cmd_line_buf, history[cmd_hist_pos], MAX_LINE - 1); + cmd_line_buf[MAX_LINE - 1] = '\0'; + cmd_line_pos = strlen(cmd_line_buf); + cmd_line_len = cmd_line_pos; + printf("\r%s", cmd_line_buf); + fflush(stdout); + } + } + } + } + } else if (c == 9) { /* Tab - try completion */ + try_tab_completion(cmd_line_buf, &cmd_line_pos); + cmd_line_len = strlen(cmd_line_buf); + } else if (c >= 32 && c < 127 && cmd_line_len < MAX_LINE - 1) { + if (cmd_line_pos < cmd_line_len) { + memmove(cmd_line_buf + cmd_line_pos + 1, + cmd_line_buf + cmd_line_pos, + cmd_line_len - cmd_line_pos); + } + cmd_line_buf[cmd_line_pos++] = (char)c; + cmd_line_len++; + printf("%c", (char)c); + fflush(stdout); + } + } + + cmd_line_buf[cmd_line_pos] = '\0'; + printf("\n"); + return cmd_line_buf; +} + +static void init_command_line() { + cmd_hist_idx = 0; + cmd_hist_pos = 0; +} + +static void free_command_line() { + (void)0; /* No dynamic allocation in simple reader */ +} + +/* Main loop */ +int main(int argc, char **argv) { + (void)argc; + (void)argv; + + /* Print welcome message */ + printf("Welcome to psh! Type 'help' for instructions.\n\n"); + + init_command_line(); + + char *line; + while (1) { + print_prompt(); + + line = read_command_line(""); + + /* Skip empty lines and comments */ + if (strlen(line) == 0 || line[0] == '#') { + continue; + } + + /* Add to history */ + add_history_entry(line); + + /* Tokenize */ + Token tokens[MAX_LINE]; + memset(tokens, 0, sizeof(tokens)); + int token_count = tokenize_line(line, tokens, MAX_LINE); + + /* Execute with && and || support */ + execute_command_list(tokens, token_count); + + /* Cleanup */ + free_tokens(tokens, token_count); + } + + free_command_line(); + cleanup_history(); + + return 0; +} diff --git a/psh.exe b/psh.exe new file mode 100644 index 0000000000000000000000000000000000000000..6a489fbeaa65787423413730bc126c7b6c1493c1 GIT binary patch literal 75445 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%0A=7>Z%MIQ`k~j<`3$e!x*|hi5f+P(Rph9D9!1x+k z%7bUf@H@DZVvmo|@4gCxN#I};Pt!j3^y|x^e1N&|94ZY?Lb$WYycPoCH!w7_Kzuep z;=xns)UoI@czYkaiuE5f`^1k!So}iI&JOg&K?)waAweu$np1Ne(m9;gT!)m+U+D0v z|6Kk*Y=Nf6)oW^No$b!L^+_{rv+A0fB&V~@wWhJ%?P_z@<2ybsXJd1H3!rtbk>Jq1 zwho`1Szjl)Y8zT4*M>&7)Zl7rm0Zp1BzzCZ-O_fiRFPL*EX`Uuwf!?vmNd1U6{fc5 zposr-@VE6QbT6NFNV{=%;FNGy00z35=K7k(CRd%=-C|zja+_;CZEdb*x4Evd%~ea( zCrGoT;_|XWX;D>SWkqFqQAuebxuh0N`A3Lq8)}-@xYQ;@>(;65w`Q8Bw$s2awYE2y zXPVbGHaD)VX)>>_X>XX>)Ofec+zwTm%!x}Ho9kNEw@;rS&Gj@kxo6^QMzt+#*VZ)G zwP#9Zb8Ve@1;n|-Oh^7OUZ{r_tcv}qXlsP@>s#9HZfstoiX;J#ENEU))3&A^q+~qY zc-BJQcB+PL6*`4N2-@+^rrrc%s5Q$6OEZ2#S?1 z*)79x!jSS>TV2g{0)u2HI@u%BX>2A|9A}zf!>*O`XQfux)ZV?Ot;N%fFqH9Z=4sPH zgf86Rs`a>CGNiGdt~PVKr?%GRs%uxqG&QueyCqM%t4&(sY65E)Rlv^*S4^9B z#~tP-S3MF|&J>Zxi7bq#)=i&bZoj*+wUx4#B;Z_nA_3mG?6dpV`;8dR3cr` z<0URyP#{`KQgw?PeN$6Qd%L-%-rU}}rn#}c5glBu%bZwUT3%HJ-W4@%?#5BEDmWIt zS>|y_X*jEKx^X^(a{y-<^jSZ1eX8AzkG8oR5g`=x=n^s~NO^U2kgVN2wbk5E({65V zF{86w9$~Sb|~K6gdl%Tj=;n1*9sW5tw6h<(uA7P&x?tEI3hnL_8~)FOq@6J_L#?`BFc0z z+oJMO(&zI+Su6PMQ)6PtH9COWS_#+dgKG?l***n{gc{}l{=9#^TH zbQ;F9FeX9Yu5E4xI@vGwOTpmxag}Ofj9z_VOzg@Dpx+l`D2$TQ0X~QG8Q`oVor+@i zkDJ$}U+j%6eK?}@Aw$Hgy1sdb=k+gxJO!xpMt12RGCWLAT-0_30_og;2ZN~;P(?A% zM9=d^-u;l_{w{sV!x8$${R>{I?mHY~n2#FKa$f(umze!&55@T}W8H=L=Eo#1c*xMD zU+#@8Ld&+kg8t^i;CXvYqG%W~O9yBfLGxk~Nenr5cZ1H!$AiJOAVWU!>Wg9?*Ds3M z2J1hrujI-WMYYRXvriAwyU+M1g27*tB53n4aHAh0KY9)MF_!x#z!QD _4Qk1>=) z>0cWLnjfYA9YBR4SVd8v9fmZQwqPcJS2c9pvLhHQrS&>BZt|44$%~0EjoG!y5YeWO z>DUArP`)>22QS@0r4oXb#5~PmYU$1iTIc6QtsMcgXhh8R5uy4w6#^9_lp^AEilD8E zptWNPs&7;XsnSb!n8Xb~H0%!s@1Tx=*{6u|O100tm?Muz>o>&oJ|3MvZxg-&#eGu<2;6XFV(X+22vfDZC+G8FO@_&tvIdU z{CNI}z$5-~!2K+)ME@b`N4ycmF{|`VG12*bpo&x-U59v1qXINaI1MlI#*DH9C-n!N zsH0c;ME_x;{}B1mYbcCy=zTHv{1^w+2urQ}sC#3qfXn)CMAIF+g2AhKUb6lh9l}Ef zZ{&grd~iW8T@)4J*M;0uqK#&If|KqI-w6gQafR(2`UNpq#+Tz^H{jP09LH!h%%~$8 zPXqTWq#xAQuYv;$ql)pDe(uITYwN(({M}&itK0_6KKdQpb+<DH8*5xrX3S+GGF*5+k*Y*RTGXrN6&h4PL9#?50^^s7(9g|QT zV_qDSP#9y*=Yr+QW5VM=3jlvR;fq*Az#0)I=nczQOMP)zQ__iI;5g3G{|N^F9@=&- z+7_|4`nH$^Ft6f(xlwnp`_b(|ejjoTaeS{nUP=lJ^e?9wgg7oV$RNOgK2qIFI5y1{muv@+oE- zCa_t9em*X;56%I{`aRyyqD-+y@7`Ae){SxqKfMF3Sr;=mszC6jIi?->`+@&F;e*e^ z`uR$~aVOeD=krmx81pH*r%^c{qjFwUQ^@E$YCe%)DSnE-&+Uv%xfco$oQ~o1JTqW~ zzZCfMaTW1z)fdPeFF+sHw&^n3iaxpnZExp%c^?glX@Y_&$;b53BsZN~K_i#bpj?N- zY|1y9+#)`=p&eemvg6}$H(UblO5oawDspNxx&$ZLTYYseJ=A z{lEDz98k3{co`{p5My5mtO2l7fOUkxIsh92Y)c4iH(*j9#*GlzNx)(O+Ythbe+6@C z!1jf}vH`OK)*Aw=11ue|BO$OJz#M=b4}qNptOBq&$Rm#IpCYCKD+q!00Ja~nk&g3# z>kokj&KWoha8}@4jk67B2hQy{pT&6y=P{fCoFCwv@RB4=!#M}%0-Q}a-8eVn+=laM zoOJBR^(f8&&JS_MAC}>X{A$Ow0B1G7zms1ZaNU4&GtNhFZo^5(SNU}hzaGK$Rh%dJ z{qJ!-k5k`|PqO2jhSQ2O9p`eK^*G%)J8^yk=d(EX;iTgbuD` zqN^BhFPdC4s=0tl9^E8I}) zYNZuQC=RhrA*g7imC2~nkqr0f3 ztqQA*O|J6Qv>=H5DaK2=viu^alPrS!HT=GILyfb>?Yzg+SbMjV7TZzR5LPDx*M`8{ z4G4ldG~@X9@H&1gwM*-3@hWfaYP|o@p{~{Gjhwvvk`s%#%`NChQ6h_`Nq8+P!bKp5y=%*qG2r;Mc<0Hm zsqA#&pYLF{sA+9=y7AgYDwXdWo3VJ@Sm$i5p|`HUb;@xB`Ik#It6SRKQlmtx{xx_& zyGw#@Y++gUM@?;;+u7K%+F9>ut`#1k7cXRN@&kFSUcNJs{oHkpEif(+nw{(FTj`x- zJ^nF};Wh2N<_W33zRAYnqxpYo*Ps)Y?#kPvJDSx3|{Rx}+Tv{DwyK`)cxA4N6b3(%M!@ z`Z_C-e|J%-$<-`L-=R`-Evlc9OQ`-HmE0|NV>Uq%%kWU7+ke}6J6?wsmL?~$x94CJ z2VTQZyl2OHKab;?GHN35YFZm-u5F*WzOi{GJUA24j+YqjnN7BtwwYE=FdKN{9EKkf zap7$$w6y6pZTr1=ufH}NRjLE-@N*cS23JjMSgQ?M`f_%LVPPJo#iN`J!`cBHaSr2) z=v#+ADGXiO1N_T5htcM*XToV80qu)DhtXpx6-wCtaH~ zCF#~ATT)xnhNLegJ)QK!q{B(CC!I_>mlRAIpFAn~`sC@!#mPTSel7Wp}7q-3TPrIe*CPI)-x^C|zG@{N?|Q~FYVk#ZvC zcPVF5{+4nfWoBx2>fF?YskNz3q&}1S{nW#$Z>J8X{wwwRwA<1gY4g%nr9GVXrL;Y1 zKTA847Mp%sx+8sF`l|H#8BG}-8QU^;W$e%RbH;<2ky+PgRb*|-+MNBZ?4M+Rke!xu zUrtZX`#Awx^f1e7kXKu0Shrc9u^zIXw4S$`Y`55wZ8^3ITf6N+o8R^&+f%mxvAt;P zx4mY2+jhqG4_ic%Dd`4SAvq~GX?4=tq>V{ilfIVpgQVXjT}X;dUYC3z`E>H%lB4bK zq&%5=BK1P5F0CZ3B5h6D7t)?h+nj!N#_^1(Oj~AJ=Jw1dGxuf|WR+&!ljY5NENgGp zYgun+8MCKkCuRRv_EXshviq~&%udd^J;$4KBxk~$>*g$+vvyARobS(hW6s~_=yR{i zP0Y>AU66Zs?&jQGx%+Z|lzTAu^;|6HSY^LnZ#7$|Sr=NrWqscIBkQYHo$VUiO*V^d zhAquD&(>n|!mEF_O-PDOvM0?+s!nQ1>P&h!DJ5loYDMZ|*u61zd+JwHznA)_)bpwT zOx34Fr(Khll2(C@Ab(sR;l)7PeNNPi&x2kAddzb)fT zhAwk_=GB?5%$7_~=7X7E$b35UJDK0hd^hvYnSalW$coAOOqM+>Gpiu0D(lWHSJv0F z{wM3XtiG(HS?^~3DeHXJM_H4yE!nfO*JXEQ`?J58{hjRRvj388&iUn>-_9AIYtH?A z?w51Fmiz78@8`Y%tzXG~HTR9&cXI!j``6qLawP|zcM;cm>jdjH*6Xb|TR&q>vSwQ6 zTIX9Ut;?;et#?`55c8W6^`EzX+4?o>x2@kt>>sqg0xP{?eaHGo>tB%tdfNn8EZvrG zE3?(YUZ1r+YJ1vt0C5noy>I)MZ9M#6mQ<6p1~ITX=}^*pNgpMpC$C6uPrfgCYw~|3 zKaspE`Pt-OCZA2dkQ`~h+J2Kg(LT$bZZEYjwclm$vit2%+Mlz(W`Eay*8UH>E@fQG z4Jjolccg4d8K3%%%x5z9XTFyCM&=(g&t+c7l(LLjrmQJhpUZwcdvA7M_AA*Zvwxrc zUiMJ-l$`lF)j5ykJdyKM&Vigj&fjxB%+bvmH|J`^OWvHuIZ|$X?oGMVa#M4&bLZzS z%w3$jBDX%bDfe@^Pv(9z_Xm^*74jPGbZdd_cH2Umvx;` z-D>SYwrsaPZtb-nvLCS@wGY^bQa((PQVprb)acZGX(!W8r2+Ln$N$SMKx?RaCZmD< zwdOda&xFWKF`#F=faUs-s{a)Ke_#PKbcmF6%(>_;k>DZ#=tMUwKdk_wgyk!IkdmYJadiszdXr2Q=Ik9Ei)VIu)WPZ zvwk&gabojZwhI{$RJOaZpL1rbycmLgdpAbRyEa1l6PJe8`t`bT>{(N981HGO2V(4$ z!{e)@H$<|Fetdfao|etSE1O!dNsD0^BUxDmL>pMSuEnz&3k)F4Zo>)ajLN;t-e^$p z4cPC82Ufj4Qf5tDjEx)8Q?Zx2&Qt63G`F=hH96~RXv4BJ-mskvd)K(H!{oZ9z^W`P zSd{N9D$5JIFDS2eVn1_%qzgf+$}4~c)JCOwRn>UFk6_%$8CYtlYs6BXL`;fwb7MKp z>O~6*omKh8U{zWe3aiMgtS-qbg%Igx2h_1j)EnNG9p${g)_f= z0k%C>SB27zV8V~sVkC5fie6AyRqZUwD}^+W(^*tr>6}-ZFGZ>tRj8_}C@rZbWvcM+ z$da8HDG1j#t3~ySzgBlnNEdfL^acEAk4QRmJ6%h|5I_@~cbA7f3gR z*H#u*FRENng`HhWEEbh7s&vj@gvco^DJ!X#Zd9xCO0kF7SzR)(xY}7-US3fpeOj$V zU{o*3tArwzg++ykNiy5q!UcszCHY8T=_VDmG{1retXTzC7DC#x@`940WzrNCOqMKJ zkY7-kFWsy{OBPfY&MT~R78IdHA@fd^bc9kp z%vhDq+ZU8CS%6rrAXTd7!aC(ubEVIyct~_+{X(+QV)P@77InZ*5sFpPZ7M#Qttzj$ z2>Tip&$1q&w7Ni=84jCUEzMFPvO?h<+1#=cY4+GC)ywl2RO6{e;Wi?j(kjUof>JPl z5jxF93y^E2Qj%Ixj=`X$h;`6#e$@ggS*_y+FTyZTh#eOSzrv*z)Yaw9t&&nyDCt~W z!h)oz1g1|7uc`{EN(-+lDyl3jtdP=GXyMXoXSy{xJ&goIhr6JmQpZt!ZE9t zRTP#MRzWbLS0&8}uO?Zn&biB+6-&!2rCha=60H(KW6+|wt8!MCI~OAt9b=#rRhE}A zq`VQ4kP#x91%l| z?3j=!;lw(GimKv8MRTQjDp-y_``l$1J4oV^r7&i(T2FlE;rC=PS`v-wRZ>ZKWmSG& zHJOyx&JVBUP3>~i+rukGQ(jvdUdx-N%T3F|D@9XYyFjg#6|yCzV}9q%FUAi@s-*Ja z)k!I-A=MSbt5a-AA=L}j>cvR~Md;NqeWbxh9vLc~#przTud`IDc$63jul#(BwuMd% zdkYE{OI6|Z8Zyi|QniYew|FT89WBEaje!`RWQ#SJA+xq6VU@6(l5tDJYDf~1waYX$ zveglw&WggyGHH1j0?ZjYPh1gJNp#r=K*?HCn2+#V8HPZuhauddL8x95I&R#lsV~c` zqK-u!TAV5hpKB~BsV;WHllTYUm88ilU6Qw~3bSBL(Vdu>V#=AGCe^42a#v5I7kn(O zRx7HCONy#(QmqPEUPxox(rUzlQ|7gxK&n&gG4z#{BMW)&PGh&^Qmc#b@jYU$Ewx+lQO)))Pt5$KX$;MK%3YG)4w4BdatE3jSj#6uB ze(}7yQfoL6YFnjyR2V5M_nkCuN^NS561LQ@VoFBU?P@iD%s{_bP$FkMCV_aqs?NI| z-gbXHB5{_HJu0$n7Zy&^Iu$JZzmOYey;{Zk?yBm1X@d$Q?JM&urF&HndpNz1Kb+Fi zK-k0SIQ`4w;WV!j4^K3!;}53%zJjdDpF~4J>2vurFas{79Eo6D+5OzAC3zLtT1rX7 z08ETXG|@ZfwzM?S3Ibm@;7{`M^Zp1A{d$A5h#u{2saO?=!25FiT62S0g&7qXUjc|! z0Ag_(~Sd&*#n8+3$q;b@IZS+AxkFfvPA&q+$#byqe0ld{4gPB$*%rWOA;_Luv;x6xKcpZ=)1tRvg?@%DKps1Ug;vkhGbbY9+ zV)x?DthyR^4Zk6(Bk4YCb$h$OXA|slq-^{^6`%1wx4QjA6#+5-pv%j1Ior)Yy!Y z*(T%Q0b|4?9dzmtJgP5>09h3Vp>Jz+gh5sV(i8@11*AcNh%At!g`#f%Wc)TROahSw zXvAVRJfdV&kp&LiG6{7ZGvylfuZZvhfaD0j4Q5vyEq-NCdnP4+Xa>r2&^0;o2cuNvk5~Fd1F{ceTjJ8FuHe~t@4+P6#Fm^ z-$tw5q7{C5BKz5l&VeEUl{kKcOE2ss`&9Ts=0!4--os?(orJ=RZEgw=8Q}|svb+I` zaSZ)K_6dNHY@Y_)kyq41*rx>`**?uRZfsy`bE^gtGCP=FB(vIHltlc{h(>xx=%^Ha z2$>HFYm9`$NAvY)D$BDTJFIGJ+UhP(9t76M#`94{9frud626@JXhudKE#EiE`D*Y@{@TLELU_Nq2J&*d5XnG#VK8WI!qpw50$uHdB?}XftxMM zmoKM?02*W-AvS0k3B3K6;v`ioONM9e=2kR0X747yga5= zc~+i805I-4`T~r0M)WRte+Uue=qK+VSp`SyodZxqePW>& zBI+*28VY3{tv-YwVjdogK!Xs_^!D&@;^-804&abIBXs-dFRzg=BI+X1T8_GqAFYQ( zUI&_W+{|?!k&`5S2{=QFHHiK<068UEDbXU+A$i(>GfW;nH@+DAD7;1(Q;0k1KBVYg zfICb9LJpz(L`C-v?hWpd=Sb2ma=wl=UR;dK9}4@7AhVF?dPN?rs)(Q1hxp>hWHKht z=>6eufV0CQVpT+@s>K}CkGxKDFF$`YAWL>{#;6SD-!C=qIuYrY`9CYA^qF%5GUji(L$q*V#BqFn2<;j zGM`Gkl;{oTLoCIUokds-Jcl1bp1e?b_>yLbNYa-=H=OI+@VyK%GK#O7NYi;l zjbJf+poD$;Bv#;nlO(+iwq^w)JSfv4dGdi1F1Lufi_txC3=a}_(tSwLeJSpcf+2_S z@=8T_Oc+R$`ojNgZZvOLJ05#%AH36Xe$ z7T#g4t_Nfw3}OXj)n`Kc!v%nx3WKZx#C%&Q&SwGH5eC^xt->JR2BZ}Rm)Fp=HtdJv z5vC!>rPy%BMMU^U#qS$oA>4!cCN3v+ZBSqi$8-FbkN#fgwBG$m}(3=e#3F1 z0FTho$f`a5z;S4fhaH0W%K;o!*vE>Q1GzIo2Nw)GuB3hb#@SG&H?NBClg;tTD&LSdhkihZ zekX6se*ZgmC64TO^0w@EdK0KU$cAQsWgAl8P2LSZC^je}QWb zhKR7?F2#oOa~%IL6-%_svtd|Q$fOb@Z1xyADCF6r$U_@3lrKCnd5G9&V4qdYrz7`c zLS`9<;wJ~KhQ$w`70@~v)r{Cb^Jn3)WJXhYWQ5a6>j<3{f5S~J^y&@Qi+`R<9vz+h zZpTIFCF2mU`QY{M)hiK!D(jU!s$NHj>D4TMcT4e?9sZ&xXx6KTn+7z|qw^JV8RwJH ztL`H7Iv%bU*Q%Du?Bw4Tb3n_F(@Vx7y>3U_@VSfVf38#-85!FQ7;(HJ$GqKnBLp%h|%dc-U@6Yt|>k@jB1a#?g#$h5foEKl|`j?_9`!*i?Kp(cFC>hrQF;9)5 zsmM4PXKcOju*G{wl4_Xlm!MbiMd%d;v-7nk>iKKdwy#khm%v-Rv#rFyOF12(yGCUaQhFC<_eJQwSkavtf13g=?Dz^G*4g9% z^N6!|;ZqrhkXeC2Z_QS|w5PJcLgh5hQFxPM>6z4x-XcWAhu(r|CkaNn=te&n*acWStw)^Kmqa6hc!K5$vww`#bb)o^!f zxWA!t=OZI|M;<}6&*lxy_%<#26t&-|cQ<&N@5UFtRbIT`I4-mZ9~jVDU29VMv5{~t zVr1k!TN1t3V?7C52Y@Ic2glL&b)o$s*Zn7B=q^TwPf#!W2H~#}-UWzW{hYA;=s0OT zA~tkWLM$T}HF{mrC=)9vE2GR-A&-FA zdF)mV_iPRK_{-vM)o_1C!`-goUZmk}zAWx`4fov|?z9g$%o?c`8t#dg#XViaeUFBF zwubvsl{+70eg>S68)4NxQ;e`7{VLBLtI%Sk(UU(@U#SuH2y&EJb&L^Km065UAu@B_ zXTxHn=`Kd1Pf#zrL-=d-(KBVA@bc(5Y1tw+bY@s8{LtuiNn_j|tc!-lb4rDfM~tU- zT4xKxN&P);IfO?`x`>ub;=WJAy;{Tlq=x%>4R^<7ao?}uzEs2gl!m)rGsYEP7WZBa z_mvv%v=SI@`DhLIip%1DM8myK!~Lv=yY&ijKdRy0py7T_!#!QYed%TCKA_>=q~U&E z!+p7i`>M<0eoVu?Rl|Kq!@XX^z2UOBAJ=eqYq)=?;qKOOZ@nz;CpFwRXt>kTQ+UjG zYPfH>EbgZ?+&5~t8#LU%q2b)+xOZr{8#UaYRk`yqE(fhf8{@{Lrifj)ZdwPtbe7fcbQc@m-Z!jPD^bbKO^sp}QCdKS90hvBF=YkKHL- zgqKIhNjocILwECVS19i|6umBKgzXfR#b}=55b_9!{fH*tk7!2N9uX~<#C?l~dx3^K zy`2ot_g6LCw_g_bts3sd8twxc?k835JnEX!>f7(Cj2#eHl>sbFOQCs)+1tLjEqzCx}=z2B`AyhN=X;; z2#CE`Xdxh}do)q^xiKs+`gxU(h`JD7JnD9Rf>F2iBBD-}Swvlk%slF7_xWfUC!+2X z)XRQI_-pitO34;p9vvsGPQ-=|KSmgKX!N?Is7n`=MMR}og**ac-zT&XkkqegqK?)a zMzg$#I+c!yx)5GG>UMvEQMchDqE3}rL|ur?JnG&ZLwDhwPf#!We&MgtBPzu%ygWKi zTDFJ{-IQT5r|#`h^tz;|GYHBeqEh08JOX0hA+!*X)CM)`c(m*j(Q--LjT-I=8tzYP zxNp#K@4YPU(Hib%4R_kP2X>P3mOOQ{hWnAr;vTEvZqaamM#KFP4flb|;vT2ro~Yrz zTf=>u%AIE%{YvEHdK)!bvI($ z%V@fb-sTh3%TBvGWxc}pJdg9NM=CEd)*~?WyIl%A+M9 zqUDmfKcnGp)Nt?7aIaRmbH6*r@Vkh4RSOaGA)0b6&Iv8RbNF$kds^Dj%xpMDPpir- zVm?G>uKR*9bQiJl3F>9vCj2#eJf}npFOQCswp+x8ZrX5HD8D&cIfm}S?;S!ON%(z8 ztZON&PoJQDk`gb%@2bqg?;$dCzuz^6?*AwJ-YM)nx=&L+6qX+yC#^&A`v|LrT=#ZG zcc-%ZxHcq8evHHl+0}Vj*$oy_csHQ^6$#;lA7YpLCPl*0cY+y&#gxe0DC~1dkx6^f z(GYuX!)2j6Av667AaXPud5zNUF>tGDBqM-udnrx?f6UULn2gb;8c0M_tNE zVfoQ<(h8KwoTxFhMlUf|rqzj70+Mo0@DdRF1|gwvm+A0mNvl?D*4KMoo$H(!K*caqNp7qjJYIp@SyjnH90vEx{sNuC#!|Sw$ zSCfX<(Tm^}tKn6x;dM&Gt3ktS|5&_uUOWvPd3K5~wu-M)x|-`mF#Q}(7!Z5f8SqU& zI`ZUQ_akOEkA!%k1>_ojh11jJrE+^4Bc>RuymRhjpta;M*R$=M`& z$U_?Lr5f(*t`PSI4fi7&?iCvD^o>m}^Z!csEgJ3t4fj?J_kkUM6I|Dnsm= zIc;jIlbak^g&_iPRKV;b%&HQcvdA@1oK?gJX` zOEug#Um@;x4fmrO?$sLZ?kmLIs^NY_!@WYoz3B>ZpP}J?NW;BU!=1iWaV4TIQNz7g z!@XF;ea;o)Zqabxui;*x;XdsOaW`wY@6&L1Xt>k2MXrS96ExiSXt-xc7?ddYPj#xaHq#4uBklkq#n9L+@m$zpVn}n zq2W$njJgt*H)^==&~Q)GaNmA~xEnOwAJ=fVXt;M=A?}if`*sa?vxa-y72^J(Iy>ca zn{68I2^#LJuMl_o=2cicr}SvJ$7{ILSLCil)ScIG->Tsrr{P|3g}9&7aNnZg9;@L# z;|g&AO6IV6o*IX z@aVmWwG{it;d-Tm`0_6b3CG#$anrwY86VbaA#nEKgtEL9{aXRzy%kzDwzs#|)VjhcKLDI? zNDm;wsxtSla~!&pA;c%VRUaU|IA!kiMe#;wjU>KiEVp_U^};@or@qZa!t{n|@g8u* zlLBzs-EFlsZb|-P0O?N0I7GooZf6;i3CIqG4r{fxRg!<{FItrXM|^!+ZnYQ?`B$O> zhrX21z@tUR*#d|d&1DGvrk@VgJ3j(MJX^^)#{dz(&y^uGQav6<`4&J#k1gY50wVVE z$&fXGtPE?_1qdB56Su}TK*YZwAh-GsAk|^5egsG?Zz1En28ehjk|6;=a<^tE=Y^mGw79O(2oTlAz%M z2_jK6Xh?*x3Hz}AZwL~p?yZ`- z>zRlG{VjKv=UbqmYT4@fM?mzmb*6a|2o22Aya?oO;|U}g{ohFtOgJ>>fgm6`$R*ex zI%~X_=;{A}^q_&}K02Ku}4 z~HkpEE%EhDNMKgDlAd z{t69ggRVD0V?By|J|6ai2Rxg;8=vo@*-b6D)eazsJS%?+2tOOuoNom(i|WCS;w~V* zR#PC4dL#WdkkjbLwt5Cg>>>XEa)%igZUq;BoG|i;D9muYLGY}h5u!t%s1Nj)}!LKXmUFAeo0O0P*Ke0pw|~ z_pbt>#A7)>1jNq;Kb1674$W_XpeqMC1>_a4_m^ZVO+(|ViWTb2X>}enk9w`%0dm$u zE};WSCw0b`zM2nvKA#EDTxD8`uw9fDYAtW1lc2d2&B#`4uY*AJ|8g?24#d~kLqO=V zHRR8M=x34K>O~;v%He#L_!xPp{Xc=AM098ZI_w^Z&1jbac@&6^W48dI%UY)e zd_KhqTrYe6lSjQie=}`DjkJwc-!!d=V>!b4ejukjnjZt1@Q^2o27|T|{so9Wt$?-h zDiFTVyL=aPw0eIVH0vJct587dchQ;87l7#h`(xx@AjHdR;|D-q_vHB*5V|bQb3pX} z)G(h5ipGxOeIJ7bkq3!8(|i<&?+MoexxdNIz;_0fgU%(z9R!$QiG%Ng({SF->zb5WOqQobLj1rPrze@|Z!s zf?tzK7u!{X%Cl0Scds>GSENMyi@4E!*;t&R`ste%}pbZ1N_3JAwQu~l)ovS z=Br_r#*65ths;(Nmvb{lasJGB2@S{G+J*E z2ziFQz%g?DEUD5W7a8~kE`YimKZp4UI*_MF>wBmZL>0DimbKg|6#cZHNG&6E$X+TO zA*)XN5lPf7mPSh6I(vTr*T;<`$i9ivnO1 zLf{y>XjnNMb@Qkf%TsVg8n0uZ`d7cec<2`i4gMYHmw%7qhJ3SZQXVu41M^pOui4z( zBt^%CSk8)en2DH90QVtzC^$BEaZp|gU)oG!+{fBXp1MtqH*$f)f@IUFJ=e>|3Zga& zsxsJ@xZ}S1(0w3gTE~`V4o&W%Hf7RBE~YI&cDix}okY^Ww0LGa^}_WQgAlUnCPYHw z_7ghAob+6!y#%~ap@*`wH8?VfL1xgCq6||Q&PXw+jt-J?KCHTplEhO}2SapD-m-WL zUM;262SMT1az2FlqC|ekTH}adni)<~l1{LaY_N)rx2WETfJUMTlvATUj)G@_h$M_- zL*g-yc$9?sEy{$#|7atzG}oe)OgoHL3?~t-hx*KOExN?ecM{@EPrN6VVpfHf_|hbRK6BLjl z?MctXK^J9MZV39C>Hit8yRcQ~?}vlkF?)K~pUCUWWw@jXrp^&Ncu>GVLJ z*svXiw1nt`i#+iXOC;fZ=Nm8wZbW}jBwPMKS`MlMX_ib+-K23Sfy#bydx{JcW{YJ}VWv;**+(PiG0USvb4NKs z967qM)I@?=Y&DL~9u`L$bBAZdT(h-AB5{Wp2CWFCS8UbsOjZmM&Wm!!(ic0>A)-0) zIDltJb8LB@&$CO1kC-0d6TPUjtft5Ha&r(ml+G(hyVYpW=!WYt!P{X!6}^F2OLIY= zU8k3&2OG$ZJ(2AZd&F)LLfdL4j6@?oM^(-eXq9&$)QF{xI99`35m47CX{BY6_u%4M zFPIT?;h;jyE6OQE$5v7u&AYNbh#NYV!AQu}a^&uKZW|`$21oXBqk%ose0g*vl1<&B z>J$zw8bybNWZiL*E`^_QT~Q=A9cdoO@_8p+9+rvLflgSItc-mH?p7%xq{@us<`fW> zm^5K@J5G^u8YG}bcF=T7*#O!^oEs+WB=a)|bZnH2nkIS9AeW_>#wwr@`D}thCfVYK zfK&7bGMOofb%(re6*&5H8?`t#ceO(s*z|BWj9^*B=oGobOcvhMgn5wXMbROxAk34m z;XE}-g^{3LPmt&NRCIh;2~84NFTAHH#bDT04!w$F*r-;cd(zqQJkHucDVwy$8OTpB z>YQxU3&=uMg3u5l158itl{uZJ4%W7HZuSrZ-D9fubSM{ip6?i2>CA*7$I)3x_9~w6 z>v-F-*@a!}YGuZmt< z)-Br4YgH8@4f%3fv_)r_ORJi&p<|e%Jt%0txp459XfCvtrsw9wY-9h?gJOPq=}_ay z5!_XEEEaB}+UV1Km6E7;J1(YQm>!e1auuuxLd%JZSf>{Ktu$19WYr)>dKaCdjEq>c zwM^S}I`k5u2{c-%yeX?l)V8S|7AY4sAp%b^5N^a7=ZjwAaGVfzs%WE7mEj6Y47mzH z6}v1GD4}yj7|(H8;7nw-*%Hvx5N&OCY3U&p+v+x#w?~Q$8Pnpr55ZSenL3-6kg6y* zxPWR-FBw1Iz9QN-Vd&+Pu0D({XlCgOLwS|9BUXCaj3%$5YOPASM3VEXRmDzuRnNDS zNy!Oh*`@4D`Cpc0GDgn06_-4CXU&=BD2zni#i6JM7AiA%Dx;ExtTQPrdLDEvOe-uY zS}0Vq2GE?OQ9`@~D{)!{<8QgcLc0Md)!?yu`RDMd=cZa6 zE`0zhqelnmT!)AHD$98BN>b)Yh8ohT4}B#`j@Dh25}Gm9dD|BaTxmNVu&u|)eBlu5 zNsC^T-;b-@w`IFUC2Tp8dIXu*LbbR##;oJ?AthDMt)YHjVpExp)}+R4GIf*8!iy2C zg1IDI-LkmjZuvovQJ1PW-IPwR31FyPQTc(bzI1V1hZpKBf_g>=CKM>FGm#FdP(=dB-Wc9>1-+ zx7L)7g-Rf(3DqfWeqxEBrK!hT zER1S9eOoPy{33BMEKHH8W37$?GG%$`oC+blkqtLNpsg)0w@($)W=_DDLsGBa{SQ8* BFn9m} literal 0 HcmV?d00001 diff --git a/test.c b/test.c new file mode 100644 index 0000000..0bc68e8 --- /dev/null +++ b/test.c @@ -0,0 +1,84 @@ +/* + * psh - Test cases for the shell + */ + +#include +#include +#include +#include + +/* Test helper */ +int run_test(const char *cmd, const char *expected_substring) { + char output[4096]; + FILE *fp; + int passed = 0; + + printf("Testing: %s\n", cmd); + + /* Run command and capture output */ + char full_cmd[4096]; + snprintf(full_cmd, sizeof(full_cmd), "psh.exe -c \"%s\" 2>&1", cmd); + + fp = _popen(full_cmd, "r"); + if (fp == NULL) { + printf(" FAILED: Could not execute command\n"); + return 0; + } + + memset(output, 0, sizeof(output)); + if (fread(output, 1, sizeof(output) - 1, fp) > 0) { + /* Remove trailing newline */ + output[strcspn(output, "\n")] = '\0'; + + if (strstr(output, expected_substring) != NULL) { + printf(" PASSED: Got expected output containing '%s'\n", expected_substring); + passed = 1; + } else { + printf(" FAILED: Expected output containing '%s', got: '%s'\n", expected_substring, output); + } + } else { + printf(" FAILED: No output from command\n"); + } + + _pclose(fp); + return passed; +} + +int main() { + int passed = 0; + int total = 0; + + printf("=== psh Test Suite ===\n\n"); + + /* Test 1: echo */ + total++; + if (run_test("echo hello", "hello")) passed++; + + /* Test 2: pwd */ + total++; + if (run_test("pwd", "F:\\Paze SH")) passed++; + + /* Test 3: environment variables */ + total++; + if (run_test("echo $HOME", "")) passed++; /* Just check it doesn't crash */ + + /* Test 4: help */ + total++; + if (run_test("help", "Built-in commands")) passed++; + + /* Test 5: history (empty) */ + total++; + if (run_test("history", "history")) passed++; + + /* Test 6: && operator */ + total++; + if (run_test("echo first && echo second", "first")) passed++; + + /* Test 7: || operator */ + total++; + if (run_test("false || echo fallback", "fallback")) passed++; + + printf("\n=== Results: %d/%d tests passed ===\n", passed, total); + + return (passed == total) ? 0 : 1; +} diff --git a/test.exe b/test.exe new file mode 100644 index 0000000000000000000000000000000000000000..3570b2b65a4aca376e2b7b796e03107a97e2a946 GIT binary patch literal 56281 zcmeHw349#YndfV%WeZD40?WjJ32h=1Lwx9H9kyhmmejHYIvTZnfuY=1x2%SiTJ&Md zUMFLaomdf?XcDq<60)%ql3{a9Ame}`?2f?(LqZaVI2i|=iI*`ut=K#9%reBAkZAwk zJL>3C%SQ&6k{&)I6J5XG3{nG_( z|7Aa%f56f9!};BP;kaus7TpvJ^t*Zj0|U{7Yh%b2OAffg1Foh{zpFnQ3@y3*@>wO4 zw9m^}o1>8J*|6gt7G$h9hq04vh9l3BwS>JNNH<{5Vr;w^$0F>d*hxA%2b|<~V5cMB zT)4u;GxHtLs~#6FflC#r4mNoZ7lVxL$Q7qk{vBs*JNRVUQXaF-hOMDGr|Ft&QatE}XJz4Z4acEVea-L%ycUR5=R zaQV*Sl<@B6ApK80;nkG#>o4)Y2pGcKz%TSa^@O)f$ajWfsz~B()KT~e;aMT)zdE#J zZ2ygfCJ)1B<{feJxJlh~En~xn=jj+r#u7KOv9=QD@Ts$t_r1MpxbsDnfoKYOX7cC5XPhI)&lWzuqGm%q+jTJc4@bN& z6aRg6xE?;|DBSsT;PIy^X}W1+L_?z~&OYb@~gp|K}GoIzR=WTJg?a+2&Z;ysosy#{r>$IQsO{-fTPKb}LX zPTYnXz2ir6NKYaGIkRv`eI6o0_I#YvF_!|KbfIgK?`ctRDRnI2pIN=Bw`zr~m2b&Q76QGs4dN$JR{VN!&XYF?!$?>6_%ahZl zpS8%dq;Fpx(C$A#$dszb#_24%tnkT(xxFj$;H|T$i?gicg5jrkLga$Y^G2UOhm*3` zc1;$3MvopDU!#@lL3`vnZtu4Wcg;g}NJj#sib%RIAd*Js3=&dN>SO$3t+?CQEAmEXi6^|v4)OcX=uz9ypOJvHV%g&-)} zSC30FIu8&yfofBPKqQKf)JNV&JclBqm2^4g{4Tj=Bzcq^^Au6`9zzVakFKCfyWaYX z4@|rbgt5(RBvFz(TKuKqLk^&hbeA|&T|i%U*U^M`)cGZ<{wa_i8+44gOHxr%v;EkJ z^K)nr&x1Dwbn4lw(Cy(Oi!R;-_QbFLh@cbA74bIOess*a`v^*T_Vt6g#PtO69-HVxby86)0&!;vbqT5eJ%^uPPv`%H^He#=Q+uFz zAoow~`~Bpkyv8M)_$#_bJ{oN*ai!}()YpLk<)?A;hO9JvrWj0FJvD-}_whymAANM> zuD3@H=>-g}WAySjMe$eWLWYrfB_r)`QUJ`Rsy`WRe{=Xu?uhps3J7|WfUY_>de_@1 z_zFYJAxB(6W%E#!Z#frdAOtqSECP?%)@5#rQdawiJ~5;>75{~we=@_wrLprD@mB495xIg8ln&Jq`q05KG&yodzgu9INs zFpe7EGngqk>jI60BB5#-+!2lfeZsXqUJf&EPE~W09c*=fwHmUx4tB| z>@^@AB!P(X-(Cr{N2q=EiF&t^O2@6F$H(_M;pS12aRK_1-toz!95Uu}j@COz=1?ix z?8NZld?81AzVi>CnLB#jakRzZ-%lpWcD><7Ee} z?M1Y&lHqgNiDFbMLW6oPpDCKX<7BJR?vjPn2$9Beso|~7;|&jZz2l_c@QPxXAqyxa ziCIT-_yuBN_*@p~opU5=Nf}_1pT1Y*MSt>p`kP2WztL=P%|d5=$SI)7|D~{be+!}u zDUf7Dj0vV1WsYpa6EVpO6eyjc8V+}C=nQqkFFq%7d`{eNNa<arDoxfaP((=ptA{^>}S)U6xWI=pf)ujG#1x}huS}4}hpmIr+BG5yj3-Ee~g44M; zt--M<^({1*;ltf!2k*RF*qQ7j38GPzq`nD)M8pl|q{nt@VKT>$qYSoo5hIlGUSm5N zS8C>!Xp$oDW5|UNI8!=W86-!)Rrr^)V48h>xF~yR_+b8M(XyY$3NRAOUX#1*r-cs; zlOvvW3?D3B_AklTsd?Uvk;cdoYU#g48Lv4u?}=ysG0Sm$WT;4VCn@jc5M>Tg&0d*5 z(sWJ!Sa(S=Tjylvkj-Ys&`p^UQRH3cJRoL_#bS}=mj+l+ymOJ8iQszS3<&I#iIZ_NhMVwC& z8rox%bI#mXPZoQG3 zbkgZ0N=6%VX-K?l6TriV^6&pWVHOVGL;Y*=Wol2O*`xm4(f{Ne+f4(r(H-=6>`D5I zp-JHr&y9GG?)Y6%;m#&d8$RbM-1&P5HGD3&aM%9Gd9GvWQ4gl({i5B6}A;X{y`1i<=BKvzV-4 z1+Ap~#ow%8J0>T$|22%&Ov5S|D+@AC{Ky<{D7;A)QnQ$NoJ>P`lGIZE-UeHbB#*(& z$U^Z}@PX(P655@B9ieAUP(MK>l?8d_4G8y)!F$=O|n8 zX{;F(?j%jNtY3k(6kLqsqVVH~@OK4f3NwrxaJr8Z=|{_J_Ax)%1osA7(J;-Jpvq%dJ&|kfJmMGTl8-${xga2xTzkA zl3JnaJt*qYID`tQ-oy|Zjml^_@yW>#51(@uew-S;i414|7WKsE9LZf&KoU##sb8WX zRSNY%?j{qt@pQ!fKm<+>$AR9aaxUmYqWldWv(|Eyuv9{nKd;L9cn1-Hyri7-zk#=< zQ#$7>xG4Pi&+%6{r-vBgN_Wl&K#)6!7CB(i2HiOaAmij|pBM)Tf81`W%?YKX1CPF6j z_KqumZTWZvD3~;h#8VE#UJ0 zk_vu61u7mV{u{2w$9vQMt-^pptoiJKktXMyLhm|y-4M>Pc7V~UdvtL=vi9f->UQ~f zjk592MqWViMVCdR^rNG9=I(keN#krm?QK*7BH9t}K~aC-cJ7PhObpbh zo$>JzjDhw*AwV`s`5-(^*%J*IQMg93@wa58k+PB@<31_k2$yxFk$QN8C%j5rF@zRD z^QP`^;>TqeMJzz{LSSLEre`6Y3>iW(fhg5wFP*+tQBg!TE8O`E*(ryN zNT!1DJW(0)V<4ZLkl2Znsq5hM)4wp;J<7`{XB-l4hL7hd=A_SY_z*5m{d6V*{k7z8 zj^vPlvU%&i#2Qb10e#o$PbdzQ=Y*Ti$*N0H)tcfhR}*o_ zp=v|Ei92y^!jS6&B=IsTF^Be;sPyXvq?R$TaGY{pnq*4$pS zm21jg8ujI4fIc(ZhtYW~pO4HJVq~7;GY04<+vDi=lG2g7l2m^VWIUIJBS*#O-y zK!AM} zEOcSye9(_sMy^?j>o^g_sZ`?AT!|T&ZX+M{-v2#G?>B`{4hzY@KxBo?uO$|gy`pSG z_;1yJ(3O~pMt^X4!ZG|DCRz)Zy_%d5DFx*hAwQOgOw!A762+HXs!hHx-PyajMvl@PcGOpL=iR@3<=J0f z4=%tjdufd>5NA+i7wa)D*5!AOyof2E)WeV~HM(O56h+YeL*bLF(LO#pW32lsh%}k_ zVcA=wx8^T<5$)sFTpX^Xhc0)cx$@L(VNx?s&!th}d>VOMNKT3pWe)sf!X^kb!u~!VxMjg>zuce+G&eOf1Y0v9hN=w$){X;0)FYv{3uqS(jzg&rN$6by8Wqqv5}FWDIskVvCFpJ! z&<6x`LP8%F(60*UPX#m=ojj^SvS$FNWuMejm~SHpem}GDGY7G5t6G+bh>7q0lVv=D z+W+5gDKtiSk#86;PEu>73sSs9YVW(bo^8Os7yDxy>Lv8O!~g3Rh=ez8>gmzqT5wDG z;<6>dNQ7xxEVL;cPlRGxFP`HGY2kt1D4@a6RB#xeI}}fZ2R1EtEr`!zgYmv4p`nm# zagXbU1@RkP<+t2e#$wFnYWB3Yd7GBI8l%Zb&_z!M0j(#Q2)TNq{r!P~Ab)hkD^|Fmi;I+V`IBMD3D7Lo7m7rpEY#B%b&1pF_@S` ziM<{AY+1ae%1sZpCBpq7S8pI3Nyb9UXE9GO2+88E1%s}>KpbK23ik&#g%-o5iEwlP zakn}gOC$r4+moT#R$dW0^hRUwpKIe*xX%?xxB@yAhE1>Ii$xQlqiSslC;AXt!H}zT zXu%NSDQ^zOL&;zi2E?;e0pd#!|t%DA>sbAFN!nm@b3-iNkwfmz!K=RZGg2l#7@_o3bZ8L+M3;rf-Jec#8jvhJVtLi?ctXLH`r3wPn@LNVFzkyj@tkKstN z3}-bK6leXYAlCx~fHLVpaN@J}c?@cCWE}-X>rumnQ#bYq0S9hy5_hhjG+p3rg8m z=Jm?6);vv!OZ*41yIwdye`R|oaQ~B}|FJAjLH=-dOTmV$NI`z%Ay9pRgvq4uT+|~S z>OrGb(jcF>2!_1|dje%^$hYv<$BF*OvONVw!%lBOebz{UyRo1iY81hN`QVl7Ia1(k z%-dQ}3b@dJJF1Q!nVh_W465R<`QzCia}MWp%#w`*QP!N7J?b!BH=tc)lAGWp_uB7I zPIlplc2J+yQGh3JL_F*T{7Qmj%S6M4GNSPiaB2C0+EUgAIMAEdg1=}Lj4i@mkG<&S z$;tmBZNTl5wO6|Cwmh#qqjsE$R~u*$uc04KPWIy{>;PMSoNW0qr>CHJIH$3ow7px@$FaBlXmax3%ys)wx0%*5>1O0hdluuS$;^;3|E=Sy%`^-q~#RG;$N zNx&{7Un;tUf4=swa1R4_33$-v!d{A<>SH~V+gY~Bt*SBB0o;xKZQ$&dIJ>A_rNu&E zRzWdbV{tvT2^Z=-guM-R`UutEUXUw!d{pru*$!ZT6gU-xgUyBgW$ecQ-n0*a#)l;h$|De(v(F?q z`Ge~1K)o4M58L^0gBO8I_Hz?aw4Y3L2~IMs2ku&lo7K(J(0OIq^HZaJQ`LX3?ZlwL zdGBVl&-?Iu7VK&}4A?wO3`#?9JHMVozXy5c|W} zpT_>BJhU~j_i!+tmR0qo=0_h6^(%Q)`Gei-{J*w0{}0oxa0zXp2=_C?s;*lD{3 z#}@2e*w;(gdp3X%9ZjJ~C=qJJ3Ix^yB7Qj+Z*+LEf)Gmwv%hmR$3mgD@WxmGt29Gd zD?^F4Ks@0UOC)1--5w1lBcT?oT17%0+!`ksX6HR}`p?({*{uWM7KmW}oX+4E*=?b~ zW*eCOGP^Ar2ui-78p-j85~~LKIG3Pzs3$Z?bNOa0DurTfdyYR63JtP-Io**s*srEp zHT!XnTwjoD1&sYfLHLRT#Yqbm^V%DmHO)H|rf}ROuX~09S|Fj_mkjsZtA#{apQTIy z`%SP!Un~>|qMF3NPnO9`=D_u!riBJJhhtd&z;ETWcqpL_gtln(Q$Th#gG2Z)zOG@t zusUPcGFs@N-+;|x8rH#j!lQO2bOQNfdE{*g=zrjMkZ`f&9DHfT&Yl`KD7G9*m8jZ zr}VR0<6H}|W}$6ha8OHZ9SpHHaUCAO5=c0x4F+g^l@?lsk0JFPEU+;eORzAbwW|Pz zvSh(PPb{H@qZ_r}tHwOMKI1l_Vu!-%8r%2 zTK0O`MA`4kvdZ(zuPUEk{=xFH@>uy$`De=?DSxW`h4P=3pC~_DK3P7qVs^zf6*pD1 zRQ#agrxmYO{8PnS6*Jr)bU)z!l=~a*U$}qe{#W-o_aEIeDvK*`u3TQ(T-jc^y7J?d zpQ`*^<<}~ISUFMYsG3!!RsB`fqg6ksI$M=pJ-@o5`oq<|)x*`FtUgfvlj_&2n``_v z_tk8v8LRonn%~z{EE`((^s*z%9Luj;zGC^t<)2vo)#cw={?zj0%YVK6kIS#EEw5c$ z+gICP8>`(_`|aBA*B-6?d99;vUEQX-{<;V19Q2=CQ{8Xtu3u5H;&Us$ zwBqR%2Unb0@y{!qx6Hl8eamNP5z~cv5bX3u>8$c1Sl|QYHMWL_3f(fRvoJPQPr=j-mLmvm9u(w_4}(AREMgg z)k#?3!RoJ8|4sG&>i=H-)9O>z|6E;Fw9-HOZRCYfjd9mfya7%kuH%S+%oj zudKbM_J-P?rc}(mouvy)7nPQlx^Yd5gC#heDod4}DLY&CX4$+7S4Bz1!iq%|r4=XK ztkPMD;`g?HXA98!MHZKV?lNGFYhk_Rbr8jXWpPi6a*p)gpZ{+x;DRPO%)y41>L)KE#9R5 zcb2;rtG#z1x;Px9rv?`HZlv3{d`!rnLIDJY@dO@uSWNe;F-^xa3D;$>yegahvy_I` z*|Hondinw}mgSt89H41ZD2N9Y61dLchpd_LKFpz9>4lLfZa{O4+#G&x0P>yuJQz)G z#1s>R`K6VBBUir`>~ozuz7x+f1Rz0Hj$ln(a>GM4Rd|*rnC#J#1F>i%qV)#2N;92% z$gua!_$o~9SPQH!Z_}zqt-0M}xo+z0)_h%^P0V3J@_U+K0i#lz$KQ=9U^eGQ&cJjv z814zh88K;gG@ys)=-sP2yqdqU1+3bwnmm?VBpaq z&F^b#?IvaXc#ja-i3_L1Z*KFf26)^Wmg!up2o&i?W+Ly zYMraPS9Z2`tkgVRQ0htp2cg~8xpI|PTji%>$nRydjbg8_)mB_=6qE7WydKn{@GSm$!RWSBD>?N5hbvtGcvXS0QrRTH9N@*>y&drwz|XY2B?WTe`Kj&Q717 zz27KAV05qXbU~3WZ?hLMNoH&Cc6ggx8_|H-^#*ENqfZ8`%K&zHA#HnSQ)}}&Hs1h~ zC0jcho4k$e1_RpK(d}L7?b4c>QNzo<<7Xc*$~%4CnrG!IR$_oWt2!JE+ycyY&4k6Z6rAEi1^gP&CzP_jcyD-V+9R+zrZ zTI4qsRa=Xio4dSTAFDB--nHFYO=(3sz7Lasz{a zt2&e)+d2{O2-Mbgw2sG|{+3nE z4Q!`W40LO#CN5qy9Guor(WI9TCIitMo%}HRF>XqEtOT>qUvqd zLRD3kwpmMM)f!Q?-CC%s%F+&_R47zdUW4(S*4ToFWBsf%y|}!x%2e!2FRm;rHx=J* z6t6CCYDQMW@R2$n(KB>uEl9q;Ce~%(QNuuZH8!HR^=jz$I+|89zqQ;(hC7FK8(5yz zYawW+7Pe{{L~A2kZNoH;+SXVKVK=?St+kYpB&yY}vy}*|BS1Bux2v7qVL^a7&Ev#7 zErmpv_W;ybTfL14zx5Ubs-1>#mkptNjk({r+g9H0@l#?kx)#kqkz~!vY?D#Q&DLI~t#0*bNP~Sw zNe3o7t2%t1)-DgClZA~EHFT%>i5P_6V=okgKC}cHkny4Yy(T27$nCr@Vi9{uODn2k zc-n6iNv+AoY`_4EKyB-kBUV3)8fDb9);6}RY+!>{Ak;2p_Zcu!R^(3VH(AUm(ZiPV zDu!fK95;&PGy{3Dsa3Ri3<5E~>h|0QZzs-=NSp<7(m)n=;o-zK8(`)C+of@~7)3mH z`@0+2kO3p@yBfRLRs+N*r`zP@l$Hi8lhYYlFR96?rwfxO8r8{(sl4`)HRVjy46136 zb6^f!OKl{ZbLH0!{xu#S9?hbjh6A`5IjEAQHAJHkT0xNO2684Z=KE7j`m>x`GfnNw zs<0}MjXSD?k7ta_t9of0J%%7Pz-`P5Q`Fuxh&a(M49JA>GNJr~zI>~XP*7EsTwRy|t zU(q`I!&lc`jfXJYY$edM34K=U?5w$1LA=7TAa{1&xFhec95b@7zxMi~_u+9@PChR$ z55)68A&)3tb`??Nm#w&*RS0S5BySG;AZX{$hpd;+XRWiBLe}@TOR_*WYUMB|TY{s* z;rM;Q<#eExU|O1VIIib62kyjm7S0Q^K0zh?56j7-n+4f_>2Ta|!@Ao_H?3H=u5{@w zH{6irK*c8D5nZ}HG$q~aYr$YM_RnGGw4Kir%OBI_&*|)S`h)84ZGT1!p#SB+kAly( z=i;{p^xGeoy+`_8wLs(YrTi{TEE-KLjmLVH z;@7~7#n0%Lir)q=70=);4Oi4ut?${o36E1P@w3L|>-Bo;`A_xAs>(`Dzt>CBBUiD6 zwzjFd)L0G<|2O(~18W$4c0^Qt#cr8K;oWw5dvjJzq zk2v9*`o;-e&~MPP9Ucxh|4}xiai_2$y%8Zjn(4kcJ_n*SwOE6G)y3a~5XK|StQ4Yh zYglkN3RCtGbVwfhrSBY3DE}(^kWS}|A2)D>eJmvSh0?tVS3-CGt4kZA4-yEONuFaU zzs81hH*g+;K$&bv704I$z!Sc}L*VJYka?pGCBn`~O7s?pG*gE`!!dwtzL zpobsQq1Faf3s0@%trYOaQ5`}!p9GF>G2It}7wP_E;LIXKeY5+5xPpvnQ?aU37LUf5 zFvKa@=TTHO>_gTxHWi26015jH1QK|(C1#366+dUWUL>2IWUc{N z#LpBs(qHR=BjnkFXK#7}v7q6wi;;)&{Q2U!UYCcgWZjf~D6)h+c=ZhBenT8y;bGWB z*vExgqLl_+*=4r+f#b+;C|!umO{kEm4H7N#dl3{Dey3bv6-wzvy_(RAe(OTN%rF#F zGE)aewWss@#mSu6&Y#ld!H1aQ$w5OJ6kz)r3Bu)>+RlXY6maSl4z{+O{UAl83itQqt;{IOMa7!XlBBpvNy2T=`p_pSJpPBV*c|e5ivhBAby-=M49sY zNgfF#50KNgi}Cwf)Fls0sh7-kUq`u+r-y#QWf|4J)ACSW74qODZ4>@#yyNV2{^GIP z=s9ydMobZppnTK%Taj0iw!^`x0 zda{eLU5uBfwW@5D)D4MNK)vpGAp`-b7yzKj<ynxao3dZhaSUH2wjK^)VjbpKbtRErx*Og%<=jB7zMn+V9$i;h2=2>z^|)KlK>*ao~LS83N5CxJmLIL;OxNBh`N6RWXJ+>LY6^EM@Vu5AlofC zRe+qe;H&{;97eL*P{>1(unVLXfQGSMOeE+MI$Q<;(;3;Y56xDzT>1(Hof(^omaEM+ z>_^0yB1L7Sy>Kc8?o>9k%WUOC994g@2j|?*L@aGn^4w^aM=o}mL}C$GRmhV$dUpWl zF)lN3^t@qcL2Dbrk4gxo#avl3rKYl%F|@F2O7zI`Lr_y#z8+U;ykt*A{k(`?`VGIf zC5GSEtDaH4x8R`CVhW4lbp-t721gYRI5GAG=zda%C=Uuc^t zYFQxL0V#z!1diQ?M<5FyUUJFk&&BwCFYwP7;dQ#-X@w61LDQz}9M|pK8@M-QLXf9l zf)=#V+FrJ!1b^*g{%=SedM+f}fkr*W_LRiQ=Qz~jNP1&a_BqQfMs#jA3gy{5X`kn% zJoMJKY{#chdy#z<$_kY9lYE%22ye<4r_lWEp+0X^BupykwrMKcy zY*0jq*q}8g>k1WNlfTGI!e8rb+AAB5p+xjn{64pO!7^`Xmu5pSPTSNt;W5B7wLvA% z=X81K7QOy<7`@@ss*;QQbm}~&WEME&cY2>+TKvcr*LkvJ%Gi*jg6U)?HQ&H~0OlvH zvmK@QYc;jfi+)Qf?9BCw4~Tc$=z7h7VK!WVUIK@B-3nfRUcFMfUUeDudfcj))T#&g zxJxD9-}Bf7=p}GSuiH=)sou1ycKS~Fi@t#;{M7_5Q~T?`S@n`HDH5X@3~aH624%vUwyh>lryK+>-)N1Kw(R)oeChdLR>$MURspD^^jZ#xnzmg?mY^!s;`ZvAW*(u@Q+lGs@ppXj*JR;_zTV=xps8;f zO_c`)&h&aMgiPETeEy!M`$c4umoGpsTIV-ql0bialbTkLc~}5?ZMXov1P^vS@%!TORY!k0C{TD*$zylr`X7+=Pp3^dC&@^!5_IJv+f30rVA(%Gh}X(2p^s_FC!D)J%hr1$wj@l=sie9M79-iw z1_h+@D5_W>Zo0o^fmGdsO9_$Ob1#Xz&&K_jjr&m>_ZA!XqD$i5ZR392#{HO$dzX#- z+)LuV*2evWjr(yM_w@#MxmIuuYKeHZ&nEf?@b*1;*YIF(qAxjcFYZ?wyrc(RN(=Bz z-&8#75j7Rg1>`6hW99q7fX5G?xuo^fA`b0QXO2v%JAE}elkO__zKeRfPbhy)wFWEO z*GXmh%sAESRctusr@KP$D|Njts=YHsS#R%ql{^aKrY91(cNC=R%gBimBBN!Yik6Gw z?zC}##KwKEjr)EZ_tHz^PM?Le=vDcsjr$`u?uTvMYc7dH%ELD92W{LJ*|=}GB<>4s++VbDKWyXfHn_`7n?$WlnO5~OM&3}p zj48KB5B4c7z%zY&r+b;bV8AUuO)q1}tj0eknWgT3KaK9Hmw6ZUa=)njmAMD0d|7!p zGfwp)6&ns0EM?tndRubwS_W0LTom`mY}^NJ z+#@#bDI52pOX9xY#yw%r2AnT_nkKGJ8ayWY~1%;689Hv+;`i!@3e9E z*|_h$Bg1FZ!Efl2c1zR8YqKcM_;=a?yJ!s>;!N#3_<0G?#^n2kneph2!Lkku2 zCQW6`A5~hs3o$=*0WojLtYY3Iv(&w08r@aQzl(aggUVl-?an_b%$?UaS0-Iif1pD=%lpsotPs!*PAaOsng4QBmhslvPAk`jk8h;%-w~ zC`i@Awy0Y*jpbF;8FWC9?keiuMZMfU<*&>U zRoSh)oEfLOM#Y9>e#WTN^}492b1KRzqAKf@JPP8jQCcWS)u0h|GFnn9S}uxvu8n)W zjeDJqJKZ0&&SB4968C%?_a+po*tnm&B<@8v?kzU%O*ZbIGPui_ADbrTReLu) zsM@>9%hH4Nqie9kT~1qw*?l?{&PCi6q9;VoH=Z|SR_)y+v(){|)99|+`@5)@yG8jc zb6c)#Q(n%DQ(dfLW13z@*URpmY3>3ahuLM@k$0)wN*>j^-K9zk1*zI#i@L|AvAl{p zgN}+i6E7Kcx$k1sExdrJGh|j#XOdY)-G84(cNKN-qF!#d@>k}Fs;p67&WuxiLdAyT zMq3I{Po~jxwPFUX>CedYn6PQJ!}RRIC3zXa#xs#Y*8a2);aE2%;m>plGv6gYfvTbv zsK`92Y^cwdFT{iNbI-TF@h>2}9Cej*FCa4a>XAuL`C2zyi&I&v`k<<%AeD<0F9mTgR1zvkl~2!g^}V4$Vo52b z1iV*_KRF}gxkzO#Rjc|L8?S{nUL`hO?hD{G*T(CBjaP|{m&?Y>bpgCwHeUN}yj(V3 z^K87F7r<+wjn`u~Uh{0c=Gu6@iS~|)c&=#*f5~>S8}f*Dfj2X$x6p(Jf+~{sqJeg+ zC$Bj0+kg~ZM|sFKb<35JV7#A1(K3!|74%Qd5=8M0?7Dzf!)_~D~gOfBI)tXc_JS`@l#?+q(nr3uStnM?ac2T+C=E28o9-LwLU0U_hxX-n5 zKVaj&-^QJOza!!&v+lYFb=~!>t~9NF*q|dvR>d~%MK(TqCao=O(US#8*Z{vRS z9pe6wjeEY0d#;W9bMFxMaU1ts8+WIT`_u0b_Xlm(cXKdVQMH&?;4l`!j3(0i}8}~Cd?kO90`n~i!!F|xiJ!Rv5%ErC;9pWCb zaX)3_e$vMMZKGBGNtW-kaX)F}e!|B6l)+ujIEvG8DlcHHIgp~c?=GH9ta0Z5UoA!1NX)62BvzaPC z3!FwkXfre=oeaNI*&rYfS-5`$kcB7_dXY~fS`Qakc$sQF4xD<#szLcBKuBl<@;V@U zEZ+GI;V_p8=Q24bFlsFTgnm_M@TvyHWsz_-AWfiT*pS{OV~x5PAnNxr!WugPF(?bY zwlVeYENTVfL-iXhGCzb`v%PG6wxWZf z0Zzy8*Mn5c;`c`Y*hK=$Z3nud2G; zFs%ApK-y5tu>2JTM9rMBuLGnG5Cf+Y5H*Sunns{$dTt3aC#o2++JIVCnSFq$8MM&r zZb0_xy5oZ_cyC90H;pBzoO;jRNHX5XT67$M*#K%$L>Urp1H_sSKLJRerPhB2q}x*K z%YZy;!Fdi4s|Sw(vJka|JUC$!fQWT1W#=sX%zZD6Y}onzfLL{3OgN}5{e@9M0G+AN z!u?L*kmU_Jy8#)u;Cvd8Z|XkPdIAx=XpE&SINt%zegjAArPoKXK~Od<_AGGJ2#s`O z>=+<>^;!fu4ah+Y(vuIUPduFT=YSj1eP2-UkS6hJBU-auiGqzpnsf zrv=ghh!Iifn|dPAc*sg83Y?P`?jHq&5}Cp4vw#@+hf4n|$1!KWCjg;#Y}7go$Z?B2 z^ueTOERd6cBv8wUvEKnwuj6Qejrj01MwJ!`bMcd08YTfv`eHsH>iI*0GS&r%p*unD z1B5n%*KR;c0Ws)&5s(*k?u7kKK&U$~aGnQ*xQo~zoL2xD!cn$UKEmI|&KaWu%pC@L z`UmG)w8)}C2R6fE*8|dI;r<~&oEC@=kUgknXmPisgA9X_OfkyrBbhD08Mn~+A|UDx zFX_hE4**fmvkA!CfDkQ1!eWpwvCvsW3@tb-08!712s-@|$B5P4fT;Jt2%OIVV)fTw z1Cp@t`mUs7$nz5$WCD;}y%x>dA{dVqSuB4JI8@P)XExp#^q^jg^tlNT+6-PbfEam$ z%GTI$q6D$f*#*ck3*;d{1}*aJ1;ncRbAW8O)cP?XG{+ZxrRI->LWA{glERQM1)Q~( zTJ#wL8cP^GY#ty+EwT^(vllJ$R05|3NJflx0%FYo8X)5C38Icuj1a^^XB!}DT|(gO z0i;fs@LRZA66(Wrmwq?ABpBM5+@u9!fq_l<*t)z75A;UWd0^wlSZK4lii8LBN)`&F zkgQEOq$5qc#oJ=S1DgYpaL@-f{h>rC))E*9MnXV{2YN%A7&!nrK`%(h$ih!=7*7r| zyuC24_3)3)BWDi;Hid$CNgC7Ega_KwFw<%p3WBV+1>cGFwBviB-j%H#=u)&`C>Gij zjzbb*2Ca91gvHC>@uhonfr6%2U|K9WpwUbD#d_Kj{!(;^EZ$^LvTfTosv3-HnbU-%Ag}I@>})lw)kjza2r9w}L|?&a2j!mGjCHL@xrCphN@`6LpmL z2NHdF2QJeZ{cG@D?ImSYCl(q+HrD_roc=)UUNGrNgrfs^=Ug}u*oZG@06gRuKnMvU zghJvDhUi-yq--MG9|A3mVFH}l7PyAQz>Dv}M7TK?2**Q)6!FBWcqrBmp}ZuN!h(vX zw}cvSKm5d{R4=VW(WZeUURu!;;x7|XaI`>03~znpp13!(RhW3mAibR4kckjT6NR8f z;C8C6;e#eZY(fbHgF1?Zc#qK1ULzLjk8TdxD{0+rej4j-4D{TqQ_^~d0_w{hN^}kb zZwxQ>qCi5sFB$H+SG=f0qZi1~Ywno&UA2oz1H52H(^gZP3M4|U{b(5t0Sq!UtvNgp z^m(N()2|!0c2JwwC0H3sNX+JF%o7ZP8IMunO{26i3je1Q^A3eb2w?fVW(iv39nk?5 zS*-(&$yh8jkl+{w=g>g1Ukms1$iz#zVu9FJjoxE$kr_zOG*hQBQ%(}ON`FrjGLL+- z63IkC9YMwlhNJRbMC$c5jK76xIt=a!Z4gxW+qnlegP`%Q6@WK|cv?fNj!SAL2-Ozc zP1R+#qVL%vS6LI1#H9;8RRevziwSyF@@Ni6knXt_X4!5}hGJVqDup@~<)l<#Fs${*HGCpW zqj!rTity@cEmEeHX{A~KGKogS$)p=PE?@bi;Z4_Q*qLZjrq(GcJv8x+vScizlvP~; zbpyInG?XU7$UU;pp#Y*(Bjuq`RP{vX!vhRmRRDQc;dMviFdH&RG9*0P-q@_N4ky%# z+%CGMM)VS4G!U682)!v3K{q}{fd+dG#(6g(B~}c@oZ>uYF*(<49a=!5PuvE2;6h}- zFtReD6EnMQAuZZVhCt4wIKqomseqayQ2RBCDk~Nx)EJCMX#IhEqA@L=+!)`Au|U7J z8Sh`Eev!(!>#@LWm|fbJ3?| zu|gsdHKG*?S3`ziRDBtR5%KnDFc}GnVVB2f1PE<0PUDntN)sm3o|0sGCgOuctq)5U z6y_K^p(37qGpRjIu93-2cMe+1pxJ|&nu2r!g4{?OnHt$t(WJNDX`81oqd5dA!|R@q z!4Xv6)6)=AaF6Uq>ub2-t&k;KLVy5^Gh?3CL(q={L8?+i&F677x{DudY3vD02=I1)ux=-HdgC_U2 zdb)KTr|+1jHMbg{t3FCMDuu1;QT4f)@BpR~$^m-!LVil?T&UY1C-H%S%vP4qbu0gg zdoUn?0jp+;6G{oHo^WfAaVS zIzpS!ly}m{)p~r~8;T8tBIx#wZaq!8Y3(isSthGbi;coTb52na4@sr?nFej5Sgs`M z*rc82rI&!~AX}rSVVGFLmN@c02KkAPchv~d7`V`rlbR*?g?MeDz~(9Yd@h|_Q`IFg zc~*3@X-(AR8=2gwEf~Emg#*IIP>rl5+{*b)*{SJ%wGH8DXs`UFH+;)z);oX^!&E~W zp`N&Hqtr9GuF2cxS*NwO`#Nz;q^Y&bOW(3zXVQd%8G{3juk9Pd5grZZ(TE6Y8gI(c zx2BorRmL!dk8sGb$g;G~CWVrBVBlFXe~U)Yn0f~FZ$KzKc_#vRvZ-(G>4{*BhyFbf zj}o~|W=u~rh9Rf6f!^$eKrjFXAzBzq8j*;S#yUZgs=#EMHzs?5qhv%4TH-Neb5cu1k|WmcuNfm)j}a9C4Qe#u02VgBI2y;ZIoI82{L^+nzqI;)nL>*dWa{Z`shLB bE!7x#5UGBN1|Y^rol7)*qUAo&)8+pK@2Kut literal 0 HcmV?d00001 diff --git a/test_input.txt b/test_input.txt new file mode 100644 index 0000000..555d458 --- /dev/null +++ b/test_input.txt @@ -0,0 +1,8 @@ +test1: echo Hello World +test2: pwd +test3: ls | findstr .exe +test4: echo $HOME +test5: mkdir testdir && cd testdir && pwd +test6: false || echo "This should print" +test7: true && echo "This should also print" +test8: env