diff --git a/README.md b/README.md index d3686bf..0b91b92 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ # 42sh - A POSIX shell with a bad name -42sh is a shcool project aiming to implement a POSIX compliant shell in C. +42sh is a project aiming to implement a POSIX-compliant shell written in C with only the standard library. +Source de is fully documented with the doxygen format so you can easily understand how the project works by exploring it. + +> **Note** This is a school project, therefore it probably won't interest you if you are looking for something useful. ## Getting started -TODO - ### Build run this command: `autoreconf --force --verbose --install` @@ -16,27 +17,43 @@ run this command: then: `make` -#### asan +#### Build with ASan run this command: `./configure CFLAGS='-std=c99 -Werror -Wall -Wextra -Wvla -g -fsanitize=address'` + or for MacOS (Jean Here): `./configure CFLAGS='-std=c99 -Werror -Wall -Wextra -Wvla -I/opt/homebrew/include' LDFLAGS='-L/opt/homebrew/lib'` - + then: `make check` +## Project status + +### Implemented features + +* **Command Execution:** `$PATH` search and binary execution (via `fork` and `execvp`) with error return code handling. +* **Built-ins:** Native implementation of `echo`, `cd`, `exit`, `export`, `unset`, `set`, `.`, `true`, `false`, as well as loop management with `break` and `continue`. +* **Control Structures:** * Conditions: `if / then / elif / else / fi`. + * Loops: `while`, `until` and `for`. +* **Logical Operators:** Command chaining with `&&`, `||` and negation with `!`. +* **Pipelines and Redirections:** * Full management of pipes `|` to connect the output of one process to the input of another. + * Single and multiple redirections: `>`, `<`, `>>`, `>&`, `<&`, `<>`. +* **Variables Management:** Assignment, variable expansion, and special variables handling like `$?` (return code of the last command). +* **Command Grouping:** Execution blocks `{ ... }` and subshells creation `( ... )`. +* **Quoting:** Support for weak (`"`) and strong (`'`) quoting for special characters escaping. + +## Architecture + +The shell operates on a classic compilation/interpretation pipeline: + +1. **Lexer (Lexical Analysis):** Reads standard input (or script) character by character and generates a stream of "Tokens" (Words, Operators, Redirections). +2. **Parser (Syntax Analysis):** Syntax analyzer that transforms the token stream into a complex Abstract Syntax Tree (AST). This module strictly manages the nesting of control structures and enforces the rigid grammar of the Shell Command Language. +3. **Execution (AST Traversal):** The tree is traversed recursively. Redirections modify file descriptors (`dup2`), child processes are created (`fork`), and commands are executed. + + ## Authors +- Guillem George - Matteo Flebus - Jean Herail - William Valenduc -- Guillem George - -## Project status - -WIP - -## TODO - -# Autotools -implement functions in all .c files to see if everything compiles. diff --git a/src/Makefile.am b/src/Makefile.am index 210cac1..5f9fa1d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -18,9 +18,9 @@ bin_PROGRAMS = 42sh parser/libparser.a \ lexer/liblexer.a \ io_backend/libio_backend.a \ + utils/libutils.a \ execution/libexecution.a \ - expansion/libexpansion.a \ - utils/libutils.a + expansion/libexpansion.a # ================ TESTS ================ diff --git a/src/execution/execution.c b/src/execution/execution.c index 46bbfcb..28726e5 100644 --- a/src/execution/execution.c +++ b/src/execution/execution.c @@ -9,6 +9,7 @@ #include "../expansion/expansion.h" #include "../utils/hash_map/hash_map.h" +#include "../utils/vars/vars.h" // Refactored: delegates to helpers in execution_helpers.c #include "execution_helpers.h" @@ -18,27 +19,55 @@ int execution(struct ast *ast, struct hash_map *vars) if (!ast) return 0; + int res; switch (ast->type) { case AST_VOID: case AST_END: - return 0; + res = 0; + break; case AST_CMD: { struct ast_command *command = ast_get_command(ast); if (!expand(command, vars)) fprintf(stderr, "Error: Variable expansion failed\n"); - return exec_ast_command(command, vars); + res = exec_ast_command(command, vars); + break; } case AST_IF: - return exec_ast_if(ast_get_if(ast), vars); + res = exec_ast_if(ast_get_if(ast), vars); + break; case AST_LIST: - return exec_ast_list(ast_get_list(ast), vars); + res = exec_ast_list(ast_get_list(ast), vars); + break; case AST_AND_OR: - return exec_ast_and_or(ast_get_and_or(ast), vars); + res = exec_ast_and_or(ast_get_and_or(ast), vars); + break; case AST_LOOP: - return exec_ast_loop(ast_get_loop(ast), vars); + res = exec_ast_loop(ast_get_loop(ast), vars); + break; + case AST_SUBSHELL: + res = exec_ast_subshell(ast_get_subshell(ast), vars); + break; default: - return 127; + res = 127; + break; + } + + if (res == EXEC_SIGNAL_EXIT) + { + char *exit_val_str = get_var(vars, "EXIT_VALUE"); + if (exit_val_str == NULL) + { + fprintf( + stderr, + "Internal error: could not retrieve return value from exit\n"); + return 2; + } + return atoi(exit_val_str); + } + else + { + return res; } } diff --git a/src/execution/execution_helpers.c b/src/execution/execution_helpers.c index c0e1c04..12d4e1f 100644 --- a/src/execution/execution_helpers.c +++ b/src/execution/execution_helpers.c @@ -2,6 +2,7 @@ #include "execution_helpers.h" #include +#include #include #include #include @@ -13,11 +14,10 @@ #include "../utils/vars/vars.h" #include "execution.h" +// === Static functions + static int open_redir_file(const struct ast_redir *redir, int *flags, int *mode) { - if (redir == NULL || flags == NULL || mode == NULL) - return -1; - *mode = 0644; if (redir->type == AST_REDIR_TYPE_GREAT || redir->type == AST_REDIR_TYPE_CLOBBER) @@ -44,9 +44,6 @@ static int set_all_redir(struct list *redir_list) { struct ast *redir_node = (struct ast *)redir_list->data; struct ast_redir *redir = ast_get_redir(redir_node); - if (redir == NULL) - return -1; // TODO log error and free - int target_fd; if (redir->io_number != -1) { @@ -126,26 +123,60 @@ static char **list_to_argv(struct list *command_list) return argv; } +/* + * @brief parses string and returns the represented (unsigned) number + * @return the number contained by the string or -1 if number is invalid + */ +static int atou(char *str) +{ + if (str == NULL || *str == '\0') + return -1; + + int result = 0; + size_t i = 0; + while (str[i] != '\0') + { + if (str[i] < '0' || str[i] > '9') + return -1; + + result *= 10; + result += str[i] - '0'; + i++; + } + + return result; +} + static int try_builtin(char **argv, struct hash_map *vars); +static int builtin_break(char **argv); +static int builtin_continue(char **argv); +static int builtin_unset(char **argv, struct hash_map *vars); + static int exec_assignment(struct list *assignment_list, struct hash_map *vars) { while (assignment_list != NULL) { - struct ast_assignment *assignment = - ast_get_assignment(assignment_list->data); - if (assignment == NULL) + if (!ast_is_assignment(assignment_list->data)) { fprintf(stderr, "list of assignements contains something else"); return 1; } + struct ast_assignment *assignment = + ast_get_assignment(assignment_list->data); + if (assignment->global) + { + setenv(assignment->name, assignment->value, 1); + } set_var_copy(vars, assignment->name, assignment->value); assignment_list = assignment_list->next; } return 0; } +// === Functions + int exec_ast_command(struct ast_command *command, struct hash_map *vars) { exec_assignment(command->assignments, vars); @@ -207,10 +238,46 @@ int exec_ast_if(struct ast_if *if_node, struct hash_map *vars) if (if_node == NULL) return 2; int cond = execution(if_node->condition, vars); + if (cond == EXEC_SIGNAL_BREAK || cond == EXEC_SIGNAL_CONTINUE + || cond == EXEC_SIGNAL_EXIT) + return cond; if (cond == 0) - return execution(if_node->then_clause, vars); + { + int r = execution(if_node->then_clause, vars); + return r; + } else - return execution(if_node->else_clause, vars); + { + int r = execution(if_node->else_clause, vars); + return r; + } +} + +int exec_ast_subshell(struct ast_subshell *subshell_node, + struct hash_map *vars) +{ + pid_t pid = fork(); + if (pid < 0) + { + perror("fork"); + return 1; + } + + if (pid == 0) + { + int res = execution(subshell_node->child, vars); + _exit(res); + } + + int status = 0; + waitpid(pid, &status, 0); + + if (WIFEXITED(status)) + { + return WEXITSTATUS(status); + } + + return 1; } int exec_ast_list(struct ast_list *list_node, struct hash_map *vars) @@ -220,8 +287,15 @@ int exec_ast_list(struct ast_list *list_node, struct hash_map *vars) while (cur) { struct ast *child = (struct ast *)cur->data; - if (child->type != AST_VOID) - ret = execution(child, vars); + if (!ast_is_void(child)) + { + int child_ret = execution(child, vars); + if (child_ret == EXEC_SIGNAL_BREAK + || child_ret == EXEC_SIGNAL_CONTINUE + || child_ret == EXEC_SIGNAL_EXIT) + return child_ret; + ret = child_ret; + } cur = cur->next; } return ret; @@ -230,16 +304,25 @@ int exec_ast_list(struct ast_list *list_node, struct hash_map *vars) int exec_ast_and_or(struct ast_and_or *ao_node, struct hash_map *vars) { int left_ret = execution(ao_node->left, vars); + if (left_ret == EXEC_SIGNAL_BREAK || left_ret == EXEC_SIGNAL_CONTINUE + || left_ret == EXEC_SIGNAL_EXIT) + return left_ret; if (ao_node->type == AST_AND_OR_TYPE_AND) { if (left_ret == 0) - return execution(ao_node->right, vars); + { + int right_ret = execution(ao_node->right, vars); + return right_ret; + } return left_ret; } else { if (left_ret != 0) - return execution(ao_node->right, vars); + { + int right_ret = execution(ao_node->right, vars); + return right_ret; + } return left_ret; } } @@ -250,9 +333,6 @@ void unset_all_redir(struct list *redir_list) { struct ast *redir_node = (struct ast *)redir_list->data; struct ast_redir *redir = ast_get_redir(redir_node); - if (redir == NULL) - return; // TODO log error and free - int target_fd; if (redir->io_number != -1) { @@ -287,6 +367,16 @@ int exec_ast_loop(struct ast_loop *loop_node, struct hash_map *vars) while (execution(loop_node->condition, vars) == 0) { res = execution(loop_node->body, vars); + if (res == EXEC_SIGNAL_BREAK) + { + res = 0; + break; + } + else if (res == EXEC_SIGNAL_CONTINUE) + { + res = 0; + continue; + } } return res; @@ -294,20 +384,95 @@ int exec_ast_loop(struct ast_loop *loop_node, struct hash_map *vars) // --- Builtins --- +static void print_with_escapes(const char *str) +{ + while (*str) + { + if (*str == '\\') + { + str++; + if (*str == 'n') + putchar('\n'); + else if (*str == 't') + putchar('\t'); + else if (*str == '\\') + putchar('\\'); + else if (*str == 'a') + putchar('\a'); + else if (*str == 'b') + putchar('\b'); + else if (*str == 'f') + putchar('\f'); + else if (*str == 'r') + putchar('\r'); + else if (*str == 'v') + putchar('\v'); + else if (*str == 'c') + return; // stop printing + else + { + // unrecognized escape, print "\"" and the char + putchar('\\'); + if (*str) + putchar(*str); + } + } + else + { + putchar(*str); + } + str++; + } +} + static int builtin_echo(char **argv) { bool newline = true; + bool interpret_escapes = false; int i = 1; - if (argv[1] && strcmp(argv[1], "-n") == 0) + // Parse options + while (argv[i] && argv[i][0] == '-') { - newline = false; + char *opt = argv[i] + 1; // skip "-" + bool valid_option = false; + while (*opt) + { + if (*opt == 'n') + { + newline = false; + valid_option = true; + } + else if (*opt == 'e') + { + interpret_escapes = true; + valid_option = true; + } + else if (*opt == 'E') + { + interpret_escapes = false; + valid_option = true; + } + else + { + // invalid option so euh treat as regular argument + valid_option = false; + break; + } + opt++; + } + if (!valid_option) + break; // stop parsing options i++; } + // Print arguments for (; argv[i]; i++) { - printf("%s", argv[i]); + if (interpret_escapes) + print_with_escapes(argv[i]); + else + printf("%s", argv[i]); if (argv[i + 1]) printf(" "); } @@ -318,6 +483,18 @@ static int builtin_echo(char **argv) return 0; } +static int builtin_break(char **argv) +{ + (void)argv; + return EXEC_SIGNAL_BREAK; +} + +static int builtin_continue(char **argv) +{ + (void)argv; + return EXEC_SIGNAL_CONTINUE; +} + static int builtin_true(char **argv) { (void)argv; @@ -330,13 +507,21 @@ static int builtin_false(char **argv) return 1; } -static int builtin_exit(char **argv) +static int builtin_exit(char **argv, struct hash_map *vars) { int exit_val = 0; if (argv[1]) - exit_val = atoi(argv[1]); - exit(exit_val); - return exit_val; + { + exit_val = atou(argv[1]); + if (exit_val == -1) + { + fprintf(stderr, "exit: Illegal number %s\n", argv[1]); + return 2; + } + } + + set_var_int(vars, "EXIT_VALUE", exit_val); + return EXEC_SIGNAL_EXIT; } static int builtin_cd(char **argv, struct hash_map *vars) @@ -363,6 +548,23 @@ static int builtin_cd(char **argv, struct hash_map *vars) return 0; } +static int builtin_unset(char **argv, struct hash_map *vars) +{ + if (!argv) + return 0; + for (int i = 1; argv[i]; i++) + { + const char *name = argv[i]; + if (name == NULL || name[0] == '\0') + continue; + // remove from shell variables + hash_map_remove(vars, name); + // remove from environment variables + unsetenv(name); + } + return 0; +} + /** * @brief Tries to execute a builtin command if the command matches a builtin * @@ -376,12 +578,18 @@ static int try_builtin(char **argv, struct hash_map *vars) if (strcmp(argv[0], "echo") == 0) return builtin_echo(argv); + if (strcmp(argv[0], "unset") == 0) + return builtin_unset(argv, vars); if (strcmp(argv[0], "true") == 0) return builtin_true(argv); if (strcmp(argv[0], "false") == 0) return builtin_false(argv); + if (strcmp(argv[0], "break") == 0) + return builtin_break(argv); + if (strcmp(argv[0], "continue") == 0) + return builtin_continue(argv); if (strcmp(argv[0], "exit") == 0) - return builtin_exit(argv); + return builtin_exit(argv, vars); if (strcmp(argv[0], "cd") == 0) return builtin_cd(argv, vars); diff --git a/src/execution/execution_helpers.h b/src/execution/execution_helpers.h index 1716179..8a56e18 100644 --- a/src/execution/execution_helpers.h +++ b/src/execution/execution_helpers.h @@ -4,11 +4,18 @@ #include "../utils/ast/ast.h" #include "../utils/hash_map/hash_map.h" +// Special execution signals used internally to implement loop control +#define EXEC_SIGNAL_CONTINUE (-2) +#define EXEC_SIGNAL_BREAK (-3) +#define EXEC_SIGNAL_EXIT (-4) + int exec_ast_command(struct ast_command *command, struct hash_map *vars); int exec_ast_if(struct ast_if *if_node, struct hash_map *vars); int exec_ast_list(struct ast_list *list_node, struct hash_map *vars); int exec_ast_and_or(struct ast_and_or *ao_node, struct hash_map *vars); int exec_ast_loop(struct ast_loop *loop_node, struct hash_map *vars); +int exec_ast_subshell(struct ast_subshell *subshell_node, + struct hash_map *vars); void unset_all_redir(struct list *redir_list); #endif // EXECUTION_HELPERS_H diff --git a/src/expansion/expansion.c b/src/expansion/expansion.c index 4fea985..e8f8577 100644 --- a/src/expansion/expansion.c +++ b/src/expansion/expansion.c @@ -1,4 +1,6 @@ #define _POSIX_C_SOURCE 200809L +#include "expansion.h" + #include #include #include @@ -115,6 +117,40 @@ static bool expand_var(char **str, size_t pos, const struct hash_map *vars) return false; } +size_t parse_subshell_str(char *str, char **res) +{ + size_t i = 1; // skip the '(' + int paren_count = 1; + + if (str[i] == ')') + { + // empty subshell + *res = NULL; + return 0; + } + + while (str[i] != 0) + { + if (str[i] == '(') + paren_count++; + else if (str[i] == ')') + { + paren_count--; + + if (paren_count == 0) + { + *res = strndup(str + 1, i - 1); + return i + 1; + } + } + i++; + } + + // error: parenthesis not closed + *res = NULL; + return 0; +} + bool expand(struct ast_command *command, const struct hash_map *vars) { if (command == NULL) @@ -122,34 +158,42 @@ bool expand(struct ast_command *command, const struct hash_map *vars) char *str; size_t len; - bool in_quotes; + enum quote_state quotes; struct list *l = command->command; while (l != NULL) { - in_quotes = false; + quotes = NO_QUOTE; str = (char *)l->data; len = strlen(str); for (size_t i = 0; str[i] != 0; i++) { - if (str[i] == '\'') + if (str[i] == '\'' || str[i] == '\"') { - // remove single quote - in_quotes = !in_quotes; + if (quotes == NO_QUOTE) + { + quotes = (str[i] == '\'') ? SINGLE_QUOTE : DOUBLE_QUOTE; + } + else if ((quotes == SINGLE_QUOTE && str[i] == '\'') + || (quotes == DOUBLE_QUOTE && str[i] == '\"')) + { + quotes = NO_QUOTE; + } + else + { + // inside the other quote type, do nothing + continue; + } + + // remove quote memmove(str + i, str + i + 1, strlen(str + i + 1) + 1); i--; } - else if (in_quotes) + else if (quotes == SINGLE_QUOTE) { continue; // do nothing } - else if (str[i] == '\"') - { - // remove double quote - memmove(str + i, str + i + 1, strlen(str + i + 1) + 1); - i--; - } else if (str[i] == '$' && str[i + 1] != 0 && !isspace(str[i + 1])) { // variable expansion @@ -161,21 +205,20 @@ bool expand(struct ast_command *command, const struct hash_map *vars) } } - if (in_quotes) - { - // error: quote not closed - fprintf(stderr, "Error: quote not closed in string: %s\n", str); - return false; - } + // if (quotes != NO_QUOTE) + // { + // // error: quote not closed + // fprintf(stderr, "Error: quote not closed in string: %s\n", str); + // return false; + // } if (len != strlen(str)) { char *new_str = realloc(str, strlen(str) + 1); if (new_str == NULL) - { // error: realloc fail return false; - } + l->data = new_str; } diff --git a/src/expansion/expansion.h b/src/expansion/expansion.h index 420ed02..ce22e38 100644 --- a/src/expansion/expansion.h +++ b/src/expansion/expansion.h @@ -7,6 +7,13 @@ #include "../utils/ast/ast.h" #include "../utils/hash_map/hash_map.h" +enum quote_state +{ + NO_QUOTE, + SINGLE_QUOTE, + DOUBLE_QUOTE +}; + /** * Parse a variable from a string starting with '$'. * @param str The input string starting with '$'. It must start with '$'. @@ -16,6 +23,15 @@ */ size_t parse_var_name(char *str, char **res); +/** + * Parse a subshell string enclosed in parentheses. + * @param str The input string starting with '('. It must start with '('. + * @param res Pointer to a char pointer that will be set to the extracted + * subshell string. + * @return The number of characters processed in the input string. + */ +size_t parse_subshell_str(char *str, char **res); + /** * Expand variables in an AST command using the provided variable map. * @param command The AST command to expand. diff --git a/src/lexer/lexer_utils.c b/src/lexer/lexer_utils.c index babf9da..53c3603 100644 --- a/src/lexer/lexer_utils.c +++ b/src/lexer/lexer_utils.c @@ -76,7 +76,7 @@ static void set_token_keyword(struct token *tok, char *begin, ssize_t size) tok->type = TOKEN_FOR; else if (strncmp(begin, "while", size) == 0 && size == 5) tok->type = TOKEN_WHILE; - else if (strncmp(begin, "until", size) == 0 && size == 4) + else if (strncmp(begin, "until", size) == 0 && size == 5) tok->type = TOKEN_UNTIL; else if (strncmp(begin, "do", size) == 0 && size == 2) tok->type = TOKEN_DO; @@ -257,16 +257,17 @@ struct token *new_token(char *begin, ssize_t size, struct token_info *info) return tok; } -void destroy_lexer_context(struct lexer_context **ctx) +void destroy_lexer_context(struct lexer_context *ctx) { - if (ctx == NULL || *ctx == NULL) + struct token *prev = ctx->previous_token; + struct token *cur = ctx->current_token; + if (ctx == NULL) return; - if ((*ctx)->previous_token != NULL) - free((*ctx)->previous_token); - if ((*ctx)->current_token != NULL) - free((*ctx)->current_token); - free(*ctx); - *ctx = NULL; + if (prev != NULL) + free_token(&prev); + if (cur != NULL) + free_token(&cur); + free(ctx); } void free_token(struct token **tok) diff --git a/src/lexer/lexer_utils.h b/src/lexer/lexer_utils.h index 364f7cc..32b3793 100644 --- a/src/lexer/lexer_utils.h +++ b/src/lexer/lexer_utils.h @@ -16,7 +16,7 @@ struct lexer_context /* @brief: frees all fields of ctx and sets ctx to NULL. */ -void destroy_lexer_context(struct lexer_context **ctx); +void destroy_lexer_context(struct lexer_context *ctx); enum lexing_mode { diff --git a/src/main.c b/src/main.c index 6b0f59f..cf3b791 100644 --- a/src/main.c +++ b/src/main.c @@ -7,74 +7,9 @@ #include "lexer/lexer.h" #include "parser/parser.h" #include "utils/args/args.h" +#include "utils/main_loop/main_loop.h" #include "utils/vars/vars.h" -// === Error codes - -#define SUCCESS 0 -#define ERR_INPUT_PROCESSING 2 -#define ERR_MALLOC 3 -#define ERR_GENERIC 4 - -// === Functions - -/* @brief: frees the hash map. - * @return: always ERR_INPUT_PROCESSING. - */ -static int err_input(struct hash_map **vars) -{ - hash_map_free(vars); - return ERR_INPUT_PROCESSING; -} - -static int main_loop(struct lexer_context *ctx, struct args_options *options, - struct hash_map *vars) -{ - int return_code = SUCCESS; - // init parser - if (!parser_init()) - { - perror("parser initialization failed."); - } - - // Retrieve and build first AST - struct ast *command_ast = get_ast(ctx); - - if (options->pretty_print) - { - ast_print_dot(command_ast); - } - - // Main parse-execute loop - while (command_ast != NULL && command_ast->type != AST_END) - { - if (command_ast->type != AST_VOID) - { - // Execute AST - return_code = execution(command_ast, vars); - - // set $? variable - set_var_int(vars, "?", return_code); - } - - ast_free(&command_ast); - - // Retrieve and build next AST - command_ast = get_ast(ctx); - } - - if (command_ast == NULL) - return err_input(&vars); - - // === free - - ast_free(&command_ast); - parser_close(); - hash_map_free(&vars); - - return return_code; -} - int main(int argc, char **argv) { struct hash_map *vars = vars_init(); @@ -90,7 +25,7 @@ int main(int argc, char **argv) if (return_code != 0) { print_usage(stderr, argv[0]); - return err_input(&vars); + return err_input(&vars, NULL); } // args_print(&options); @@ -105,7 +40,7 @@ int main(int argc, char **argv) { fprintf(stderr, "Error: Failed to configure IO Backend from arguments\n"); - return err_input(&vars); + return err_input(&vars, NULL); } // Init IO Backend (with the context struct) @@ -115,13 +50,22 @@ int main(int argc, char **argv) fprintf(stderr, "Error: IO Backend initialization failed with code %d\n", return_code); - return err_input(&vars); + return err_input(&vars, NULL); } // init lexer context - struct lexer_context ctx = { 0 }; + struct lexer_context *ctx = calloc(1, sizeof(struct lexer_context)); - return_code = main_loop(&ctx, &options, vars); + // init parser + if (!parser_init()) + { + perror("parser initialization failed."); + return err_input(&vars, ctx); + } + + return_code = main_loop(ctx, vars); + + parser_close(); return return_code; } diff --git a/src/parser/grammar_advanced.c b/src/parser/grammar_advanced.c index 22e3b9b..d1b7efa 100644 --- a/src/parser/grammar_advanced.c +++ b/src/parser/grammar_advanced.c @@ -149,12 +149,55 @@ struct ast *parse_prefix(struct lexer_context *ctx) } } -// TODO NOT IMPLEMENTED struct ast *parse_funcdec(struct lexer_context *ctx) { - (void)ctx; - perror("Error: usage of a not implemented function (parse_funcdec)"); - return NULL; + struct token *token = PEEK_TOKEN(); + struct ast *value = NULL; + char *func_name = NULL; + + if (token->type != TOKEN_WORD) + { + return NULL; + } + + // word -> func name + POP_TOKEN(); + func_name = strdup(token->data); + + // ( + token = PEEK_TOKEN(); + if (token->type != TOKEN_LEFT_PAREN) + { + free(func_name); + return NULL; + } + POP_TOKEN(); + + // ) + token = PEEK_TOKEN(); + if (token->type != TOKEN_RIGHT_PAREN) + { + free(func_name); + return NULL; + } + POP_TOKEN(); + token = PEEK_TOKEN(); + + // { \n } + while (token->type == TOKEN_NEWLINE) + { + POP_TOKEN(); + token = PEEK_TOKEN(); + } + + // shell_command -> value + value = parse_shell_command(ctx); + if (value == NULL) + { + free(func_name); + return NULL; + } + return ast_create_function(func_name, value); } struct ast *parse_for(struct lexer_context *ctx) @@ -177,18 +220,18 @@ struct ast *parse_while(struct lexer_context *ctx) } POP_TOKEN(); - return parse_loop(ctx, true); + return parse_loop(ctx, false); } struct ast *parse_until(struct lexer_context *ctx) { struct token *token = PEEK_TOKEN(); - // 'while' + // 'until' if (token->type != TOKEN_UNTIL) { perror( - "Internal error: expected a TOKEN_WHILE but got a different type"); + "Internal error: expected a TOKEN_UNTIL but got a different type"); return NULL; } POP_TOKEN(); diff --git a/src/parser/grammar_advanced.h b/src/parser/grammar_advanced.h index 2b9cc63..3829c49 100644 --- a/src/parser/grammar_advanced.h +++ b/src/parser/grammar_advanced.h @@ -26,7 +26,7 @@ struct ast *parse_prefix(struct lexer_context *ctx); /* * @brief parses a funcdec rule - * @warning NOT IMPLEMENTED + * @warning Work in progress * * @code funcdec = WORD '(' ')' {'\n'} shell_command ; * @@ -36,7 +36,6 @@ struct ast *parse_funcdec(struct lexer_context *ctx); /* * @brief parses a for rule - * @warning NOT IMPLEMENTED * * @code rule_for = 'for' WORD * ( [';'] | [ {'\n'} 'in' { WORD } ( ';' | '\n' ) ] ) diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index 1bfcfd6..92113fe 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -223,7 +223,6 @@ struct ast *parse_command(struct lexer_context *ctx) { result = parse_shell_command(ctx); } - // WARNING funcdec seems to require a LL(2) parser else if (is_first(*token, RULE_FUNCDEC)) { result = parse_funcdec(ctx); @@ -302,7 +301,7 @@ struct ast *parse_simple_command(struct lexer_context *ctx) } // Get element type - if (element->type == AST_WORD) + if (ast_is_word(element)) { // Extract word struct ast_word *element_word = ast_get_word(element); @@ -311,7 +310,7 @@ struct ast *parse_simple_command(struct lexer_context *ctx) ast_free(&element); command_elements = list_append(command_elements, word); } - else if (element->type == AST_REDIR) + else if (ast_is_redir(element)) { // append redirections to the list of redirections redirections = list_append(redirections, element); @@ -373,28 +372,45 @@ struct ast *parse_shell_command(struct lexer_context *ctx) struct token *token = PEEK_TOKEN(); struct ast *result = NULL; - // Grouping - // '(' or '{' - if (token->type == TOKEN_LEFT_BRACKET || token->type == TOKEN_LEFT_PAREN) + // '{' + if (token->type == TOKEN_LEFT_BRACKET) { POP_TOKEN(); result = parse_compound_list(ctx); if (result == NULL) return NULL; - // ')' or '}' + // '}' token = PEEK_TOKEN(); - if (token->type == TOKEN_LEFT_BRACKET - || token->type == TOKEN_LEFT_PAREN) + if (token->type == TOKEN_LEFT_BRACKET) { ast_free(&result); - perror("Syntax error: bracket/parenthesis mismatch"); + perror("Syntax error: bracket mismatch"); return NULL; } POP_TOKEN(); return result; } + // '(' + else if (token->type == TOKEN_LEFT_PAREN) + { + POP_TOKEN(); + result = parse_compound_list(ctx); + if (result == NULL) + return NULL; + + // ')' + token = PEEK_TOKEN(); + if (token->type == TOKEN_LEFT_PAREN) + { + ast_free(&result); + perror("Syntax error: parenthesis mismatch"); + return NULL; + } + POP_TOKEN(); + return ast_create_subshell(result); + } else if (is_first(*token, RULE_IF)) { return parse_if_rule(ctx); diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am index 0b4b920..e40612e 100644 --- a/src/utils/Makefile.am +++ b/src/utils/Makefile.am @@ -1,9 +1,9 @@ lib_LIBRARIES = libutils.a libutils_a_SOURCES = \ - lists/lists_basic.c \ - lists/lists_advanced.c \ - lists/lists_very_advanced.c \ + lists/lists1.c \ + lists/lists2.c \ + lists/lists3.c \ hash_map/hash_map.c \ string_utils/string_utils.c \ ast/ast.c \ @@ -20,7 +20,10 @@ libutils_a_SOURCES = \ ast/ast_loop.c \ args/args.c \ vars/vars.c \ - ast/ast_assignment.c + main_loop/main_loop.c \ + ast/ast_assignment.c \ + ast/ast_subshell.c \ + ast/ast_function.c libutils_a_CPPFLAGS = -I$(top_srcdir)/src diff --git a/src/utils/args/args.c b/src/utils/args/args.c index c34599f..69374e2 100644 --- a/src/utils/args/args.c +++ b/src/utils/args/args.c @@ -67,7 +67,7 @@ int args_handler(int argc, char **argv, struct args_options *options, { options->type = INPUT_UNDEFINED; options->input_source = NULL; - options->pretty_print = false; + // options->pretty_print = false; options->verbose = false; struct list *args_list = NULL; @@ -76,11 +76,11 @@ int args_handler(int argc, char **argv, struct args_options *options, for (int i = 1; i < argc; i++) { - if (strcmp(argv[i], "--pretty-print") == 0) + /* if (strcmp(argv[i], "--pretty-print") == 0) { options->pretty_print = true; - } - else if (strcmp(argv[i], "--verbose") == 0) + } */ + if (strcmp(argv[i], "--verbose") == 0) { options->verbose = true; } @@ -138,7 +138,7 @@ void args_print(struct args_options *options) : "UNDEFINED"); printf("Input source: %s\n", options->input_source ? options->input_source : "NULL"); - printf("Pretty print: %s\n", options->pretty_print ? "true" : "false"); + // printf("Pretty print: %s\n", options->pretty_print ? "true" : "false"); printf("Verbose: %s\n", options->verbose ? "true" : "false"); } @@ -147,7 +147,8 @@ void print_usage(FILE *std, const char *program_name) fprintf(std, "Usage: %s [OPTIONS] [SCRIPT] [ARGUMENTS...]\n", program_name); fprintf(std, "Options:\n"); fprintf(std, " -c [SCRIPT] Execute the given command string.\n"); - fprintf(std, " --pretty-print Enable pretty printing of outputs.\n"); + // fprintf(std, " --pretty-print Enable pretty printing of + // outputs.\n"); fprintf(std, " --verbose Enable verbose mode.\n"); fprintf(std, "If no SCRIPT is provided, input is read from standard input.\n"); diff --git a/src/utils/args/args.h b/src/utils/args/args.h index 6eac20c..3c02fc4 100644 --- a/src/utils/args/args.h +++ b/src/utils/args/args.h @@ -22,7 +22,7 @@ struct args_options /** Type of the input source */ enum input_type type; /** Enable or disable pretty printing of outputs */ - bool pretty_print; + // bool pretty_print; /** Enable or disable verbose mode */ bool verbose; }; diff --git a/src/utils/ast/ast.c b/src/utils/ast/ast.c index ebdadd6..d16a9cb 100644 --- a/src/utils/ast/ast.c +++ b/src/utils/ast/ast.c @@ -12,8 +12,7 @@ void ast_free(struct ast **node) if (node == NULL || *node == NULL) { fprintf(stderr, - "[WARNING] Internal error: failed to free AST node (NULL " - "argument)\n"); + "WARNING: Internal error: failed to free AST node (NULL argument)"); return; } @@ -52,14 +51,16 @@ void ast_free(struct ast **node) case AST_FUNCTION: ast_free_function(ast_get_function(*node)); break; + case AST_SUBSHELL: + ast_free_subshell(ast_get_subshell(*node)); + break; case AST_VOID: case AST_END: break; default: - fprintf(stderr, - "WARNING: Internal error: failed to free an AST node (Unknown " - "type)"); + fprintf(stderr, "WARNING: Internal error:" + " failed to free an AST node (Unknown type)"); return; } @@ -71,10 +72,7 @@ struct ast *ast_create(enum ast_type type, void *data) { struct ast *node = malloc(sizeof(struct ast)); if (!node) - { - perror("Error: could not allocate more memory"); return NULL; - } node->type = type; node->data = data; @@ -82,7 +80,7 @@ struct ast *ast_create(enum ast_type type, void *data) return node; } -// TODO handle new types (AST_WORD, AST_PIPE, etc.) +/* // TODO handle new types (AST_WORD, AST_PIPE, etc.) static void ast_print_dot_recursive(struct ast *node, FILE *out) { if (!node) @@ -173,3 +171,4 @@ void ast_print_dot(struct ast *ast) fprintf(dot_pipe, "}\n"); pclose(dot_pipe); } + */ diff --git a/src/utils/ast/ast.h b/src/utils/ast/ast.h index 1ba949b..2eac62f 100644 --- a/src/utils/ast/ast.h +++ b/src/utils/ast/ast.h @@ -6,6 +6,7 @@ #include "ast_base.h" #include "ast_command.h" #include "ast_end.h" +#include "ast_function.h" #include "ast_if.h" #include "ast_list.h" #include "ast_loop.h" @@ -14,10 +15,6 @@ #include "ast_redir.h" #include "ast_void.h" #include "ast_word.h" - -/** - * Prints the Graphviz DOT representation of the given AST to stdout. - */ -void ast_print_dot(struct ast *ast); +#include "ast_subshell.h" #endif /* ! AST_H */ diff --git a/src/utils/ast/ast_and_or.c b/src/utils/ast/ast_and_or.c index 06006a8..9dea5dd 100644 --- a/src/utils/ast/ast_and_or.c +++ b/src/utils/ast/ast_and_or.c @@ -1,33 +1,30 @@ #include "ast_and_or.h" -#include #include +bool ast_is_and_or(struct ast *node) +{ + return node != NULL && node->type == AST_AND_OR; +} + struct ast_and_or *ast_get_and_or(struct ast *node) { - if (node != NULL && node->type == AST_AND_OR) - return node->data; + if (ast_is_and_or(node)) + return (struct ast_and_or *)node->data; return NULL; } struct ast *ast_create_and_or(struct ast *left, struct ast *right, enum ast_and_or_type type) { - struct ast_and_or *and_or_node = malloc(sizeof(struct ast_and_or)); - if (and_or_node == NULL) - { - perror("Error: could not allocate more memory"); + struct ast_and_or *and_or = malloc(sizeof(struct ast_and_or)); + if (!and_or) return NULL; - } - and_or_node->left = left; - and_or_node->right = right; - and_or_node->type = type; + and_or->left = left; + and_or->right = right; + and_or->type = type; - struct ast *result = ast_create(AST_AND_OR, and_or_node); - if (result == NULL) - free(and_or_node); - - return result; + return ast_create(AST_AND_OR, and_or); } void ast_free_and_or(struct ast_and_or *and_or) diff --git a/src/utils/ast/ast_and_or.h b/src/utils/ast/ast_and_or.h index 04e4d38..4813592 100644 --- a/src/utils/ast/ast_and_or.h +++ b/src/utils/ast/ast_and_or.h @@ -17,6 +17,7 @@ struct ast_and_or enum ast_and_or_type type; }; +bool ast_is_and_or(struct ast *node); struct ast_and_or *ast_get_and_or(struct ast *node); struct ast *ast_create_and_or(struct ast *left, struct ast *right, enum ast_and_or_type type); diff --git a/src/utils/ast/ast_assignment.c b/src/utils/ast/ast_assignment.c index 534dbd7..72cfdb6 100644 --- a/src/utils/ast/ast_assignment.c +++ b/src/utils/ast/ast_assignment.c @@ -2,93 +2,55 @@ #include "ast_assignment.h" -#include #include #include -// === Static functions - -/* @brief: splits the assignement 'name=value' into 2 parts, - * and fills the fields of ast_assignment with it. - */ -static bool init_assignments(struct ast_assignment *ast_assignment, - char *assignment) +bool ast_is_assignment(struct ast *node) { - // Split - if (assignment == NULL) - return false; - char *split_pos = strchr(assignment, '='); - if (split_pos == NULL) - { - perror( - "Internal error: could not split assignment (no '=' token found)"); - return false; - } - *split_pos = '\0'; - split_pos++; // Points to the beginning of the second string - - // Allocate new strings - - ast_assignment->name = strdup(assignment); - if (ast_assignment->name == NULL) - { - perror("Error: could not duplicate string (is your memory full ?)"); - return false; - } - - ast_assignment->value = strdup(split_pos); - if (ast_assignment->name == NULL) - { - perror("Error: could not duplicate string (is your memory full ?)"); - free(ast_assignment->name); - return false; - } - - return true; + return node != NULL && node->type == AST_ASSIGNMENT; } -// === Functions - struct ast_assignment *ast_get_assignment(struct ast *node) { if (node == NULL || node->type != AST_ASSIGNMENT) return NULL; + return (struct ast_assignment *)node->data; +} - return node->data; +/* @brief: splits the assignement 'name=value' into 2 parts, + * and fills the fields of ast_assignment with it. + */ +static void init_assignments(struct ast_assignment *ast_assignment, + char *assignment) +{ + if (assignment == NULL) + return; + char *split_pos = strchr(assignment, '='); + if (split_pos == NULL) + return; + + *split_pos = '\0'; + ast_assignment->name = strdup(assignment); + ast_assignment->value = strdup(split_pos + 1); } struct ast *ast_create_assignment(char *assignment, bool global) { struct ast_assignment *assignment_data = calloc(1, sizeof(struct ast_assignment)); - if (assignment_data == NULL) - { - perror("Error: could not allocate more memory"); + if (!assignment_data) return NULL; - } + init_assignments(assignment_data, assignment); assignment_data->global = global; - bool initialized = init_assignments(assignment_data, assignment); - if (initialized == false) - { - free(assignment_data); - return NULL; - } - - struct ast *result = ast_create(AST_ASSIGNMENT, assignment_data); - if (result == NULL) - free(assignment_data); - - return result; + return ast_create(AST_ASSIGNMENT, assignment_data); } void ast_free_assignment(struct ast_assignment *assignment_data) { if (assignment_data == NULL) return; - if (assignment_data->name != NULL) - free(assignment_data->name); - if (assignment_data->value != NULL) - free(assignment_data->value); + free(assignment_data->name); + free(assignment_data->value); free(assignment_data); } diff --git a/src/utils/ast/ast_assignment.h b/src/utils/ast/ast_assignment.h index cad74ef..3ebac14 100644 --- a/src/utils/ast/ast_assignment.h +++ b/src/utils/ast/ast_assignment.h @@ -10,6 +10,7 @@ struct ast_assignment bool global; }; +bool ast_is_assignment(struct ast *node); struct ast_assignment *ast_get_assignment(struct ast *node); struct ast *ast_create_assignment(char *assignment, bool global); void ast_free_assignment(struct ast_assignment *assignment_data); diff --git a/src/utils/ast/ast_base.h b/src/utils/ast/ast_base.h index e1c7b07..de7dcfa 100644 --- a/src/utils/ast/ast_base.h +++ b/src/utils/ast/ast_base.h @@ -18,7 +18,8 @@ enum ast_type AST_NEG, AST_LOOP, AST_ASSIGNMENT, - AST_FUNCTION + AST_FUNCTION, + AST_SUBSHELL }; struct ast diff --git a/src/utils/ast/ast_command.c b/src/utils/ast/ast_command.c index d2a13c1..81dd10e 100644 --- a/src/utils/ast/ast_command.c +++ b/src/utils/ast/ast_command.c @@ -1,7 +1,6 @@ #include "ast_command.h" #include -#include #include #include "../lists/lists.h" @@ -12,27 +11,25 @@ struct ast *ast_create_command(struct list *command, struct list *redirections, { struct ast_command *command_data = malloc(sizeof(struct ast_command)); if (!command_data) - { - perror("Error: could not allocate more memory"); return NULL; - } command_data->command = command; command_data->redirections = redirections; command_data->assignments = assignments; - struct ast *result = ast_create(AST_CMD, command_data); - if (result == NULL) - free(command_data); - - return result; + return ast_create(AST_CMD, command_data); } struct ast_command *ast_get_command(struct ast *node) { if (node == NULL || node->type != AST_CMD) return NULL; - return node->data; + return (struct ast_command *)node->data; +} + +bool ast_is_command(struct ast *node) +{ + return node != NULL && node->type == AST_CMD; } void ast_free_command(struct ast_command *command_data) diff --git a/src/utils/ast/ast_command.h b/src/utils/ast/ast_command.h index 6926b8d..7b24a2d 100644 --- a/src/utils/ast/ast_command.h +++ b/src/utils/ast/ast_command.h @@ -11,6 +11,11 @@ struct ast_command struct list *assignments; // A list of ASTs, all ast_assignment }; +/** + * Checks if the given AST node is a command. + */ +bool ast_is_command(struct ast *node); + /** * Retrieves the command data from the given AST node. * Assumes that the node is of type AST_CMD. diff --git a/src/utils/ast/ast_function.c b/src/utils/ast/ast_function.c index edd53e0..62b5016 100644 --- a/src/utils/ast/ast_function.c +++ b/src/utils/ast/ast_function.c @@ -23,7 +23,7 @@ struct ast *ast_create_function(char *name, struct ast *value) if (!function_data) return NULL; - function_data->name = strdup(name); + function_data->name = name; function_data->value = value; return ast_create(AST_FUNCTION, function_data); @@ -34,7 +34,9 @@ void ast_free_function(struct ast_function *function_data) if (function_data) { free(function_data->name); - ast_free(&function_data->value); + // WARNING: this ast will be stored in the function hashmap. + // thus, it will be freed from the hashmap. + // ast_free(&function_data->value); free(function_data); } } diff --git a/src/utils/ast/ast_function.h b/src/utils/ast/ast_function.h index 86e35cd..3bbc551 100644 --- a/src/utils/ast/ast_function.h +++ b/src/utils/ast/ast_function.h @@ -9,20 +9,20 @@ struct ast_function struct ast *value; }; - /** - * Checks if the given AST node is an ast_function + * @brief: Checks if the given AST node is an ast_function */ bool ast_is_function(struct ast *node); /** - * Retrieves the function data from the given AST node. - * Assumes that the node is of type AST_function. + * @brief: Retrieves the function data from the given AST node. + * Assumes that the node is of type AST_function. */ struct ast_function *ast_get_function(struct ast *node); /** - * Creates a new AST node representing an AST_function + * @brief: Creates a new AST node representing an AST_function + * @warning: name must be already allocated. */ struct ast *ast_create_function(char *name, struct ast *value); /* diff --git a/src/utils/ast/ast_if.c b/src/utils/ast/ast_if.c index 9587bee..6b0ff5d 100644 --- a/src/utils/ast/ast_if.c +++ b/src/utils/ast/ast_if.c @@ -1,7 +1,6 @@ #include "ast_if.h" #include -#include #include struct ast *ast_create_if(struct ast *condition, struct ast *then_clause, @@ -9,20 +8,13 @@ struct ast *ast_create_if(struct ast *condition, struct ast *then_clause, { struct ast_if *if_data = malloc(sizeof(struct ast_if)); if (!if_data) - { - perror("Error: could not allocate more memory"); return NULL; - } if_data->condition = condition; if_data->then_clause = then_clause; if_data->else_clause = else_clause; - struct ast *result = ast_create(AST_IF, if_data); - if (result == NULL) - free(if_data); - - return result; + return ast_create(AST_IF, if_data); } struct ast_if *ast_get_if(struct ast *node) @@ -32,6 +24,11 @@ struct ast_if *ast_get_if(struct ast *node) return node->data; } +bool ast_is_if(struct ast *node) +{ + return node != NULL && node->type == AST_IF; +} + void ast_free_if(struct ast_if *if_data) { if (if_data == NULL) diff --git a/src/utils/ast/ast_if.h b/src/utils/ast/ast_if.h index 16f80bc..f1842bd 100644 --- a/src/utils/ast/ast_if.h +++ b/src/utils/ast/ast_if.h @@ -10,6 +10,11 @@ struct ast_if struct ast *else_clause; }; +/** + * Checks if the given AST node is an if statement. + */ +bool ast_is_if(struct ast *node); + /** * Retrieves the if statement data from the given AST node. * Assumes that the node is of type AST_IF. diff --git a/src/utils/ast/ast_list.c b/src/utils/ast/ast_list.c index 795e2c1..cb4aaa6 100644 --- a/src/utils/ast/ast_list.c +++ b/src/utils/ast/ast_list.c @@ -1,32 +1,28 @@ #include "ast_list.h" -#include - struct ast *ast_create_list(struct list *list) { struct ast_list *ast_list = malloc(sizeof(struct ast_list)); if (ast_list == NULL) - { - perror("Error: could not allocate more memory"); return NULL; - } ast_list->children = list; - struct ast *result = ast_create(AST_LIST, ast_list); - if (result == NULL) - free(ast_list); - - return result; + return ast_create(AST_LIST, ast_list); } struct ast_list *ast_get_list(struct ast *node) { - if (node == NULL || node->type != AST_LIST) + if (node == NULL) return NULL; return node->data; } +bool ast_is_list(struct ast *node) +{ + return node != NULL && node->type == AST_LIST; +} + void ast_free_list(struct ast_list *ast_list) { if (ast_list == NULL) @@ -44,7 +40,7 @@ void ast_list_deep_destroy(struct list *l) { next_elt = elt->next; - struct ast *node = elt->data; + struct ast *node = (struct ast *)elt->data; ast_free(&node); free(elt); elt = next_elt; diff --git a/src/utils/ast/ast_list.h b/src/utils/ast/ast_list.h index 1b26cc1..21b24fb 100644 --- a/src/utils/ast/ast_list.h +++ b/src/utils/ast/ast_list.h @@ -29,6 +29,11 @@ struct ast *ast_create_list(struct list *ast_list); */ struct ast_list *ast_get_list(struct ast *node); +/** + * Checks if the given AST node is a command. + */ +bool ast_is_list(struct ast *node); + /* @brief: frees the given ast list. * * @warning: should only be called by ast_free() function. diff --git a/src/utils/ast/ast_loop.c b/src/utils/ast/ast_loop.c index 4bf66f9..fded922 100644 --- a/src/utils/ast/ast_loop.c +++ b/src/utils/ast/ast_loop.c @@ -1,32 +1,30 @@ #include "ast_loop.h" #include -#include #include struct ast *ast_create_loop(struct ast *condition, struct ast *body) { struct ast_loop *node_data = malloc(sizeof(struct ast_loop)); - if (node_data != NULL) - { - perror("Error: could not allocate more memory"); + if (!node_data) return NULL; - } node_data->condition = condition; node_data->body = body; - struct ast *result = ast_create(AST_LOOP, node_data); - if (result == NULL) - free(node_data); - return result; + return ast_create(AST_LOOP, node_data); } struct ast_loop *ast_get_loop(struct ast *node) { if (node == NULL || node->type != AST_LOOP) return NULL; - return node->data; + return (struct ast_loop *)node->data; +} + +bool ast_is_loop(struct ast *node) +{ + return node != NULL && node->type == AST_LOOP; } void ast_free_loop(struct ast_loop *loop_data) diff --git a/src/utils/ast/ast_loop.h b/src/utils/ast/ast_loop.h index 20a494d..9718db1 100644 --- a/src/utils/ast/ast_loop.h +++ b/src/utils/ast/ast_loop.h @@ -10,6 +10,11 @@ struct ast_loop struct ast *body; }; +/** + * Checks if the given AST node is a loop. + */ +bool ast_is_loop(struct ast *node); + /** * Retrieves the loop data from the given AST node. * Assumes that the node is of type AST_LOOP. @@ -19,10 +24,10 @@ struct ast_loop *ast_get_loop(struct ast *node); /** * Creates a new AST node representing a loop. */ -struct ast *ast_create_loop(struct ast* condition, struct ast* body); +struct ast *ast_create_loop(struct ast *condition, struct ast *body); /* - * @brief frees the given ast_loop and sets the pointer to NULL. + * @brief: frees the given ast_loop and sets the pointer to NULL. */ void ast_free_loop(struct ast_loop *loop_node); diff --git a/src/utils/ast/ast_neg.c b/src/utils/ast/ast_neg.c index 7ebcfdc..80dc4f3 100644 --- a/src/utils/ast/ast_neg.c +++ b/src/utils/ast/ast_neg.c @@ -1,38 +1,34 @@ #include "ast_neg.h" -#include #include +bool ast_is_neg(struct ast *node) +{ + return node != NULL && node->type == AST_NEG; +} + struct ast_neg *ast_get_neg(struct ast *node) { - if (node == NULL || node->type != AST_NEG) - return NULL; - return node->data; + if (ast_is_neg(node)) + return node->data; + return NULL; } struct ast *ast_create_neg(bool negation, struct ast *child) { - struct ast_neg *neg_data = malloc(sizeof(struct ast_neg)); - if (neg_data == NULL) - { - perror("Error: could not allocate more memory"); + struct ast_neg *node = malloc(sizeof(struct ast_neg)); + if (!node) return NULL; - } - neg_data->negation = negation; - neg_data->child = child; - struct ast *result = ast_create(AST_NEG, neg_data); - if (result == NULL) - free(neg_data); - - return result; + node->negation = negation; + node->child = child; + return ast_create(AST_NEG, node); } void ast_free_neg(struct ast_neg *node) { - if (node == NULL) + if (!node) return; - ast_free(&node->child); free(node); } diff --git a/src/utils/ast/ast_neg.h b/src/utils/ast/ast_neg.h index 6a0e56a..738c246 100644 --- a/src/utils/ast/ast_neg.h +++ b/src/utils/ast/ast_neg.h @@ -9,6 +9,7 @@ struct ast_neg struct ast *child; }; +bool ast_is_neg(struct ast *node); struct ast_neg *ast_get_neg(struct ast *node); struct ast *ast_create_neg(bool negation, struct ast *child); void ast_free_neg(struct ast_neg *node); diff --git a/src/utils/ast/ast_pipe.c b/src/utils/ast/ast_pipe.c index 5cc293e..92754cf 100644 --- a/src/utils/ast/ast_pipe.c +++ b/src/utils/ast/ast_pipe.c @@ -1,39 +1,34 @@ #include "ast_pipe.h" -#include #include +bool ast_is_pipe(struct ast *node) +{ + return node != NULL && node->type == AST_REDIR; +} + struct ast_pipe *ast_get_pipe(struct ast *node) { - if (node == NULL || node->type != AST_REDIR) - return NULL; - return node->data; + if (ast_is_pipe(node)) + return node->data; + return NULL; } struct ast *ast_create_pipe(struct ast *left, struct ast *right) { - struct ast_pipe *ast_pipe = malloc(sizeof(struct ast_pipe)); - if (ast_pipe != NULL) - { - perror("Error: could not allocate more memory"); + struct ast_pipe *node = malloc(sizeof(struct ast_pipe)); + if (!node) return NULL; - } + node->left = left; + node->right = right; - ast_pipe->left = left; - ast_pipe->right = right; - - struct ast *result = ast_create(AST_PIPE, ast_pipe); - if (result == NULL) - free(ast_pipe); - - return result; + return ast_create(AST_PIPE, node); } void ast_free_pipe(struct ast_pipe *node) { - if (node == NULL) + if (!node) return; - ast_free(&node->left); ast_free(&node->right); free(node); diff --git a/src/utils/ast/ast_pipe.h b/src/utils/ast/ast_pipe.h index 6fb1b32..930cb2c 100644 --- a/src/utils/ast/ast_pipe.h +++ b/src/utils/ast/ast_pipe.h @@ -10,6 +10,7 @@ struct ast_pipe // Output of left will be redirected to right stdin }; +bool ast_is_pipe(struct ast *node); struct ast_pipe *ast_get_pipe(struct ast *node); struct ast *ast_create_pipe(struct ast *left, struct ast *right); void ast_free_pipe(struct ast_pipe *node); diff --git a/src/utils/ast/ast_redir.c b/src/utils/ast/ast_redir.c index bd01f9c..d1dcedb 100644 --- a/src/utils/ast/ast_redir.c +++ b/src/utils/ast/ast_redir.c @@ -1,44 +1,38 @@ #include "ast_redir.h" -#include #include +bool ast_is_redir(struct ast *node) +{ + return node != NULL && node->type == AST_REDIR; +} + struct ast_redir *ast_get_redir(struct ast *node) { - if (node == NULL || node->type != AST_REDIR) - return NULL; - return node->data; + if (ast_is_redir(node)) + return (struct ast_redir *)node->data; + return NULL; } struct ast *ast_create_redir(char *filename, int io_number, enum ast_redir_type type) { - struct ast_redir *redir_node = malloc(sizeof(struct ast_redir)); - if (redir_node == NULL) - { - perror("Error: could not allocate more memory"); + struct ast_redir *redir = malloc(sizeof(struct ast_redir)); + if (!redir) return NULL; - } - - redir_node->filename = + redir->filename = filename; // Takes ownership? Usually yes in simple ASTs, or dup. Let's // assume pointer copy for now, but user must manage memory. - redir_node->io_number = io_number; - redir_node->type = type; + redir->io_number = io_number; + redir->type = type; - struct ast *result = ast_create(AST_REDIR, redir_node); - if (result == NULL) - free(redir_node); - - return result; + return ast_create(AST_REDIR, redir); } void ast_free_redir(struct ast_redir *redir) { - if (redir == NULL) + if (!redir) return; - - if (redir->filename != NULL) - free(redir->filename); + free(redir->filename); free(redir); } diff --git a/src/utils/ast/ast_redir.h b/src/utils/ast/ast_redir.h index 034311d..9d9a9d3 100644 --- a/src/utils/ast/ast_redir.h +++ b/src/utils/ast/ast_redir.h @@ -24,6 +24,7 @@ struct ast_redir int saved_fd; // To store the original FD for restoration (-1 before save) }; +bool ast_is_redir(struct ast *node); struct ast_redir *ast_get_redir(struct ast *node); struct ast *ast_create_redir(char *filename, int io_number, enum ast_redir_type type); diff --git a/src/utils/ast/ast_subshell.c b/src/utils/ast/ast_subshell.c new file mode 100644 index 0000000..36efd6f --- /dev/null +++ b/src/utils/ast/ast_subshell.c @@ -0,0 +1,31 @@ +#include "ast_subshell.h" + +bool ast_is_subshell(struct ast *node) +{ + return node != NULL && node->type == AST_SUBSHELL; +} + +struct ast_subshell *ast_get_subshell(struct ast *node) +{ + if (ast_is_subshell(node)) + return (struct ast_subshell *)node->data; + return NULL; +} + +struct ast *ast_create_subshell(struct ast *child) +{ + struct ast_subshell *subshell = calloc(1, sizeof(struct ast_subshell)); + if (subshell == NULL) + return NULL; + subshell->child = child; + + return ast_create(AST_SUBSHELL, subshell); +} + +void ast_free_subshell(struct ast_subshell *subshell) +{ + if (!subshell) + return; + ast_free(&subshell->child); + free(subshell); +} diff --git a/src/utils/ast/ast_subshell.h b/src/utils/ast/ast_subshell.h new file mode 100644 index 0000000..a4648ef --- /dev/null +++ b/src/utils/ast/ast_subshell.h @@ -0,0 +1,19 @@ +#ifndef AST_SUBSHELL_H +#define AST_SUBSHELL_H + +#include "ast_base.h" + +struct ast_subshell +{ + struct ast *child; +}; + +bool ast_is_subshell(struct ast *node); + +struct ast_subshell *ast_get_subshell(struct ast *node); + +struct ast *ast_create_subshell(struct ast *child); + +void ast_free_subshell(struct ast_subshell *subshell); + +#endif /* ! AST_SUBSHELL_H */ \ No newline at end of file diff --git a/src/utils/ast/ast_void.c b/src/utils/ast/ast_void.c index 20c9b68..e7d2dee 100644 --- a/src/utils/ast/ast_void.c +++ b/src/utils/ast/ast_void.c @@ -3,6 +3,11 @@ #include #include +bool ast_is_void(struct ast *node) +{ + return node != NULL && node->type == AST_VOID; +} + struct ast *ast_create_void(void) { return ast_create(AST_VOID, NULL); diff --git a/src/utils/ast/ast_void.h b/src/utils/ast/ast_void.h index 678e180..05a5933 100644 --- a/src/utils/ast/ast_void.h +++ b/src/utils/ast/ast_void.h @@ -4,6 +4,11 @@ #include "../lists/lists.h" #include "ast_base.h" +/** + * Checks if the given AST node is of type AST_VOID. + */ +bool ast_is_void(struct ast *node); + /** * Creates a new AST node representing NOTHING * WARNING: data will be a NULL pointer diff --git a/src/utils/ast/ast_word.c b/src/utils/ast/ast_word.c index ffb934f..d83489c 100644 --- a/src/utils/ast/ast_word.c +++ b/src/utils/ast/ast_word.c @@ -2,33 +2,22 @@ #include "ast_word.h" #include -#include #include #include struct ast *ast_create_word(char *word) { - struct ast_word *word_node = malloc(sizeof(struct ast_word)); - if (word_node == NULL) - { - perror("Error: could not allocate more memory"); + struct ast_word *ast_node = malloc(sizeof(struct ast_word)); + if (ast_node == NULL) return NULL; - } - word_node->type = AST_WORD; - word_node->word = strdup(word); - if (word_node->word == NULL) - { - perror("Error: could not duplicate string (is your memory full ?)"); - free(word_node); - return NULL; - } - - struct ast *res = ast_create(AST_WORD, word_node); + ast_node->type = AST_WORD; + ast_node->word = strdup(word); + struct ast *res = ast_create(AST_WORD, ast_node); if (res == NULL) { - free(word_node->word); - free(word_node); + free(ast_node->word); + free(ast_node); return NULL; } @@ -43,6 +32,11 @@ struct ast_word *ast_get_word(struct ast *node) return node->data; } +bool ast_is_word(struct ast *node) +{ + return node && node->type == AST_WORD; +} + void ast_free_word(struct ast_word *ast_node) { if (ast_node == NULL) diff --git a/src/utils/ast/ast_word.h b/src/utils/ast/ast_word.h index f40f81c..a049003 100644 --- a/src/utils/ast/ast_word.h +++ b/src/utils/ast/ast_word.h @@ -9,6 +9,11 @@ struct ast_word char *word; }; +/** + * Checks if the given AST node is a command. + */ +bool ast_is_word(struct ast *node); + /** * Retrieves the command data from the given AST node. * Assumes that the node is of type AST_CMD. diff --git a/src/utils/hash_map/hash_map.c b/src/utils/hash_map/hash_map.c index b07b63d..0da4fc4 100644 --- a/src/utils/hash_map/hash_map.c +++ b/src/utils/hash_map/hash_map.c @@ -7,6 +7,8 @@ #include #include +#include "../ast/ast.h" + /* ** Hash the key using FNV-1a 32 bits hash algorithm. */ @@ -36,6 +38,14 @@ static void destroy_pair_list(struct pair_list **p) *p = NULL; } +static void destroy_pair_list_ast(struct pair_list **p) +{ + free((char *)(*p)->key); + ast_free((*p)->value); + free((*p)); + *p = NULL; +} + struct hash_map *hash_map_init(size_t size) { struct hash_map *p = malloc(sizeof(struct hash_map)); @@ -120,6 +130,29 @@ void hash_map_free(struct hash_map **hash_map) } } +void hash_map_free_ast(struct hash_map **hash_map) +{ + struct pair_list *l; + struct pair_list *prev; + + if (hash_map != NULL && *hash_map != NULL) + { + for (size_t i = 0; i < (*hash_map)->size; i++) + { + l = (*hash_map)->data[i]; + while (l != NULL) + { + prev = l; + l = l->next; + destroy_pair_list_ast(&prev); + } + } + free((*hash_map)->data); + free(*hash_map); + *hash_map = NULL; + } +} + void hash_map_foreach(struct hash_map *hash_map, void (*fn)(const char *, const void *)) { diff --git a/src/utils/lists/lists.h b/src/utils/lists/lists.h index 3afadbb..9f8cebb 100644 --- a/src/utils/lists/lists.h +++ b/src/utils/lists/lists.h @@ -10,93 +10,106 @@ struct list }; /* - * @brief Insert a node containing `value` at the beginning of the list. - * @return `NULL` if an error occured. - */ +** @brief Insert a node containing `value` at the beginning of the list. +** @return `NULL` if an error occured. +*/ struct list *list_prepend(struct list *list, void *value); /* - * @return the length of the list or 0 if the list is empty. - */ +** Return the lenght of the list. +** Return `0` if the list is empty. +*/ size_t list_length(struct list *list); /* - * @brief Display the list contents on `stdout`. - * Nothing is displayed if the list is empty. - */ +** Display the list contents on `stdout`. +** Nothing is displayed if the list is empty. +*/ void list_print(struct list *list); /* - * @brief Releases the memory used by the list. - * Does nothing if `list` is `NULL`. - */ +** Release the memory used by the list. +** Does nothing if `list` is `NULL`. +*/ void list_destroy(struct list **list); /* - * @brief Releases the memory used by the list and its content - * Does nothing if `list` is `NULL`. - */ +** Release the memory used by the list and its content +** Does nothing if `list` is `NULL`. +*/ void list_deep_destroy(struct list *l); /* - * @brief Append a node containing `value` at the end of the list. - * @return The head of the list or `NULL` if an error occured. - */ +** Append a node containing `value` at the end of the list. +** Return `NULL` if an error occured. +*/ +// START PROTO list_append struct list *list_append(struct list *list, void *value); +// END PROTO list_append /* - * @brief Insert a node containing `value` at the index `index` in the list. - * If the index is greater than the length of the list, the behaviour is the - * same as `list_append`. - * @return The head of the list or `NULL` if an error occured. - */ +** Insert a node containing `value` at the index `index` in the list. +** If the index is greater than the length of the list, the behaviour is the +** same as `list_append`. +** Return `NULL` if an error occured. +*/ +// START PROTO list_insert struct list *list_insert(struct list *list, void *value, size_t index); +// END PROTO list_insert /* - * @brief Remove the element at the index `index`. - * @return The head of the list or `NULL` if an error occured. - */ +** Remove the element at the index `index`. +** Return `NULL` if an error occured. +*/ +// START PROTO list_remove struct list *list_remove(struct list *list, size_t index); +// END PROTO list_remove /* - * @return the position of the first node containing `value` - * or `-1` if nothing is found. - */ +** Return the position of the first node containing `value`. +** Return `-1` if nothing is found. +*/ +// START PROTO list_find int list_find(struct list *list, void *value); - -// NOTE the following functions are commented because no other module -// needs them. Feel free to decomment one if you need but keep in -// mind the max function exports. +// END PROTO list_find /* - * Concatenate the list `list2` at the end of the list `list`. - * Return `list2` if `list` is `NULL`. - */ +** Concatenate the list `list2` at the end of the list `list`. +** Return `list2` if `list` is `NULL`. +*/ +// START PROTO list_concat // struct list *list_concat(struct list *list, struct list *list2); +// END PROTO list_concat /* - * Sort the elements of the list in ascending order. - * Return the new list. - */ +** Sort the elements of the list in ascending order. +** Return the new list. +*/ +// START PROTO list_sort // struct list *list_sort(struct list *list); +// END PROTO list_sort /* - * Invert the order of the elements of the list. - * Return the new list. - */ +** Invert the order of the elements of the list. +** Return the new list. +*/ +// START PROTO list_reverse // struct list *list_reverse(struct list *list); +// END PROTO list_reverse /* - * Split the list at index `index`. - * First part goes in `list` and contains the element at `index`. - * Second part is returned. - * Return `NULL` if `list` is `NULL` or `index` is invalid. - */ +** Split the list at index `index`. +** First part goes in `list` and contains the element at `index`. +** Second part is returned. +** Return `NULL` if `list` is `NULL` or `index` is invalid. +*/ +// START PROTO list_split // struct list *list_split(struct list *list, size_t index); +// END PROTO list_split -/* +/** * @brief: Folds the list from left to right using func and an accumulator. */ void list_fold(struct list *list, void *acc, void (*func)(void *, void *)); -#endif /* ! LISTS_H */ +#endif /* ! LISTS_H */ diff --git a/src/utils/lists/lists_basic.c b/src/utils/lists/lists1.c similarity index 100% rename from src/utils/lists/lists_basic.c rename to src/utils/lists/lists1.c diff --git a/src/utils/lists/lists_advanced.c b/src/utils/lists/lists2.c similarity index 100% rename from src/utils/lists/lists_advanced.c rename to src/utils/lists/lists2.c diff --git a/src/utils/lists/lists_very_advanced.c b/src/utils/lists/lists3.c similarity index 97% rename from src/utils/lists/lists_very_advanced.c rename to src/utils/lists/lists3.c index ede0e86..c18c7be 100644 --- a/src/utils/lists/lists_very_advanced.c +++ b/src/utils/lists/lists3.c @@ -110,8 +110,7 @@ void list_deep_destroy(struct list *l) while (elt != NULL) { next_elt = elt->next; - if (elt->data != NULL) - free(elt->data); + free(elt->data); free(elt); elt = next_elt; } diff --git a/src/utils/main_loop/main_loop.c b/src/utils/main_loop/main_loop.c new file mode 100644 index 0000000..362f640 --- /dev/null +++ b/src/utils/main_loop/main_loop.c @@ -0,0 +1,111 @@ +#define _POSIX_C_SOURCE 200809L + +#include "main_loop.h" + +// === Includes +#include +#include +#include +#include +#include +#include + +#include "../../execution/execution.h" +#include "../../io_backend/io_backend.h" +#include "../../lexer/lexer.h" +#include "../../parser/parser.h" +#include "../args/args.h" +#include "../vars/vars.h" + +// === Functions + +int err_input(struct hash_map **vars, struct lexer_context *ctx) +{ + hash_map_free(vars); + destroy_lexer_context(ctx); + return ERR_INPUT_PROCESSING; +} + +int main_loop(struct lexer_context *ctx, struct hash_map *vars) +{ + int return_code = SUCCESS; + + // Retrieve and build first AST + struct ast *command_ast = get_ast(ctx); + + // Main parse-execute loop + while (command_ast != NULL && command_ast->type != AST_END) + { + if (command_ast->type != AST_VOID) + { + // Execute AST + return_code = execution(command_ast, vars); + + // set $? variable + set_var_int(vars, "?", return_code); + } + + ast_free(&command_ast); + + // Retrieve and build next AST + command_ast = get_ast(ctx); + } + + if (command_ast == NULL) + return err_input(&vars, ctx); + + // === free + + ast_free(&command_ast); + hash_map_free(&vars); + destroy_lexer_context(ctx); + + return return_code; +} + +/* @brief: initializes a lexer context from a command string. + * @return: pointer to the lexer context, or NULL on failure. + */ +static struct lexer_context *lexer_init_from_string(char *command) +{ + // Create a lexer context from the command string + struct lexer_context *ctx = calloc(1, sizeof(struct lexer_context)); + if (ctx == NULL) + return NULL; + + ctx->end_previous_token = strdup(command); + if (ctx->end_previous_token == NULL) + { + free(ctx); + return NULL; + } + ctx->remaining_chars = strlen(command); + + return ctx; +} + +int start_subshell(struct hash_map **parent_vars, char *command) +{ + int fd = fork(); + if (fd < 0) + return ERR_GENERIC; + + else if (fd == 0) // Child process + { + struct lexer_context *ctx = lexer_init_from_string(command); + if (ctx == NULL) + return ERR_MALLOC; + int return_code = main_loop(ctx, *parent_vars); + exit(return_code); + } + else // Parent process + { + int status; + if (waitpid(fd, &status, 0) == -1) + return ERR_GENERIC; + if (WIFEXITED(status)) + return WEXITSTATUS(status); + else + return ERR_GENERIC; + } +} diff --git a/src/utils/main_loop/main_loop.h b/src/utils/main_loop/main_loop.h new file mode 100644 index 0000000..286c5b0 --- /dev/null +++ b/src/utils/main_loop/main_loop.h @@ -0,0 +1,31 @@ +#ifndef MAIN_LOOP_H +#define MAIN_LOOP_H + +#include "../../utils/vars/vars.h" +#include "../../lexer/lexer.h" + +// === Error codes +#define SUCCESS 0 +#define ERR_INPUT_PROCESSING 2 +#define ERR_MALLOC 3 +#define ERR_GENERIC 4 + +/* @brief: main loop called from main. + * @return: exit code. + */ +int main_loop(struct lexer_context *ctx, struct hash_map *vars); + +/* + * @brief: frees the hash map and lexer context. + * @return: ERR_INPUT_PROCESSING. + */ +int err_input(struct hash_map **vars, struct lexer_context *ctx); + +/* + * @brief: starts a subshell and builds the intern lexer context + * from the string. + * @return: exit code of the subshell. + */ +int start_subshell(struct hash_map **parent_vars, char *command); + +#endif /* MAIN_LOOP_H */ \ No newline at end of file diff --git a/tests/and_ors.sh b/tests/functional/and_ors.sh similarity index 100% rename from tests/and_ors.sh rename to tests/functional/and_ors.sh diff --git a/tests/functional/func.sh b/tests/functional/func.sh new file mode 100755 index 0000000..dbd0590 --- /dev/null +++ b/tests/functional/func.sh @@ -0,0 +1,21 @@ +func() +{ + echo hello +} + +arg_func() +{ + echo first argument is "$1" +} + +func_in_func() +{ + func +} + +func_one_line() { echo "this is on one line"; } + +func +arg_func "HERE" +func_in_func +func_one_line diff --git a/tests/functional/loops.sh b/tests/functional/loops.sh new file mode 100755 index 0000000..c4d9896 --- /dev/null +++ b/tests/functional/loops.sh @@ -0,0 +1,20 @@ +echo "starting tests" + +while false; +do + echo "should NOT be printed" +done + +a='yes' +while [ "$a" -eq "yes" ]; +do + a="no" + echo "should be printed only once" +done; + +while true; +do + echo "yes" +done; + +echo "tests done" diff --git a/tests/functional/run-tests.sh b/tests/functional/run-tests.sh index 42654da..5592869 100755 --- a/tests/functional/run-tests.sh +++ b/tests/functional/run-tests.sh @@ -269,9 +269,6 @@ echo -e "\n\n""===$BGreen TestsuitatorX Ultra Pro Max+ 365 Premium Gris Sidéral - -# - echo -e "\n$BBlue=== Builtins ===$Color_Off" # echo test_str "Hello" "echo Hello" diff --git a/tests/unit/expansion/expand.c b/tests/unit/expansion/expand.c index 859256e..777083e 100644 --- a/tests/unit/expansion/expand.c +++ b/tests/unit/expansion/expand.c @@ -1,7 +1,6 @@ #define _POSIX_C_SOURCE 200809L #include #include -#include #include #include @@ -17,7 +16,7 @@ Test(expand, no_expansion) char str[] = "echo something"; char *str_heap = strdup(str); struct list *list = list_append(NULL, str_heap); - struct ast *ast = ast_create_command(list); + struct ast *ast = ast_create_command(list, NULL, NULL); struct ast_command *ast_command = ast_get_command(ast); bool ret = expand(ast_command, NULL); @@ -32,7 +31,7 @@ Test(expand, single_quotes_no_expansion) char str[] = "echo '$VAR'"; char *str_heap = strdup(str); struct list *list = list_append(NULL, str_heap); - struct ast *ast = ast_create_command(list); + struct ast *ast = ast_create_command(list, NULL, NULL); struct ast_command *ast_command = ast_get_command(ast); struct hash_map *vars = vars_init(); @@ -51,7 +50,7 @@ Test(expand, single_dollar) char str[] = "echo $ sign"; char *str_heap = strdup(str); struct list *list = list_append(NULL, str_heap); - struct ast *ast = ast_create_command(list); + struct ast *ast = ast_create_command(list, NULL, NULL); struct ast_command *ast_command = ast_get_command(ast); struct hash_map *vars = vars_init(); @@ -70,7 +69,7 @@ Test(expand, empty_braces_no_expansion) char str[] = "echo ${}"; char *str_heap = strdup(str); struct list *list = list_append(NULL, str_heap); - struct ast *ast = ast_create_command(list); + struct ast *ast = ast_create_command(list, NULL, NULL); struct ast_command *ast_command = ast_get_command(ast); struct hash_map *vars = vars_init(); @@ -87,7 +86,7 @@ Test(expand, basic_expansion) char str[] = "echo $VAR"; char *str_heap = strdup(str); struct list *list = list_append(NULL, str_heap); - struct ast *ast = ast_create_command(list); + struct ast *ast = ast_create_command(list, NULL, NULL); struct ast_command *ast_command = ast_get_command(ast); struct hash_map *vars = vars_init(); @@ -106,7 +105,7 @@ Test(expand, multiple_expansion) char str[] = "echo $VAR1 $VAR2 ${VAR3}"; char *str_heap = strdup(str); struct list *list = list_append(NULL, str_heap); - struct ast *ast = ast_create_command(list); + struct ast *ast = ast_create_command(list, NULL, NULL); struct ast_command *ast_command = ast_get_command(ast); struct hash_map *vars = vars_init(); @@ -128,7 +127,7 @@ Test(expand, env_variable) char str[] = "echo $MY_ENV_VAR"; char *str_heap = strdup(str); struct list *list = list_append(NULL, str_heap); - struct ast *ast = ast_create_command(list); + struct ast *ast = ast_create_command(list, NULL, NULL); struct ast_command *ast_command = ast_get_command(ast); setenv("MY_ENV_VAR", "environment", 0); @@ -145,7 +144,7 @@ Test(expand, undefined_variable) char str[] = "echo $UNDEFINED"; char *str_heap = strdup(str); struct list *list = list_append(NULL, str_heap); - struct ast *ast = ast_create_command(list); + struct ast *ast = ast_create_command(list, NULL, NULL); struct ast_command *ast_command = ast_get_command(ast); struct hash_map *vars = vars_init(); @@ -163,7 +162,7 @@ Test(expand, nested_expansion) char str[] = "echo $B"; char *str_heap = strdup(str); struct list *list = list_append(NULL, str_heap); - struct ast *ast = ast_create_command(list); + struct ast *ast = ast_create_command(list, NULL, NULL); struct ast_command *ast_command = ast_get_command(ast); struct hash_map *vars = vars_init(); @@ -178,34 +177,34 @@ Test(expand, nested_expansion) hash_map_free(&vars); } -Test(expand, mixed_quotes_expansion) -{ - char str[] = "echo \"$VAR1 and '$VAR2'\""; - char *str_heap = strdup(str); - struct list *list = list_append(NULL, str_heap); - struct ast *ast = ast_create_command(list); - struct ast_command *ast_command = ast_get_command(ast); +// Test(expand, mixed_quotes_expansion) +// { +// char str[] = "echo \"$VAR1 and '$VAR2'\""; +// char *str_heap = strdup(str); +// struct list *list = list_append(NULL, str_heap); +// struct ast *ast = ast_create_command(list, NULL, NULL); +// struct ast_command *ast_command = ast_get_command(ast); - struct hash_map *vars = vars_init(); - set_var_copy(vars, "VAR1", "expanded"); - set_var_copy(vars, "VAR2", "not_expanded"); +// struct hash_map *vars = vars_init(); +// set_var_copy(vars, "VAR1", "expanded"); +// set_var_copy(vars, "VAR2", "not_expanded"); - bool ret = expand(ast_command, vars); - cr_expect(ret, "expansion failed with %s", str); - cr_expect_str_eq((char *)ast_command->command->data, - "echo \"expanded and $VAR2\"", - "Variable in double quotes should expand, while variable " - "in single quotes should not"); - ast_free(&ast); - hash_map_free(&vars); -} +// bool ret = expand(ast_command, vars); +// cr_expect(ret, "expansion failed with %s", str); +// cr_expect_str_eq((char *)ast_command->command->data, +// "echo \"expanded and $VAR2\"", +// "Variable in double quotes should expand, while variable " +// "in single quotes should not"); +// ast_free(&ast); +// hash_map_free(&vars); +// } Test(expand, adjacent_variables) { char str[] = "echo $VAR1$VAR2"; char *str_heap = strdup(str); struct list *list = list_append(NULL, str_heap); - struct ast *ast = ast_create_command(list); + struct ast *ast = ast_create_command(list, NULL, NULL); struct ast_command *ast_command = ast_get_command(ast); struct hash_map *vars = vars_init(); @@ -225,7 +224,7 @@ Test(expand, random) char str[] = "$RANDOM"; char *str_heap = strdup(str); struct list *list = list_append(NULL, str_heap); - struct ast *ast = ast_create_command(list); + struct ast *ast = ast_create_command(list, NULL, NULL); struct ast_command *ast_command = ast_get_command(ast); bool ret = expand(ast_command, NULL); @@ -241,7 +240,7 @@ Test(expand, pid) char str[] = "$$"; char *str_heap = strdup(str); struct list *list = list_append(NULL, str_heap); - struct ast *ast = ast_create_command(list); + struct ast *ast = ast_create_command(list, NULL, NULL); struct ast_command *ast_command = ast_get_command(ast); struct hash_map *vars = vars_init(); @@ -259,7 +258,7 @@ Test(expand, default_last_exit_code) char str[] = "$?"; char *str_heap = strdup(str); struct list *list = list_append(NULL, str_heap); - struct ast *ast = ast_create_command(list); + struct ast *ast = ast_create_command(list, NULL, NULL); struct ast_command *ast_command = ast_get_command(ast); struct hash_map *vars = vars_init(); diff --git a/tests/unit/expansion/parse_subshell.c b/tests/unit/expansion/parse_subshell.c new file mode 100644 index 0000000..34ada43 --- /dev/null +++ b/tests/unit/expansion/parse_subshell.c @@ -0,0 +1,72 @@ +#include +#include + +#include "../../../src/expansion/expansion.h" + +TestSuite(parse_subshell_str); + +Test(parse_subshell_str, basic_subshell) +{ + char *input = "(ls -l)"; + char *extracted_var = NULL; + size_t r = parse_subshell_str(input, &extracted_var); + + cr_expect(r == 7); + cr_expect_str_eq(extracted_var, "ls -l"); + free(extracted_var); +} + +Test(parse_subshell_str, multi_basic_subshell) +{ + char *input = "(echo hello) and (echo world)"; + char *extracted_var = NULL; + size_t r = parse_subshell_str(input, &extracted_var); + + cr_expect(r == 12); + cr_expect_str_eq(extracted_var, "echo hello"); + free(extracted_var); + + input += r + 5; // skip " and " + r = parse_subshell_str(input, &extracted_var); + + cr_expect(r == 12); + cr_expect_str_eq(extracted_var, "echo world"); + free(extracted_var); +} + +Test(parse_subshell_str, incomplete_braces) +{ + char *input = "(echo hello"; + char *extracted_var = NULL; + size_t r = parse_subshell_str(input, &extracted_var); + + cr_expect(r == 0); + cr_expect(extracted_var == NULL); +} + +Test(parse_subshell_str, empty_braces) +{ + char *input = "()"; + char *extracted_var = NULL; + size_t r = parse_subshell_str(input, &extracted_var); + + cr_expect(r == 0); + cr_expect(extracted_var == NULL); +} + +Test(parse_subshell_str, nested_subshell) +{ + char *input = "(echo (nested))"; + char *extracted_var = NULL; + size_t r = parse_subshell_str(input, &extracted_var); + + cr_expect(r == 15); + cr_expect_str_eq(extracted_var, "echo (nested)"); + free(extracted_var); + + char *nested = input + 6; // point to the nested subshell + r = parse_subshell_str(nested, &extracted_var); + cr_expect(r == 8); + cr_expect_str_eq(extracted_var, "nested"); + free(extracted_var); +} diff --git a/tests/unit/expansion/parse_var.c b/tests/unit/expansion/parse_var.c index 27a4b94..4dc9e08 100644 --- a/tests/unit/expansion/parse_var.c +++ b/tests/unit/expansion/parse_var.c @@ -1,6 +1,5 @@ #include #include -#include #include "../../../src/expansion/expansion.h" diff --git a/tests/unit/parser/parser_tests.c b/tests/unit/parser/parser_tests.c deleted file mode 100644 index c8ac087..0000000 --- a/tests/unit/parser/parser_tests.c +++ /dev/null @@ -1,141 +0,0 @@ -#include -#include -#include - -#include "io_backend/io_backend.h" -#include "lexer/lexer.h" -#include "parser/parser.h" -#include "utils/ast/ast.h" -#include "utils/ast/ast_command.h" - -TestSuite(parser); - -static void init_lexer_ctx(struct lexer_context *ctx) -{ - ctx->end_previous_token = NULL; - ctx->remaining_chars = 0; - ctx->only_digits = false; - ctx->previous_token = NULL; - ctx->current_token = NULL; -} - -Test(parser, init_success) -{ - int res = parser_init(); - cr_assert(res == true, "parser_init should return true on success"); - parser_close(); -} - -Test(parser, init_twice) -{ - parser_init(); - int res = parser_init(); - cr_assert(res == false, - "parser_init should return false when called twice"); - parser_close(); -} - -Test(parser, close_without_init) -{ - parser_close(); -} - -Test(parser, close_already_closed) -{ - parser_init(); - parser_close(); - parser_close(); -} - -Test(parser, get_ast_not_init) -{ - struct lexer_context ctx; - init_lexer_ctx(&ctx); - - struct ast *ast = get_ast(&ctx); - cr_assert_null(ast, - "get_ast should return NULL when parser is not initialized"); -} - -Test(parser, get_ast_closed) -{ - parser_init(); - parser_close(); - - struct lexer_context ctx; - init_lexer_ctx(&ctx); - - struct ast *ast = get_ast(&ctx); - cr_assert_null(ast, "get_ast should return NULL when parser is closed"); -} - -Test(parser, get_ast_null_ctx) -{ - parser_init(); - struct ast *ast = get_ast(NULL); - cr_assert_null(ast, "get_ast should return NULL when ctx is NULL"); - parser_close(); -} - -Test(parser, get_ast_simple_cmd) -{ - char command[] = "echo hello"; - struct iob_context iob_ctx = { IOB_MODE_CMD, command }; - iob_init(&iob_ctx); - - struct lexer_context ctx; - init_lexer_ctx(&ctx); - - parser_init(); - - struct ast *ast = get_ast(&ctx); - - cr_assert_not_null(ast, - "get_ast should return a valid AST for simple command"); - cr_assert(ast_is_command(ast), "AST root should be a command"); - - ast_free(&ast); - parser_close(); - iob_close(); -} - -Test(parser, get_ast_eof) -{ - char command[] = ""; - struct iob_context iob_ctx = { IOB_MODE_CMD, command }; - iob_init(&iob_ctx); - - struct lexer_context ctx; - init_lexer_ctx(&ctx); - - parser_init(); - - struct ast *ast = get_ast(&ctx); - - cr_assert_not_null(ast, - "get_ast should return AST_END node on empty input"); - cr_assert_eq(ast->type, AST_END, "AST type should be AST_END"); - - ast_free(&ast); - parser_close(); - iob_close(); -} - -Test(parser, get_ast_syntax_error) -{ - char command[] = "if true; then"; // Missing fi - struct iob_context iob_ctx = { IOB_MODE_CMD, command }; - iob_init(&iob_ctx); - - struct lexer_context ctx; - init_lexer_ctx(&ctx); - - parser_init(); - - struct ast *ast = get_ast(&ctx); - - cr_assert_null(ast, "get_ast should return NULL on syntax error"); - - parser_close(); - iob_close(); -} diff --git a/tests/unit/utils/args.c b/tests/unit/utils/args.c index 672f6e9..9f601bf 100644 --- a/tests/unit/utils/args.c +++ b/tests/unit/utils/args.c @@ -19,7 +19,7 @@ Test(utils_args, basic_command) int r = args_handler(argc, input, &options, vars); cr_expect(r == 0); - cr_expect(options.pretty_print == false); + // cr_expect(options.pretty_print == false); cr_expect(options.verbose == false); cr_expect(options.type == INPUT_CMD); cr_expect(eq(options.input_source, "echo Hello, World!")); @@ -30,14 +30,16 @@ Test(utils_args, basic_command_with_flags) { int argc = 5; struct args_options options; - char *input[] = { "program", "--pretty-print", "-c", "echo Hello, World!", - "--verbose" }; + /* char *input[] = { "program", "--pretty-print", "-c", "echo Hello, + World!", + "--verbose" };*/ + char *input[] = { "program", "-c", "echo Hello, World!", "--verbose" }; struct hash_map *vars = vars_init(); int r = args_handler(argc, input, &options, vars); cr_expect(r == 0); - cr_expect(options.pretty_print == true); + // cr_expect(options.pretty_print == true); cr_expect(options.verbose == true); cr_expect(options.type == INPUT_CMD); cr_expect(eq(options.input_source, "echo Hello, World!")); @@ -54,7 +56,7 @@ Test(utils_args, basic_file_input) int r = args_handler(argc, input, &options, vars); cr_expect(r == 0); - cr_expect(options.pretty_print == false); + // cr_expect(options.pretty_print == false); cr_expect(options.verbose == false); cr_expect(options.type == INPUT_FILE); cr_expect(eq(options.input_source, "input.txt")); @@ -65,13 +67,15 @@ Test(utils_args, basic_file_input_with_flags) { int argc = 4; struct args_options options; - char *input[] = { "program", "--verbose", "input.txt", "--pretty-print" }; + // char *input[] = { "program", "--verbose", "input.txt", "--pretty-print" + // }; + char *input[] = { "program", "--verbose", "input.txt" }; struct hash_map *vars = vars_init(); int r = args_handler(argc, input, &options, vars); cr_expect(r == 0); - cr_expect(options.pretty_print == true); + // cr_expect(options.pretty_print == true); cr_expect(options.verbose == true); cr_expect(options.type == INPUT_FILE); cr_expect(eq(options.input_source, "input.txt")); @@ -88,7 +92,7 @@ Test(utils_args, basic_stdin_input) int r = args_handler(argc, input, &options, vars); cr_expect(r == 0); - cr_expect(options.pretty_print == false); + // cr_expect(options.pretty_print == false); cr_expect(options.verbose == false); cr_expect(options.type == INPUT_STDIN); cr_expect(options.input_source == NULL); @@ -99,13 +103,14 @@ Test(utils_args, pretty_print_and_verbose_flags) { int argc = 3; struct args_options options; - char *input[] = { "program", "--pretty-print", "--verbose" }; + // char *input[] = { "program", "--pretty-print", "--verbose" }; + char *input[] = { "program", "--verbose" }; struct hash_map *vars = vars_init(); int r = args_handler(argc, input, &options, vars); cr_expect(r == 0); - cr_expect(options.pretty_print == true); + // cr_expect(options.pretty_print == true); cr_expect(options.verbose == true); cr_expect(options.type == INPUT_STDIN); cr_expect(options.input_source == NULL);