From 399d1ed3e15077e277164880caff15d9649143ed Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Tue, 27 Jan 2026 21:06:36 +0100 Subject: [PATCH 001/104] feat(redirections): ast commands now have a field for the list of redirections + redirections implemented in parser --- src/parser/grammar_basic.c | 10 ++++++---- src/utils/ast/ast_command.c | 5 ++++- src/utils/ast/ast_command.h | 4 +++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index d4a740f..f3e63b6 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -171,6 +171,7 @@ struct ast *parse_command(struct lexer_context *ctx) struct ast *parse_simple_command(struct lexer_context *ctx) { struct list *command_elements = NULL; + struct list *redirections = NULL; // list of redirection ASTs // WORD struct token *token = POP_TOKEN(); @@ -204,15 +205,15 @@ struct ast *parse_simple_command(struct lexer_context *ctx) } else if (ast_is_redir(element)) { - // TODO - perror("NOT IMPLEMENTED"); - return NULL; + // append redirections to the list of redirections + redirections = list_append(redirections, element); } else { perror("Internal error: unexpected return value from parse_element " "in parse_simple_command"); list_deep_destroy(command_elements); + list_deep_destroy(redirections); return NULL; } @@ -221,10 +222,11 @@ struct ast *parse_simple_command(struct lexer_context *ctx) } // Result - struct ast *result = ast_create_command(command_elements); + struct ast *result = ast_create_command(command_elements, redirections); if (result == NULL) { list_deep_destroy(command_elements); + list_deep_destroy(redirections); return NULL; } return result; diff --git a/src/utils/ast/ast_command.c b/src/utils/ast/ast_command.c index 14904ae..c4fe3e6 100644 --- a/src/utils/ast/ast_command.c +++ b/src/utils/ast/ast_command.c @@ -5,13 +5,15 @@ #include "../lists/lists.h" -struct ast *ast_create_command(struct list *command) +struct ast *ast_create_command(struct list *command, + struct list *redirections) { struct ast_command *command_data = malloc(sizeof(struct ast_command)); if (!command_data) return NULL; command_data->command = command; + command_data->redirections = redirections; return ast_create(AST_CMD, command_data); } @@ -33,5 +35,6 @@ void ast_free_command(struct ast_command *command_data) if (command_data == NULL) return; list_deep_destroy(command_data->command); + ast_list_deep_destroy(command_data->redirections); free(command_data); } diff --git a/src/utils/ast/ast_command.h b/src/utils/ast/ast_command.h index cf6b913..835bf5d 100644 --- a/src/utils/ast/ast_command.h +++ b/src/utils/ast/ast_command.h @@ -7,6 +7,7 @@ struct ast_command { struct list *command; // A list of words (char*) + struct ast_list *redirections; // A list of ASTs, all ast_redir }; /** @@ -23,7 +24,8 @@ struct ast_command *ast_get_command(struct ast *node); /** * Creates a new AST node representing a command. */ -struct ast *ast_create_command(struct list *command); +struct ast *ast_create_command(struct list *command, + struct list *redirections); /* * @brief: frees the given ast_command and sets the pointer to NULL. From 9a0f9bc6f1965ac683dd9b8208643517b8b4a0a9 Mon Sep 17 00:00:00 2001 From: matteo Date: Wed, 28 Jan 2026 11:34:29 +0100 Subject: [PATCH 002/104] fix: redirections types --- src/execution/execution_helpers.c | 2 +- src/parser/grammar_advanced.c | 11 ++++++++++- src/utils/ast/ast_redir.h | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/execution/execution_helpers.c b/src/execution/execution_helpers.c index 8041a3c..25dd4a7 100644 --- a/src/execution/execution_helpers.c +++ b/src/execution/execution_helpers.c @@ -121,7 +121,7 @@ static int get_fd_target(const struct ast_redir *redir) if (redir->io_number != -1) return redir->io_number; if (redir->type == AST_REDIR_TYPE_LESS - || redir->type == AST_REDIR_TYPE_DLESS + || redir->type == AST_REDIR_TYPE_LESSGREAT || redir->type == AST_REDIR_TYPE_LESSAND) return 0; return 1; diff --git a/src/parser/grammar_advanced.c b/src/parser/grammar_advanced.c index 3850564..8c4b68c 100644 --- a/src/parser/grammar_advanced.c +++ b/src/parser/grammar_advanced.c @@ -16,7 +16,16 @@ static enum ast_redir_type redir_tok_to_ast_type(enum token_type tok_type) return AST_REDIR_TYPE_LESS; case TOKEN_REDIR_RIGHT: return AST_REDIR_TYPE_GREAT; - // TODO finish this + case TOKEN_REDIR_DOUBLE_RIGHT: + return AST_REDIR_TYPE_DGREAT; + case TOKEN_REDIR_LEFT_RIGHT: + return AST_REDIR_TYPE_LESSGREAT; + case TOKEN_REDIR_LEFT_AMP: + return AST_REDIR_TYPE_LESSAND; + case TOKEN_REDIR_RIGHT_AMP: + return AST_REDIR_TYPE_GREATAND; + case TOKEN_REDIR_RIGHT_PIPE: + return AST_REDIR_TYPE_CLOBBER; default: return AST_REDIR_TYPE_NULL; } diff --git a/src/utils/ast/ast_redir.h b/src/utils/ast/ast_redir.h index eb95942..fdea88c 100644 --- a/src/utils/ast/ast_redir.h +++ b/src/utils/ast/ast_redir.h @@ -8,7 +8,7 @@ enum ast_redir_type AST_REDIR_TYPE_NULL, AST_REDIR_TYPE_LESS, // < AST_REDIR_TYPE_GREAT, // > - AST_REDIR_TYPE_DLESS, // << + AST_REDIR_TYPE_LESSGREAT, // <> AST_REDIR_TYPE_DGREAT, // >> AST_REDIR_TYPE_LESSAND, // <& AST_REDIR_TYPE_GREATAND, // >& From b657d65664a27f55c23d58d984fcae5892afcfb8 Mon Sep 17 00:00:00 2001 From: matteo Date: Wed, 28 Jan 2026 12:11:40 +0100 Subject: [PATCH 003/104] feat(parser): parse_command version with prefixes and elements --- src/parser/grammar_basic.c | 69 +++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 19 deletions(-) diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index f3e63b6..932d7f5 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -153,11 +153,11 @@ struct ast *parse_command(struct lexer_context *ctx) { struct token *token = PEEK_TOKEN(); - if (token->type == TOKEN_WORD) + if (is_first(*token, RULE_SIMPLE_COMMAND)) { return parse_simple_command(ctx); } - else if (token->type == TOKEN_IF) + else if (is_first(*token, RULE_SHELL_COMMAND)) { return parse_shell_command(ctx); } @@ -168,23 +168,54 @@ struct ast *parse_command(struct lexer_context *ctx) } } +/* @brief: frees command_elements and redirections lists + * @return: NULL + */ +static err_simple_command(struct list *command_elements, + struct list *redirections) +{ + list_deep_destroy(command_elements); + list_deep_destroy(redirections); + return NULL; +} + struct ast *parse_simple_command(struct lexer_context *ctx) { struct list *command_elements = NULL; struct list *redirections = NULL; // list of redirection ASTs - // WORD - struct token *token = POP_TOKEN(); - if (token->type != TOKEN_WORD) + bool has_prefix = false; + struct token *token = PEEK_TOKEN(); + if (is_first(*token, RULE_PREFIX)) + { + has_prefix = true; + while (is_first(*token, RULE_PREFIX)) + { + struct ast *redir = parse_prefix(ctx); + if (redir == NULL) + { + return err_simple_command(command_elements, redirections); + } + redirections = list_append(redirections, redir); + token = PEEK_TOKEN(); + } + } + + if (token->type != TOKEN_WORD && !has_prefix) { perror("Expected a command but got a different token type"); - return NULL; + return err_simple_command(command_elements, redirections); } - char *command = strdup(token->data); - command_elements = list_append(command_elements, command); - - token = PEEK_TOKEN(); + + // we can have only prefixes askip + if (token->type == TOKEN_WORD) + { + char *command = strdup(token->data); + command_elements = list_append(command_elements, command); + POP_TOKEN(); + token = PEEK_TOKEN(); + } // Eventual elements while (is_first(*token, RULE_ELEMENT)) { @@ -192,16 +223,20 @@ struct ast *parse_simple_command(struct lexer_context *ctx) struct ast *element = parse_element(ctx); if (element == NULL) { - list_deep_destroy(command_elements); - return NULL; + return err_simple_command(command_elements, redirections); } // Get element type if (ast_is_word(element)) { struct ast_word *element_word = ast_get_word(element); + + // TODO test this fix for the memory leaks + char *word = strdup(element_word->word); + ast_free(element_word); command_elements = - list_append(command_elements, element_word->word); + list_append(command_elements, word); + // end of fix } else if (ast_is_redir(element)) { @@ -212,9 +247,7 @@ struct ast *parse_simple_command(struct lexer_context *ctx) { perror("Internal error: unexpected return value from parse_element " "in parse_simple_command"); - list_deep_destroy(command_elements); - list_deep_destroy(redirections); - return NULL; + return err_simple_command(command_elements, redirections); } // Forward @@ -225,9 +258,7 @@ struct ast *parse_simple_command(struct lexer_context *ctx) struct ast *result = ast_create_command(command_elements, redirections); if (result == NULL) { - list_deep_destroy(command_elements); - list_deep_destroy(redirections); - return NULL; + return err_simple_command(command_elements, redirections); } return result; } From 97e4b6c0f3203ac7700ea3cae7efe49e226c8119 Mon Sep 17 00:00:00 2001 From: matteo Date: Wed, 28 Jan 2026 12:21:57 +0100 Subject: [PATCH 004/104] style(parser): refactor parse_command --- src/parser/grammar_basic.c | 94 ++++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 44 deletions(-) diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index 932d7f5..ab4194c 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -152,14 +152,15 @@ struct ast *parse_pipeline(struct lexer_context *ctx) struct ast *parse_command(struct lexer_context *ctx) { struct token *token = PEEK_TOKEN(); + struct ast *result = NULL; if (is_first(*token, RULE_SIMPLE_COMMAND)) { - return parse_simple_command(ctx); + result = parse_simple_command(ctx); } else if (is_first(*token, RULE_SHELL_COMMAND)) { - return parse_shell_command(ctx); + parse_shell_command(ctx); } else { @@ -201,57 +202,62 @@ struct ast *parse_simple_command(struct lexer_context *ctx) } } - if (token->type != TOKEN_WORD && !has_prefix) + if (token->type != TOKEN_WORD) { - perror("Expected a command but got a different token type"); - return err_simple_command(command_elements, redirections); - } - - // we can have only prefixes askip - if (token->type == TOKEN_WORD) - { - char *command = strdup(token->data); - command_elements = list_append(command_elements, command); - - POP_TOKEN(); - token = PEEK_TOKEN(); - } - // Eventual elements - while (is_first(*token, RULE_ELEMENT)) - { - // Get element - struct ast *element = parse_element(ctx); - if (element == NULL) + if (!has_prefix) { + perror("Expected a command but got a different token type"); return err_simple_command(command_elements, redirections); } - - // Get element type - if (ast_is_word(element)) + // else : only prefixes + } + else + { + if (token->type == TOKEN_WORD) { - struct ast_word *element_word = ast_get_word(element); + char *command = strdup(token->data); + command_elements = list_append(command_elements, command); - // TODO test this fix for the memory leaks - char *word = strdup(element_word->word); - ast_free(element_word); - command_elements = - list_append(command_elements, word); - // end of fix + POP_TOKEN(); + token = PEEK_TOKEN(); } - else if (ast_is_redir(element)) + // Eventual elements + while (is_first(*token, RULE_ELEMENT)) { - // append redirections to the list of redirections - redirections = list_append(redirections, element); - } - else - { - perror("Internal error: unexpected return value from parse_element " - "in parse_simple_command"); - return err_simple_command(command_elements, redirections); - } + // Get element + struct ast *element = parse_element(ctx); + if (element == NULL) + { + return err_simple_command(command_elements, redirections); + } - // Forward - token = PEEK_TOKEN(); + // Get element type + if (ast_is_word(element)) + { + struct ast_word *element_word = ast_get_word(element); + + // TODO test this fix for the memory leaks + char *word = strdup(element_word->word); + ast_free(element_word); + command_elements = list_append(command_elements, word); + // end of fix + } + else if (ast_is_redir(element)) + { + // append redirections to the list of redirections + redirections = list_append(redirections, element); + } + else + { + perror("Internal error: unexpected return value from " + "parse_element " + "in parse_simple_command"); + return err_simple_command(command_elements, redirections); + } + + // Forward + token = PEEK_TOKEN(); + } } // Result From 0db50e28de94d5dfdfffc169898d07e3a337bf97 Mon Sep 17 00:00:00 2001 From: matteo Date: Wed, 28 Jan 2026 16:26:17 +0100 Subject: [PATCH 005/104] fix(parser): small typos --- src/parser/grammar_basic.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index ab4194c..fb1b12d 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -172,7 +172,7 @@ struct ast *parse_command(struct lexer_context *ctx) /* @brief: frees command_elements and redirections lists * @return: NULL */ -static err_simple_command(struct list *command_elements, +static void *err_simple_command(struct list *command_elements, struct list *redirections) { list_deep_destroy(command_elements); @@ -238,7 +238,7 @@ struct ast *parse_simple_command(struct lexer_context *ctx) // TODO test this fix for the memory leaks char *word = strdup(element_word->word); - ast_free(element_word); + ast_free(&element); command_elements = list_append(command_elements, word); // end of fix } From f1310b7b0932d6766efef7509f10e8da77c39027 Mon Sep 17 00:00:00 2001 From: matteo Date: Wed, 28 Jan 2026 16:42:40 +0100 Subject: [PATCH 006/104] feat(lexer): parsing and_or operators --- src/lexer/lexer_utils.c | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/lexer/lexer_utils.c b/src/lexer/lexer_utils.c index 07455a7..54859e8 100644 --- a/src/lexer/lexer_utils.c +++ b/src/lexer/lexer_utils.c @@ -97,35 +97,43 @@ static void set_token_operator(struct token *tok, char *begin, ssize_t size) { if (tok->type != TOKEN_NULL) return; - if (strncmp(begin, ">", size) == 0) + if (strncmp(begin, "||", size) == 0 && size == 2) + { + tok->type = TOKEN_OR; + } + else if (strncmp(begin, "&&", size) == 0 && size == 2) + { + tok->type = TOKEN_AND; + } + else if (strncmp(begin, ">", size) == 0 && size == 1) { tok->type = TOKEN_REDIR_RIGHT; } - else if (strncmp(begin, "<", size) == 0) + else if (strncmp(begin, "<", size) == 0 && size == 1) { tok->type = TOKEN_REDIR_LEFT; } - else if (strncmp(begin, ">>", size) == 0) + else if (strncmp(begin, ">>", size) == 0 && size == 2) { tok->type = TOKEN_REDIR_DOUBLE_RIGHT; } - else if (strncmp(begin, ">&", size) == 0) + else if (strncmp(begin, ">&", size) == 0 && size == 2) { tok->type = TOKEN_REDIR_RIGHT_AMP; } - else if (strncmp(begin, ">|", size) == 0) + else if (strncmp(begin, ">|", size) == 0 && size == 2) { tok->type = TOKEN_REDIR_RIGHT_PIPE; } - else if (strncmp(begin, "<&", size) == 0) + else if (strncmp(begin, "<&", size) == 0 && size == 2) { tok->type = TOKEN_REDIR_LEFT_AMP; } - else if (strncmp(begin, "<>", size) == 0) + else if (strncmp(begin, "<>", size) == 0 && size == 2) { tok->type = TOKEN_REDIR_LEFT_RIGHT; } - else if (strncmp(begin, "|", size) == 0) + else if (strncmp(begin, "|", size) == 0 && size == 1) { tok->type = TOKEN_PIPE; } @@ -267,6 +275,12 @@ ssize_t len_op_sepchar(char *stream, ssize_t i) if (!is_special_char(stream, i)) return -1; // should never happen + if (stream[i] == '|' || stream[i + 1] == '|') + return 2; // OR || + + if (stream[i] == '&' && stream[i + 1] == '&') + return 2; // AND && + if (stream[i] != '>' && stream[i] != '<') return 1; // special character (cannot be operator) From c40e5c2d0f7ee9e8ebaa76e504453b1d3b7ebba6 Mon Sep 17 00:00:00 2001 From: matteo Date: Wed, 28 Jan 2026 19:16:48 +0100 Subject: [PATCH 007/104] feat(parser): helper static function --- src/parser/grammar_basic.c | 42 +++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index fb1b12d..17e006f 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -169,7 +169,7 @@ struct ast *parse_command(struct lexer_context *ctx) } } -/* @brief: frees command_elements and redirections lists +/* @brief: frees command_elements and redirections lists (helper func) * @return: NULL */ static void *err_simple_command(struct list *command_elements, @@ -293,6 +293,17 @@ struct ast *parse_shell_command(struct lexer_context *ctx) return parse_if_rule(ctx); } +/* @brief: frees all the arguments. (helper func) + * @return: NULL. + */ +static void *err_if_rule(struct ast **cond, struct ast **then_clause, struct ast **else_clause) +{ + ast_free(cond); + ast_free(then_clause); + ast_free(else_clause); + return NULL; +} + struct ast *parse_if_rule(struct lexer_context *ctx) { // If keyword @@ -311,38 +322,34 @@ struct ast *parse_if_rule(struct lexer_context *ctx) token = POP_TOKEN(); if (token->type != TOKEN_THEN) { - ast_free(&condition_content); perror("Expected the 'then' keyword but token has different type"); - return NULL; + return err_if_rule(&condition_content, NULL, NULL); } // Then content struct ast *then_content = parse_compound_list(ctx); if (then_content == NULL) { - ast_free(&condition_content); - ast_free(&then_content); - return NULL; + return err_if_rule(&condition_content, &then_content, NULL); } + struct ast *else_content = NULL; // Eventual else/elif clause(s) - struct ast *else_content = parse_else_clause(ctx); - if (else_content == NULL) + if (is_first(*token, RULE_ELSE_CLAUSE)) { - ast_free(&condition_content); - ast_free(&then_content); - return NULL; + else_content = parse_else_clause(ctx); + if (else_content == NULL) + { + return err_if_rule(&condition_content, &then_content, NULL); + } } // Fi keyword token = POP_TOKEN(); if (token->type != TOKEN_FI) { - ast_free(&condition_content); - ast_free(&then_content); - ast_free(&else_content); perror("Expected the 'fi' keyword but token has different type"); - return NULL; + return err_if_rule(&condition_content, &then_content, &else_content); } // Result @@ -350,11 +357,8 @@ struct ast *parse_if_rule(struct lexer_context *ctx) ast_create_if(condition_content, then_content, else_content); if (result == NULL) { - ast_free(&condition_content); - ast_free(&then_content); - ast_free(&else_content); perror("Internal error: could not create a new AST (AST_IF)"); - return NULL; + return err_if_rule(&condition_content, &then_content, &else_content); } return result; From 28b2e820789ebacf75adfeedb988250323f1ade2 Mon Sep 17 00:00:00 2001 From: Jean <47366872+jean-voila@users.noreply.github.com> Date: Wed, 28 Jan 2026 21:55:55 +0100 Subject: [PATCH 008/104] began to reimplement the new execution --- src/execution/execution_helpers.c | 46 +++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/src/execution/execution_helpers.c b/src/execution/execution_helpers.c index 24c4100..971688a 100644 --- a/src/execution/execution_helpers.c +++ b/src/execution/execution_helpers.c @@ -72,7 +72,9 @@ int exec_ast_command(struct ast_command *command, struct hash_map *vars) waitpid(pid, &status, 0); free(argv); if (WIFEXITED(status)) + { return WEXITSTATUS(status); + } return 1; } @@ -121,7 +123,7 @@ static int get_fd_target(const struct ast_redir *redir) if (redir->io_number != -1) return redir->io_number; if (redir->type == AST_REDIR_TYPE_LESS - || redir->type == AST_REDIR_TYPE_LESSGREAT + || redir->type == AST_REDIR_TYPE_DLESS || redir->type == AST_REDIR_TYPE_LESSAND) return 0; return 1; @@ -163,6 +165,43 @@ static int handle_and_restore_fd(int saved_fd, int fd_target) return 0; } +/* + +static open_all_redir(const struct ast_list redir_list) +{ + while (redir_list){ + struct ast_redir *redir = (struct ast_redir*)redir_list->data; + int target_fd; + if (redir->io_number != -1) + { + target_fd = redir->io_number; + } + else + { + // assign target_fd depending on redir type + } + + int saved_fd = dup(target_fd); + + // if redir type is not with '&' + // then we open("filename") + // else, no need to open, just new_fd = atoi(filename) + + open(); + + dup2(target_fd, new_fd); + + close(new_fd); + + // append target_fd and saved_fd to a list + // in order to be able to restore all the fds + } + +} + + +*/ +/* int exec_ast_redir(struct ast_redir *redir, struct hash_map *vars) { int fd_target = get_fd_target(redir); @@ -206,7 +245,7 @@ int exec_ast_redir(struct ast_redir *redir, struct hash_map *vars) int ret = execution(redir->child, vars); handle_and_restore_fd(saved_fd, fd_target); return ret; -} +} */ // --- Builtins --- @@ -234,7 +273,6 @@ static int builtin_echo(char **argv) return 0; } - static int builtin_true(char **argv) { (void)argv; @@ -256,7 +294,6 @@ static int builtin_exit(char **argv) return exit_val; } - static int builtin_cd(char **argv) { const char *path = argv[1]; @@ -277,7 +314,6 @@ static int builtin_cd(char **argv) return 0; } - /** * @brief Tries to execute a builtin command if the command matches a builtin * From 75d417eecfc2bbbad58438e2bb1387472056fecb Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Wed, 28 Jan 2026 21:26:51 +0000 Subject: [PATCH 009/104] feat(expansion): remove double quotes --- src/expansion/expansion.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/expansion/expansion.c b/src/expansion/expansion.c index ab6241e..4fea985 100644 --- a/src/expansion/expansion.c +++ b/src/expansion/expansion.c @@ -135,7 +135,7 @@ bool expand(struct ast_command *command, const struct hash_map *vars) { if (str[i] == '\'') { - // remove quote + // remove single quote in_quotes = !in_quotes; memmove(str + i, str + i + 1, strlen(str + i + 1) + 1); i--; @@ -144,6 +144,12 @@ bool expand(struct ast_command *command, const struct hash_map *vars) { 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 From fde653ac5d5f4f133ce7d3c0819656fe3d2d6073 Mon Sep 17 00:00:00 2001 From: matteo Date: Thu, 29 Jan 2026 09:56:28 +0100 Subject: [PATCH 010/104] feat(ast): add ast_assignements. Similar to how redirections are handled. --- src/utils/ast/ast_assignement.c | 36 +++++++++++++++++++++++++++++++++ src/utils/ast/ast_assignement.h | 17 ++++++++++++++++ src/utils/ast/ast_base.h | 3 ++- src/utils/ast/ast_command.c | 3 ++- src/utils/ast/ast_command.h | 3 ++- 5 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 src/utils/ast/ast_assignement.c create mode 100644 src/utils/ast/ast_assignement.h diff --git a/src/utils/ast/ast_assignement.c b/src/utils/ast/ast_assignement.c new file mode 100644 index 0000000..036e4d2 --- /dev/null +++ b/src/utils/ast/ast_assignement.c @@ -0,0 +1,36 @@ +#include "ast_assignement.h" + +#include + +bool ast_is_assignement(struct ast *node) +{ + return node != NULL && node->type == AST_ASSIGNEMENT; +} + +struct ast_assignement *ast_get_assignement(struct ast *node) +{ + if (node == NULL || node->type != AST_ASSIGNEMENT) + return NULL; + return (struct ast_assignement *)node->data; +} + +struct ast *ast_create_assignement(char *name, char *value) +{ + struct ast_assignement *assignement_data = malloc(sizeof(struct ast_assignement)); + if (!assignement_data) + return NULL; + + assignement_data->name = name; + assignement_data->value = value; + + return ast_create(AST_ASSIGNEMENT, assignement_data); +} + +void ast_free_assignement(struct ast_assignement *assignement_data) +{ + if (assignement_data == NULL) + return; + free(assignement_data->name); + free(assignement_data->value); + free(assignement_data); +} \ No newline at end of file diff --git a/src/utils/ast/ast_assignement.h b/src/utils/ast/ast_assignement.h new file mode 100644 index 0000000..5975497 --- /dev/null +++ b/src/utils/ast/ast_assignement.h @@ -0,0 +1,17 @@ +#ifndef AST_ASSIGNEMENT_H +#define AST_ASSIGNEMENT_H + +#include "ast_base.h" + +struct ast_assignement +{ + char *name; + char *value; +}; + +bool ast_is_assignement(struct ast *node); +struct ast_assignement *ast_get_assignement(struct ast *node); +struct ast *ast_create_assignement(char *name, char *value); +void ast_free_assignement(struct ast_assignement *assignement_data); + +#endif /* ! AST_ASSIGNEMENT_H */ \ No newline at end of file diff --git a/src/utils/ast/ast_base.h b/src/utils/ast/ast_base.h index f81f2f0..f04014d 100644 --- a/src/utils/ast/ast_base.h +++ b/src/utils/ast/ast_base.h @@ -15,7 +15,8 @@ enum ast_type AST_CMD, AST_WORD, AST_PIPE, - AST_NEG + AST_NEG, + AST_ASSIGNEMENT }; struct ast diff --git a/src/utils/ast/ast_command.c b/src/utils/ast/ast_command.c index c4fe3e6..affe45e 100644 --- a/src/utils/ast/ast_command.c +++ b/src/utils/ast/ast_command.c @@ -6,7 +6,7 @@ #include "../lists/lists.h" struct ast *ast_create_command(struct list *command, - struct list *redirections) + struct list *redirections, struct ast_list *assignements) { struct ast_command *command_data = malloc(sizeof(struct ast_command)); if (!command_data) @@ -36,5 +36,6 @@ void ast_free_command(struct ast_command *command_data) return; list_deep_destroy(command_data->command); ast_list_deep_destroy(command_data->redirections); + ast_list_deep_destroy(command_data->assignements); free(command_data); } diff --git a/src/utils/ast/ast_command.h b/src/utils/ast/ast_command.h index 835bf5d..0641f1d 100644 --- a/src/utils/ast/ast_command.h +++ b/src/utils/ast/ast_command.h @@ -8,6 +8,7 @@ struct ast_command { struct list *command; // A list of words (char*) struct ast_list *redirections; // A list of ASTs, all ast_redir + struct ast_list *assignements; // A list of ASTs, all ast_assignement }; /** @@ -25,7 +26,7 @@ struct ast_command *ast_get_command(struct ast *node); * Creates a new AST node representing a command. */ struct ast *ast_create_command(struct list *command, - struct list *redirections); + struct list *redirections, struct ast_list *assignements); /* * @brief: frees the given ast_command and sets the pointer to NULL. From e1291107d4b11827c265da185ffbdb4bec134b33 Mon Sep 17 00:00:00 2001 From: matteo Date: Thu, 29 Jan 2026 09:59:16 +0100 Subject: [PATCH 011/104] fix: small errors in exec and parser --- src/execution/execution_helpers.c | 2 +- src/parser/grammar.c | 6 +++--- src/parser/grammar_basic.c | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/execution/execution_helpers.c b/src/execution/execution_helpers.c index 971688a..9dcaf41 100644 --- a/src/execution/execution_helpers.c +++ b/src/execution/execution_helpers.c @@ -123,7 +123,7 @@ static int get_fd_target(const struct ast_redir *redir) if (redir->io_number != -1) return redir->io_number; if (redir->type == AST_REDIR_TYPE_LESS - || redir->type == AST_REDIR_TYPE_DLESS + || redir->type == AST_REDIR_TYPE_LESSGREAT || redir->type == AST_REDIR_TYPE_LESSAND) return 0; return 1; diff --git a/src/parser/grammar.c b/src/parser/grammar.c index 83f2266..95f1bd2 100644 --- a/src/parser/grammar.c +++ b/src/parser/grammar.c @@ -106,15 +106,15 @@ bool grammar_init(void) add_first(RULE_LIST, TOKEN_WORD); add_first(RULE_LIST, TOKEN_IF); - add_first(RULE_COMMAND, TOKEN_NEGATION); + add_first(RULE_LIST, TOKEN_NEGATION); add_first(RULE_AND_OR, TOKEN_WORD); add_first(RULE_AND_OR, TOKEN_IF); - add_first(RULE_COMMAND, TOKEN_NEGATION); + add_first(RULE_AND_OR, TOKEN_NEGATION); add_first(RULE_PIPELINE, TOKEN_WORD); add_first(RULE_PIPELINE, TOKEN_IF); - add_first(RULE_COMMAND, TOKEN_NEGATION); + add_first(RULE_PIPELINE, TOKEN_NEGATION); add_first(RULE_COMMAND, TOKEN_WORD); add_first(RULE_COMMAND, TOKEN_IF); diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index 17e006f..9102593 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -261,6 +261,7 @@ struct ast *parse_simple_command(struct lexer_context *ctx) } // Result + // TODO handle assignements struct ast *result = ast_create_command(command_elements, redirections); if (result == NULL) { From f0a7173b11c588f55e01d0a15e30df8e542ce288 Mon Sep 17 00:00:00 2001 From: matteo Date: Thu, 29 Jan 2026 11:18:56 +0100 Subject: [PATCH 012/104] feat(lexer): implementing assignements --- src/lexer/lexer.c | 18 +++++++++++++++++- src/lexer/lexer_utils.h | 15 +++++++++++---- src/parser/grammar_advanced.c | 14 +++++++++++++- 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index ff7f351..21f362b 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -78,6 +78,21 @@ static bool update_lexing_mode(char *stream, ssize_t i, return *lexing_mode != mode_before_update; } +/* @brief: updates the flags only_digits and equal_count + * according to the character at stream[i]. + */ +static void update_flags(char *stream, ssize_t i, struct lexer_context *ctx) +{ + if (stream[i] == '=') + { + ctx->equal_count++; + } + else if (!isdigit(stream[i]) && ctx->only_digits) + { + ctx->only_digits = false; + } +} + struct token *peek_token(struct lexer_context *ctx) { // we already created the upcoming token during the previous call to peek() @@ -100,6 +115,7 @@ struct token *peek_token(struct lexer_context *ctx) if (!update_lexing_mode(stream, i, &lexing_mode) && lexing_mode == LEXER_NORMAL) { + update_flags(stream, i, ctx); if (is_special_char(stream, i)) { if (i == 0) // where we create spe_char token @@ -121,7 +137,7 @@ struct token *peek_token(struct lexer_context *ctx) i++; } - struct token *tok = new_token(stream, i, ctx->only_digits); + struct token *tok = new_token(stream, i, ctx->only_digits, ctx->equal_count); // if token is comment, we don't want it if (tok->type == TOKEN_COMMENT) diff --git a/src/lexer/lexer_utils.h b/src/lexer/lexer_utils.h index c6c26c9..2708662 100644 --- a/src/lexer/lexer_utils.h +++ b/src/lexer/lexer_utils.h @@ -14,6 +14,9 @@ struct lexer_context // tells us if we only lexed digits in current token. bool only_digits; + // usefull to detect assignments, and syntax errors with '='. + int equal_count; + struct token *previous_token; struct token *current_token; }; @@ -34,10 +37,13 @@ enum token_type // Blanks TOKEN_NULL = 0, TOKEN_EOF, - TOKEN_WORD, TOKEN_NEWLINE, - // SPecial characters + // words + TOKEN_WORD, + TOKEN_ASSIGNMENT_WORD, + + // Special characters TOKEN_GRAVE, TOKEN_SEMICOLON, TOKEN_COMMENT, @@ -87,12 +93,13 @@ struct token */ bool is_special_char(char *stream, ssize_t i); -/* @brief: return a newly allocated token, with the corresponding type. +/* @brief: return a newly allocated token, with the type corresponding + * to the context given in arguments. * The data contains [size] char, starting from [begin]. * * @return: NULL on error, a token otherwise. */ -struct token *new_token(char *begin, ssize_t size, bool only_digits); +struct token *new_token(char *begin, ssize_t size, bool only_digits, int equal_count); /* @brief: frees the token given in argument */ diff --git a/src/parser/grammar_advanced.c b/src/parser/grammar_advanced.c index 8c4b68c..1964f49 100644 --- a/src/parser/grammar_advanced.c +++ b/src/parser/grammar_advanced.c @@ -66,5 +66,17 @@ struct ast *parse_redirection(struct lexer_context *ctx) struct ast *parse_prefix(struct lexer_context *ctx) { - return parse_redirection(ctx); + struct token *token = TOKEN_PEEK(); + if (token->type == TOKEN_ASSIGNMENT_WORD) + { + token = TOKEN_POP(); + return ast_create_assignment_word(token->data); + } + else if (is_first(*token, RULE_REDIRECTION)) + return parse_redirection(ctx); + else + { + perror("Syntax error: expected a prefix (redirection or assignment)"); + return NULL; + } } From ac77d79a603719e05db37dfa033e77b3899d42c1 Mon Sep 17 00:00:00 2001 From: matteo Date: Thu, 29 Jan 2026 11:32:15 +0100 Subject: [PATCH 013/104] feat(ast_assignement): changed (name, value) to assignement --- src/utils/ast/ast_assignement.c | 10 ++++------ src/utils/ast/ast_assignement.h | 8 ++++---- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/utils/ast/ast_assignement.c b/src/utils/ast/ast_assignement.c index 036e4d2..86e892d 100644 --- a/src/utils/ast/ast_assignement.c +++ b/src/utils/ast/ast_assignement.c @@ -14,14 +14,13 @@ struct ast_assignement *ast_get_assignement(struct ast *node) return (struct ast_assignement *)node->data; } -struct ast *ast_create_assignement(char *name, char *value) +struct ast *ast_create_assignement(char* assignement) { struct ast_assignement *assignement_data = malloc(sizeof(struct ast_assignement)); if (!assignement_data) return NULL; - assignement_data->name = name; - assignement_data->value = value; + assignement_data->assignement = assignement; return ast_create(AST_ASSIGNEMENT, assignement_data); } @@ -30,7 +29,6 @@ void ast_free_assignement(struct ast_assignement *assignement_data) { if (assignement_data == NULL) return; - free(assignement_data->name); - free(assignement_data->value); + free(assignement_data->assignement); free(assignement_data); -} \ No newline at end of file +} diff --git a/src/utils/ast/ast_assignement.h b/src/utils/ast/ast_assignement.h index 5975497..533086d 100644 --- a/src/utils/ast/ast_assignement.h +++ b/src/utils/ast/ast_assignement.h @@ -5,13 +5,13 @@ struct ast_assignement { - char *name; - char *value; + char *assignement; // assignement of the form 'a=b'. + // needs to be parsed lexer }; bool ast_is_assignement(struct ast *node); struct ast_assignement *ast_get_assignement(struct ast *node); -struct ast *ast_create_assignement(char *name, char *value); +struct ast *ast_create_assignement(char *assignement); void ast_free_assignement(struct ast_assignement *assignement_data); -#endif /* ! AST_ASSIGNEMENT_H */ \ No newline at end of file +#endif /* ! AST_ASSIGNEMENT_H */ From fe3c4243c8673e832f64691f757fd88f7b89e593 Mon Sep 17 00:00:00 2001 From: matteo Date: Thu, 29 Jan 2026 11:37:20 +0100 Subject: [PATCH 014/104] Revert "feat(ast_assignement): changed (name, value) to assignement" This reverts commit ac77d79a603719e05db37dfa033e77b3899d42c1. --- src/utils/ast/ast_assignement.c | 10 ++++++---- src/utils/ast/ast_assignement.h | 8 ++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/utils/ast/ast_assignement.c b/src/utils/ast/ast_assignement.c index 86e892d..036e4d2 100644 --- a/src/utils/ast/ast_assignement.c +++ b/src/utils/ast/ast_assignement.c @@ -14,13 +14,14 @@ struct ast_assignement *ast_get_assignement(struct ast *node) return (struct ast_assignement *)node->data; } -struct ast *ast_create_assignement(char* assignement) +struct ast *ast_create_assignement(char *name, char *value) { struct ast_assignement *assignement_data = malloc(sizeof(struct ast_assignement)); if (!assignement_data) return NULL; - assignement_data->assignement = assignement; + assignement_data->name = name; + assignement_data->value = value; return ast_create(AST_ASSIGNEMENT, assignement_data); } @@ -29,6 +30,7 @@ void ast_free_assignement(struct ast_assignement *assignement_data) { if (assignement_data == NULL) return; - free(assignement_data->assignement); + free(assignement_data->name); + free(assignement_data->value); free(assignement_data); -} +} \ No newline at end of file diff --git a/src/utils/ast/ast_assignement.h b/src/utils/ast/ast_assignement.h index 533086d..5975497 100644 --- a/src/utils/ast/ast_assignement.h +++ b/src/utils/ast/ast_assignement.h @@ -5,13 +5,13 @@ struct ast_assignement { - char *assignement; // assignement of the form 'a=b'. - // needs to be parsed lexer + char *name; + char *value; }; bool ast_is_assignement(struct ast *node); struct ast_assignement *ast_get_assignement(struct ast *node); -struct ast *ast_create_assignement(char *assignement); +struct ast *ast_create_assignement(char *name, char *value); void ast_free_assignement(struct ast_assignement *assignement_data); -#endif /* ! AST_ASSIGNEMENT_H */ +#endif /* ! AST_ASSIGNEMENT_H */ \ No newline at end of file From 567192b77194a5f5c66e70e424e94d18a86bb845 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Thu, 29 Jan 2026 11:18:33 +0000 Subject: [PATCH 015/104] feat(vars): unit tests --- tests/unit/utils/vars.c | 85 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 tests/unit/utils/vars.c diff --git a/tests/unit/utils/vars.c b/tests/unit/utils/vars.c new file mode 100644 index 0000000..73784cf --- /dev/null +++ b/tests/unit/utils/vars.c @@ -0,0 +1,85 @@ +#define _POSIX_C_SOURCE 200809L +#include "../../../src/utils/vars/vars.h" + +#include +#include +#include +#include +#include + +#include "../../../src/utils/hash_map/hash_map.h" +#include "../../../src/utils/string_utils/string_utils.h" + +TestSuite(utils_vars); + +Test(utils_vars, init_free) +{ + struct hash_map *map = vars_init(); + cr_expect_not_null(map); + hash_map_free(&map); +} + +Test(utils_vars, get_defaults) +{ + struct hash_map *map = vars_init(); + cr_expect_not_null(map); + cr_assert_str_eq(get_var(map, "?"), "0"); + char int_str[11]; + int_to_str((int)getpid(), int_str); + cr_assert_str_eq(get_var(map, "$"), int_str); + int_to_str((int)getuid(), int_str); + cr_assert_str_eq(get_var(map, "UID"), int_str); + + hash_map_free(&map); +} + +Test(utils_vars, set_vars) +{ + struct hash_map *map = vars_init(); + cr_expect_not_null(map); + set_var_copy(map, "key1", "value1"); + cr_assert_str_eq(get_var(map, "key1"), "value1"); + set_var_copy(map, "key2", "value2"); + cr_assert_str_eq(get_var(map, "key2"), "value2"); + + hash_map_free(&map); +} + +Test(utils_vars, get_env_vars) +{ + struct hash_map *map = vars_init(); + cr_expect_not_null(map); + cr_assert_eq(get_var_or_env(map, "ENV_TEST"), NULL); + setenv("ENV_TEST", "value1", 0); + cr_assert_str_eq(get_var_or_env(map, "ENV_TEST"), "value1"); + setenv("ENV_TEST", "value2", 1); + cr_assert_str_eq(get_var_or_env(map, "ENV_TEST"), "value2"); + + hash_map_free(&map); +} + +Test(utils_vars, set_vars_update) +{ + struct hash_map *map = vars_init(); + cr_expect_not_null(map); + set_var_copy(map, "key", "value1"); + cr_assert_str_eq(get_var(map, "key"), "value1"); + set_var_copy(map, "key", "value2"); + cr_assert_str_eq(get_var(map, "key"), "value2"); + + hash_map_free(&map); +} + +Test(utils_vars, set_vars_int) +{ + struct hash_map *map = vars_init(); + cr_expect_not_null(map); + set_var_int(map, "key1", 100); + cr_assert_str_eq(get_var(map, "key1"), "100"); + set_var_int(map, "key1", 200); + cr_assert_str_eq(get_var(map, "key1"), "200"); + set_var_int(map, "key2", 10); + cr_assert_str_eq(get_var(map, "key2"), "10"); + + hash_map_free(&map); +} From 1e4b3fb0a82468e168c3c6ec37dd378afa124c8e Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Tue, 27 Jan 2026 21:06:36 +0100 Subject: [PATCH 016/104] feat(redirections): ast commands now have a field for the list of redirections + redirections implemented in parser --- src/parser/grammar_basic.c | 10 ++++++---- src/utils/ast/ast_command.c | 5 ++++- src/utils/ast/ast_command.h | 4 +++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index d4a740f..f3e63b6 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -171,6 +171,7 @@ struct ast *parse_command(struct lexer_context *ctx) struct ast *parse_simple_command(struct lexer_context *ctx) { struct list *command_elements = NULL; + struct list *redirections = NULL; // list of redirection ASTs // WORD struct token *token = POP_TOKEN(); @@ -204,15 +205,15 @@ struct ast *parse_simple_command(struct lexer_context *ctx) } else if (ast_is_redir(element)) { - // TODO - perror("NOT IMPLEMENTED"); - return NULL; + // append redirections to the list of redirections + redirections = list_append(redirections, element); } else { perror("Internal error: unexpected return value from parse_element " "in parse_simple_command"); list_deep_destroy(command_elements); + list_deep_destroy(redirections); return NULL; } @@ -221,10 +222,11 @@ struct ast *parse_simple_command(struct lexer_context *ctx) } // Result - struct ast *result = ast_create_command(command_elements); + struct ast *result = ast_create_command(command_elements, redirections); if (result == NULL) { list_deep_destroy(command_elements); + list_deep_destroy(redirections); return NULL; } return result; diff --git a/src/utils/ast/ast_command.c b/src/utils/ast/ast_command.c index 14904ae..c4fe3e6 100644 --- a/src/utils/ast/ast_command.c +++ b/src/utils/ast/ast_command.c @@ -5,13 +5,15 @@ #include "../lists/lists.h" -struct ast *ast_create_command(struct list *command) +struct ast *ast_create_command(struct list *command, + struct list *redirections) { struct ast_command *command_data = malloc(sizeof(struct ast_command)); if (!command_data) return NULL; command_data->command = command; + command_data->redirections = redirections; return ast_create(AST_CMD, command_data); } @@ -33,5 +35,6 @@ void ast_free_command(struct ast_command *command_data) if (command_data == NULL) return; list_deep_destroy(command_data->command); + ast_list_deep_destroy(command_data->redirections); free(command_data); } diff --git a/src/utils/ast/ast_command.h b/src/utils/ast/ast_command.h index cf6b913..835bf5d 100644 --- a/src/utils/ast/ast_command.h +++ b/src/utils/ast/ast_command.h @@ -7,6 +7,7 @@ struct ast_command { struct list *command; // A list of words (char*) + struct ast_list *redirections; // A list of ASTs, all ast_redir }; /** @@ -23,7 +24,8 @@ struct ast_command *ast_get_command(struct ast *node); /** * Creates a new AST node representing a command. */ -struct ast *ast_create_command(struct list *command); +struct ast *ast_create_command(struct list *command, + struct list *redirections); /* * @brief: frees the given ast_command and sets the pointer to NULL. From 9d586402df51effd64507f7207137e09a349b4e5 Mon Sep 17 00:00:00 2001 From: matteo Date: Wed, 28 Jan 2026 11:34:29 +0100 Subject: [PATCH 017/104] fix: redirections types --- src/execution/execution_helpers.c | 2 +- src/parser/grammar_advanced.c | 11 ++++++++++- src/utils/ast/ast_redir.h | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/execution/execution_helpers.c b/src/execution/execution_helpers.c index e82cbfe..24c4100 100644 --- a/src/execution/execution_helpers.c +++ b/src/execution/execution_helpers.c @@ -121,7 +121,7 @@ static int get_fd_target(const struct ast_redir *redir) if (redir->io_number != -1) return redir->io_number; if (redir->type == AST_REDIR_TYPE_LESS - || redir->type == AST_REDIR_TYPE_DLESS + || redir->type == AST_REDIR_TYPE_LESSGREAT || redir->type == AST_REDIR_TYPE_LESSAND) return 0; return 1; diff --git a/src/parser/grammar_advanced.c b/src/parser/grammar_advanced.c index 3850564..8c4b68c 100644 --- a/src/parser/grammar_advanced.c +++ b/src/parser/grammar_advanced.c @@ -16,7 +16,16 @@ static enum ast_redir_type redir_tok_to_ast_type(enum token_type tok_type) return AST_REDIR_TYPE_LESS; case TOKEN_REDIR_RIGHT: return AST_REDIR_TYPE_GREAT; - // TODO finish this + case TOKEN_REDIR_DOUBLE_RIGHT: + return AST_REDIR_TYPE_DGREAT; + case TOKEN_REDIR_LEFT_RIGHT: + return AST_REDIR_TYPE_LESSGREAT; + case TOKEN_REDIR_LEFT_AMP: + return AST_REDIR_TYPE_LESSAND; + case TOKEN_REDIR_RIGHT_AMP: + return AST_REDIR_TYPE_GREATAND; + case TOKEN_REDIR_RIGHT_PIPE: + return AST_REDIR_TYPE_CLOBBER; default: return AST_REDIR_TYPE_NULL; } diff --git a/src/utils/ast/ast_redir.h b/src/utils/ast/ast_redir.h index eb95942..fdea88c 100644 --- a/src/utils/ast/ast_redir.h +++ b/src/utils/ast/ast_redir.h @@ -8,7 +8,7 @@ enum ast_redir_type AST_REDIR_TYPE_NULL, AST_REDIR_TYPE_LESS, // < AST_REDIR_TYPE_GREAT, // > - AST_REDIR_TYPE_DLESS, // << + AST_REDIR_TYPE_LESSGREAT, // <> AST_REDIR_TYPE_DGREAT, // >> AST_REDIR_TYPE_LESSAND, // <& AST_REDIR_TYPE_GREATAND, // >& From 51fc8a3330c8dae863b49e44f97b5bf7bca0b1ce Mon Sep 17 00:00:00 2001 From: matteo Date: Wed, 28 Jan 2026 12:11:40 +0100 Subject: [PATCH 018/104] feat(parser): parse_command version with prefixes and elements --- src/parser/grammar_basic.c | 69 +++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 19 deletions(-) diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index f3e63b6..932d7f5 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -153,11 +153,11 @@ struct ast *parse_command(struct lexer_context *ctx) { struct token *token = PEEK_TOKEN(); - if (token->type == TOKEN_WORD) + if (is_first(*token, RULE_SIMPLE_COMMAND)) { return parse_simple_command(ctx); } - else if (token->type == TOKEN_IF) + else if (is_first(*token, RULE_SHELL_COMMAND)) { return parse_shell_command(ctx); } @@ -168,23 +168,54 @@ struct ast *parse_command(struct lexer_context *ctx) } } +/* @brief: frees command_elements and redirections lists + * @return: NULL + */ +static err_simple_command(struct list *command_elements, + struct list *redirections) +{ + list_deep_destroy(command_elements); + list_deep_destroy(redirections); + return NULL; +} + struct ast *parse_simple_command(struct lexer_context *ctx) { struct list *command_elements = NULL; struct list *redirections = NULL; // list of redirection ASTs - // WORD - struct token *token = POP_TOKEN(); - if (token->type != TOKEN_WORD) + bool has_prefix = false; + struct token *token = PEEK_TOKEN(); + if (is_first(*token, RULE_PREFIX)) + { + has_prefix = true; + while (is_first(*token, RULE_PREFIX)) + { + struct ast *redir = parse_prefix(ctx); + if (redir == NULL) + { + return err_simple_command(command_elements, redirections); + } + redirections = list_append(redirections, redir); + token = PEEK_TOKEN(); + } + } + + if (token->type != TOKEN_WORD && !has_prefix) { perror("Expected a command but got a different token type"); - return NULL; + return err_simple_command(command_elements, redirections); } - char *command = strdup(token->data); - command_elements = list_append(command_elements, command); - - token = PEEK_TOKEN(); + + // we can have only prefixes askip + if (token->type == TOKEN_WORD) + { + char *command = strdup(token->data); + command_elements = list_append(command_elements, command); + POP_TOKEN(); + token = PEEK_TOKEN(); + } // Eventual elements while (is_first(*token, RULE_ELEMENT)) { @@ -192,16 +223,20 @@ struct ast *parse_simple_command(struct lexer_context *ctx) struct ast *element = parse_element(ctx); if (element == NULL) { - list_deep_destroy(command_elements); - return NULL; + return err_simple_command(command_elements, redirections); } // Get element type if (ast_is_word(element)) { struct ast_word *element_word = ast_get_word(element); + + // TODO test this fix for the memory leaks + char *word = strdup(element_word->word); + ast_free(element_word); command_elements = - list_append(command_elements, element_word->word); + list_append(command_elements, word); + // end of fix } else if (ast_is_redir(element)) { @@ -212,9 +247,7 @@ struct ast *parse_simple_command(struct lexer_context *ctx) { perror("Internal error: unexpected return value from parse_element " "in parse_simple_command"); - list_deep_destroy(command_elements); - list_deep_destroy(redirections); - return NULL; + return err_simple_command(command_elements, redirections); } // Forward @@ -225,9 +258,7 @@ struct ast *parse_simple_command(struct lexer_context *ctx) struct ast *result = ast_create_command(command_elements, redirections); if (result == NULL) { - list_deep_destroy(command_elements); - list_deep_destroy(redirections); - return NULL; + return err_simple_command(command_elements, redirections); } return result; } From efb0a6f148e501ea6df83e9079445cfa5fbd6087 Mon Sep 17 00:00:00 2001 From: matteo Date: Wed, 28 Jan 2026 12:21:57 +0100 Subject: [PATCH 019/104] style(parser): refactor parse_command --- src/parser/grammar_basic.c | 94 ++++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 44 deletions(-) diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index 932d7f5..ab4194c 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -152,14 +152,15 @@ struct ast *parse_pipeline(struct lexer_context *ctx) struct ast *parse_command(struct lexer_context *ctx) { struct token *token = PEEK_TOKEN(); + struct ast *result = NULL; if (is_first(*token, RULE_SIMPLE_COMMAND)) { - return parse_simple_command(ctx); + result = parse_simple_command(ctx); } else if (is_first(*token, RULE_SHELL_COMMAND)) { - return parse_shell_command(ctx); + parse_shell_command(ctx); } else { @@ -201,57 +202,62 @@ struct ast *parse_simple_command(struct lexer_context *ctx) } } - if (token->type != TOKEN_WORD && !has_prefix) + if (token->type != TOKEN_WORD) { - perror("Expected a command but got a different token type"); - return err_simple_command(command_elements, redirections); - } - - // we can have only prefixes askip - if (token->type == TOKEN_WORD) - { - char *command = strdup(token->data); - command_elements = list_append(command_elements, command); - - POP_TOKEN(); - token = PEEK_TOKEN(); - } - // Eventual elements - while (is_first(*token, RULE_ELEMENT)) - { - // Get element - struct ast *element = parse_element(ctx); - if (element == NULL) + if (!has_prefix) { + perror("Expected a command but got a different token type"); return err_simple_command(command_elements, redirections); } - - // Get element type - if (ast_is_word(element)) + // else : only prefixes + } + else + { + if (token->type == TOKEN_WORD) { - struct ast_word *element_word = ast_get_word(element); + char *command = strdup(token->data); + command_elements = list_append(command_elements, command); - // TODO test this fix for the memory leaks - char *word = strdup(element_word->word); - ast_free(element_word); - command_elements = - list_append(command_elements, word); - // end of fix + POP_TOKEN(); + token = PEEK_TOKEN(); } - else if (ast_is_redir(element)) + // Eventual elements + while (is_first(*token, RULE_ELEMENT)) { - // append redirections to the list of redirections - redirections = list_append(redirections, element); - } - else - { - perror("Internal error: unexpected return value from parse_element " - "in parse_simple_command"); - return err_simple_command(command_elements, redirections); - } + // Get element + struct ast *element = parse_element(ctx); + if (element == NULL) + { + return err_simple_command(command_elements, redirections); + } - // Forward - token = PEEK_TOKEN(); + // Get element type + if (ast_is_word(element)) + { + struct ast_word *element_word = ast_get_word(element); + + // TODO test this fix for the memory leaks + char *word = strdup(element_word->word); + ast_free(element_word); + command_elements = list_append(command_elements, word); + // end of fix + } + else if (ast_is_redir(element)) + { + // append redirections to the list of redirections + redirections = list_append(redirections, element); + } + else + { + perror("Internal error: unexpected return value from " + "parse_element " + "in parse_simple_command"); + return err_simple_command(command_elements, redirections); + } + + // Forward + token = PEEK_TOKEN(); + } } // Result From 967a78e1a402520926793872ec8443fdd8625e40 Mon Sep 17 00:00:00 2001 From: matteo Date: Wed, 28 Jan 2026 16:26:17 +0100 Subject: [PATCH 020/104] fix(parser): small typos --- src/parser/grammar_basic.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index ab4194c..fb1b12d 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -172,7 +172,7 @@ struct ast *parse_command(struct lexer_context *ctx) /* @brief: frees command_elements and redirections lists * @return: NULL */ -static err_simple_command(struct list *command_elements, +static void *err_simple_command(struct list *command_elements, struct list *redirections) { list_deep_destroy(command_elements); @@ -238,7 +238,7 @@ struct ast *parse_simple_command(struct lexer_context *ctx) // TODO test this fix for the memory leaks char *word = strdup(element_word->word); - ast_free(element_word); + ast_free(&element); command_elements = list_append(command_elements, word); // end of fix } From 050a1909f0b7db5b6bcf501b42c1c2dc08ef3281 Mon Sep 17 00:00:00 2001 From: matteo Date: Wed, 28 Jan 2026 19:16:48 +0100 Subject: [PATCH 021/104] feat(parser): helper static function --- src/parser/grammar_basic.c | 42 +++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index fb1b12d..17e006f 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -169,7 +169,7 @@ struct ast *parse_command(struct lexer_context *ctx) } } -/* @brief: frees command_elements and redirections lists +/* @brief: frees command_elements and redirections lists (helper func) * @return: NULL */ static void *err_simple_command(struct list *command_elements, @@ -293,6 +293,17 @@ struct ast *parse_shell_command(struct lexer_context *ctx) return parse_if_rule(ctx); } +/* @brief: frees all the arguments. (helper func) + * @return: NULL. + */ +static void *err_if_rule(struct ast **cond, struct ast **then_clause, struct ast **else_clause) +{ + ast_free(cond); + ast_free(then_clause); + ast_free(else_clause); + return NULL; +} + struct ast *parse_if_rule(struct lexer_context *ctx) { // If keyword @@ -311,38 +322,34 @@ struct ast *parse_if_rule(struct lexer_context *ctx) token = POP_TOKEN(); if (token->type != TOKEN_THEN) { - ast_free(&condition_content); perror("Expected the 'then' keyword but token has different type"); - return NULL; + return err_if_rule(&condition_content, NULL, NULL); } // Then content struct ast *then_content = parse_compound_list(ctx); if (then_content == NULL) { - ast_free(&condition_content); - ast_free(&then_content); - return NULL; + return err_if_rule(&condition_content, &then_content, NULL); } + struct ast *else_content = NULL; // Eventual else/elif clause(s) - struct ast *else_content = parse_else_clause(ctx); - if (else_content == NULL) + if (is_first(*token, RULE_ELSE_CLAUSE)) { - ast_free(&condition_content); - ast_free(&then_content); - return NULL; + else_content = parse_else_clause(ctx); + if (else_content == NULL) + { + return err_if_rule(&condition_content, &then_content, NULL); + } } // Fi keyword token = POP_TOKEN(); if (token->type != TOKEN_FI) { - ast_free(&condition_content); - ast_free(&then_content); - ast_free(&else_content); perror("Expected the 'fi' keyword but token has different type"); - return NULL; + return err_if_rule(&condition_content, &then_content, &else_content); } // Result @@ -350,11 +357,8 @@ struct ast *parse_if_rule(struct lexer_context *ctx) ast_create_if(condition_content, then_content, else_content); if (result == NULL) { - ast_free(&condition_content); - ast_free(&then_content); - ast_free(&else_content); perror("Internal error: could not create a new AST (AST_IF)"); - return NULL; + return err_if_rule(&condition_content, &then_content, &else_content); } return result; From ed64a1bc18f445daa1f1db5dce897e9f06fa8638 Mon Sep 17 00:00:00 2001 From: Jean <47366872+jean-voila@users.noreply.github.com> Date: Wed, 28 Jan 2026 21:55:55 +0100 Subject: [PATCH 022/104] began to reimplement the new execution --- src/execution/execution_helpers.c | 46 +++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/src/execution/execution_helpers.c b/src/execution/execution_helpers.c index 24c4100..971688a 100644 --- a/src/execution/execution_helpers.c +++ b/src/execution/execution_helpers.c @@ -72,7 +72,9 @@ int exec_ast_command(struct ast_command *command, struct hash_map *vars) waitpid(pid, &status, 0); free(argv); if (WIFEXITED(status)) + { return WEXITSTATUS(status); + } return 1; } @@ -121,7 +123,7 @@ static int get_fd_target(const struct ast_redir *redir) if (redir->io_number != -1) return redir->io_number; if (redir->type == AST_REDIR_TYPE_LESS - || redir->type == AST_REDIR_TYPE_LESSGREAT + || redir->type == AST_REDIR_TYPE_DLESS || redir->type == AST_REDIR_TYPE_LESSAND) return 0; return 1; @@ -163,6 +165,43 @@ static int handle_and_restore_fd(int saved_fd, int fd_target) return 0; } +/* + +static open_all_redir(const struct ast_list redir_list) +{ + while (redir_list){ + struct ast_redir *redir = (struct ast_redir*)redir_list->data; + int target_fd; + if (redir->io_number != -1) + { + target_fd = redir->io_number; + } + else + { + // assign target_fd depending on redir type + } + + int saved_fd = dup(target_fd); + + // if redir type is not with '&' + // then we open("filename") + // else, no need to open, just new_fd = atoi(filename) + + open(); + + dup2(target_fd, new_fd); + + close(new_fd); + + // append target_fd and saved_fd to a list + // in order to be able to restore all the fds + } + +} + + +*/ +/* int exec_ast_redir(struct ast_redir *redir, struct hash_map *vars) { int fd_target = get_fd_target(redir); @@ -206,7 +245,7 @@ int exec_ast_redir(struct ast_redir *redir, struct hash_map *vars) int ret = execution(redir->child, vars); handle_and_restore_fd(saved_fd, fd_target); return ret; -} +} */ // --- Builtins --- @@ -234,7 +273,6 @@ static int builtin_echo(char **argv) return 0; } - static int builtin_true(char **argv) { (void)argv; @@ -256,7 +294,6 @@ static int builtin_exit(char **argv) return exit_val; } - static int builtin_cd(char **argv) { const char *path = argv[1]; @@ -277,7 +314,6 @@ static int builtin_cd(char **argv) return 0; } - /** * @brief Tries to execute a builtin command if the command matches a builtin * From 308ac9ac8d9140dbe455740e38568d9f2e94741a Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Wed, 28 Jan 2026 21:26:51 +0000 Subject: [PATCH 023/104] feat(expansion): remove double quotes --- src/expansion/expansion.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/expansion/expansion.c b/src/expansion/expansion.c index ab6241e..4fea985 100644 --- a/src/expansion/expansion.c +++ b/src/expansion/expansion.c @@ -135,7 +135,7 @@ bool expand(struct ast_command *command, const struct hash_map *vars) { if (str[i] == '\'') { - // remove quote + // remove single quote in_quotes = !in_quotes; memmove(str + i, str + i + 1, strlen(str + i + 1) + 1); i--; @@ -144,6 +144,12 @@ bool expand(struct ast_command *command, const struct hash_map *vars) { 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 From d775ab6c1a79a64cde76499e22eba4b31419ade6 Mon Sep 17 00:00:00 2001 From: matteo Date: Thu, 29 Jan 2026 09:56:28 +0100 Subject: [PATCH 024/104] feat(ast): add ast_assignements. Similar to how redirections are handled. --- src/utils/ast/ast_assignement.c | 36 +++++++++++++++++++++++++++++++++ src/utils/ast/ast_assignement.h | 17 ++++++++++++++++ src/utils/ast/ast_base.h | 3 ++- src/utils/ast/ast_command.c | 3 ++- src/utils/ast/ast_command.h | 3 ++- 5 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 src/utils/ast/ast_assignement.c create mode 100644 src/utils/ast/ast_assignement.h diff --git a/src/utils/ast/ast_assignement.c b/src/utils/ast/ast_assignement.c new file mode 100644 index 0000000..036e4d2 --- /dev/null +++ b/src/utils/ast/ast_assignement.c @@ -0,0 +1,36 @@ +#include "ast_assignement.h" + +#include + +bool ast_is_assignement(struct ast *node) +{ + return node != NULL && node->type == AST_ASSIGNEMENT; +} + +struct ast_assignement *ast_get_assignement(struct ast *node) +{ + if (node == NULL || node->type != AST_ASSIGNEMENT) + return NULL; + return (struct ast_assignement *)node->data; +} + +struct ast *ast_create_assignement(char *name, char *value) +{ + struct ast_assignement *assignement_data = malloc(sizeof(struct ast_assignement)); + if (!assignement_data) + return NULL; + + assignement_data->name = name; + assignement_data->value = value; + + return ast_create(AST_ASSIGNEMENT, assignement_data); +} + +void ast_free_assignement(struct ast_assignement *assignement_data) +{ + if (assignement_data == NULL) + return; + free(assignement_data->name); + free(assignement_data->value); + free(assignement_data); +} \ No newline at end of file diff --git a/src/utils/ast/ast_assignement.h b/src/utils/ast/ast_assignement.h new file mode 100644 index 0000000..5975497 --- /dev/null +++ b/src/utils/ast/ast_assignement.h @@ -0,0 +1,17 @@ +#ifndef AST_ASSIGNEMENT_H +#define AST_ASSIGNEMENT_H + +#include "ast_base.h" + +struct ast_assignement +{ + char *name; + char *value; +}; + +bool ast_is_assignement(struct ast *node); +struct ast_assignement *ast_get_assignement(struct ast *node); +struct ast *ast_create_assignement(char *name, char *value); +void ast_free_assignement(struct ast_assignement *assignement_data); + +#endif /* ! AST_ASSIGNEMENT_H */ \ No newline at end of file diff --git a/src/utils/ast/ast_base.h b/src/utils/ast/ast_base.h index f81f2f0..f04014d 100644 --- a/src/utils/ast/ast_base.h +++ b/src/utils/ast/ast_base.h @@ -15,7 +15,8 @@ enum ast_type AST_CMD, AST_WORD, AST_PIPE, - AST_NEG + AST_NEG, + AST_ASSIGNEMENT }; struct ast diff --git a/src/utils/ast/ast_command.c b/src/utils/ast/ast_command.c index c4fe3e6..affe45e 100644 --- a/src/utils/ast/ast_command.c +++ b/src/utils/ast/ast_command.c @@ -6,7 +6,7 @@ #include "../lists/lists.h" struct ast *ast_create_command(struct list *command, - struct list *redirections) + struct list *redirections, struct ast_list *assignements) { struct ast_command *command_data = malloc(sizeof(struct ast_command)); if (!command_data) @@ -36,5 +36,6 @@ void ast_free_command(struct ast_command *command_data) return; list_deep_destroy(command_data->command); ast_list_deep_destroy(command_data->redirections); + ast_list_deep_destroy(command_data->assignements); free(command_data); } diff --git a/src/utils/ast/ast_command.h b/src/utils/ast/ast_command.h index 835bf5d..0641f1d 100644 --- a/src/utils/ast/ast_command.h +++ b/src/utils/ast/ast_command.h @@ -8,6 +8,7 @@ struct ast_command { struct list *command; // A list of words (char*) struct ast_list *redirections; // A list of ASTs, all ast_redir + struct ast_list *assignements; // A list of ASTs, all ast_assignement }; /** @@ -25,7 +26,7 @@ struct ast_command *ast_get_command(struct ast *node); * Creates a new AST node representing a command. */ struct ast *ast_create_command(struct list *command, - struct list *redirections); + struct list *redirections, struct ast_list *assignements); /* * @brief: frees the given ast_command and sets the pointer to NULL. From 46dde29c2006de727fc08fc07e554b1d41c2db90 Mon Sep 17 00:00:00 2001 From: matteo Date: Thu, 29 Jan 2026 09:59:16 +0100 Subject: [PATCH 025/104] fix: small errors in exec and parser --- src/execution/execution_helpers.c | 2 +- src/parser/grammar.c | 6 +++--- src/parser/grammar_basic.c | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/execution/execution_helpers.c b/src/execution/execution_helpers.c index 971688a..9dcaf41 100644 --- a/src/execution/execution_helpers.c +++ b/src/execution/execution_helpers.c @@ -123,7 +123,7 @@ static int get_fd_target(const struct ast_redir *redir) if (redir->io_number != -1) return redir->io_number; if (redir->type == AST_REDIR_TYPE_LESS - || redir->type == AST_REDIR_TYPE_DLESS + || redir->type == AST_REDIR_TYPE_LESSGREAT || redir->type == AST_REDIR_TYPE_LESSAND) return 0; return 1; diff --git a/src/parser/grammar.c b/src/parser/grammar.c index 83f2266..95f1bd2 100644 --- a/src/parser/grammar.c +++ b/src/parser/grammar.c @@ -106,15 +106,15 @@ bool grammar_init(void) add_first(RULE_LIST, TOKEN_WORD); add_first(RULE_LIST, TOKEN_IF); - add_first(RULE_COMMAND, TOKEN_NEGATION); + add_first(RULE_LIST, TOKEN_NEGATION); add_first(RULE_AND_OR, TOKEN_WORD); add_first(RULE_AND_OR, TOKEN_IF); - add_first(RULE_COMMAND, TOKEN_NEGATION); + add_first(RULE_AND_OR, TOKEN_NEGATION); add_first(RULE_PIPELINE, TOKEN_WORD); add_first(RULE_PIPELINE, TOKEN_IF); - add_first(RULE_COMMAND, TOKEN_NEGATION); + add_first(RULE_PIPELINE, TOKEN_NEGATION); add_first(RULE_COMMAND, TOKEN_WORD); add_first(RULE_COMMAND, TOKEN_IF); diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index 17e006f..9102593 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -261,6 +261,7 @@ struct ast *parse_simple_command(struct lexer_context *ctx) } // Result + // TODO handle assignements struct ast *result = ast_create_command(command_elements, redirections); if (result == NULL) { From 6da801675a7c2b3f3269bbc4e6ace5487847e139 Mon Sep 17 00:00:00 2001 From: matteo Date: Thu, 29 Jan 2026 11:18:56 +0100 Subject: [PATCH 026/104] feat(lexer): implementing assignements --- src/lexer/lexer.c | 18 +++++++++++++++++- src/lexer/lexer_utils.h | 15 +++++++++++---- src/parser/grammar_advanced.c | 14 +++++++++++++- 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index ff7f351..21f362b 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -78,6 +78,21 @@ static bool update_lexing_mode(char *stream, ssize_t i, return *lexing_mode != mode_before_update; } +/* @brief: updates the flags only_digits and equal_count + * according to the character at stream[i]. + */ +static void update_flags(char *stream, ssize_t i, struct lexer_context *ctx) +{ + if (stream[i] == '=') + { + ctx->equal_count++; + } + else if (!isdigit(stream[i]) && ctx->only_digits) + { + ctx->only_digits = false; + } +} + struct token *peek_token(struct lexer_context *ctx) { // we already created the upcoming token during the previous call to peek() @@ -100,6 +115,7 @@ struct token *peek_token(struct lexer_context *ctx) if (!update_lexing_mode(stream, i, &lexing_mode) && lexing_mode == LEXER_NORMAL) { + update_flags(stream, i, ctx); if (is_special_char(stream, i)) { if (i == 0) // where we create spe_char token @@ -121,7 +137,7 @@ struct token *peek_token(struct lexer_context *ctx) i++; } - struct token *tok = new_token(stream, i, ctx->only_digits); + struct token *tok = new_token(stream, i, ctx->only_digits, ctx->equal_count); // if token is comment, we don't want it if (tok->type == TOKEN_COMMENT) diff --git a/src/lexer/lexer_utils.h b/src/lexer/lexer_utils.h index c6c26c9..2708662 100644 --- a/src/lexer/lexer_utils.h +++ b/src/lexer/lexer_utils.h @@ -14,6 +14,9 @@ struct lexer_context // tells us if we only lexed digits in current token. bool only_digits; + // usefull to detect assignments, and syntax errors with '='. + int equal_count; + struct token *previous_token; struct token *current_token; }; @@ -34,10 +37,13 @@ enum token_type // Blanks TOKEN_NULL = 0, TOKEN_EOF, - TOKEN_WORD, TOKEN_NEWLINE, - // SPecial characters + // words + TOKEN_WORD, + TOKEN_ASSIGNMENT_WORD, + + // Special characters TOKEN_GRAVE, TOKEN_SEMICOLON, TOKEN_COMMENT, @@ -87,12 +93,13 @@ struct token */ bool is_special_char(char *stream, ssize_t i); -/* @brief: return a newly allocated token, with the corresponding type. +/* @brief: return a newly allocated token, with the type corresponding + * to the context given in arguments. * The data contains [size] char, starting from [begin]. * * @return: NULL on error, a token otherwise. */ -struct token *new_token(char *begin, ssize_t size, bool only_digits); +struct token *new_token(char *begin, ssize_t size, bool only_digits, int equal_count); /* @brief: frees the token given in argument */ diff --git a/src/parser/grammar_advanced.c b/src/parser/grammar_advanced.c index 8c4b68c..1964f49 100644 --- a/src/parser/grammar_advanced.c +++ b/src/parser/grammar_advanced.c @@ -66,5 +66,17 @@ struct ast *parse_redirection(struct lexer_context *ctx) struct ast *parse_prefix(struct lexer_context *ctx) { - return parse_redirection(ctx); + struct token *token = TOKEN_PEEK(); + if (token->type == TOKEN_ASSIGNMENT_WORD) + { + token = TOKEN_POP(); + return ast_create_assignment_word(token->data); + } + else if (is_first(*token, RULE_REDIRECTION)) + return parse_redirection(ctx); + else + { + perror("Syntax error: expected a prefix (redirection or assignment)"); + return NULL; + } } From 2884fa78d9db5957af437048555b50fb0c113898 Mon Sep 17 00:00:00 2001 From: matteo Date: Thu, 29 Jan 2026 11:32:15 +0100 Subject: [PATCH 027/104] feat(ast_assignement): changed (name, value) to assignement --- src/utils/ast/ast_assignement.c | 10 ++++------ src/utils/ast/ast_assignement.h | 8 ++++---- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/utils/ast/ast_assignement.c b/src/utils/ast/ast_assignement.c index 036e4d2..86e892d 100644 --- a/src/utils/ast/ast_assignement.c +++ b/src/utils/ast/ast_assignement.c @@ -14,14 +14,13 @@ struct ast_assignement *ast_get_assignement(struct ast *node) return (struct ast_assignement *)node->data; } -struct ast *ast_create_assignement(char *name, char *value) +struct ast *ast_create_assignement(char* assignement) { struct ast_assignement *assignement_data = malloc(sizeof(struct ast_assignement)); if (!assignement_data) return NULL; - assignement_data->name = name; - assignement_data->value = value; + assignement_data->assignement = assignement; return ast_create(AST_ASSIGNEMENT, assignement_data); } @@ -30,7 +29,6 @@ void ast_free_assignement(struct ast_assignement *assignement_data) { if (assignement_data == NULL) return; - free(assignement_data->name); - free(assignement_data->value); + free(assignement_data->assignement); free(assignement_data); -} \ No newline at end of file +} diff --git a/src/utils/ast/ast_assignement.h b/src/utils/ast/ast_assignement.h index 5975497..533086d 100644 --- a/src/utils/ast/ast_assignement.h +++ b/src/utils/ast/ast_assignement.h @@ -5,13 +5,13 @@ struct ast_assignement { - char *name; - char *value; + char *assignement; // assignement of the form 'a=b'. + // needs to be parsed lexer }; bool ast_is_assignement(struct ast *node); struct ast_assignement *ast_get_assignement(struct ast *node); -struct ast *ast_create_assignement(char *name, char *value); +struct ast *ast_create_assignement(char *assignement); void ast_free_assignement(struct ast_assignement *assignement_data); -#endif /* ! AST_ASSIGNEMENT_H */ \ No newline at end of file +#endif /* ! AST_ASSIGNEMENT_H */ From dd8a7165e1aa521a8348704da965de70426f9d80 Mon Sep 17 00:00:00 2001 From: matteo Date: Thu, 29 Jan 2026 11:37:20 +0100 Subject: [PATCH 028/104] Revert "feat(ast_assignement): changed (name, value) to assignement" This reverts commit ac77d79a603719e05db37dfa033e77b3899d42c1. --- src/utils/ast/ast_assignement.c | 10 ++++++---- src/utils/ast/ast_assignement.h | 8 ++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/utils/ast/ast_assignement.c b/src/utils/ast/ast_assignement.c index 86e892d..036e4d2 100644 --- a/src/utils/ast/ast_assignement.c +++ b/src/utils/ast/ast_assignement.c @@ -14,13 +14,14 @@ struct ast_assignement *ast_get_assignement(struct ast *node) return (struct ast_assignement *)node->data; } -struct ast *ast_create_assignement(char* assignement) +struct ast *ast_create_assignement(char *name, char *value) { struct ast_assignement *assignement_data = malloc(sizeof(struct ast_assignement)); if (!assignement_data) return NULL; - assignement_data->assignement = assignement; + assignement_data->name = name; + assignement_data->value = value; return ast_create(AST_ASSIGNEMENT, assignement_data); } @@ -29,6 +30,7 @@ void ast_free_assignement(struct ast_assignement *assignement_data) { if (assignement_data == NULL) return; - free(assignement_data->assignement); + free(assignement_data->name); + free(assignement_data->value); free(assignement_data); -} +} \ No newline at end of file diff --git a/src/utils/ast/ast_assignement.h b/src/utils/ast/ast_assignement.h index 533086d..5975497 100644 --- a/src/utils/ast/ast_assignement.h +++ b/src/utils/ast/ast_assignement.h @@ -5,13 +5,13 @@ struct ast_assignement { - char *assignement; // assignement of the form 'a=b'. - // needs to be parsed lexer + char *name; + char *value; }; bool ast_is_assignement(struct ast *node); struct ast_assignement *ast_get_assignement(struct ast *node); -struct ast *ast_create_assignement(char *assignement); +struct ast *ast_create_assignement(char *name, char *value); void ast_free_assignement(struct ast_assignement *assignement_data); -#endif /* ! AST_ASSIGNEMENT_H */ +#endif /* ! AST_ASSIGNEMENT_H */ \ No newline at end of file From 988d8ef298918656ad00c946cf85212e5e697307 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Thu, 29 Jan 2026 11:29:03 +0000 Subject: [PATCH 029/104] feat(execution): update $PWD and $OLD_PWD --- src/execution/execution_helpers.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/execution/execution_helpers.c b/src/execution/execution_helpers.c index 9dcaf41..8ea5aef 100644 --- a/src/execution/execution_helpers.c +++ b/src/execution/execution_helpers.c @@ -12,6 +12,7 @@ #include "../expansion/expansion.h" #include "../utils/ast/ast.h" #include "../utils/hash_map/hash_map.h" +#include "../utils/vars/vars.h" #include "execution.h" static char **list_to_argv(struct list *command_list) @@ -36,7 +37,7 @@ static char **list_to_argv(struct list *command_list) return argv; } -static int try_builtin(char **argv); +static int try_builtin(char **argv, struct hash_map *vars); int exec_ast_command(struct ast_command *command, struct hash_map *vars) { @@ -49,7 +50,7 @@ int exec_ast_command(struct ast_command *command, struct hash_map *vars) free(argv); return 0; } - int builtin_ret = try_builtin(argv); + int builtin_ret = try_builtin(argv, vars); if (builtin_ret != -1) { free(argv); @@ -294,7 +295,7 @@ static int builtin_exit(char **argv) return exit_val; } -static int builtin_cd(char **argv) +static int builtin_cd(char **argv, struct hash_map *vars) { const char *path = argv[1]; if (!path) @@ -306,11 +307,15 @@ static int builtin_cd(char **argv) return 1; } } + // char *pwd = getcwd("", ""); + char *pwd = get_var_or_env(pwd, "PWD"); if (chdir(path) != 0) { perror("cd"); return 1; } + set_var_copy(vars, "OLD_PWD", pwd); + set_var_copy(vars, "PWD", path); return 0; } @@ -320,7 +325,7 @@ static int builtin_cd(char **argv) * @param argv Array of command arguments * @return int Exit status of the builtin command, or -1 if not a builtin */ -static int try_builtin(char **argv) +static int try_builtin(char **argv, struct hash_map *vars) { if (!argv || !argv[0]) return 0; @@ -334,7 +339,7 @@ static int try_builtin(char **argv) if (strcmp(argv[0], "exit") == 0) return builtin_exit(argv); if (strcmp(argv[0], "cd") == 0) - return builtin_cd(argv); + return builtin_cd(argv, vars); return -1; } From 28749a299284fdf848d3ee4de03d994ea9cdf14b Mon Sep 17 00:00:00 2001 From: matteo Date: Thu, 29 Jan 2026 12:38:03 +0100 Subject: [PATCH 030/104] feat(parser): assignments handled --- src/parser/grammar_advanced.c | 2 +- src/parser/grammar_basic.c | 27 +++++++++++++++++---------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/parser/grammar_advanced.c b/src/parser/grammar_advanced.c index 1964f49..eb5869d 100644 --- a/src/parser/grammar_advanced.c +++ b/src/parser/grammar_advanced.c @@ -74,7 +74,7 @@ struct ast *parse_prefix(struct lexer_context *ctx) } else if (is_first(*token, RULE_REDIRECTION)) return parse_redirection(ctx); - else + else { perror("Syntax error: expected a prefix (redirection or assignment)"); return NULL; diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index 9102593..ad78673 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -173,7 +173,7 @@ struct ast *parse_command(struct lexer_context *ctx) * @return: NULL */ static void *err_simple_command(struct list *command_elements, - struct list *redirections) + struct list *redirections) { list_deep_destroy(command_elements); list_deep_destroy(redirections); @@ -184,6 +184,7 @@ struct ast *parse_simple_command(struct lexer_context *ctx) { struct list *command_elements = NULL; struct list *redirections = NULL; // list of redirection ASTs + struct list *assignments = NULL; bool has_prefix = false; struct token *token = PEEK_TOKEN(); @@ -192,12 +193,19 @@ struct ast *parse_simple_command(struct lexer_context *ctx) has_prefix = true; while (is_first(*token, RULE_PREFIX)) { - struct ast *redir = parse_prefix(ctx); - if (redir == NULL) + struct ast *prefix = parse_prefix(ctx); + if (prefix == NULL) { return err_simple_command(command_elements, redirections); } - redirections = list_append(redirections, redir); + if (prefix->type == AST_ASSIGNEMENT) + { + assignments = list_append(assignments, prefix) + } + else if (prefix->type == AST_REDIR) + { + redirections = list_append(redirections, prefix); + } token = PEEK_TOKEN(); } } @@ -250,8 +258,7 @@ struct ast *parse_simple_command(struct lexer_context *ctx) else { perror("Internal error: unexpected return value from " - "parse_element " - "in parse_simple_command"); + "parse_element in parse_simple_command"); return err_simple_command(command_elements, redirections); } @@ -260,9 +267,8 @@ struct ast *parse_simple_command(struct lexer_context *ctx) } } - // Result - // TODO handle assignements - struct ast *result = ast_create_command(command_elements, redirections); + struct ast *result = + ast_create_command(command_elements, redirections, assignments); if (result == NULL) { return err_simple_command(command_elements, redirections); @@ -297,7 +303,8 @@ struct ast *parse_shell_command(struct lexer_context *ctx) /* @brief: frees all the arguments. (helper func) * @return: NULL. */ -static void *err_if_rule(struct ast **cond, struct ast **then_clause, struct ast **else_clause) +static void *err_if_rule(struct ast **cond, struct ast **then_clause, + struct ast **else_clause) { ast_free(cond); ast_free(then_clause); From a98161d88582b0881e3cfb13e39794e5444c06a3 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Thu, 29 Jan 2026 11:40:55 +0000 Subject: [PATCH 031/104] fix(ast)!: assignment typo --- src/parser/grammar_basic.c | 2 +- src/utils/ast/ast.h | 1 + src/utils/ast/ast_assignement.c | 36 --------------------------------- src/utils/ast/ast_assignement.h | 17 ---------------- src/utils/ast/ast_assignment.c | 36 +++++++++++++++++++++++++++++++++ src/utils/ast/ast_assignment.h | 17 ++++++++++++++++ src/utils/ast/ast_base.h | 2 +- src/utils/ast/ast_command.c | 4 ++-- src/utils/ast/ast_command.h | 4 ++-- 9 files changed, 60 insertions(+), 59 deletions(-) delete mode 100644 src/utils/ast/ast_assignement.c delete mode 100644 src/utils/ast/ast_assignement.h create mode 100644 src/utils/ast/ast_assignment.c create mode 100644 src/utils/ast/ast_assignment.h diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index 9102593..9621d31 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -261,7 +261,7 @@ struct ast *parse_simple_command(struct lexer_context *ctx) } // Result - // TODO handle assignements + // TODO handle assignments struct ast *result = ast_create_command(command_elements, redirections); if (result == NULL) { diff --git a/src/utils/ast/ast.h b/src/utils/ast/ast.h index 3964968..9827d8d 100644 --- a/src/utils/ast/ast.h +++ b/src/utils/ast/ast.h @@ -2,6 +2,7 @@ #define AST_H #include "ast_and_or.h" +#include "ast_assignment.h" #include "ast_base.h" #include "ast_command.h" #include "ast_end.h" diff --git a/src/utils/ast/ast_assignement.c b/src/utils/ast/ast_assignement.c deleted file mode 100644 index 036e4d2..0000000 --- a/src/utils/ast/ast_assignement.c +++ /dev/null @@ -1,36 +0,0 @@ -#include "ast_assignement.h" - -#include - -bool ast_is_assignement(struct ast *node) -{ - return node != NULL && node->type == AST_ASSIGNEMENT; -} - -struct ast_assignement *ast_get_assignement(struct ast *node) -{ - if (node == NULL || node->type != AST_ASSIGNEMENT) - return NULL; - return (struct ast_assignement *)node->data; -} - -struct ast *ast_create_assignement(char *name, char *value) -{ - struct ast_assignement *assignement_data = malloc(sizeof(struct ast_assignement)); - if (!assignement_data) - return NULL; - - assignement_data->name = name; - assignement_data->value = value; - - return ast_create(AST_ASSIGNEMENT, assignement_data); -} - -void ast_free_assignement(struct ast_assignement *assignement_data) -{ - if (assignement_data == NULL) - return; - free(assignement_data->name); - free(assignement_data->value); - free(assignement_data); -} \ No newline at end of file diff --git a/src/utils/ast/ast_assignement.h b/src/utils/ast/ast_assignement.h deleted file mode 100644 index 5975497..0000000 --- a/src/utils/ast/ast_assignement.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef AST_ASSIGNEMENT_H -#define AST_ASSIGNEMENT_H - -#include "ast_base.h" - -struct ast_assignement -{ - char *name; - char *value; -}; - -bool ast_is_assignement(struct ast *node); -struct ast_assignement *ast_get_assignement(struct ast *node); -struct ast *ast_create_assignement(char *name, char *value); -void ast_free_assignement(struct ast_assignement *assignement_data); - -#endif /* ! AST_ASSIGNEMENT_H */ \ No newline at end of file diff --git a/src/utils/ast/ast_assignment.c b/src/utils/ast/ast_assignment.c new file mode 100644 index 0000000..a2564c2 --- /dev/null +++ b/src/utils/ast/ast_assignment.c @@ -0,0 +1,36 @@ +#include "ast_assignment.h" + +#include + +bool ast_is_assignment(struct ast *node) +{ + return node != NULL && node->type == AST_ASSIGNMENT; +} + +struct ast_assignment *ast_get_assignment(struct ast *node) +{ + if (node == NULL || node->type != AST_ASSIGNMENT) + return NULL; + return (struct ast_assignment *)node->data; +} + +struct ast *ast_create_assignment(char *name, char *value) +{ + struct ast_assignment *assignment_data = malloc(sizeof(struct ast_assignment)); + if (!assignment_data) + return NULL; + + assignment_data->name = name; + assignment_data->value = value; + + return ast_create(AST_ASSIGNMENT, assignment_data); +} + +void ast_free_assignment(struct ast_assignment *assignment_data) +{ + if (assignment_data == NULL) + return; + free(assignment_data->name); + free(assignment_data->value); + free(assignment_data); +} \ No newline at end of file diff --git a/src/utils/ast/ast_assignment.h b/src/utils/ast/ast_assignment.h new file mode 100644 index 0000000..c958c2d --- /dev/null +++ b/src/utils/ast/ast_assignment.h @@ -0,0 +1,17 @@ +#ifndef AST_ASSIGNMENT_H +#define AST_ASSIGNMENT_H + +#include "ast_base.h" + +struct ast_assignment +{ + char *name; + char *value; +}; + +bool ast_is_assignment(struct ast *node); +struct ast_assignment *ast_get_assignment(struct ast *node); +struct ast *ast_create_assignment(char *name, char *value); +void ast_free_assignment(struct ast_assignment *assignment_data); + +#endif /* ! AST_ASSIGNMENT_H */ \ No newline at end of file diff --git a/src/utils/ast/ast_base.h b/src/utils/ast/ast_base.h index f04014d..ae99a39 100644 --- a/src/utils/ast/ast_base.h +++ b/src/utils/ast/ast_base.h @@ -16,7 +16,7 @@ enum ast_type AST_WORD, AST_PIPE, AST_NEG, - AST_ASSIGNEMENT + AST_ASSIGNMENT }; struct ast diff --git a/src/utils/ast/ast_command.c b/src/utils/ast/ast_command.c index affe45e..d7e148d 100644 --- a/src/utils/ast/ast_command.c +++ b/src/utils/ast/ast_command.c @@ -6,7 +6,7 @@ #include "../lists/lists.h" struct ast *ast_create_command(struct list *command, - struct list *redirections, struct ast_list *assignements) + struct list *redirections, struct ast_list *assignments) { struct ast_command *command_data = malloc(sizeof(struct ast_command)); if (!command_data) @@ -36,6 +36,6 @@ void ast_free_command(struct ast_command *command_data) return; list_deep_destroy(command_data->command); ast_list_deep_destroy(command_data->redirections); - ast_list_deep_destroy(command_data->assignements); + ast_list_deep_destroy(command_data->assignments); free(command_data); } diff --git a/src/utils/ast/ast_command.h b/src/utils/ast/ast_command.h index 0641f1d..9322ac7 100644 --- a/src/utils/ast/ast_command.h +++ b/src/utils/ast/ast_command.h @@ -8,7 +8,7 @@ struct ast_command { struct list *command; // A list of words (char*) struct ast_list *redirections; // A list of ASTs, all ast_redir - struct ast_list *assignements; // A list of ASTs, all ast_assignement + struct ast_list *assignments; // A list of ASTs, all ast_assignment }; /** @@ -26,7 +26,7 @@ struct ast_command *ast_get_command(struct ast *node); * Creates a new AST node representing a command. */ struct ast *ast_create_command(struct list *command, - struct list *redirections, struct ast_list *assignements); + struct list *redirections, struct ast_list *assignments); /* * @brief: frees the given ast_command and sets the pointer to NULL. From ec63be42e560725517f477fdc1b6f333178fe4b6 Mon Sep 17 00:00:00 2001 From: Jean <47366872+jean-voila@users.noreply.github.com> Date: Thu, 29 Jan 2026 18:18:04 +0100 Subject: [PATCH 032/104] fix(execution): Reimplemented the redirection logic --- src/execution/execution.c | 5 +- src/execution/execution_helpers.c | 100 ++++++++++++++++++++++-------- src/execution/execution_helpers.h | 1 - src/utils/ast/ast_redir.h | 1 + 4 files changed, 75 insertions(+), 32 deletions(-) diff --git a/src/execution/execution.c b/src/execution/execution.c index 90bb8eb..cf751f7 100644 --- a/src/execution/execution.c +++ b/src/execution/execution.c @@ -14,8 +14,6 @@ #include "../utils/ast/ast.h" #include "../utils/hash_map/hash_map.h" - - // Refactored: delegates to helpers in execution_helpers.c #include "execution_helpers.h" @@ -33,6 +31,7 @@ int execution(struct ast *ast, struct hash_map *vars) 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); } case AST_IF: @@ -41,8 +40,6 @@ int execution(struct ast *ast, struct hash_map *vars) return exec_ast_list(ast_get_list(ast), vars); case AST_AND_OR: return exec_ast_and_or(ast_get_and_or(ast), vars); - case AST_REDIR: - return exec_ast_redir(ast_get_redir(ast), vars); default: return 127; } diff --git a/src/execution/execution_helpers.c b/src/execution/execution_helpers.c index 8ea5aef..aba6dc7 100644 --- a/src/execution/execution_helpers.c +++ b/src/execution/execution_helpers.c @@ -12,6 +12,7 @@ #include "../expansion/expansion.h" #include "../utils/ast/ast.h" #include "../utils/hash_map/hash_map.h" +#include "../utils/lists/lists.h" #include "../utils/vars/vars.h" #include "execution.h" @@ -42,40 +43,56 @@ static int try_builtin(char **argv, struct hash_map *vars); int exec_ast_command(struct ast_command *command, struct hash_map *vars) { (void)vars; + set_all_redir(command->redirections); + if (!command || !(command->command)) + { return 1; + } + char **argv = list_to_argv(command->command); if (!argv || !(argv[0])) { free(argv); + unset_all_redir(); return 0; } + int builtin_ret = try_builtin(argv, vars); if (builtin_ret != -1) { free(argv); + unset_all_redir(); return builtin_ret; } + pid_t pid = fork(); if (pid < 0) { perror("fork"); free(argv); + unset_all_redir(); return 1; } + if (pid == 0) { execvp(argv[0], argv); perror("execvp"); + unset_all_redir(); _exit(127); } + int status = 0; waitpid(pid, &status, 0); free(argv); + unset_all_redir(); + if (WIFEXITED(status)) { return WEXITSTATUS(status); } + return 1; } @@ -166,42 +183,71 @@ static int handle_and_restore_fd(int saved_fd, int fd_target) return 0; } -/* - -static open_all_redir(const struct ast_list redir_list) +static int set_all_redir(struct list *redir_list) { - while (redir_list){ - struct ast_redir *redir = (struct ast_redir*)redir_list->data; + while (redir_list) + { + struct ast_redir *redir = (struct ast_redir *)redir_list->data; int target_fd; + if (redir->io_number != -1) { - target_fd = redir->io_number; + target_fd = redir->io_number; + } + else + { + // assign target_fd depending on redir type + if (redir->type == AST_REDIR_TYPE_LESS + || redir->type == AST_REDIR_TYPE_LESSGREAT + || redir->type == AST_REDIR_TYPE_LESSAND) + { + target_fd = 0; + } + else + { + target_fd = 1; + } } - else - { - // assign target_fd depending on redir type - } - int saved_fd = dup(target_fd); - - // if redir type is not with '&' - // then we open("filename") - // else, no need to open, just new_fd = atoi(filename) - - open(); - - dup2(target_fd, new_fd); - - close(new_fd); - - // append target_fd and saved_fd to a list - // in order to be able to restore all the fds + redir->saved_fd = dup(target_fd); + int new_fd = -1; + int flags = 0; + int mode = 0644; + if (redir->type == AST_REDIR_TYPE_GREAT + || redir->type == AST_REDIR_TYPE_CLOBBER + || redir->type == AST_REDIR_TYPE_DGREAT + || redir->type == AST_REDIR_TYPE_LESS) + { + new_fd = open_redir_file(redir, &flags, &mode); + if (new_fd == -1) + { + perror("open"); + return -1; + } + } + else if (redir->type == AST_REDIR_TYPE_GREATAND + || redir->type == AST_REDIR_TYPE_LESSAND) + { + new_fd = atoi(redir->filename); + } + if (dup2(new_fd, target_fd) == -1) + { + perror("dup2"); + if (new_fd != -1) + { + close(new_fd); + } + return -1; + } + if (new_fd != -1) + { + close(new_fd); + } + redir_list = redir_list->next; } - + return 0; } - -*/ /* int exec_ast_redir(struct ast_redir *redir, struct hash_map *vars) { diff --git a/src/execution/execution_helpers.h b/src/execution/execution_helpers.h index ebac0cb..fd8026c 100644 --- a/src/execution/execution_helpers.h +++ b/src/execution/execution_helpers.h @@ -8,6 +8,5 @@ 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_redir(struct ast_redir *redir, struct hash_map *vars); #endif // EXECUTION_HELPERS_H diff --git a/src/utils/ast/ast_redir.h b/src/utils/ast/ast_redir.h index fdea88c..9d9a9d3 100644 --- a/src/utils/ast/ast_redir.h +++ b/src/utils/ast/ast_redir.h @@ -21,6 +21,7 @@ struct ast_redir int io_number; // The FD being redirected (default -1 if not specified, // implies 0 or 1 based on type) enum ast_redir_type type; + int saved_fd; // To store the original FD for restoration (-1 before save) }; bool ast_is_redir(struct ast *node); From 25079bfebf51bc6f23be26a4dd03f8fe180a3986 Mon Sep 17 00:00:00 2001 From: matteo Date: Thu, 29 Jan 2026 18:21:44 +0100 Subject: [PATCH 033/104] feat(lexer + grammar): inch les redirections ca marche --- src/lexer/lexer.c | 55 +++++++++++++++++++++-------------- src/lexer/lexer_utils.c | 33 ++++++++++++++++----- src/lexer/lexer_utils.h | 26 ++++++++--------- src/parser/grammar_advanced.c | 5 ++-- src/parser/grammar_basic.c | 2 +- src/utils/ast/ast_command.h | 2 +- 6 files changed, 77 insertions(+), 46 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index 21f362b..db4e066 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -78,36 +78,43 @@ static bool update_lexing_mode(char *stream, ssize_t i, return *lexing_mode != mode_before_update; } -/* @brief: updates the flags only_digits and equal_count +/* @brief: updates the flags only_digits and has_equal. * according to the character at stream[i]. */ -static void update_flags(char *stream, ssize_t i, struct lexer_context *ctx) +static void update_flags(char *stream, ssize_t i, struct token_info *info) { - if (stream[i] == '=') + if (stream[i] == '=' && !info->has_equal) { - ctx->equal_count++; + if (i == 0) + { + perror("Syntax error: word start with a '='"); + return; + } + else + info->has_equal = true; } - else if (!isdigit(stream[i]) && ctx->only_digits) + else if (!isdigit(stream[i]) && info->only_digits) { - ctx->only_digits = false; + info->only_digits = false; } } struct token *peek_token(struct lexer_context *ctx) { + stream_init(ctx); + + // Usefull to know if we are inside a quote or double quote + enum lexing_mode lexing_mode = LEXER_NORMAL; + struct token_info info = {true, 0}; + char *stream = ctx->end_previous_token; + ssize_t i = 0; + // we already created the upcoming token during the previous call to peek() if (ctx->current_token != NULL) { return ctx->current_token; } - stream_init(ctx); - char *stream = ctx->end_previous_token; - ssize_t i = 0; - - // Usefull to know if we are inside a quote or double quote - enum lexing_mode lexing_mode = LEXER_NORMAL; - while (i < ctx->remaining_chars) { // true if we didn't encounter a quote of any type at stream[i] @@ -115,7 +122,7 @@ struct token *peek_token(struct lexer_context *ctx) if (!update_lexing_mode(stream, i, &lexing_mode) && lexing_mode == LEXER_NORMAL) { - update_flags(stream, i, ctx); + update_flags(stream, i, &info); if (is_special_char(stream, i)) { if (i == 0) // where we create spe_char token @@ -137,7 +144,7 @@ struct token *peek_token(struct lexer_context *ctx) i++; } - struct token *tok = new_token(stream, i, ctx->only_digits, ctx->equal_count); + struct token *tok = new_token(stream, i, &info); // if token is comment, we don't want it if (tok->type == TOKEN_COMMENT) @@ -154,6 +161,15 @@ struct token *peek_token(struct lexer_context *ctx) struct token *pop_token(struct lexer_context *ctx) { + stream_init(ctx); + + // Usefull to know if we are inside a quote or double quote + enum lexing_mode lexing_mode = LEXER_NORMAL; + struct token_info info = {true, 0}; + char *stream = ctx->end_previous_token; + ssize_t i = 0; + + if (ctx->current_token != NULL && ctx->current_token->type == TOKEN_EOF) { // we reached end of input, frees all the token still allocated. @@ -161,12 +177,6 @@ struct token *pop_token(struct lexer_context *ctx) free_token(&ctx->current_token); return NULL; } - stream_init(ctx); - char *stream = ctx->end_previous_token; - ssize_t i = 0; - - // Usefull to know if we are inside a quote or double quote - enum lexing_mode lexing_mode = LEXER_NORMAL; while (i < ctx->remaining_chars) { @@ -175,6 +185,7 @@ struct token *pop_token(struct lexer_context *ctx) if (!update_lexing_mode(stream, i, &lexing_mode) && lexing_mode == LEXER_NORMAL) { + update_flags(stream, i, &info); if (is_special_char(stream, i)) { if (i == 0) // where we create spe_char token @@ -200,7 +211,7 @@ struct token *pop_token(struct lexer_context *ctx) // (this should never happen) if (ctx->current_token == NULL) { - ctx->current_token = new_token(stream, i, ctx->only_digits); + ctx->current_token = new_token(stream, i, &info); } save_state(stream, i, ctx); diff --git a/src/lexer/lexer_utils.c b/src/lexer/lexer_utils.c index 07455a7..3029394 100644 --- a/src/lexer/lexer_utils.c +++ b/src/lexer/lexer_utils.c @@ -146,6 +146,21 @@ static void set_token_word(struct token *tok, char *begin, ssize_t size) } } +/* @brief: Sets the token to an assignment_word + * Also allocates the data and fills it. + */ +static void set_token_assignment(struct token *tok, char *begin, ssize_t size) +{ + if (tok->type == TOKEN_NULL && size != 0) + { + tok->type = TOKEN_ASSIGNMENT_WORD; + tok->data = calloc(size + 1, sizeof(char)); + if (tok->data == NULL) + return; + strncpy(tok->data, begin, size); + } +} + /* @brief: Sets the token to an IO number * Also allocates the data and fills it. */ @@ -204,19 +219,23 @@ bool is_special_char(char *stream, ssize_t i) return strchr(special_chars, c) != NULL; } -struct token *new_token(char *begin, ssize_t size, bool only_digits) +struct token *new_token(char *begin, ssize_t size, struct token_info *info) { struct token *tok = calloc(1, sizeof(struct token)); if (tok == NULL) return NULL; - if (only_digits) + if (info->only_digits) set_token_ION(tok, begin, size); - - set_token_operator(tok, begin, size); - set_token_spechar(tok, begin, size); - set_token_keyword(tok, begin, size); - set_token_word(tok, begin, size); + else if (info->has_equal) + set_token_assignment(tok, begin, size); + else + { + set_token_operator(tok, begin, size); + set_token_spechar(tok, begin, size); + set_token_keyword(tok, begin, size); + set_token_word(tok, begin, size); + } return tok; } diff --git a/src/lexer/lexer_utils.h b/src/lexer/lexer_utils.h index 2708662..8fb5219 100644 --- a/src/lexer/lexer_utils.h +++ b/src/lexer/lexer_utils.h @@ -10,13 +10,6 @@ struct lexer_context char *end_previous_token; ssize_t remaining_chars; - // usefull to detect IO numbers. - // tells us if we only lexed digits in current token. - bool only_digits; - - // usefull to detect assignments, and syntax errors with '='. - int equal_count; - struct token *previous_token; struct token *current_token; }; @@ -57,10 +50,6 @@ enum token_type TOKEN_PIPE, TOKEN_NEGATION, - // TODO merge into one and use the data field - // (Too difficult to handle in the parser because of firsts) - // TOKEN_REDIRECTION - // // Redirections TOKEN_REDIR_LEFT, TOKEN_REDIR_RIGHT, @@ -88,18 +77,29 @@ struct token char *data; }; +// used to give info from lexing when creating a new token. +struct token_info +{ + // usefull to detect IO numbers. + // tells us if we only lexed digits in current token. + bool only_digits; + + // usefull to detect assignments, and syntax errors with '='. + bool has_equal; +}; + /* @return: true if a special character from the grammar was found at stream[i], * false otherwise. */ bool is_special_char(char *stream, ssize_t i); /* @brief: return a newly allocated token, with the type corresponding - * to the context given in arguments. + * to the info given in arguments. * The data contains [size] char, starting from [begin]. * * @return: NULL on error, a token otherwise. */ -struct token *new_token(char *begin, ssize_t size, bool only_digits, int equal_count); +struct token *new_token(char *begin, ssize_t size, struct token_info *info); /* @brief: frees the token given in argument */ diff --git a/src/parser/grammar_advanced.c b/src/parser/grammar_advanced.c index eb5869d..713fd99 100644 --- a/src/parser/grammar_advanced.c +++ b/src/parser/grammar_advanced.c @@ -7,6 +7,7 @@ #include #include "grammar_basic.h" +#include "grammar.h" static enum ast_redir_type redir_tok_to_ast_type(enum token_type tok_type) { @@ -66,10 +67,10 @@ struct ast *parse_redirection(struct lexer_context *ctx) struct ast *parse_prefix(struct lexer_context *ctx) { - struct token *token = TOKEN_PEEK(); + struct token *token = PEEK_TOKEN(); if (token->type == TOKEN_ASSIGNMENT_WORD) { - token = TOKEN_POP(); + token = POP_TOKEN(); return ast_create_assignment_word(token->data); } else if (is_first(*token, RULE_REDIRECTION)) diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index ad78673..b979b6e 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -200,7 +200,7 @@ struct ast *parse_simple_command(struct lexer_context *ctx) } if (prefix->type == AST_ASSIGNEMENT) { - assignments = list_append(assignments, prefix) + assignments = list_append(assignments, prefix); } else if (prefix->type == AST_REDIR) { diff --git a/src/utils/ast/ast_command.h b/src/utils/ast/ast_command.h index 0641f1d..c629d21 100644 --- a/src/utils/ast/ast_command.h +++ b/src/utils/ast/ast_command.h @@ -26,7 +26,7 @@ struct ast_command *ast_get_command(struct ast *node); * Creates a new AST node representing a command. */ struct ast *ast_create_command(struct list *command, - struct list *redirections, struct ast_list *assignements); + struct list *redirections, struct list *assignements); /* * @brief: frees the given ast_command and sets the pointer to NULL. From 98d18eef7da920d53b8ba6b057eeae08516a757f Mon Sep 17 00:00:00 2001 From: matteo Date: Thu, 29 Jan 2026 18:46:11 +0100 Subject: [PATCH 034/104] fix: ast_redir and ast_assignment accorded for every part --- src/execution/execution_helpers.c | 2 +- src/lexer/lexer.c | 9 ++++----- src/parser/grammar_advanced.c | 4 ++-- src/parser/grammar_basic.c | 2 +- src/utils/ast/ast_assignment.c | 30 ++++++++++++++++++++++++------ src/utils/ast/ast_assignment.h | 4 ++-- src/utils/ast/ast_command.c | 4 ++-- src/utils/ast/ast_command.h | 6 +++--- 8 files changed, 39 insertions(+), 22 deletions(-) diff --git a/src/execution/execution_helpers.c b/src/execution/execution_helpers.c index aba6dc7..cffcdb0 100644 --- a/src/execution/execution_helpers.c +++ b/src/execution/execution_helpers.c @@ -354,7 +354,7 @@ static int builtin_cd(char **argv, struct hash_map *vars) } } // char *pwd = getcwd("", ""); - char *pwd = get_var_or_env(pwd, "PWD"); + char *pwd = get_var_or_env(vars, "PWD"); if (chdir(path) != 0) { perror("cd"); diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index db4e066..fd5764d 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -87,8 +87,8 @@ static void update_flags(char *stream, ssize_t i, struct token_info *info) { if (i == 0) { - perror("Syntax error: word start with a '='"); - return; + perror("Syntax error: word start with a '='"); + return; } else info->has_equal = true; @@ -105,7 +105,7 @@ struct token *peek_token(struct lexer_context *ctx) // Usefull to know if we are inside a quote or double quote enum lexing_mode lexing_mode = LEXER_NORMAL; - struct token_info info = {true, 0}; + struct token_info info = { true, 0 }; char *stream = ctx->end_previous_token; ssize_t i = 0; @@ -165,11 +165,10 @@ struct token *pop_token(struct lexer_context *ctx) // Usefull to know if we are inside a quote or double quote enum lexing_mode lexing_mode = LEXER_NORMAL; - struct token_info info = {true, 0}; + struct token_info info = { true, 0 }; char *stream = ctx->end_previous_token; ssize_t i = 0; - if (ctx->current_token != NULL && ctx->current_token->type == TOKEN_EOF) { // we reached end of input, frees all the token still allocated. diff --git a/src/parser/grammar_advanced.c b/src/parser/grammar_advanced.c index 713fd99..d57e70e 100644 --- a/src/parser/grammar_advanced.c +++ b/src/parser/grammar_advanced.c @@ -6,8 +6,8 @@ #include #include -#include "grammar_basic.h" #include "grammar.h" +#include "grammar_basic.h" static enum ast_redir_type redir_tok_to_ast_type(enum token_type tok_type) { @@ -71,7 +71,7 @@ struct ast *parse_prefix(struct lexer_context *ctx) if (token->type == TOKEN_ASSIGNMENT_WORD) { token = POP_TOKEN(); - return ast_create_assignment_word(token->data); + return ast_create_assignment(token->data); } else if (is_first(*token, RULE_REDIRECTION)) return parse_redirection(ctx); diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index b979b6e..598c70a 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -198,7 +198,7 @@ struct ast *parse_simple_command(struct lexer_context *ctx) { return err_simple_command(command_elements, redirections); } - if (prefix->type == AST_ASSIGNEMENT) + if (prefix->type == AST_ASSIGNMENT) { assignments = list_append(assignments, prefix); } diff --git a/src/utils/ast/ast_assignment.c b/src/utils/ast/ast_assignment.c index a2564c2..a839e9e 100644 --- a/src/utils/ast/ast_assignment.c +++ b/src/utils/ast/ast_assignment.c @@ -1,3 +1,5 @@ +#define _POSIX_C_SOURCE 200809L + #include "ast_assignment.h" #include @@ -14,15 +16,31 @@ struct ast_assignment *ast_get_assignment(struct ast *node) return (struct ast_assignment *)node->data; } -struct ast *ast_create_assignment(char *name, char *value) +/* @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) { - struct ast_assignment *assignment_data = malloc(sizeof(struct ast_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) +{ + struct ast_assignment *assignment_data = + calloc(1, sizeof(struct ast_assignment)); if (!assignment_data) return NULL; - assignment_data->name = name; - assignment_data->value = value; - + init_assignments(assignement_data); return ast_create(AST_ASSIGNMENT, assignment_data); } @@ -33,4 +51,4 @@ void ast_free_assignment(struct ast_assignment *assignment_data) free(assignment_data->name); free(assignment_data->value); free(assignment_data); -} \ No newline at end of file +} diff --git a/src/utils/ast/ast_assignment.h b/src/utils/ast/ast_assignment.h index c958c2d..5be56c8 100644 --- a/src/utils/ast/ast_assignment.h +++ b/src/utils/ast/ast_assignment.h @@ -11,7 +11,7 @@ struct ast_assignment bool ast_is_assignment(struct ast *node); struct ast_assignment *ast_get_assignment(struct ast *node); -struct ast *ast_create_assignment(char *name, char *value); +struct ast *ast_create_assignment(char *assignment); void ast_free_assignment(struct ast_assignment *assignment_data); -#endif /* ! AST_ASSIGNMENT_H */ \ No newline at end of file +#endif /* ! AST_ASSIGNMENT_H */ diff --git a/src/utils/ast/ast_command.c b/src/utils/ast/ast_command.c index d7e148d..3865e0e 100644 --- a/src/utils/ast/ast_command.c +++ b/src/utils/ast/ast_command.c @@ -5,8 +5,8 @@ #include "../lists/lists.h" -struct ast *ast_create_command(struct list *command, - struct list *redirections, struct ast_list *assignments) +struct ast *ast_create_command(struct list *command, struct list *redirections, + struct ast_list *assignments) { struct ast_command *command_data = malloc(sizeof(struct ast_command)); if (!command_data) diff --git a/src/utils/ast/ast_command.h b/src/utils/ast/ast_command.h index 9322ac7..75c8b2e 100644 --- a/src/utils/ast/ast_command.h +++ b/src/utils/ast/ast_command.h @@ -8,7 +8,7 @@ struct ast_command { struct list *command; // A list of words (char*) struct ast_list *redirections; // A list of ASTs, all ast_redir - struct ast_list *assignments; // A list of ASTs, all ast_assignment + struct list *assignments; // A list of ASTs, all ast_assignment }; /** @@ -25,8 +25,8 @@ struct ast_command *ast_get_command(struct ast *node); /** * Creates a new AST node representing a command. */ -struct ast *ast_create_command(struct list *command, - struct list *redirections, struct ast_list *assignments); +struct ast *ast_create_command(struct list *command, struct list *redirections, + struct list *assignments); /* * @brief: frees the given ast_command and sets the pointer to NULL. From a0b723fb30f2d7a729bfe5efbd6535dc6ea0d70c Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Thu, 29 Jan 2026 11:29:03 +0000 Subject: [PATCH 035/104] feat(execution): update $PWD and $OLD_PWD --- src/execution/execution_helpers.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/execution/execution_helpers.c b/src/execution/execution_helpers.c index 9dcaf41..8ea5aef 100644 --- a/src/execution/execution_helpers.c +++ b/src/execution/execution_helpers.c @@ -12,6 +12,7 @@ #include "../expansion/expansion.h" #include "../utils/ast/ast.h" #include "../utils/hash_map/hash_map.h" +#include "../utils/vars/vars.h" #include "execution.h" static char **list_to_argv(struct list *command_list) @@ -36,7 +37,7 @@ static char **list_to_argv(struct list *command_list) return argv; } -static int try_builtin(char **argv); +static int try_builtin(char **argv, struct hash_map *vars); int exec_ast_command(struct ast_command *command, struct hash_map *vars) { @@ -49,7 +50,7 @@ int exec_ast_command(struct ast_command *command, struct hash_map *vars) free(argv); return 0; } - int builtin_ret = try_builtin(argv); + int builtin_ret = try_builtin(argv, vars); if (builtin_ret != -1) { free(argv); @@ -294,7 +295,7 @@ static int builtin_exit(char **argv) return exit_val; } -static int builtin_cd(char **argv) +static int builtin_cd(char **argv, struct hash_map *vars) { const char *path = argv[1]; if (!path) @@ -306,11 +307,15 @@ static int builtin_cd(char **argv) return 1; } } + // char *pwd = getcwd("", ""); + char *pwd = get_var_or_env(pwd, "PWD"); if (chdir(path) != 0) { perror("cd"); return 1; } + set_var_copy(vars, "OLD_PWD", pwd); + set_var_copy(vars, "PWD", path); return 0; } @@ -320,7 +325,7 @@ static int builtin_cd(char **argv) * @param argv Array of command arguments * @return int Exit status of the builtin command, or -1 if not a builtin */ -static int try_builtin(char **argv) +static int try_builtin(char **argv, struct hash_map *vars) { if (!argv || !argv[0]) return 0; @@ -334,7 +339,7 @@ static int try_builtin(char **argv) if (strcmp(argv[0], "exit") == 0) return builtin_exit(argv); if (strcmp(argv[0], "cd") == 0) - return builtin_cd(argv); + return builtin_cd(argv, vars); return -1; } From 3d26c0a897245802351b057cd936ad6f47e156c3 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Thu, 29 Jan 2026 11:40:55 +0000 Subject: [PATCH 036/104] fix(ast)!: assignment typo --- src/parser/grammar_basic.c | 2 +- src/utils/ast/ast.h | 1 + src/utils/ast/ast_assignement.c | 36 --------------------------------- src/utils/ast/ast_assignement.h | 17 ---------------- src/utils/ast/ast_assignment.c | 36 +++++++++++++++++++++++++++++++++ src/utils/ast/ast_assignment.h | 17 ++++++++++++++++ src/utils/ast/ast_base.h | 2 +- src/utils/ast/ast_command.c | 4 ++-- src/utils/ast/ast_command.h | 4 ++-- 9 files changed, 60 insertions(+), 59 deletions(-) delete mode 100644 src/utils/ast/ast_assignement.c delete mode 100644 src/utils/ast/ast_assignement.h create mode 100644 src/utils/ast/ast_assignment.c create mode 100644 src/utils/ast/ast_assignment.h diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index 9102593..9621d31 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -261,7 +261,7 @@ struct ast *parse_simple_command(struct lexer_context *ctx) } // Result - // TODO handle assignements + // TODO handle assignments struct ast *result = ast_create_command(command_elements, redirections); if (result == NULL) { diff --git a/src/utils/ast/ast.h b/src/utils/ast/ast.h index 3964968..9827d8d 100644 --- a/src/utils/ast/ast.h +++ b/src/utils/ast/ast.h @@ -2,6 +2,7 @@ #define AST_H #include "ast_and_or.h" +#include "ast_assignment.h" #include "ast_base.h" #include "ast_command.h" #include "ast_end.h" diff --git a/src/utils/ast/ast_assignement.c b/src/utils/ast/ast_assignement.c deleted file mode 100644 index 036e4d2..0000000 --- a/src/utils/ast/ast_assignement.c +++ /dev/null @@ -1,36 +0,0 @@ -#include "ast_assignement.h" - -#include - -bool ast_is_assignement(struct ast *node) -{ - return node != NULL && node->type == AST_ASSIGNEMENT; -} - -struct ast_assignement *ast_get_assignement(struct ast *node) -{ - if (node == NULL || node->type != AST_ASSIGNEMENT) - return NULL; - return (struct ast_assignement *)node->data; -} - -struct ast *ast_create_assignement(char *name, char *value) -{ - struct ast_assignement *assignement_data = malloc(sizeof(struct ast_assignement)); - if (!assignement_data) - return NULL; - - assignement_data->name = name; - assignement_data->value = value; - - return ast_create(AST_ASSIGNEMENT, assignement_data); -} - -void ast_free_assignement(struct ast_assignement *assignement_data) -{ - if (assignement_data == NULL) - return; - free(assignement_data->name); - free(assignement_data->value); - free(assignement_data); -} \ No newline at end of file diff --git a/src/utils/ast/ast_assignement.h b/src/utils/ast/ast_assignement.h deleted file mode 100644 index 5975497..0000000 --- a/src/utils/ast/ast_assignement.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef AST_ASSIGNEMENT_H -#define AST_ASSIGNEMENT_H - -#include "ast_base.h" - -struct ast_assignement -{ - char *name; - char *value; -}; - -bool ast_is_assignement(struct ast *node); -struct ast_assignement *ast_get_assignement(struct ast *node); -struct ast *ast_create_assignement(char *name, char *value); -void ast_free_assignement(struct ast_assignement *assignement_data); - -#endif /* ! AST_ASSIGNEMENT_H */ \ No newline at end of file diff --git a/src/utils/ast/ast_assignment.c b/src/utils/ast/ast_assignment.c new file mode 100644 index 0000000..a2564c2 --- /dev/null +++ b/src/utils/ast/ast_assignment.c @@ -0,0 +1,36 @@ +#include "ast_assignment.h" + +#include + +bool ast_is_assignment(struct ast *node) +{ + return node != NULL && node->type == AST_ASSIGNMENT; +} + +struct ast_assignment *ast_get_assignment(struct ast *node) +{ + if (node == NULL || node->type != AST_ASSIGNMENT) + return NULL; + return (struct ast_assignment *)node->data; +} + +struct ast *ast_create_assignment(char *name, char *value) +{ + struct ast_assignment *assignment_data = malloc(sizeof(struct ast_assignment)); + if (!assignment_data) + return NULL; + + assignment_data->name = name; + assignment_data->value = value; + + return ast_create(AST_ASSIGNMENT, assignment_data); +} + +void ast_free_assignment(struct ast_assignment *assignment_data) +{ + if (assignment_data == NULL) + return; + free(assignment_data->name); + free(assignment_data->value); + free(assignment_data); +} \ No newline at end of file diff --git a/src/utils/ast/ast_assignment.h b/src/utils/ast/ast_assignment.h new file mode 100644 index 0000000..c958c2d --- /dev/null +++ b/src/utils/ast/ast_assignment.h @@ -0,0 +1,17 @@ +#ifndef AST_ASSIGNMENT_H +#define AST_ASSIGNMENT_H + +#include "ast_base.h" + +struct ast_assignment +{ + char *name; + char *value; +}; + +bool ast_is_assignment(struct ast *node); +struct ast_assignment *ast_get_assignment(struct ast *node); +struct ast *ast_create_assignment(char *name, char *value); +void ast_free_assignment(struct ast_assignment *assignment_data); + +#endif /* ! AST_ASSIGNMENT_H */ \ No newline at end of file diff --git a/src/utils/ast/ast_base.h b/src/utils/ast/ast_base.h index f04014d..ae99a39 100644 --- a/src/utils/ast/ast_base.h +++ b/src/utils/ast/ast_base.h @@ -16,7 +16,7 @@ enum ast_type AST_WORD, AST_PIPE, AST_NEG, - AST_ASSIGNEMENT + AST_ASSIGNMENT }; struct ast diff --git a/src/utils/ast/ast_command.c b/src/utils/ast/ast_command.c index affe45e..d7e148d 100644 --- a/src/utils/ast/ast_command.c +++ b/src/utils/ast/ast_command.c @@ -6,7 +6,7 @@ #include "../lists/lists.h" struct ast *ast_create_command(struct list *command, - struct list *redirections, struct ast_list *assignements) + struct list *redirections, struct ast_list *assignments) { struct ast_command *command_data = malloc(sizeof(struct ast_command)); if (!command_data) @@ -36,6 +36,6 @@ void ast_free_command(struct ast_command *command_data) return; list_deep_destroy(command_data->command); ast_list_deep_destroy(command_data->redirections); - ast_list_deep_destroy(command_data->assignements); + ast_list_deep_destroy(command_data->assignments); free(command_data); } diff --git a/src/utils/ast/ast_command.h b/src/utils/ast/ast_command.h index 0641f1d..9322ac7 100644 --- a/src/utils/ast/ast_command.h +++ b/src/utils/ast/ast_command.h @@ -8,7 +8,7 @@ struct ast_command { struct list *command; // A list of words (char*) struct ast_list *redirections; // A list of ASTs, all ast_redir - struct ast_list *assignements; // A list of ASTs, all ast_assignement + struct ast_list *assignments; // A list of ASTs, all ast_assignment }; /** @@ -26,7 +26,7 @@ struct ast_command *ast_get_command(struct ast *node); * Creates a new AST node representing a command. */ struct ast *ast_create_command(struct list *command, - struct list *redirections, struct ast_list *assignements); + struct list *redirections, struct ast_list *assignments); /* * @brief: frees the given ast_command and sets the pointer to NULL. From 6948bbae63e388befe6f6814d982cd391200476d Mon Sep 17 00:00:00 2001 From: Jean <47366872+jean-voila@users.noreply.github.com> Date: Thu, 29 Jan 2026 18:18:04 +0100 Subject: [PATCH 037/104] fix(execution): Reimplemented the redirection logic --- src/execution/execution.c | 5 +- src/execution/execution_helpers.c | 100 ++++++++++++++++++++++-------- src/execution/execution_helpers.h | 1 - src/utils/ast/ast_redir.h | 1 + 4 files changed, 75 insertions(+), 32 deletions(-) diff --git a/src/execution/execution.c b/src/execution/execution.c index 90bb8eb..cf751f7 100644 --- a/src/execution/execution.c +++ b/src/execution/execution.c @@ -14,8 +14,6 @@ #include "../utils/ast/ast.h" #include "../utils/hash_map/hash_map.h" - - // Refactored: delegates to helpers in execution_helpers.c #include "execution_helpers.h" @@ -33,6 +31,7 @@ int execution(struct ast *ast, struct hash_map *vars) 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); } case AST_IF: @@ -41,8 +40,6 @@ int execution(struct ast *ast, struct hash_map *vars) return exec_ast_list(ast_get_list(ast), vars); case AST_AND_OR: return exec_ast_and_or(ast_get_and_or(ast), vars); - case AST_REDIR: - return exec_ast_redir(ast_get_redir(ast), vars); default: return 127; } diff --git a/src/execution/execution_helpers.c b/src/execution/execution_helpers.c index 8ea5aef..aba6dc7 100644 --- a/src/execution/execution_helpers.c +++ b/src/execution/execution_helpers.c @@ -12,6 +12,7 @@ #include "../expansion/expansion.h" #include "../utils/ast/ast.h" #include "../utils/hash_map/hash_map.h" +#include "../utils/lists/lists.h" #include "../utils/vars/vars.h" #include "execution.h" @@ -42,40 +43,56 @@ static int try_builtin(char **argv, struct hash_map *vars); int exec_ast_command(struct ast_command *command, struct hash_map *vars) { (void)vars; + set_all_redir(command->redirections); + if (!command || !(command->command)) + { return 1; + } + char **argv = list_to_argv(command->command); if (!argv || !(argv[0])) { free(argv); + unset_all_redir(); return 0; } + int builtin_ret = try_builtin(argv, vars); if (builtin_ret != -1) { free(argv); + unset_all_redir(); return builtin_ret; } + pid_t pid = fork(); if (pid < 0) { perror("fork"); free(argv); + unset_all_redir(); return 1; } + if (pid == 0) { execvp(argv[0], argv); perror("execvp"); + unset_all_redir(); _exit(127); } + int status = 0; waitpid(pid, &status, 0); free(argv); + unset_all_redir(); + if (WIFEXITED(status)) { return WEXITSTATUS(status); } + return 1; } @@ -166,42 +183,71 @@ static int handle_and_restore_fd(int saved_fd, int fd_target) return 0; } -/* - -static open_all_redir(const struct ast_list redir_list) +static int set_all_redir(struct list *redir_list) { - while (redir_list){ - struct ast_redir *redir = (struct ast_redir*)redir_list->data; + while (redir_list) + { + struct ast_redir *redir = (struct ast_redir *)redir_list->data; int target_fd; + if (redir->io_number != -1) { - target_fd = redir->io_number; + target_fd = redir->io_number; + } + else + { + // assign target_fd depending on redir type + if (redir->type == AST_REDIR_TYPE_LESS + || redir->type == AST_REDIR_TYPE_LESSGREAT + || redir->type == AST_REDIR_TYPE_LESSAND) + { + target_fd = 0; + } + else + { + target_fd = 1; + } } - else - { - // assign target_fd depending on redir type - } - int saved_fd = dup(target_fd); - - // if redir type is not with '&' - // then we open("filename") - // else, no need to open, just new_fd = atoi(filename) - - open(); - - dup2(target_fd, new_fd); - - close(new_fd); - - // append target_fd and saved_fd to a list - // in order to be able to restore all the fds + redir->saved_fd = dup(target_fd); + int new_fd = -1; + int flags = 0; + int mode = 0644; + if (redir->type == AST_REDIR_TYPE_GREAT + || redir->type == AST_REDIR_TYPE_CLOBBER + || redir->type == AST_REDIR_TYPE_DGREAT + || redir->type == AST_REDIR_TYPE_LESS) + { + new_fd = open_redir_file(redir, &flags, &mode); + if (new_fd == -1) + { + perror("open"); + return -1; + } + } + else if (redir->type == AST_REDIR_TYPE_GREATAND + || redir->type == AST_REDIR_TYPE_LESSAND) + { + new_fd = atoi(redir->filename); + } + if (dup2(new_fd, target_fd) == -1) + { + perror("dup2"); + if (new_fd != -1) + { + close(new_fd); + } + return -1; + } + if (new_fd != -1) + { + close(new_fd); + } + redir_list = redir_list->next; } - + return 0; } - -*/ /* int exec_ast_redir(struct ast_redir *redir, struct hash_map *vars) { diff --git a/src/execution/execution_helpers.h b/src/execution/execution_helpers.h index ebac0cb..fd8026c 100644 --- a/src/execution/execution_helpers.h +++ b/src/execution/execution_helpers.h @@ -8,6 +8,5 @@ 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_redir(struct ast_redir *redir, struct hash_map *vars); #endif // EXECUTION_HELPERS_H diff --git a/src/utils/ast/ast_redir.h b/src/utils/ast/ast_redir.h index fdea88c..9d9a9d3 100644 --- a/src/utils/ast/ast_redir.h +++ b/src/utils/ast/ast_redir.h @@ -21,6 +21,7 @@ struct ast_redir int io_number; // The FD being redirected (default -1 if not specified, // implies 0 or 1 based on type) enum ast_redir_type type; + int saved_fd; // To store the original FD for restoration (-1 before save) }; bool ast_is_redir(struct ast *node); From 148db797880aaf3896b006afa41a53af8d4ec3e3 Mon Sep 17 00:00:00 2001 From: matteo Date: Thu, 29 Jan 2026 18:52:51 +0100 Subject: [PATCH 038/104] fix(merge conflicts) --- src/parser/grammar_advanced.c | 2 +- src/parser/grammar_basic.c | 27 +++++++++++++++++---------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/parser/grammar_advanced.c b/src/parser/grammar_advanced.c index 1964f49..eb5869d 100644 --- a/src/parser/grammar_advanced.c +++ b/src/parser/grammar_advanced.c @@ -74,7 +74,7 @@ struct ast *parse_prefix(struct lexer_context *ctx) } else if (is_first(*token, RULE_REDIRECTION)) return parse_redirection(ctx); - else + else { perror("Syntax error: expected a prefix (redirection or assignment)"); return NULL; diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index 9621d31..ad78673 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -173,7 +173,7 @@ struct ast *parse_command(struct lexer_context *ctx) * @return: NULL */ static void *err_simple_command(struct list *command_elements, - struct list *redirections) + struct list *redirections) { list_deep_destroy(command_elements); list_deep_destroy(redirections); @@ -184,6 +184,7 @@ struct ast *parse_simple_command(struct lexer_context *ctx) { struct list *command_elements = NULL; struct list *redirections = NULL; // list of redirection ASTs + struct list *assignments = NULL; bool has_prefix = false; struct token *token = PEEK_TOKEN(); @@ -192,12 +193,19 @@ struct ast *parse_simple_command(struct lexer_context *ctx) has_prefix = true; while (is_first(*token, RULE_PREFIX)) { - struct ast *redir = parse_prefix(ctx); - if (redir == NULL) + struct ast *prefix = parse_prefix(ctx); + if (prefix == NULL) { return err_simple_command(command_elements, redirections); } - redirections = list_append(redirections, redir); + if (prefix->type == AST_ASSIGNEMENT) + { + assignments = list_append(assignments, prefix) + } + else if (prefix->type == AST_REDIR) + { + redirections = list_append(redirections, prefix); + } token = PEEK_TOKEN(); } } @@ -250,8 +258,7 @@ struct ast *parse_simple_command(struct lexer_context *ctx) else { perror("Internal error: unexpected return value from " - "parse_element " - "in parse_simple_command"); + "parse_element in parse_simple_command"); return err_simple_command(command_elements, redirections); } @@ -260,9 +267,8 @@ struct ast *parse_simple_command(struct lexer_context *ctx) } } - // Result - // TODO handle assignments - struct ast *result = ast_create_command(command_elements, redirections); + struct ast *result = + ast_create_command(command_elements, redirections, assignments); if (result == NULL) { return err_simple_command(command_elements, redirections); @@ -297,7 +303,8 @@ struct ast *parse_shell_command(struct lexer_context *ctx) /* @brief: frees all the arguments. (helper func) * @return: NULL. */ -static void *err_if_rule(struct ast **cond, struct ast **then_clause, struct ast **else_clause) +static void *err_if_rule(struct ast **cond, struct ast **then_clause, + struct ast **else_clause) { ast_free(cond); ast_free(then_clause); From 51158ec4f9829e79b0bf58cde214ba87988735ce Mon Sep 17 00:00:00 2001 From: matteo Date: Thu, 29 Jan 2026 18:53:37 +0100 Subject: [PATCH 039/104] fix(lexer): merge conflicts --- src/lexer/lexer.c | 55 +++++++++++++++++++++-------------- src/lexer/lexer_utils.c | 33 ++++++++++++++++----- src/lexer/lexer_utils.h | 26 ++++++++--------- src/parser/grammar_advanced.c | 5 ++-- src/parser/grammar_basic.c | 2 +- 5 files changed, 76 insertions(+), 45 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index 21f362b..db4e066 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -78,36 +78,43 @@ static bool update_lexing_mode(char *stream, ssize_t i, return *lexing_mode != mode_before_update; } -/* @brief: updates the flags only_digits and equal_count +/* @brief: updates the flags only_digits and has_equal. * according to the character at stream[i]. */ -static void update_flags(char *stream, ssize_t i, struct lexer_context *ctx) +static void update_flags(char *stream, ssize_t i, struct token_info *info) { - if (stream[i] == '=') + if (stream[i] == '=' && !info->has_equal) { - ctx->equal_count++; + if (i == 0) + { + perror("Syntax error: word start with a '='"); + return; + } + else + info->has_equal = true; } - else if (!isdigit(stream[i]) && ctx->only_digits) + else if (!isdigit(stream[i]) && info->only_digits) { - ctx->only_digits = false; + info->only_digits = false; } } struct token *peek_token(struct lexer_context *ctx) { + stream_init(ctx); + + // Usefull to know if we are inside a quote or double quote + enum lexing_mode lexing_mode = LEXER_NORMAL; + struct token_info info = {true, 0}; + char *stream = ctx->end_previous_token; + ssize_t i = 0; + // we already created the upcoming token during the previous call to peek() if (ctx->current_token != NULL) { return ctx->current_token; } - stream_init(ctx); - char *stream = ctx->end_previous_token; - ssize_t i = 0; - - // Usefull to know if we are inside a quote or double quote - enum lexing_mode lexing_mode = LEXER_NORMAL; - while (i < ctx->remaining_chars) { // true if we didn't encounter a quote of any type at stream[i] @@ -115,7 +122,7 @@ struct token *peek_token(struct lexer_context *ctx) if (!update_lexing_mode(stream, i, &lexing_mode) && lexing_mode == LEXER_NORMAL) { - update_flags(stream, i, ctx); + update_flags(stream, i, &info); if (is_special_char(stream, i)) { if (i == 0) // where we create spe_char token @@ -137,7 +144,7 @@ struct token *peek_token(struct lexer_context *ctx) i++; } - struct token *tok = new_token(stream, i, ctx->only_digits, ctx->equal_count); + struct token *tok = new_token(stream, i, &info); // if token is comment, we don't want it if (tok->type == TOKEN_COMMENT) @@ -154,6 +161,15 @@ struct token *peek_token(struct lexer_context *ctx) struct token *pop_token(struct lexer_context *ctx) { + stream_init(ctx); + + // Usefull to know if we are inside a quote or double quote + enum lexing_mode lexing_mode = LEXER_NORMAL; + struct token_info info = {true, 0}; + char *stream = ctx->end_previous_token; + ssize_t i = 0; + + if (ctx->current_token != NULL && ctx->current_token->type == TOKEN_EOF) { // we reached end of input, frees all the token still allocated. @@ -161,12 +177,6 @@ struct token *pop_token(struct lexer_context *ctx) free_token(&ctx->current_token); return NULL; } - stream_init(ctx); - char *stream = ctx->end_previous_token; - ssize_t i = 0; - - // Usefull to know if we are inside a quote or double quote - enum lexing_mode lexing_mode = LEXER_NORMAL; while (i < ctx->remaining_chars) { @@ -175,6 +185,7 @@ struct token *pop_token(struct lexer_context *ctx) if (!update_lexing_mode(stream, i, &lexing_mode) && lexing_mode == LEXER_NORMAL) { + update_flags(stream, i, &info); if (is_special_char(stream, i)) { if (i == 0) // where we create spe_char token @@ -200,7 +211,7 @@ struct token *pop_token(struct lexer_context *ctx) // (this should never happen) if (ctx->current_token == NULL) { - ctx->current_token = new_token(stream, i, ctx->only_digits); + ctx->current_token = new_token(stream, i, &info); } save_state(stream, i, ctx); diff --git a/src/lexer/lexer_utils.c b/src/lexer/lexer_utils.c index 54859e8..f860649 100644 --- a/src/lexer/lexer_utils.c +++ b/src/lexer/lexer_utils.c @@ -154,6 +154,21 @@ static void set_token_word(struct token *tok, char *begin, ssize_t size) } } +/* @brief: Sets the token to an assignment_word + * Also allocates the data and fills it. + */ +static void set_token_assignment(struct token *tok, char *begin, ssize_t size) +{ + if (tok->type == TOKEN_NULL && size != 0) + { + tok->type = TOKEN_ASSIGNMENT_WORD; + tok->data = calloc(size + 1, sizeof(char)); + if (tok->data == NULL) + return; + strncpy(tok->data, begin, size); + } +} + /* @brief: Sets the token to an IO number * Also allocates the data and fills it. */ @@ -212,19 +227,23 @@ bool is_special_char(char *stream, ssize_t i) return strchr(special_chars, c) != NULL; } -struct token *new_token(char *begin, ssize_t size, bool only_digits) +struct token *new_token(char *begin, ssize_t size, struct token_info *info) { struct token *tok = calloc(1, sizeof(struct token)); if (tok == NULL) return NULL; - if (only_digits) + if (info->only_digits) set_token_ION(tok, begin, size); - - set_token_operator(tok, begin, size); - set_token_spechar(tok, begin, size); - set_token_keyword(tok, begin, size); - set_token_word(tok, begin, size); + else if (info->has_equal) + set_token_assignment(tok, begin, size); + else + { + set_token_operator(tok, begin, size); + set_token_spechar(tok, begin, size); + set_token_keyword(tok, begin, size); + set_token_word(tok, begin, size); + } return tok; } diff --git a/src/lexer/lexer_utils.h b/src/lexer/lexer_utils.h index 2708662..8fb5219 100644 --- a/src/lexer/lexer_utils.h +++ b/src/lexer/lexer_utils.h @@ -10,13 +10,6 @@ struct lexer_context char *end_previous_token; ssize_t remaining_chars; - // usefull to detect IO numbers. - // tells us if we only lexed digits in current token. - bool only_digits; - - // usefull to detect assignments, and syntax errors with '='. - int equal_count; - struct token *previous_token; struct token *current_token; }; @@ -57,10 +50,6 @@ enum token_type TOKEN_PIPE, TOKEN_NEGATION, - // TODO merge into one and use the data field - // (Too difficult to handle in the parser because of firsts) - // TOKEN_REDIRECTION - // // Redirections TOKEN_REDIR_LEFT, TOKEN_REDIR_RIGHT, @@ -88,18 +77,29 @@ struct token char *data; }; +// used to give info from lexing when creating a new token. +struct token_info +{ + // usefull to detect IO numbers. + // tells us if we only lexed digits in current token. + bool only_digits; + + // usefull to detect assignments, and syntax errors with '='. + bool has_equal; +}; + /* @return: true if a special character from the grammar was found at stream[i], * false otherwise. */ bool is_special_char(char *stream, ssize_t i); /* @brief: return a newly allocated token, with the type corresponding - * to the context given in arguments. + * to the info given in arguments. * The data contains [size] char, starting from [begin]. * * @return: NULL on error, a token otherwise. */ -struct token *new_token(char *begin, ssize_t size, bool only_digits, int equal_count); +struct token *new_token(char *begin, ssize_t size, struct token_info *info); /* @brief: frees the token given in argument */ diff --git a/src/parser/grammar_advanced.c b/src/parser/grammar_advanced.c index eb5869d..713fd99 100644 --- a/src/parser/grammar_advanced.c +++ b/src/parser/grammar_advanced.c @@ -7,6 +7,7 @@ #include #include "grammar_basic.h" +#include "grammar.h" static enum ast_redir_type redir_tok_to_ast_type(enum token_type tok_type) { @@ -66,10 +67,10 @@ struct ast *parse_redirection(struct lexer_context *ctx) struct ast *parse_prefix(struct lexer_context *ctx) { - struct token *token = TOKEN_PEEK(); + struct token *token = PEEK_TOKEN(); if (token->type == TOKEN_ASSIGNMENT_WORD) { - token = TOKEN_POP(); + token = POP_TOKEN(); return ast_create_assignment_word(token->data); } else if (is_first(*token, RULE_REDIRECTION)) diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index ad78673..b979b6e 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -200,7 +200,7 @@ struct ast *parse_simple_command(struct lexer_context *ctx) } if (prefix->type == AST_ASSIGNEMENT) { - assignments = list_append(assignments, prefix) + assignments = list_append(assignments, prefix); } else if (prefix->type == AST_REDIR) { From 79fb96e5e29ca62064b969c309c479ad72810e48 Mon Sep 17 00:00:00 2001 From: matteo Date: Thu, 29 Jan 2026 18:46:11 +0100 Subject: [PATCH 040/104] fix: ast_redir and ast_assignment accorded for every part --- src/execution/execution_helpers.c | 2 +- src/lexer/lexer.c | 9 ++++----- src/parser/grammar_advanced.c | 4 ++-- src/parser/grammar_basic.c | 2 +- src/utils/ast/ast_assignment.c | 30 ++++++++++++++++++++++++------ src/utils/ast/ast_assignment.h | 4 ++-- src/utils/ast/ast_command.c | 4 ++-- src/utils/ast/ast_command.h | 6 +++--- 8 files changed, 39 insertions(+), 22 deletions(-) diff --git a/src/execution/execution_helpers.c b/src/execution/execution_helpers.c index aba6dc7..cffcdb0 100644 --- a/src/execution/execution_helpers.c +++ b/src/execution/execution_helpers.c @@ -354,7 +354,7 @@ static int builtin_cd(char **argv, struct hash_map *vars) } } // char *pwd = getcwd("", ""); - char *pwd = get_var_or_env(pwd, "PWD"); + char *pwd = get_var_or_env(vars, "PWD"); if (chdir(path) != 0) { perror("cd"); diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index db4e066..fd5764d 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -87,8 +87,8 @@ static void update_flags(char *stream, ssize_t i, struct token_info *info) { if (i == 0) { - perror("Syntax error: word start with a '='"); - return; + perror("Syntax error: word start with a '='"); + return; } else info->has_equal = true; @@ -105,7 +105,7 @@ struct token *peek_token(struct lexer_context *ctx) // Usefull to know if we are inside a quote or double quote enum lexing_mode lexing_mode = LEXER_NORMAL; - struct token_info info = {true, 0}; + struct token_info info = { true, 0 }; char *stream = ctx->end_previous_token; ssize_t i = 0; @@ -165,11 +165,10 @@ struct token *pop_token(struct lexer_context *ctx) // Usefull to know if we are inside a quote or double quote enum lexing_mode lexing_mode = LEXER_NORMAL; - struct token_info info = {true, 0}; + struct token_info info = { true, 0 }; char *stream = ctx->end_previous_token; ssize_t i = 0; - if (ctx->current_token != NULL && ctx->current_token->type == TOKEN_EOF) { // we reached end of input, frees all the token still allocated. diff --git a/src/parser/grammar_advanced.c b/src/parser/grammar_advanced.c index 713fd99..d57e70e 100644 --- a/src/parser/grammar_advanced.c +++ b/src/parser/grammar_advanced.c @@ -6,8 +6,8 @@ #include #include -#include "grammar_basic.h" #include "grammar.h" +#include "grammar_basic.h" static enum ast_redir_type redir_tok_to_ast_type(enum token_type tok_type) { @@ -71,7 +71,7 @@ struct ast *parse_prefix(struct lexer_context *ctx) if (token->type == TOKEN_ASSIGNMENT_WORD) { token = POP_TOKEN(); - return ast_create_assignment_word(token->data); + return ast_create_assignment(token->data); } else if (is_first(*token, RULE_REDIRECTION)) return parse_redirection(ctx); diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index b979b6e..598c70a 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -198,7 +198,7 @@ struct ast *parse_simple_command(struct lexer_context *ctx) { return err_simple_command(command_elements, redirections); } - if (prefix->type == AST_ASSIGNEMENT) + if (prefix->type == AST_ASSIGNMENT) { assignments = list_append(assignments, prefix); } diff --git a/src/utils/ast/ast_assignment.c b/src/utils/ast/ast_assignment.c index a2564c2..a839e9e 100644 --- a/src/utils/ast/ast_assignment.c +++ b/src/utils/ast/ast_assignment.c @@ -1,3 +1,5 @@ +#define _POSIX_C_SOURCE 200809L + #include "ast_assignment.h" #include @@ -14,15 +16,31 @@ struct ast_assignment *ast_get_assignment(struct ast *node) return (struct ast_assignment *)node->data; } -struct ast *ast_create_assignment(char *name, char *value) +/* @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) { - struct ast_assignment *assignment_data = malloc(sizeof(struct ast_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) +{ + struct ast_assignment *assignment_data = + calloc(1, sizeof(struct ast_assignment)); if (!assignment_data) return NULL; - assignment_data->name = name; - assignment_data->value = value; - + init_assignments(assignement_data); return ast_create(AST_ASSIGNMENT, assignment_data); } @@ -33,4 +51,4 @@ void ast_free_assignment(struct ast_assignment *assignment_data) free(assignment_data->name); free(assignment_data->value); free(assignment_data); -} \ No newline at end of file +} diff --git a/src/utils/ast/ast_assignment.h b/src/utils/ast/ast_assignment.h index c958c2d..5be56c8 100644 --- a/src/utils/ast/ast_assignment.h +++ b/src/utils/ast/ast_assignment.h @@ -11,7 +11,7 @@ struct ast_assignment bool ast_is_assignment(struct ast *node); struct ast_assignment *ast_get_assignment(struct ast *node); -struct ast *ast_create_assignment(char *name, char *value); +struct ast *ast_create_assignment(char *assignment); void ast_free_assignment(struct ast_assignment *assignment_data); -#endif /* ! AST_ASSIGNMENT_H */ \ No newline at end of file +#endif /* ! AST_ASSIGNMENT_H */ diff --git a/src/utils/ast/ast_command.c b/src/utils/ast/ast_command.c index d7e148d..3865e0e 100644 --- a/src/utils/ast/ast_command.c +++ b/src/utils/ast/ast_command.c @@ -5,8 +5,8 @@ #include "../lists/lists.h" -struct ast *ast_create_command(struct list *command, - struct list *redirections, struct ast_list *assignments) +struct ast *ast_create_command(struct list *command, struct list *redirections, + struct ast_list *assignments) { struct ast_command *command_data = malloc(sizeof(struct ast_command)); if (!command_data) diff --git a/src/utils/ast/ast_command.h b/src/utils/ast/ast_command.h index 9322ac7..75c8b2e 100644 --- a/src/utils/ast/ast_command.h +++ b/src/utils/ast/ast_command.h @@ -8,7 +8,7 @@ struct ast_command { struct list *command; // A list of words (char*) struct ast_list *redirections; // A list of ASTs, all ast_redir - struct ast_list *assignments; // A list of ASTs, all ast_assignment + struct list *assignments; // A list of ASTs, all ast_assignment }; /** @@ -25,8 +25,8 @@ struct ast_command *ast_get_command(struct ast *node); /** * Creates a new AST node representing a command. */ -struct ast *ast_create_command(struct list *command, - struct list *redirections, struct ast_list *assignments); +struct ast *ast_create_command(struct list *command, struct list *redirections, + struct list *assignments); /* * @brief: frees the given ast_command and sets the pointer to NULL. From a70943e5cca83a2a83469db272f2ceaf1d06d8b6 Mon Sep 17 00:00:00 2001 From: matteo Date: Thu, 29 Jan 2026 19:11:13 +0100 Subject: [PATCH 041/104] fix(parser): small typo --- src/parser/grammar_basic.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index 598c70a..3f0566f 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -160,13 +160,14 @@ struct ast *parse_command(struct lexer_context *ctx) } else if (is_first(*token, RULE_SHELL_COMMAND)) { - parse_shell_command(ctx); + result = parse_shell_command(ctx); } else { perror("Syntax error: unexpected token"); return NULL; } + return result; } /* @brief: frees command_elements and redirections lists (helper func) From 4315eb5c0d5011ea483c5bef4e8e470cb04f8295 Mon Sep 17 00:00:00 2001 From: Jean <47366872+jean-voila@users.noreply.github.com> Date: Thu, 29 Jan 2026 19:12:58 +0100 Subject: [PATCH 042/104] fix(execution): Redir ahah type --- src/execution/execution_helpers.c | 305 ++++++++++++++++-------------- src/execution/execution_helpers.h | 2 + 2 files changed, 162 insertions(+), 145 deletions(-) diff --git a/src/execution/execution_helpers.c b/src/execution/execution_helpers.c index cffcdb0..501b159 100644 --- a/src/execution/execution_helpers.c +++ b/src/execution/execution_helpers.c @@ -16,137 +16,6 @@ #include "../utils/vars/vars.h" #include "execution.h" -static char **list_to_argv(struct list *command_list) -{ - size_t len = 0; - struct list *cur = command_list; - while (cur) - { - len++; - cur = cur->next; - } - char **argv = calloc(len + 1, sizeof(char *)); - if (!argv) - return NULL; - cur = command_list; - for (size_t i = 0; i < len; i++) - { - argv[i] = (char *)cur->data; - cur = cur->next; - } - argv[len] = NULL; - return argv; -} - -static int try_builtin(char **argv, struct hash_map *vars); - -int exec_ast_command(struct ast_command *command, struct hash_map *vars) -{ - (void)vars; - set_all_redir(command->redirections); - - if (!command || !(command->command)) - { - return 1; - } - - char **argv = list_to_argv(command->command); - if (!argv || !(argv[0])) - { - free(argv); - unset_all_redir(); - return 0; - } - - int builtin_ret = try_builtin(argv, vars); - if (builtin_ret != -1) - { - free(argv); - unset_all_redir(); - return builtin_ret; - } - - pid_t pid = fork(); - if (pid < 0) - { - perror("fork"); - free(argv); - unset_all_redir(); - return 1; - } - - if (pid == 0) - { - execvp(argv[0], argv); - perror("execvp"); - unset_all_redir(); - _exit(127); - } - - int status = 0; - waitpid(pid, &status, 0); - free(argv); - unset_all_redir(); - - if (WIFEXITED(status)) - { - return WEXITSTATUS(status); - } - - return 1; -} - -int exec_ast_if(struct ast_if *if_node, struct hash_map *vars) -{ - int cond = execution(if_node->condition, vars); - if (cond == 0) - return execution(if_node->then_clause, vars); - else - return execution(if_node->else_clause, vars); -} - -int exec_ast_list(struct ast_list *list_node, struct hash_map *vars) -{ - struct list *cur = list_node->children; - int ret = 0; - while (cur) - { - struct ast *child = (struct ast *)cur->data; - if (!ast_is_void(child)) - ret = execution(child, vars); - cur = cur->next; - } - return ret; -} - -int exec_ast_and_or(struct ast_and_or *ao_node, struct hash_map *vars) -{ - int left_ret = execution(ao_node->left, vars); - if (ao_node->type == AST_AND_OR_TYPE_AND) - { - if (left_ret == 0) - return execution(ao_node->right, vars); - return left_ret; - } - else - { - if (left_ret != 0) - return execution(ao_node->right, vars); - return left_ret; - } -} - -static int get_fd_target(const struct ast_redir *redir) -{ - if (redir->io_number != -1) - return redir->io_number; - if (redir->type == AST_REDIR_TYPE_LESS - || redir->type == AST_REDIR_TYPE_LESSGREAT - || redir->type == AST_REDIR_TYPE_LESSAND) - return 0; - return 1; -} - static int open_redir_file(const struct ast_redir *redir, int *flags, int *mode) { *mode = 0644; @@ -169,20 +38,6 @@ static int open_redir_file(const struct ast_redir *redir, int *flags, int *mode) return -3; // not a file open } -static int handle_and_restore_fd(int saved_fd, int fd_target) -{ - if (saved_fd != -1) - { - dup2(saved_fd, fd_target); - close(saved_fd); - } - else - { - close(fd_target); - } - return 0; -} - static int set_all_redir(struct list *redir_list) { while (redir_list) @@ -248,6 +103,166 @@ static int set_all_redir(struct list *redir_list) return 0; } +static char **list_to_argv(struct list *command_list) +{ + size_t len = 0; + struct list *cur = command_list; + while (cur) + { + len++; + cur = cur->next; + } + char **argv = calloc(len + 1, sizeof(char *)); + if (!argv) + return NULL; + cur = command_list; + for (size_t i = 0; i < len; i++) + { + argv[i] = (char *)cur->data; + cur = cur->next; + } + argv[len] = NULL; + return argv; +} + +static int try_builtin(char **argv, struct hash_map *vars); + +int exec_ast_command(struct ast_command *command, struct hash_map *vars) +{ + (void)vars; + set_all_redir(command->redirections ? command->redirections->children + : NULL); + + if (!command || !(command->command)) + { + return 1; + } + + char **argv = list_to_argv(command->command); + if (!argv || !(argv[0])) + { + free(argv); + unset_all_redir(command->redirections ? command->redirections->children + : NULL); + return 0; + } + + int builtin_ret = try_builtin(argv, vars); + if (builtin_ret != -1) + { + free(argv); + unset_all_redir(command->redirections ? command->redirections->children + : NULL); + return builtin_ret; + } + + pid_t pid = fork(); + if (pid < 0) + { + perror("fork"); + free(argv); + unset_all_redir(command->redirections ? command->redirections->children + : NULL); + return 1; + } + + if (pid == 0) + { + execvp(argv[0], argv); + perror("execvp"); + unset_all_redir(command->redirections ? command->redirections->children + : NULL); + _exit(127); + } + + int status = 0; + waitpid(pid, &status, 0); + free(argv); + unset_all_redir(command->redirections ? command->redirections->children + : NULL); + + if (WIFEXITED(status)) + { + return WEXITSTATUS(status); + } + + return 1; +} + +int exec_ast_if(struct ast_if *if_node, struct hash_map *vars) +{ + int cond = execution(if_node->condition, vars); + if (cond == 0) + return execution(if_node->then_clause, vars); + else + return execution(if_node->else_clause, vars); +} + +int exec_ast_list(struct ast_list *list_node, struct hash_map *vars) +{ + struct list *cur = list_node->children; + int ret = 0; + while (cur) + { + struct ast *child = (struct ast *)cur->data; + if (!ast_is_void(child)) + ret = execution(child, vars); + cur = cur->next; + } + return ret; +} + +int exec_ast_and_or(struct ast_and_or *ao_node, struct hash_map *vars) +{ + int left_ret = execution(ao_node->left, vars); + if (ao_node->type == AST_AND_OR_TYPE_AND) + { + if (left_ret == 0) + return execution(ao_node->right, vars); + return left_ret; + } + else + { + if (left_ret != 0) + return execution(ao_node->right, vars); + return left_ret; + } +} + +void unset_all_redir(struct list *redir_list) +{ + while (redir_list) + { + struct ast_redir *redir = (struct ast_redir *)redir_list->data; + int target_fd; + if (redir->io_number != -1) + { + target_fd = redir->io_number; + } + else + { + if (redir->type == AST_REDIR_TYPE_LESS + || redir->type == AST_REDIR_TYPE_LESSGREAT + || redir->type == AST_REDIR_TYPE_LESSAND) + { + target_fd = 0; + } + else + { + target_fd = 1; + } + } + if (redir->saved_fd != -1) + { + dup2(redir->saved_fd, target_fd); + close(redir->saved_fd); + redir->saved_fd = -1; + } + + redir_list = redir_list->next; + } +} + /* int exec_ast_redir(struct ast_redir *redir, struct hash_map *vars) { diff --git a/src/execution/execution_helpers.h b/src/execution/execution_helpers.h index fd8026c..622c9fe 100644 --- a/src/execution/execution_helpers.h +++ b/src/execution/execution_helpers.h @@ -9,4 +9,6 @@ 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); +void unset_all_redir(struct list *redir_list); + #endif // EXECUTION_HELPERS_H From 08803bd591e9be320c05372d68c2de1c10c62f29 Mon Sep 17 00:00:00 2001 From: matteo Date: Thu, 29 Jan 2026 19:20:29 +0100 Subject: [PATCH 043/104] fix(ast_command): ast_list redir -> list redir --- src/utils/ast/ast_command.c | 3 ++- src/utils/ast/ast_command.h | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/utils/ast/ast_command.c b/src/utils/ast/ast_command.c index 3865e0e..7e192ca 100644 --- a/src/utils/ast/ast_command.c +++ b/src/utils/ast/ast_command.c @@ -4,9 +4,10 @@ #include #include "../lists/lists.h" +#include "ast_list.h" struct ast *ast_create_command(struct list *command, struct list *redirections, - struct ast_list *assignments) + struct list *assignments) { struct ast_command *command_data = malloc(sizeof(struct ast_command)); if (!command_data) diff --git a/src/utils/ast/ast_command.h b/src/utils/ast/ast_command.h index 75c8b2e..7b24a2d 100644 --- a/src/utils/ast/ast_command.h +++ b/src/utils/ast/ast_command.h @@ -7,7 +7,7 @@ struct ast_command { struct list *command; // A list of words (char*) - struct ast_list *redirections; // A list of ASTs, all ast_redir + struct list *redirections; // A list of ASTs, all ast_redir struct list *assignments; // A list of ASTs, all ast_assignment }; From 6ca10b673a8ac2c5f58c06e636dc8c2be272306d Mon Sep 17 00:00:00 2001 From: Jean <47366872+jean-voila@users.noreply.github.com> Date: Thu, 29 Jan 2026 19:35:08 +0100 Subject: [PATCH 044/104] feat(compiling en fait): Execution --- src/execution/execution_helpers.c | 28 ++++++++++------------------ src/utils/Makefile.am | 3 ++- src/utils/ast/ast_assignment.c | 3 ++- src/utils/ast/ast_command.c | 1 + 4 files changed, 15 insertions(+), 20 deletions(-) diff --git a/src/execution/execution_helpers.c b/src/execution/execution_helpers.c index 501b159..edbe220 100644 --- a/src/execution/execution_helpers.c +++ b/src/execution/execution_helpers.c @@ -42,16 +42,15 @@ static int set_all_redir(struct list *redir_list) { while (redir_list) { - struct ast_redir *redir = (struct ast_redir *)redir_list->data; + struct ast *redir_node = (struct ast *)redir_list->data; + struct ast_redir *redir = ast_get_redir(redir_node); int target_fd; - if (redir->io_number != -1) { target_fd = redir->io_number; } else { - // assign target_fd depending on redir type if (redir->type == AST_REDIR_TYPE_LESS || redir->type == AST_REDIR_TYPE_LESSGREAT || redir->type == AST_REDIR_TYPE_LESSAND) @@ -63,7 +62,6 @@ static int set_all_redir(struct list *redir_list) target_fd = 1; } } - redir->saved_fd = dup(target_fd); int new_fd = -1; int flags = 0; @@ -130,8 +128,7 @@ static int try_builtin(char **argv, struct hash_map *vars); int exec_ast_command(struct ast_command *command, struct hash_map *vars) { (void)vars; - set_all_redir(command->redirections ? command->redirections->children - : NULL); + set_all_redir(command->redirections); if (!command || !(command->command)) { @@ -142,8 +139,7 @@ int exec_ast_command(struct ast_command *command, struct hash_map *vars) if (!argv || !(argv[0])) { free(argv); - unset_all_redir(command->redirections ? command->redirections->children - : NULL); + unset_all_redir(command->redirections); return 0; } @@ -151,8 +147,7 @@ int exec_ast_command(struct ast_command *command, struct hash_map *vars) if (builtin_ret != -1) { free(argv); - unset_all_redir(command->redirections ? command->redirections->children - : NULL); + unset_all_redir(command->redirections); return builtin_ret; } @@ -161,8 +156,7 @@ int exec_ast_command(struct ast_command *command, struct hash_map *vars) { perror("fork"); free(argv); - unset_all_redir(command->redirections ? command->redirections->children - : NULL); + unset_all_redir(command->redirections); return 1; } @@ -170,16 +164,14 @@ int exec_ast_command(struct ast_command *command, struct hash_map *vars) { execvp(argv[0], argv); perror("execvp"); - unset_all_redir(command->redirections ? command->redirections->children - : NULL); + unset_all_redir(command->redirections); _exit(127); } int status = 0; waitpid(pid, &status, 0); free(argv); - unset_all_redir(command->redirections ? command->redirections->children - : NULL); + unset_all_redir(command->redirections); if (WIFEXITED(status)) { @@ -233,7 +225,8 @@ void unset_all_redir(struct list *redir_list) { while (redir_list) { - struct ast_redir *redir = (struct ast_redir *)redir_list->data; + struct ast *redir_node = (struct ast *)redir_list->data; + struct ast_redir *redir = ast_get_redir(redir_node); int target_fd; if (redir->io_number != -1) { @@ -258,7 +251,6 @@ void unset_all_redir(struct list *redir_list) close(redir->saved_fd); redir->saved_fd = -1; } - redir_list = redir_list->next; } } diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am index c2cf250..7876682 100644 --- a/src/utils/Makefile.am +++ b/src/utils/Makefile.am @@ -18,7 +18,8 @@ libutils_a_SOURCES = \ ast/ast_neg.c \ ast/ast_pipe.c \ args/args.c \ - vars/vars.c + vars/vars.c \ + ast/ast_assignment.c libutils_a_CPPFLAGS = -I$(top_srcdir)/src diff --git a/src/utils/ast/ast_assignment.c b/src/utils/ast/ast_assignment.c index a839e9e..e859912 100644 --- a/src/utils/ast/ast_assignment.c +++ b/src/utils/ast/ast_assignment.c @@ -3,6 +3,7 @@ #include "ast_assignment.h" #include +#include bool ast_is_assignment(struct ast *node) { @@ -40,7 +41,7 @@ struct ast *ast_create_assignment(char *assignment) if (!assignment_data) return NULL; - init_assignments(assignement_data); + init_assignments(assignment_data, assignment); return ast_create(AST_ASSIGNMENT, assignment_data); } diff --git a/src/utils/ast/ast_command.c b/src/utils/ast/ast_command.c index 7e192ca..81dd10e 100644 --- a/src/utils/ast/ast_command.c +++ b/src/utils/ast/ast_command.c @@ -15,6 +15,7 @@ struct ast *ast_create_command(struct list *command, struct list *redirections, command_data->command = command; command_data->redirections = redirections; + command_data->assignments = assignments; return ast_create(AST_CMD, command_data); } From 3601c3136fb0bcd273f3b30e6cd53bdb718c3cdf Mon Sep 17 00:00:00 2001 From: Guillem George Date: Thu, 29 Jan 2026 19:47:59 +0100 Subject: [PATCH 045/104] feat: final firsts initialization --- src/lexer/lexer_utils.h | 6 +- src/parser/grammar.c | 134 ++++++++++++++++++++++++------------- src/parser/grammar.h | 8 +++ src/parser/grammar_basic.c | 4 +- src/parser/grammar_basic.h | 2 +- 5 files changed, 104 insertions(+), 50 deletions(-) diff --git a/src/lexer/lexer_utils.h b/src/lexer/lexer_utils.h index 8fb5219..af64455 100644 --- a/src/lexer/lexer_utils.h +++ b/src/lexer/lexer_utils.h @@ -68,7 +68,11 @@ enum token_type TOKEN_FI, TOKEN_ELIF, TOKEN_AND, - TOKEN_OR + TOKEN_OR, + TOKEN_FOR, + TOKEN_WHILE, + TOKEN_UNTIL, + TOKEN_CASE }; struct token diff --git a/src/parser/grammar.c b/src/parser/grammar.c index 95f1bd2..d0258bf 100644 --- a/src/parser/grammar.c +++ b/src/parser/grammar.c @@ -4,7 +4,6 @@ #include #include -#include "../lexer/lexer.h" #include "grammar_basic.h" // === Static variables @@ -57,6 +56,23 @@ static bool add_first(enum rule rule, enum token_type token) return true; } +/* @brief Add a list of tokens to a rule's firsts (in firsts_map) + * + * @arg rule the rule to which add a first + * @arg tokens_list the list of tokens to add to the rule's firsts + * @return true on success, false on error + */ +static bool add_firsts(enum rule rule, struct firsts_list *tokens_list) +{ + for (size_t i = 0; i < tokens_list->list_length; i++) + { + bool res = add_first(rule, tokens_list->tokens[i]); + if (!res) + return false; + } + return true; +} + /* @brief initializes the firsts_map static variable (does not populate it) * @return true on success, false on error */ @@ -73,21 +89,6 @@ static bool init_firsts_map(void) return true; } -/* @brief: add all the redirection token_types to the first of [rule]. - * this also contains IONUMBER - */ -static void add_first_redir(enum rule rule) -{ - add_first(rule, TOKEN_IONUMBER); - add_first(rule, TOKEN_REDIR_LEFT); - add_first(rule, TOKEN_REDIR_RIGHT); - add_first(rule, TOKEN_REDIR_LEFT_RIGHT); - add_first(rule, TOKEN_REDIR_DOUBLE_RIGHT); - add_first(rule, TOKEN_REDIR_LEFT_AMP); - add_first(rule, TOKEN_REDIR_RIGHT_AMP); - add_first(rule, TOKEN_REDIR_RIGHT_PIPE); -} - // === Functions bool grammar_init(void) @@ -98,46 +99,85 @@ bool grammar_init(void) return false; // Populate the firsts map - add_first(RULE_INPUT, TOKEN_WORD); - add_first(RULE_INPUT, TOKEN_IF); - add_first(RULE_COMMAND, TOKEN_NEGATION); - add_first(RULE_INPUT, TOKEN_NEWLINE); - add_first(RULE_INPUT, TOKEN_EOF); - - add_first(RULE_LIST, TOKEN_WORD); - add_first(RULE_LIST, TOKEN_IF); - add_first(RULE_LIST, TOKEN_NEGATION); - - add_first(RULE_AND_OR, TOKEN_WORD); - add_first(RULE_AND_OR, TOKEN_IF); - add_first(RULE_AND_OR, TOKEN_NEGATION); - - add_first(RULE_PIPELINE, TOKEN_WORD); - add_first(RULE_PIPELINE, TOKEN_IF); - add_first(RULE_PIPELINE, TOKEN_NEGATION); - - add_first(RULE_COMMAND, TOKEN_WORD); - add_first(RULE_COMMAND, TOKEN_IF); - - add_first(RULE_SIMPLE_COMMAND, TOKEN_WORD); - - add_first(RULE_SHELL_COMMAND, TOKEN_IF); + // TODO CHECK ORDER + // If add_first(RULE_IF, TOKEN_IF); - add_first(RULE_COMPOUND_LIST, TOKEN_NEWLINE); - add_first(RULE_COMPOUND_LIST, TOKEN_WORD); - add_first(RULE_COMPOUND_LIST, TOKEN_IF); - + // Else clause add_first(RULE_ELSE_CLAUSE, TOKEN_ELSE); add_first(RULE_ELSE_CLAUSE, TOKEN_ELIF); + // For + add_first(RULE_FOR, TOKEN_FOR); + + // While + add_first(RULE_WHILE, TOKEN_WHILE); + + // Until + add_first(RULE_WHILE, TOKEN_UNTIL); + + // Case + add_first(RULE_CASE, TOKEN_CASE); + + // Case item + add_first(RULE_CASE_ITEM, TOKEN_LEFT_PAREN); + add_first(RULE_CASE_ITEM, TOKEN_WORD); + + // Case clause + add_firsts(RULE_CASE_CLAUSE, first(RULE_CASE_ITEM)); + + // Redirection + add_first(RULE_REDIRECTION, TOKEN_IONUMBER); + add_first(RULE_REDIRECTION, TOKEN_REDIR_LEFT); + add_first(RULE_REDIRECTION, TOKEN_REDIR_RIGHT); + add_first(RULE_REDIRECTION, TOKEN_REDIR_LEFT_RIGHT); + add_first(RULE_REDIRECTION, TOKEN_REDIR_DOUBLE_RIGHT); + add_first(RULE_REDIRECTION, TOKEN_REDIR_LEFT_AMP); + add_first(RULE_REDIRECTION, TOKEN_REDIR_RIGHT_AMP); + add_first(RULE_REDIRECTION, TOKEN_REDIR_RIGHT_PIPE); + // %RIP Matteo 30/01/2026 + + // Element add_first(RULE_ELEMENT, TOKEN_WORD); - add_first_redir(RULE_ELEMENT); + add_firsts(RULE_ELEMENT, first(RULE_REDIRECTION)); - add_first_redir(RULE_REDIRECTION); + // Prefix + add_first(RULE_PREFIX, TOKEN_ASSIGNMENT_WORD); + add_firsts(RULE_PREFIX, first(RULE_REDIRECTION)); - add_first_redir(RULE_PREFIX); + // Shell command + add_firsts(RULE_SHELL_COMMAND, first(RULE_IF)); + + // Simple command + add_firsts(RULE_SIMPLE_COMMAND, first(RULE_PREFIX)); + + // Funcdec + add_first(RULE_FUNCDEC, TOKEN_WORD); + + // Command + add_firsts(RULE_COMMAND, first(RULE_SIMPLE_COMMAND)); + add_firsts(RULE_COMMAND, first(RULE_SHELL_COMMAND)); + add_firsts(RULE_COMMAND, first(RULE_FUNCDEC)); + + // Pipeline + add_first(RULE_PIPELINE, TOKEN_WORD); + add_firsts(RULE_PIPELINE, first(RULE_COMMAND)); + + // And Or + add_firsts(RULE_AND_OR, first(RULE_PIPELINE)); + + // Compound list + add_first(RULE_COMPOUND_LIST, TOKEN_NEWLINE); + add_firsts(RULE_COMPOUND_LIST, first(RULE_AND_OR)); + + // List + add_firsts(RULE_LIST, first(RULE_AND_OR)); + + // Input + add_first(RULE_INPUT, TOKEN_NEWLINE); + add_first(RULE_INPUT, TOKEN_EOF); + add_firsts(RULE_INPUT, first(RULE_LIST)); return true; } diff --git a/src/parser/grammar.h b/src/parser/grammar.h index 3c77747..663d37b 100644 --- a/src/parser/grammar.h +++ b/src/parser/grammar.h @@ -41,6 +41,13 @@ enum rule RULE_ELEMENT, RULE_REDIRECTION, RULE_PREFIX, + RULE_FUNCDEC, + RULE_WHILE, + RULE_UNTIL, + RULE_FOR, + RULE_CASE, + RULE_CASE_CLAUSE, + RULE_CASE_ITEM, NUMBER_OF_RULES }; @@ -89,6 +96,7 @@ bool is_first(struct token token, enum rule rule); * | '\n' * | EOF * ; + * @first first(list), '\n', EOF */ struct ast *parse_input(struct lexer_context *ctx); diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index 598c70a..9b4add2 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -160,13 +160,15 @@ struct ast *parse_command(struct lexer_context *ctx) } else if (is_first(*token, RULE_SHELL_COMMAND)) { - parse_shell_command(ctx); + result = parse_shell_command(ctx); } else { perror("Syntax error: unexpected token"); return NULL; } + + return result; } /* @brief: frees command_elements and redirections lists (helper func) diff --git a/src/parser/grammar_basic.h b/src/parser/grammar_basic.h index 5ef6a56..46f7b4f 100644 --- a/src/parser/grammar_basic.h +++ b/src/parser/grammar_basic.h @@ -99,7 +99,7 @@ struct ast *parse_if_rule(struct lexer_context *ctx); struct ast *parse_compound_list(struct lexer_context *ctx); /* - * @brief + * @brief parses an else clause rule (inside if) * * @code else_clause = 'else' compound_list * | 'elif' compound_list 'then' compound_list [else_clause] From 028d4312af2b5851e5498c16e013730ae9c886a8 Mon Sep 17 00:00:00 2001 From: matteo Date: Thu, 29 Jan 2026 20:29:02 +0100 Subject: [PATCH 046/104] feat: fuckning working --- src/Makefile.am | 4 +- src/execution/execution_helpers.c | 68 ++++++++++--------------------- src/parser/grammar.c | 1 + src/utils/ast/ast.c | 4 +- 4 files changed, 28 insertions(+), 49 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 0dad66f..6ac8528 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -34,7 +34,7 @@ check_PROGRAMS = testsuite #testsuite_CFLAGS = $(42sh_CFLAGS) #testsuite_CFLAGS += $(CRITERION_CFLAGS) -testsuite_SOURCES = ../tests/unit/lexer/lexer_tests.c \ +testsuite_SOURCES = ../tests/unit/utils/utils_tests.c \ ../tests/unit/expansion/expand.c \ ../tests/unit/expansion/parse_var.c \ @@ -42,7 +42,7 @@ testsuite_SOURCES = ../tests/unit/lexer/lexer_tests.c \ ../tests/unit/utils/args.c \ ../tests/unit/utils/hash_map.c \ ../tests/unit/utils/insert_into.c - + #../tests/unit/lexer/lexer_tests.c \ testsuite_CPPFLAGS = $(42sh_CPPFLAGS) testsuite_LDADD = $(42sh_LDADD) -lcriterion diff --git a/src/execution/execution_helpers.c b/src/execution/execution_helpers.c index edbe220..9e44dfa 100644 --- a/src/execution/execution_helpers.c +++ b/src/execution/execution_helpers.c @@ -125,9 +125,31 @@ static char **list_to_argv(struct list *command_list) static int try_builtin(char **argv, struct hash_map *vars); +static int exec_assignment(struct list *assignment_list, struct hash_map *vars) +{ + while (assignment_list != 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); + + char *key = strdup(assignment->name); + char *value = strdup(assignment->value); + hash_map_insert(vars, key, value, NULL); + + assignment_list = assignment_list->next; + } + return 0; +} + int exec_ast_command(struct ast_command *command, struct hash_map *vars) { (void)vars; + exec_assignment(command->assignments, vars); set_all_redir(command->redirections); if (!command || !(command->command)) @@ -255,52 +277,6 @@ void unset_all_redir(struct list *redir_list) } } -/* -int exec_ast_redir(struct ast_redir *redir, struct hash_map *vars) -{ - int fd_target = get_fd_target(redir); - int saved_fd = dup(fd_target); - int new_fd = -1, flags = 0, mode = 0644; - if (redir->type == AST_REDIR_TYPE_GREAT - || redir->type == AST_REDIR_TYPE_CLOBBER - || redir->type == AST_REDIR_TYPE_DGREAT - || redir->type == AST_REDIR_TYPE_LESS) - { - new_fd = open_redir_file(redir, &flags, &mode); - if (new_fd == -1) - { - perror("open"); - if (saved_fd != -1) - close(saved_fd); - return 1; - } - if (dup2(new_fd, fd_target) == -1) - { - perror("dup2"); - close(new_fd); - if (saved_fd != -1) - close(saved_fd); - return 1; - } - close(new_fd); - } - else if (redir->type == AST_REDIR_TYPE_GREATAND - || redir->type == AST_REDIR_TYPE_LESSAND) - { - new_fd = atoi(redir->filename); - if (dup2(new_fd, fd_target) == -1) - { - perror("dup2"); - if (saved_fd != -1) - close(saved_fd); - return 1; - } - } - int ret = execution(redir->child, vars); - handle_and_restore_fd(saved_fd, fd_target); - return ret; -} */ - // --- Builtins --- static int builtin_echo(char **argv) diff --git a/src/parser/grammar.c b/src/parser/grammar.c index d0258bf..57d7d20 100644 --- a/src/parser/grammar.c +++ b/src/parser/grammar.c @@ -151,6 +151,7 @@ bool grammar_init(void) // Simple command add_firsts(RULE_SIMPLE_COMMAND, first(RULE_PREFIX)); + add_first(RULE_SIMPLE_COMMAND, TOKEN_WORD); // Funcdec add_first(RULE_FUNCDEC, TOKEN_WORD); diff --git a/src/utils/ast/ast.c b/src/utils/ast/ast.c index 16db64e..95a2f15 100644 --- a/src/utils/ast/ast.c +++ b/src/utils/ast/ast.c @@ -39,7 +39,9 @@ void ast_free(struct ast **node) case AST_WORD: ast_free_word(ast_get_word(*node)); break; - + case AST_ASSIGNMENT: + ast_free_assignment(ast_get_assignment(*node)); + break; case AST_VOID: case AST_END: break; From 5784f557b8d93ee5e09457de447d90a1a2e4a3a7 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Thu, 29 Jan 2026 20:39:58 +0100 Subject: [PATCH 047/104] fix: Makefile --- src/Makefile.am | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 6ac8528..3015877 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -34,15 +34,16 @@ check_PROGRAMS = testsuite #testsuite_CFLAGS = $(42sh_CFLAGS) #testsuite_CFLAGS += $(CRITERION_CFLAGS) -testsuite_SOURCES = - ../tests/unit/utils/utils_tests.c \ - ../tests/unit/expansion/expand.c \ +testsuite_SOURCES = ../tests/unit/utils/utils_tests.c \ ../tests/unit/expansion/parse_var.c \ ../tests/unit/io_backend/io_backend.c \ ../tests/unit/utils/args.c \ ../tests/unit/utils/hash_map.c \ ../tests/unit/utils/insert_into.c - #../tests/unit/lexer/lexer_tests.c \ + +# ../tests/unit/lexer/lexer_tests.c +# ../tests/unit/expansion/expand.c + testsuite_CPPFLAGS = $(42sh_CPPFLAGS) testsuite_LDADD = $(42sh_LDADD) -lcriterion From 6cdcf3f36baf8aadf500d73e45a730876050ca57 Mon Sep 17 00:00:00 2001 From: Guillem George Date: Thu, 29 Jan 2026 20:41:45 +0100 Subject: [PATCH 048/104] fix: readded fsanitize for check_flemme.sh (linux only) --- check_flemme.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/check_flemme.sh b/check_flemme.sh index 12398ae..0543001 100755 --- a/check_flemme.sh +++ b/check_flemme.sh @@ -32,7 +32,7 @@ run_cmd "Running autoreconf" autoreconf --force --verbose --install if [[ "$(uname)" == "Darwin" ]]; then run_cmd "Configuring for MacOS" ./configure CFLAGS='-std=c99 -Werror -Wall -Wextra -Wvla -I/opt/homebrew/include' LDFLAGS='-L/opt/homebrew/lib' else - run_cmd "Configuring for Linux" ./configure CFLAGS='-std=c99 -Werror -Wall -Wextra -Wvla -g' + run_cmd "Configuring for Linux" ./configure CFLAGS='-std=c99 -Werror -Wall -Wextra -Wvla -g -fsanitize=address' fi run_cmd "Cleaning build" make clean From e32715ce1351d659b0f2599572395ef6d6772b87 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Thu, 29 Jan 2026 19:46:22 +0000 Subject: [PATCH 049/104] feat(execution): var assign --- src/execution/execution.c | 2 ++ src/execution/execution_helpers.c | 7 +++++++ src/execution/execution_helpers.h | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/execution/execution.c b/src/execution/execution.c index cf751f7..be3b378 100644 --- a/src/execution/execution.c +++ b/src/execution/execution.c @@ -40,6 +40,8 @@ int execution(struct ast *ast, struct hash_map *vars) return exec_ast_list(ast_get_list(ast), vars); case AST_AND_OR: return exec_ast_and_or(ast_get_and_or(ast), vars); + case AST_ASSIGNMENT: + return exec_ast_assignment(ast_get_assignment(ast), vars); default: return 127; } diff --git a/src/execution/execution_helpers.c b/src/execution/execution_helpers.c index 9e44dfa..c77c9b3 100644 --- a/src/execution/execution_helpers.c +++ b/src/execution/execution_helpers.c @@ -203,6 +203,13 @@ int exec_ast_command(struct ast_command *command, struct hash_map *vars) return 1; } + +int exec_ast_assignment(struct ast_assignment *assign, struct hash_map *vars) +{ + set_var_copy(vars, assign->name, assign->value); + return 0; +} + int exec_ast_if(struct ast_if *if_node, struct hash_map *vars) { int cond = execution(if_node->condition, vars); diff --git a/src/execution/execution_helpers.h b/src/execution/execution_helpers.h index 622c9fe..9fbe90a 100644 --- a/src/execution/execution_helpers.h +++ b/src/execution/execution_helpers.h @@ -8,7 +8,7 @@ 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_assignment(struct ast_assignment *assign, struct hash_map *vars); void unset_all_redir(struct list *redir_list); - #endif // EXECUTION_HELPERS_H From c4487762684f94134d37c73bf3484ba1effd3bef Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Thu, 29 Jan 2026 19:56:21 +0000 Subject: [PATCH 050/104] fix(execution): var update leak --- src/execution/execution.c | 2 -- src/execution/execution_helpers.c | 13 +------------ src/execution/execution_helpers.h | 1 - 3 files changed, 1 insertion(+), 15 deletions(-) diff --git a/src/execution/execution.c b/src/execution/execution.c index be3b378..cf751f7 100644 --- a/src/execution/execution.c +++ b/src/execution/execution.c @@ -40,8 +40,6 @@ int execution(struct ast *ast, struct hash_map *vars) return exec_ast_list(ast_get_list(ast), vars); case AST_AND_OR: return exec_ast_and_or(ast_get_and_or(ast), vars); - case AST_ASSIGNMENT: - return exec_ast_assignment(ast_get_assignment(ast), vars); default: return 127; } diff --git a/src/execution/execution_helpers.c b/src/execution/execution_helpers.c index c77c9b3..3166578 100644 --- a/src/execution/execution_helpers.c +++ b/src/execution/execution_helpers.c @@ -137,10 +137,7 @@ static int exec_assignment(struct list *assignment_list, struct hash_map *vars) struct ast_assignment *assignment = ast_get_assignment(assignment_list->data); - char *key = strdup(assignment->name); - char *value = strdup(assignment->value); - hash_map_insert(vars, key, value, NULL); - + set_var_copy(vars, assignment->name, assignment->value); assignment_list = assignment_list->next; } return 0; @@ -148,7 +145,6 @@ static int exec_assignment(struct list *assignment_list, struct hash_map *vars) int exec_ast_command(struct ast_command *command, struct hash_map *vars) { - (void)vars; exec_assignment(command->assignments, vars); set_all_redir(command->redirections); @@ -203,13 +199,6 @@ int exec_ast_command(struct ast_command *command, struct hash_map *vars) return 1; } - -int exec_ast_assignment(struct ast_assignment *assign, struct hash_map *vars) -{ - set_var_copy(vars, assign->name, assign->value); - return 0; -} - int exec_ast_if(struct ast_if *if_node, struct hash_map *vars) { int cond = execution(if_node->condition, vars); diff --git a/src/execution/execution_helpers.h b/src/execution/execution_helpers.h index 9fbe90a..ebf6858 100644 --- a/src/execution/execution_helpers.h +++ b/src/execution/execution_helpers.h @@ -8,7 +8,6 @@ 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_assignment(struct ast_assignment *assign, struct hash_map *vars); void unset_all_redir(struct list *redir_list); #endif // EXECUTION_HELPERS_H From e65c55f5c994779117b5e05dde85a25a0c874d40 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Thu, 29 Jan 2026 11:18:33 +0000 Subject: [PATCH 051/104] feat(vars): unit tests --- tests/unit/utils/vars.c | 85 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 tests/unit/utils/vars.c diff --git a/tests/unit/utils/vars.c b/tests/unit/utils/vars.c new file mode 100644 index 0000000..73784cf --- /dev/null +++ b/tests/unit/utils/vars.c @@ -0,0 +1,85 @@ +#define _POSIX_C_SOURCE 200809L +#include "../../../src/utils/vars/vars.h" + +#include +#include +#include +#include +#include + +#include "../../../src/utils/hash_map/hash_map.h" +#include "../../../src/utils/string_utils/string_utils.h" + +TestSuite(utils_vars); + +Test(utils_vars, init_free) +{ + struct hash_map *map = vars_init(); + cr_expect_not_null(map); + hash_map_free(&map); +} + +Test(utils_vars, get_defaults) +{ + struct hash_map *map = vars_init(); + cr_expect_not_null(map); + cr_assert_str_eq(get_var(map, "?"), "0"); + char int_str[11]; + int_to_str((int)getpid(), int_str); + cr_assert_str_eq(get_var(map, "$"), int_str); + int_to_str((int)getuid(), int_str); + cr_assert_str_eq(get_var(map, "UID"), int_str); + + hash_map_free(&map); +} + +Test(utils_vars, set_vars) +{ + struct hash_map *map = vars_init(); + cr_expect_not_null(map); + set_var_copy(map, "key1", "value1"); + cr_assert_str_eq(get_var(map, "key1"), "value1"); + set_var_copy(map, "key2", "value2"); + cr_assert_str_eq(get_var(map, "key2"), "value2"); + + hash_map_free(&map); +} + +Test(utils_vars, get_env_vars) +{ + struct hash_map *map = vars_init(); + cr_expect_not_null(map); + cr_assert_eq(get_var_or_env(map, "ENV_TEST"), NULL); + setenv("ENV_TEST", "value1", 0); + cr_assert_str_eq(get_var_or_env(map, "ENV_TEST"), "value1"); + setenv("ENV_TEST", "value2", 1); + cr_assert_str_eq(get_var_or_env(map, "ENV_TEST"), "value2"); + + hash_map_free(&map); +} + +Test(utils_vars, set_vars_update) +{ + struct hash_map *map = vars_init(); + cr_expect_not_null(map); + set_var_copy(map, "key", "value1"); + cr_assert_str_eq(get_var(map, "key"), "value1"); + set_var_copy(map, "key", "value2"); + cr_assert_str_eq(get_var(map, "key"), "value2"); + + hash_map_free(&map); +} + +Test(utils_vars, set_vars_int) +{ + struct hash_map *map = vars_init(); + cr_expect_not_null(map); + set_var_int(map, "key1", 100); + cr_assert_str_eq(get_var(map, "key1"), "100"); + set_var_int(map, "key1", 200); + cr_assert_str_eq(get_var(map, "key1"), "200"); + set_var_int(map, "key2", 10); + cr_assert_str_eq(get_var(map, "key2"), "10"); + + hash_map_free(&map); +} From f0b39535fb82aa0779185813c406ce1ea17ae8bb Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Thu, 29 Jan 2026 20:35:56 +0000 Subject: [PATCH 052/104] feat(utils): lists tests --- src/utils/args/args.c | 2 +- src/utils/lists/lists.h | 2 +- src/utils/lists/lists1.c | 5 +- tests/unit/utils/lists.c | 305 +++++++++++++++++++++++++++++++++++++++ tests/unit/utils/vars.c | 1 - 5 files changed, 310 insertions(+), 5 deletions(-) create mode 100644 tests/unit/utils/lists.c diff --git a/src/utils/args/args.c b/src/utils/args/args.c index a957382..c34599f 100644 --- a/src/utils/args/args.c +++ b/src/utils/args/args.c @@ -121,7 +121,7 @@ int args_handler(int argc, char **argv, struct args_options *options, } args_in_var(vars, args_list); - list_destroy(args_list); + list_destroy(&args_list); if (options->type == INPUT_UNDEFINED) options->type = INPUT_STDIN; diff --git a/src/utils/lists/lists.h b/src/utils/lists/lists.h index 9b5d38e..9f8cebb 100644 --- a/src/utils/lists/lists.h +++ b/src/utils/lists/lists.h @@ -31,7 +31,7 @@ void list_print(struct list *list); ** Release the memory used by the list. ** Does nothing if `list` is `NULL`. */ -void list_destroy(struct list *list); +void list_destroy(struct list **list); /* ** Release the memory used by the list and its content diff --git a/src/utils/lists/lists1.c b/src/utils/lists/lists1.c index d191bf7..521e131 100644 --- a/src/utils/lists/lists1.c +++ b/src/utils/lists/lists1.c @@ -49,9 +49,9 @@ void list_print(struct list *list) } } -void list_destroy(struct list *list) +void list_destroy(struct list **list) { - struct list *elt = list; + struct list *elt = *list; struct list *next_elt; while (elt != NULL) { @@ -59,6 +59,7 @@ void list_destroy(struct list *list) free(elt); elt = next_elt; } + *list = NULL; } struct list *list_append(struct list *list, void *value) diff --git a/tests/unit/utils/lists.c b/tests/unit/utils/lists.c new file mode 100644 index 0000000..c852803 --- /dev/null +++ b/tests/unit/utils/lists.c @@ -0,0 +1,305 @@ +#define _POSIX_C_SOURCE 200809L + +#include "../../../src/utils/lists/lists.h" + +#include +#include +#include +#include +#include +#include +#include + +TestSuite(lists); + +Test(lists, append_empty) +{ + struct list *lst = NULL; + lst = list_append(lst, (void *)1); + cr_expect(lst != NULL); + cr_expect(lst->data == (void *)1); + cr_expect(lst->next == NULL); + cr_expect(list_length(lst) == 1); + + list_destroy(&lst); + cr_expect(lst == NULL); +} + +Test(lists, prepend_empty) +{ + struct list *lst = NULL; + lst = list_prepend(lst, (void *)1); + cr_expect(lst != NULL); + cr_expect(lst->data == (void *)1); + cr_expect(lst->next == NULL); + cr_expect(list_length(lst) == 1); + + list_destroy(&lst); + cr_expect(lst == NULL); +} + +Test(lists, insert_empty) +{ + struct list *lst = NULL; + lst = list_insert(lst, (void *)1, 0); + cr_expect(lst != NULL); + cr_expect(lst->data == (void *)1); + cr_expect(lst->next == NULL); + cr_expect(list_length(lst) == 1); + + list_destroy(&lst); + cr_expect(lst == NULL); +} + +Test(lists, insert_out_of_bounds) +{ + struct list *lst = NULL; + lst = list_insert(lst, (void *)1, 5); + cr_expect(lst != NULL); + cr_expect(lst->data == (void *)1); + cr_expect(lst->next == NULL); + cr_expect(list_length(lst) == 1); + + list_destroy(&lst); + cr_expect(lst == NULL); +} + +Test(lists, remove_out_of_bounds) +{ + struct list *lst = NULL; + lst = list_append(lst, (void *)1); + lst = list_remove(lst, 5); + cr_expect(lst != NULL); + cr_expect(lst->data == (void *)1); + cr_expect(lst->next == NULL); + cr_expect(list_length(lst) == 1); + + list_destroy(&lst); + cr_expect(lst == NULL); +} + +Test(lists, append_multiple) +{ + struct list *lst = NULL; + lst = list_append(lst, (void *)1); + lst = list_append(lst, (void *)2); + lst = list_append(lst, (void *)3); + cr_expect(lst != NULL); + cr_expect(lst->data == (void *)1); + cr_expect(lst->next->data == (void *)2); + cr_expect(lst->next->next->data == (void *)3); + cr_expect(lst->next->next->next == NULL); + cr_expect(list_length(lst) == 3); + + list_destroy(&lst); + cr_expect(lst == NULL); +} + +Test(lists, prepend_multiple) +{ + struct list *lst = NULL; + lst = list_prepend(lst, (void *)1); + lst = list_prepend(lst, (void *)2); + lst = list_prepend(lst, (void *)3); + cr_expect(lst != NULL); + cr_expect(lst->data == (void *)3); + cr_expect(lst->next->data == (void *)2); + cr_expect(lst->next->next->data == (void *)1); + cr_expect(lst->next->next->next == NULL); + cr_expect(list_length(lst) == 3); + + list_destroy(&lst); + cr_expect(lst == NULL); +} + +Test(lists, insert_multiple) +{ + struct list *lst = NULL; + lst = list_insert(lst, (void *)1, 0); + lst = list_insert(lst, (void *)3, 1); + lst = list_insert(lst, (void *)2, 1); + cr_expect(lst != NULL); + cr_expect(lst->data == (void *)1); + cr_expect(lst->next->data == (void *)2); + cr_expect(lst->next->next->data == (void *)3); + cr_expect(lst->next->next->next == NULL); + cr_expect(list_length(lst) == 3); + + list_destroy(&lst); + cr_expect(lst == NULL); +} + +Test(lists, append) +{ + struct list *lst = NULL; + lst = list_prepend(lst, (void *)2); + lst = list_prepend(lst, (void *)1); + + lst = list_append(lst, (void *)3); + + cr_expect(lst != NULL); + cr_expect(lst->data == (void *)1); + cr_expect(lst->next->data == (void *)2); + cr_expect(lst->next->next->data == (void *)3); + cr_expect(lst->next->next->next == NULL); + cr_expect(list_length(lst) == 3); + + list_destroy(&lst); + cr_expect(lst == NULL); +} + +Test(lists, prepend) +{ + struct list *lst = NULL; + lst = list_append(lst, (void *)1); + lst = list_append(lst, (void *)2); + + lst = list_prepend(lst, (void *)0); + + cr_expect(lst != NULL); + cr_expect(lst->data == (void *)0); + cr_expect(lst->next->data == (void *)1); + cr_expect(lst->next->next->data == (void *)2); + cr_expect(lst->next->next->next == NULL); + cr_expect(list_length(lst) == 3); + + list_destroy(&lst); + cr_expect(lst == NULL); +} + +Test(lists, insert) +{ + struct list *lst = NULL; + lst = list_append(lst, (void *)1); + lst = list_append(lst, (void *)3); + + lst = list_insert(lst, (void *)2, 1); + lst = list_insert(lst, (void *)0, 0); + + cr_expect(lst != NULL); + cr_expect(lst->data == (void *)0); + cr_expect(lst->next->data == (void *)1); + cr_expect(lst->next->next->data == (void *)2); + cr_expect(lst->next->next->next->data == (void *)3); + cr_expect(lst->next->next->next->next == NULL); + cr_expect(list_length(lst) == 4); + + list_destroy(&lst); + cr_expect(lst == NULL); +} + +Test(lists, remove) +{ + struct list *lst = NULL; + lst = list_append(lst, (void *)1); + lst = list_append(lst, (void *)2); + lst = list_append(lst, (void *)3); + lst = list_append(lst, (void *)4); + + lst = list_remove(lst, 1); // remove 2 + lst = list_remove(lst, 2); // remove 4 + lst = list_remove(lst, 0); // remove 1 + + cr_expect(lst != NULL); + cr_expect(lst->data == (void *)3); + cr_expect(lst->next == NULL); + cr_expect(list_length(lst) == 1); + + list_destroy(&lst); + cr_expect(lst == NULL); +} + +Test(lists, destroy_null) +{ + struct list *lst = NULL; + list_destroy(&lst); + cr_expect(lst == NULL); +} + +Test(lists, deep_destroy_null) +{ + struct list *lst = NULL; + list_destroy(&lst); + cr_expect(lst == NULL); +} + +Test(lists, deep_destroy) +{ + struct list *lst = NULL; + lst = list_append(lst, strdup("string1")); + lst = list_append(lst, strdup("string2")); + lst = list_append(lst, strdup("string3")); + + list_deep_destroy(lst); +} + +Test(lists, length_empty) +{ + struct list *lst = NULL; + cr_expect(list_length(lst) == 0); +} + +Test(lists, print_empty, .init = cr_redirect_stdout) +{ + struct list *lst = NULL; + list_print(lst); + cr_expect_stdout_eq_str(""); +} + +Test(lists, print_non_empty, .init = cr_redirect_stdout) +{ + struct list *lst = NULL; + lst = list_append(lst, (void *)1); + lst = list_append(lst, (void *)2); + lst = list_append(lst, (void *)3); + + list_print(lst); + fflush(stdout); + cr_expect_stdout_eq_str("0x1 0x2 0x3\n"); + + list_destroy(&lst); + cr_expect(lst == NULL); +} + +Test(lists, find_empty) +{ + struct list *lst = NULL; + cr_expect(list_find(lst, (void *)1) == -1); +} + +Test(lists, find_non_empty) +{ + struct list *lst = NULL; + lst = list_append(lst, (void *)1); + lst = list_append(lst, (void *)2); + lst = list_append(lst, (void *)3); + + cr_expect(list_find(lst, (void *)1) == 0); + cr_expect(list_find(lst, (void *)2) == 1); + cr_expect(list_find(lst, (void *)3) == 2); + cr_expect(list_find(lst, (void *)4) == -1); // not found + + list_destroy(&lst); + cr_expect(lst == NULL); +} + +static void fold_func(void *acc, void *data) +{ + *(int *)acc += *(int *)data; +} + +Test(lists, fold) +{ + struct list *lst = NULL; + int v1 = 10, v2 = 20, v3 = 30; + lst = list_append(lst, &v1); + lst = list_append(lst, &v2); + lst = list_append(lst, &v3); + + int sum = 0; + list_fold(lst, &sum, fold_func); + cr_expect(sum == 60); + + list_destroy(&lst); + cr_expect(lst == NULL); +} diff --git a/tests/unit/utils/vars.c b/tests/unit/utils/vars.c index 73784cf..0c6d6ea 100644 --- a/tests/unit/utils/vars.c +++ b/tests/unit/utils/vars.c @@ -3,7 +3,6 @@ #include #include -#include #include #include From 3fa7b97282b70ac96d1755197fcf36477853cb6e Mon Sep 17 00:00:00 2001 From: matteo Date: Fri, 30 Jan 2026 12:21:29 +0100 Subject: [PATCH 053/104] fix: heap-use-after-free and memory leaks on erorr cases --- src/main.c | 7 +++---- src/parser/grammar.c | 14 +++++++++----- src/parser/grammar_basic.c | 7 +++++-- src/utils/hash_map/hash_map.c | 1 + 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/main.c b/src/main.c index c8988ca..6b0f59f 100644 --- a/src/main.c +++ b/src/main.c @@ -66,8 +66,11 @@ static int main_loop(struct lexer_context *ctx, struct args_options *options, if (command_ast == NULL) return err_input(&vars); + // === free + ast_free(&command_ast); parser_close(); + hash_map_free(&vars); return return_code; } @@ -120,9 +123,5 @@ int main(int argc, char **argv) return_code = main_loop(&ctx, &options, vars); - // === free - - hash_map_free(&vars); - return return_code; } diff --git a/src/parser/grammar.c b/src/parser/grammar.c index 57d7d20..73258ea 100644 --- a/src/parser/grammar.c +++ b/src/parser/grammar.c @@ -238,16 +238,20 @@ struct ast *parse_input(struct lexer_context *ctx) } struct ast *ast = parse_list(ctx); - if (ast == NULL) - return NULL; - token = PEEK_TOKEN(); - if (token->type == TOKEN_NEWLINE || token->type == TOKEN_EOF) + + if (ast == NULL) { - if (token->type == TOKEN_NEWLINE) + if (token != NULL && token->type == TOKEN_EOF) { POP_TOKEN(); } + return NULL; + } + + if (token->type == TOKEN_NEWLINE || token->type == TOKEN_EOF) + { + POP_TOKEN(); return ast; } diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index 9b4add2..6a6839b 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -329,12 +329,13 @@ struct ast *parse_if_rule(struct lexer_context *ctx) struct ast *condition_content = parse_compound_list(ctx); // Then keyword - token = POP_TOKEN(); + token = PEEK_TOKEN(); if (token->type != TOKEN_THEN) { perror("Expected the 'then' keyword but token has different type"); return err_if_rule(&condition_content, NULL, NULL); } + POP_TOKEN(); // Then content struct ast *then_content = parse_compound_list(ctx); @@ -344,6 +345,7 @@ struct ast *parse_if_rule(struct lexer_context *ctx) } struct ast *else_content = NULL; + token = PEEK_TOKEN(); // Eventual else/elif clause(s) if (is_first(*token, RULE_ELSE_CLAUSE)) { @@ -355,12 +357,13 @@ struct ast *parse_if_rule(struct lexer_context *ctx) } // Fi keyword - token = POP_TOKEN(); + token = PEEK_TOKEN(); if (token->type != TOKEN_FI) { perror("Expected the 'fi' keyword but token has different type"); return err_if_rule(&condition_content, &then_content, &else_content); } + POP_TOKEN(); // Result struct ast *result = diff --git a/src/utils/hash_map/hash_map.c b/src/utils/hash_map/hash_map.c index 3d77734..6f9a513 100644 --- a/src/utils/hash_map/hash_map.c +++ b/src/utils/hash_map/hash_map.c @@ -117,6 +117,7 @@ void hash_map_free(struct hash_map **hash_map) free((*hash_map)->data); free(*hash_map); } + *hash_map = NULL; } void hash_map_foreach(struct hash_map *hash_map, From f8b91d4da3eab1a76192b16f0dce37fe32b044f9 Mon Sep 17 00:00:00 2001 From: matteo Date: Fri, 30 Jan 2026 16:51:10 +0100 Subject: [PATCH 054/104] fix: memory issues, parser errors and get_ast_if returning always NULL --- src/execution/execution_helpers.c | 2 ++ src/parser/grammar.c | 1 + src/parser/grammar_basic.c | 2 +- src/utils/ast/ast_if.c | 2 +- 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/execution/execution_helpers.c b/src/execution/execution_helpers.c index 9e44dfa..60ec0c3 100644 --- a/src/execution/execution_helpers.c +++ b/src/execution/execution_helpers.c @@ -205,6 +205,8 @@ 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) { + if (if_node == NULL) + return 2; int cond = execution(if_node->condition, vars); if (cond == 0) return execution(if_node->then_clause, vars); diff --git a/src/parser/grammar.c b/src/parser/grammar.c index 73258ea..b89a99e 100644 --- a/src/parser/grammar.c +++ b/src/parser/grammar.c @@ -140,6 +140,7 @@ bool grammar_init(void) // Element add_first(RULE_ELEMENT, TOKEN_WORD); + add_first(RULE_ELEMENT, TOKEN_ASSIGNMENT_WORD); add_firsts(RULE_ELEMENT, first(RULE_REDIRECTION)); // Prefix diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index 6a6839b..86ed089 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -281,7 +281,7 @@ struct ast *parse_simple_command(struct lexer_context *ctx) struct ast *parse_element(struct lexer_context *ctx) { struct token *token = PEEK_TOKEN(); - if (token->type == TOKEN_WORD) + if (token->type == TOKEN_WORD || token->type == TOKEN_ASSIGNMENT_WORD) { token = POP_TOKEN(); return ast_create_word(token->data); diff --git a/src/utils/ast/ast_if.c b/src/utils/ast/ast_if.c index 1402ff6..6b0ff5d 100644 --- a/src/utils/ast/ast_if.c +++ b/src/utils/ast/ast_if.c @@ -19,7 +19,7 @@ struct ast *ast_create_if(struct ast *condition, struct ast *then_clause, struct ast_if *ast_get_if(struct ast *node) { - if (node == NULL || node->type == AST_IF) + if (node == NULL || node->type != AST_IF) return NULL; return node->data; } From 5740195cb3ce8bbfd2b0b48828888945de85b725 Mon Sep 17 00:00:00 2001 From: matteo Date: Fri, 30 Jan 2026 12:21:29 +0100 Subject: [PATCH 055/104] fix: heap-use-after-free and memory leaks on erorr cases --- src/main.c | 7 +++---- src/parser/grammar.c | 14 +++++++++----- src/parser/grammar_basic.c | 7 +++++-- src/utils/hash_map/hash_map.c | 1 + 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/main.c b/src/main.c index c8988ca..6b0f59f 100644 --- a/src/main.c +++ b/src/main.c @@ -66,8 +66,11 @@ static int main_loop(struct lexer_context *ctx, struct args_options *options, if (command_ast == NULL) return err_input(&vars); + // === free + ast_free(&command_ast); parser_close(); + hash_map_free(&vars); return return_code; } @@ -120,9 +123,5 @@ int main(int argc, char **argv) return_code = main_loop(&ctx, &options, vars); - // === free - - hash_map_free(&vars); - return return_code; } diff --git a/src/parser/grammar.c b/src/parser/grammar.c index 57d7d20..73258ea 100644 --- a/src/parser/grammar.c +++ b/src/parser/grammar.c @@ -238,16 +238,20 @@ struct ast *parse_input(struct lexer_context *ctx) } struct ast *ast = parse_list(ctx); - if (ast == NULL) - return NULL; - token = PEEK_TOKEN(); - if (token->type == TOKEN_NEWLINE || token->type == TOKEN_EOF) + + if (ast == NULL) { - if (token->type == TOKEN_NEWLINE) + if (token != NULL && token->type == TOKEN_EOF) { POP_TOKEN(); } + return NULL; + } + + if (token->type == TOKEN_NEWLINE || token->type == TOKEN_EOF) + { + POP_TOKEN(); return ast; } diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index 9b4add2..6a6839b 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -329,12 +329,13 @@ struct ast *parse_if_rule(struct lexer_context *ctx) struct ast *condition_content = parse_compound_list(ctx); // Then keyword - token = POP_TOKEN(); + token = PEEK_TOKEN(); if (token->type != TOKEN_THEN) { perror("Expected the 'then' keyword but token has different type"); return err_if_rule(&condition_content, NULL, NULL); } + POP_TOKEN(); // Then content struct ast *then_content = parse_compound_list(ctx); @@ -344,6 +345,7 @@ struct ast *parse_if_rule(struct lexer_context *ctx) } struct ast *else_content = NULL; + token = PEEK_TOKEN(); // Eventual else/elif clause(s) if (is_first(*token, RULE_ELSE_CLAUSE)) { @@ -355,12 +357,13 @@ struct ast *parse_if_rule(struct lexer_context *ctx) } // Fi keyword - token = POP_TOKEN(); + token = PEEK_TOKEN(); if (token->type != TOKEN_FI) { perror("Expected the 'fi' keyword but token has different type"); return err_if_rule(&condition_content, &then_content, &else_content); } + POP_TOKEN(); // Result struct ast *result = diff --git a/src/utils/hash_map/hash_map.c b/src/utils/hash_map/hash_map.c index 3d77734..6f9a513 100644 --- a/src/utils/hash_map/hash_map.c +++ b/src/utils/hash_map/hash_map.c @@ -117,6 +117,7 @@ void hash_map_free(struct hash_map **hash_map) free((*hash_map)->data); free(*hash_map); } + *hash_map = NULL; } void hash_map_foreach(struct hash_map *hash_map, From 52d35cf3552201ec3d6c62668ef62527b06589d6 Mon Sep 17 00:00:00 2001 From: matteo Date: Fri, 30 Jan 2026 16:51:10 +0100 Subject: [PATCH 056/104] fix: memory issues, parser errors and get_ast_if returning always NULL --- src/execution/execution_helpers.c | 2 ++ src/parser/grammar.c | 1 + src/parser/grammar_basic.c | 2 +- src/utils/ast/ast_if.c | 2 +- 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/execution/execution_helpers.c b/src/execution/execution_helpers.c index 3166578..9a72c10 100644 --- a/src/execution/execution_helpers.c +++ b/src/execution/execution_helpers.c @@ -201,6 +201,8 @@ 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) { + if (if_node == NULL) + return 2; int cond = execution(if_node->condition, vars); if (cond == 0) return execution(if_node->then_clause, vars); diff --git a/src/parser/grammar.c b/src/parser/grammar.c index 73258ea..b89a99e 100644 --- a/src/parser/grammar.c +++ b/src/parser/grammar.c @@ -140,6 +140,7 @@ bool grammar_init(void) // Element add_first(RULE_ELEMENT, TOKEN_WORD); + add_first(RULE_ELEMENT, TOKEN_ASSIGNMENT_WORD); add_firsts(RULE_ELEMENT, first(RULE_REDIRECTION)); // Prefix diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index 6a6839b..86ed089 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -281,7 +281,7 @@ struct ast *parse_simple_command(struct lexer_context *ctx) struct ast *parse_element(struct lexer_context *ctx) { struct token *token = PEEK_TOKEN(); - if (token->type == TOKEN_WORD) + if (token->type == TOKEN_WORD || token->type == TOKEN_ASSIGNMENT_WORD) { token = POP_TOKEN(); return ast_create_word(token->data); diff --git a/src/utils/ast/ast_if.c b/src/utils/ast/ast_if.c index 1402ff6..6b0ff5d 100644 --- a/src/utils/ast/ast_if.c +++ b/src/utils/ast/ast_if.c @@ -19,7 +19,7 @@ struct ast *ast_create_if(struct ast *condition, struct ast *then_clause, struct ast_if *ast_get_if(struct ast *node) { - if (node == NULL || node->type == AST_IF) + if (node == NULL || node->type != AST_IF) return NULL; return node->data; } From 30e30f55e7ab47064e44a0284a993da9f76f8878 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Fri, 30 Jan 2026 17:43:18 +0100 Subject: [PATCH 057/104] fix(lexer): ongoing-> recongize OR as PIPE --- src/lexer/lexer_utils.c | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/lexer/lexer_utils.c b/src/lexer/lexer_utils.c index 3029394..d9a84dc 100644 --- a/src/lexer/lexer_utils.c +++ b/src/lexer/lexer_utils.c @@ -72,10 +72,6 @@ static void set_token_keyword(struct token *tok, char *begin, ssize_t size) tok->type = TOKEN_ELSE; else if (strncmp(begin, "elif", size) == 0 && size == 4) tok->type = TOKEN_ELIF; - else if (strncmp(begin, "&&", size) == 0 && size == 2) - tok->type = TOKEN_AND; - else if (strncmp(begin, "||", size) == 0 && size == 2) - tok->type = TOKEN_OR; // no keywords found. if (tok->type == TOKEN_NULL) @@ -97,7 +93,12 @@ static void set_token_operator(struct token *tok, char *begin, ssize_t size) { if (tok->type != TOKEN_NULL) return; - if (strncmp(begin, ">", size) == 0) + if (strncmp(begin, "&&", size) == 0 && size == 2) + tok->type = TOKEN_AND; + else if (strncmp(begin, "||", size) == 0 && size == 2) + tok->type = TOKEN_OR; + + else if (strncmp(begin, ">", size) == 0) { tok->type = TOKEN_REDIR_RIGHT; } @@ -231,9 +232,9 @@ struct token *new_token(char *begin, ssize_t size, struct token_info *info) set_token_assignment(tok, begin, size); else { + set_token_keyword(tok, begin, size); set_token_operator(tok, begin, size); set_token_spechar(tok, begin, size); - set_token_keyword(tok, begin, size); set_token_word(tok, begin, size); } @@ -286,10 +287,19 @@ ssize_t len_op_sepchar(char *stream, ssize_t i) if (!is_special_char(stream, i)) return -1; // should never happen - if (stream[i] != '>' && stream[i] != '<') - return 1; // special character (cannot be operator) + // OR + if (stream[i] == '|' && stream[i + 1] == '|') + return 2; - // operator + // AND + if (stream[i] == '|' && stream[i + 1] == '|') + return 2; + + // special chars + if (stream[i] != '>' && stream[i] != '<') + return 1; + + // REDIRS if (stream[i] == '<') { From fd59d63c4705cd52169508eca2ac16c8572b0550 Mon Sep 17 00:00:00 2001 From: matteo Date: Fri, 30 Jan 2026 17:47:51 +0100 Subject: [PATCH 058/104] fix(lexer): recongize OR as PIPE --- src/lexer/lexer_utils.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/lexer/lexer_utils.c b/src/lexer/lexer_utils.c index d9a84dc..dee3f88 100644 --- a/src/lexer/lexer_utils.c +++ b/src/lexer/lexer_utils.c @@ -94,39 +94,43 @@ static void set_token_operator(struct token *tok, char *begin, ssize_t size) if (tok->type != TOKEN_NULL) return; if (strncmp(begin, "&&", size) == 0 && size == 2) + { tok->type = TOKEN_AND; + } else if (strncmp(begin, "||", size) == 0 && size == 2) + { tok->type = TOKEN_OR; + } - else if (strncmp(begin, ">", size) == 0) + else if (strncmp(begin, ">", size) == 0 && size == 1) { tok->type = TOKEN_REDIR_RIGHT; } - else if (strncmp(begin, "<", size) == 0) + else if (strncmp(begin, "<", size) == 0 && size == 1) { tok->type = TOKEN_REDIR_LEFT; } - else if (strncmp(begin, ">>", size) == 0) + else if (strncmp(begin, ">>", size) == 0 && size == 2) { tok->type = TOKEN_REDIR_DOUBLE_RIGHT; } - else if (strncmp(begin, ">&", size) == 0) + else if (strncmp(begin, ">&", size) == 0 && size == 2) { tok->type = TOKEN_REDIR_RIGHT_AMP; } - else if (strncmp(begin, ">|", size) == 0) + else if (strncmp(begin, ">|", size) == 0 && size == 2) { tok->type = TOKEN_REDIR_RIGHT_PIPE; } - else if (strncmp(begin, "<&", size) == 0) + else if (strncmp(begin, "<&", size) == 0 && size == 2) { tok->type = TOKEN_REDIR_LEFT_AMP; } - else if (strncmp(begin, "<>", size) == 0) + else if (strncmp(begin, "<>", size) == 0 && size == 2) { tok->type = TOKEN_REDIR_LEFT_RIGHT; } - else if (strncmp(begin, "|", size) == 0) + else if (strncmp(begin, "|", size) == 0 && size == 1) { tok->type = TOKEN_PIPE; } From fcfc7fedc97b9995e82fe56bc293832284e277ae Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Fri, 30 Jan 2026 18:26:56 +0100 Subject: [PATCH 059/104] fix(clang-tidy): for clang, function cannot return bool if it takes no arguments ???? --- src/parser/grammar.c | 2 +- src/parser/grammar.h | 2 +- src/parser/parser.c | 2 +- src/parser/parser.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/parser/grammar.c b/src/parser/grammar.c index b89a99e..6089c18 100644 --- a/src/parser/grammar.c +++ b/src/parser/grammar.c @@ -91,7 +91,7 @@ static bool init_firsts_map(void) // === Functions -bool grammar_init(void) +int grammar_init(void) { // Initialize the firsts map bool success = init_firsts_map(); diff --git a/src/parser/grammar.h b/src/parser/grammar.h index 663d37b..020d733 100644 --- a/src/parser/grammar.h +++ b/src/parser/grammar.h @@ -64,7 +64,7 @@ struct firsts_list * @return PARSER_INIT_SUCCESS on success PARSER_INIT_ERROR on error * @warning Do not use outside the parser */ -bool grammar_init(void); +int grammar_init(void); /* * @brief Closes the grammar submodule diff --git a/src/parser/parser.c b/src/parser/parser.c index c2b8aaf..ec60b71 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -10,7 +10,7 @@ static enum parser_state state = PARSER_STATE_NOT_INITIALIZED; // === Functions -bool parser_init(void) +int parser_init(void) { if (state == PARSER_STATE_READY) { diff --git a/src/parser/parser.h b/src/parser/parser.h index a79497b..dc01a5e 100644 --- a/src/parser/parser.h +++ b/src/parser/parser.h @@ -18,7 +18,7 @@ enum parser_state * * @return Returns false on error and true on success */ -bool parser_init(void); +int parser_init(void); /* @brief Closes the parser module after use */ From bada9c6a298223499707d8ea9445a8fac591e139 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Fri, 30 Jan 2026 18:35:26 +0100 Subject: [PATCH 060/104] fix: building now --- src/parser/parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parser/parser.c b/src/parser/parser.c index ec60b71..d1f0ebc 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -15,7 +15,7 @@ int parser_init(void) if (state == PARSER_STATE_READY) { perror("Internal error: tried to initialize the parser module twice."); - return NULL; + return false; } int success = grammar_init(); if (success == false) From 115377edfe5bc36758ea7883846c7e5e0764ca64 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Fri, 30 Jan 2026 19:13:27 +0100 Subject: [PATCH 061/104] feat(lexer + parser): export handling --- src/lexer/lexer_utils.c | 2 + src/lexer/lexer_utils.h | 3 +- src/parser/grammar_basic.c | 125 ++++++++++++++++++++++--------------- 3 files changed, 80 insertions(+), 50 deletions(-) diff --git a/src/lexer/lexer_utils.c b/src/lexer/lexer_utils.c index dee3f88..c2265e8 100644 --- a/src/lexer/lexer_utils.c +++ b/src/lexer/lexer_utils.c @@ -72,6 +72,8 @@ static void set_token_keyword(struct token *tok, char *begin, ssize_t size) tok->type = TOKEN_ELSE; else if (strncmp(begin, "elif", size) == 0 && size == 4) tok->type = TOKEN_ELIF; + else if (strncmp(begin, "export", size) == 0 && size == 6) + tok->type = TOKEN_ELIF; // no keywords found. if (tok->type == TOKEN_NULL) diff --git a/src/lexer/lexer_utils.h b/src/lexer/lexer_utils.h index af64455..6f6ade2 100644 --- a/src/lexer/lexer_utils.h +++ b/src/lexer/lexer_utils.h @@ -72,7 +72,8 @@ enum token_type TOKEN_FOR, TOKEN_WHILE, TOKEN_UNTIL, - TOKEN_CASE + TOKEN_CASE, + TOKEN_EXPORT }; struct token diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index 86ed089..c3a4ea0 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -174,14 +174,33 @@ struct ast *parse_command(struct lexer_context *ctx) /* @brief: frees command_elements and redirections lists (helper func) * @return: NULL */ -static void *err_simple_command(struct list *command_elements, - struct list *redirections) +static void *err_s_com(struct list *command_elements, struct list *redirections, + struct list *assignments); { list_deep_destroy(command_elements); list_deep_destroy(redirections); + list_deep_destroy(assignments); return NULL; } +static ast *parse_export(struct lexer_context *ctx) +{ + struct token *token = PEEK_TOKEN(); + if (token->type != TOKEN_EXPORT) + { + fprintf(stderr, "expected the export keyword in parse_export"); + return NULL; + } + POP_TOKEN(); + token = PEEK_TOKEN(); + if (token->type != TOKEN_ASSIGNMENT_WORD) + { + fprintf(stderr, "in parser: export must be followed by 'x=y'"); + return NULL; + } + return ast_create_assignment(token->data, true); +} + struct ast *parse_simple_command(struct lexer_context *ctx) { struct list *command_elements = NULL; @@ -198,7 +217,7 @@ struct ast *parse_simple_command(struct lexer_context *ctx) struct ast *prefix = parse_prefix(ctx); if (prefix == NULL) { - return err_simple_command(command_elements, redirections); + return err_s_com(command_elements, redirections); } if (prefix->type == AST_ASSIGNMENT) { @@ -214,66 +233,70 @@ struct ast *parse_simple_command(struct lexer_context *ctx) if (token->type != TOKEN_WORD) { - if (!has_prefix) + if (!has_prefix && token->type != TOKEN_EXPORT) { perror("Expected a command but got a different token type"); - return err_simple_command(command_elements, redirections); + return err_s_com(command_elements, redirections, assignments); + } + if (token->type == TOKEN_EXPORT) + { + struct ast *assignment_export = parse_export(ctx); + if (assignment == NULL) + return err_s_com(command_elements, redirections, assignments); + + assignments = list_append(assignments, assignment_export); } - // else : only prefixes } - else + else // TOKEN WORD { - if (token->type == TOKEN_WORD) + char *command = strdup(token->data); + command_elements = list_append(command_elements, command); + + POP_TOKEN(); + token = PEEK_TOKEN(); + } + // Eventual elements + while (is_first(*token, RULE_ELEMENT)) + { + // Get element + struct ast *element = parse_element(ctx); + if (element == NULL) { - char *command = strdup(token->data); - command_elements = list_append(command_elements, command); - - POP_TOKEN(); - token = PEEK_TOKEN(); + return err_s_com(command_elements, redirections, assignments); } - // Eventual elements - while (is_first(*token, RULE_ELEMENT)) + + // Get element type + if (ast_is_word(element)) { - // Get element - struct ast *element = parse_element(ctx); - if (element == NULL) - { - return err_simple_command(command_elements, redirections); - } + struct ast_word *element_word = ast_get_word(element); - // Get element type - if (ast_is_word(element)) - { - struct ast_word *element_word = ast_get_word(element); - - // TODO test this fix for the memory leaks - char *word = strdup(element_word->word); - ast_free(&element); - command_elements = list_append(command_elements, word); - // end of fix - } - else if (ast_is_redir(element)) - { - // append redirections to the list of redirections - redirections = list_append(redirections, element); - } - else - { - perror("Internal error: unexpected return value from " - "parse_element in parse_simple_command"); - return err_simple_command(command_elements, redirections); - } - - // Forward - token = PEEK_TOKEN(); + // TODO test this fix for the memory leaks + char *word = strdup(element_word->word); + ast_free(&element); + command_elements = list_append(command_elements, word); + // end of fix } + else if (ast_is_redir(element)) + { + // append redirections to the list of redirections + redirections = list_append(redirections, element); + } + else + { + perror("Internal error: unexpected return value from " + "parse_element in parse_simple_command"); + return err_s_com(command_elements, redirections, assignments); + } + + // Forward + token = PEEK_TOKEN(); } struct ast *result = ast_create_command(command_elements, redirections, assignments); if (result == NULL) { - return err_simple_command(command_elements, redirections); + return err_s_com(command_elements, redirections, assignments); } return result; } @@ -290,6 +313,10 @@ struct ast *parse_element(struct lexer_context *ctx) { return parse_redirection(ctx); } + else if (token->type == TOKEN_EXPORT) + { + return parse_export(ctx); + } else { perror("Syntax error: unexpected token at parse_element"); @@ -455,8 +482,8 @@ struct ast *parse_else_clause(struct lexer_context *ctx) token = POP_TOKEN(); if (token->type != TOKEN_THEN) { - perror( - "Expected the 'then' keyword but got a different token type"); + perror("Expected the 'then' keyword but got a different token " + "type"); return NULL; } From 1367598047a3c66ab3243d92c545ed7ece25bb76 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Fri, 30 Jan 2026 19:14:01 +0100 Subject: [PATCH 062/104] fix(lexer): typo on export --- src/lexer/lexer_utils.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lexer/lexer_utils.c b/src/lexer/lexer_utils.c index c2265e8..02f0a3f 100644 --- a/src/lexer/lexer_utils.c +++ b/src/lexer/lexer_utils.c @@ -73,7 +73,7 @@ static void set_token_keyword(struct token *tok, char *begin, ssize_t size) else if (strncmp(begin, "elif", size) == 0 && size == 4) tok->type = TOKEN_ELIF; else if (strncmp(begin, "export", size) == 0 && size == 6) - tok->type = TOKEN_ELIF; + tok->type = TOKEN_EXPORT; // no keywords found. if (tok->type == TOKEN_NULL) From aa45e3d30f484aca1b1da991eab88421874dffc0 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Fri, 30 Jan 2026 19:37:05 +0100 Subject: [PATCH 063/104] feat: export handling, except exec --- src/parser/grammar.c | 23 +++++++++++++++-------- src/parser/grammar_advanced.c | 2 +- src/parser/grammar_basic.c | 20 +++++++++++++++----- src/utils/ast/ast_assignment.c | 3 ++- src/utils/ast/ast_assignment.h | 3 ++- 5 files changed, 35 insertions(+), 16 deletions(-) diff --git a/src/parser/grammar.c b/src/parser/grammar.c index 6089c18..0680a79 100644 --- a/src/parser/grammar.c +++ b/src/parser/grammar.c @@ -89,6 +89,18 @@ static bool init_firsts_map(void) return true; } +static void add_first_redir(void) +{ + add_first(RULE_REDIRECTION, TOKEN_IONUMBER); + add_first(RULE_REDIRECTION, TOKEN_REDIR_LEFT); + add_first(RULE_REDIRECTION, TOKEN_REDIR_RIGHT); + add_first(RULE_REDIRECTION, TOKEN_REDIR_LEFT_RIGHT); + add_first(RULE_REDIRECTION, TOKEN_REDIR_DOUBLE_RIGHT); + add_first(RULE_REDIRECTION, TOKEN_REDIR_LEFT_AMP); + add_first(RULE_REDIRECTION, TOKEN_REDIR_RIGHT_AMP); + add_first(RULE_REDIRECTION, TOKEN_REDIR_RIGHT_PIPE); +} + // === Functions int grammar_init(void) @@ -128,15 +140,9 @@ int grammar_init(void) add_firsts(RULE_CASE_CLAUSE, first(RULE_CASE_ITEM)); // Redirection - add_first(RULE_REDIRECTION, TOKEN_IONUMBER); - add_first(RULE_REDIRECTION, TOKEN_REDIR_LEFT); - add_first(RULE_REDIRECTION, TOKEN_REDIR_RIGHT); - add_first(RULE_REDIRECTION, TOKEN_REDIR_LEFT_RIGHT); - add_first(RULE_REDIRECTION, TOKEN_REDIR_DOUBLE_RIGHT); - add_first(RULE_REDIRECTION, TOKEN_REDIR_LEFT_AMP); - add_first(RULE_REDIRECTION, TOKEN_REDIR_RIGHT_AMP); - add_first(RULE_REDIRECTION, TOKEN_REDIR_RIGHT_PIPE); + add_first_redir(); // %RIP Matteo 30/01/2026 + // %RAX Guillem 30/01/2026 hehe // Element add_first(RULE_ELEMENT, TOKEN_WORD); @@ -153,6 +159,7 @@ int grammar_init(void) // Simple command add_firsts(RULE_SIMPLE_COMMAND, first(RULE_PREFIX)); add_first(RULE_SIMPLE_COMMAND, TOKEN_WORD); + add_first(RULE_SIMPLE_COMMAND, TOKEN_EXPORT); // Funcdec add_first(RULE_FUNCDEC, TOKEN_WORD); diff --git a/src/parser/grammar_advanced.c b/src/parser/grammar_advanced.c index d57e70e..4dd8f77 100644 --- a/src/parser/grammar_advanced.c +++ b/src/parser/grammar_advanced.c @@ -71,7 +71,7 @@ struct ast *parse_prefix(struct lexer_context *ctx) if (token->type == TOKEN_ASSIGNMENT_WORD) { token = POP_TOKEN(); - return ast_create_assignment(token->data); + return ast_create_assignment(token->data, false); } else if (is_first(*token, RULE_REDIRECTION)) return parse_redirection(ctx); diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index c3a4ea0..d2a2343 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -175,7 +175,7 @@ struct ast *parse_command(struct lexer_context *ctx) * @return: NULL */ static void *err_s_com(struct list *command_elements, struct list *redirections, - struct list *assignments); + struct list *assignments) { list_deep_destroy(command_elements); list_deep_destroy(redirections); @@ -183,7 +183,10 @@ static void *err_s_com(struct list *command_elements, struct list *redirections, return NULL; } -static ast *parse_export(struct lexer_context *ctx) +/* @brief: used when export keyword is found, and expects an assignment after. + * @return: an ast_assignment with the field [global] set to true. + */ +static struct ast *parse_export(struct lexer_context *ctx) { struct token *token = PEEK_TOKEN(); if (token->type != TOKEN_EXPORT) @@ -191,13 +194,20 @@ static ast *parse_export(struct lexer_context *ctx) fprintf(stderr, "expected the export keyword in parse_export"); return NULL; } + // export POP_TOKEN(); + token = PEEK_TOKEN(); + if (token->type != TOKEN_ASSIGNMENT_WORD) { fprintf(stderr, "in parser: export must be followed by 'x=y'"); return NULL; } + + // assignment + POP_TOKEN(); + return ast_create_assignment(token->data, true); } @@ -217,7 +227,7 @@ struct ast *parse_simple_command(struct lexer_context *ctx) struct ast *prefix = parse_prefix(ctx); if (prefix == NULL) { - return err_s_com(command_elements, redirections); + return err_s_com(command_elements, redirections, assignments); } if (prefix->type == AST_ASSIGNMENT) { @@ -241,7 +251,7 @@ struct ast *parse_simple_command(struct lexer_context *ctx) if (token->type == TOKEN_EXPORT) { struct ast *assignment_export = parse_export(ctx); - if (assignment == NULL) + if (assignment_export == NULL) return err_s_com(command_elements, redirections, assignments); assignments = list_append(assignments, assignment_export); @@ -253,8 +263,8 @@ struct ast *parse_simple_command(struct lexer_context *ctx) command_elements = list_append(command_elements, command); POP_TOKEN(); - token = PEEK_TOKEN(); } + token = PEEK_TOKEN(); // Eventual elements while (is_first(*token, RULE_ELEMENT)) { diff --git a/src/utils/ast/ast_assignment.c b/src/utils/ast/ast_assignment.c index e859912..72cfdb6 100644 --- a/src/utils/ast/ast_assignment.c +++ b/src/utils/ast/ast_assignment.c @@ -34,7 +34,7 @@ static void init_assignments(struct ast_assignment *ast_assignment, ast_assignment->value = strdup(split_pos + 1); } -struct ast *ast_create_assignment(char *assignment) +struct ast *ast_create_assignment(char *assignment, bool global) { struct ast_assignment *assignment_data = calloc(1, sizeof(struct ast_assignment)); @@ -42,6 +42,7 @@ struct ast *ast_create_assignment(char *assignment) return NULL; init_assignments(assignment_data, assignment); + assignment_data->global = global; return ast_create(AST_ASSIGNMENT, assignment_data); } diff --git a/src/utils/ast/ast_assignment.h b/src/utils/ast/ast_assignment.h index 5be56c8..3ebac14 100644 --- a/src/utils/ast/ast_assignment.h +++ b/src/utils/ast/ast_assignment.h @@ -7,11 +7,12 @@ struct ast_assignment { char *name; char *value; + 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); +struct ast *ast_create_assignment(char *assignment, bool global); void ast_free_assignment(struct ast_assignment *assignment_data); #endif /* ! AST_ASSIGNMENT_H */ From 423793903dadf71f11c9e8deec323967fba5fd88 Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Fri, 30 Jan 2026 19:48:31 +0100 Subject: [PATCH 064/104] feat: while and for loops support for parser, plus new ASTs, new tokens and fixes inside parser --- src/lexer/lexer_utils.h | 2 + src/parser/grammar_advanced.c | 142 ++++++++++++++++++++++++++++++++++ src/parser/grammar_advanced.h | 42 ++++++++++ src/parser/grammar_basic.c | 111 ++++++++++++++++++-------- src/parser/grammar_basic.h | 6 +- src/utils/ast/ast.h | 1 + src/utils/ast/ast_base.h | 1 + src/utils/ast/ast_loop.c | 37 +++++++++ src/utils/ast/ast_loop.h | 34 ++++++++ 9 files changed, 341 insertions(+), 35 deletions(-) create mode 100644 src/utils/ast/ast_loop.c create mode 100644 src/utils/ast/ast_loop.h diff --git a/src/lexer/lexer_utils.h b/src/lexer/lexer_utils.h index af64455..4a09968 100644 --- a/src/lexer/lexer_utils.h +++ b/src/lexer/lexer_utils.h @@ -72,6 +72,8 @@ enum token_type TOKEN_FOR, TOKEN_WHILE, TOKEN_UNTIL, + TOKEN_DO, + TOKEN_DONE, TOKEN_CASE }; diff --git a/src/parser/grammar_advanced.c b/src/parser/grammar_advanced.c index d57e70e..c1e9d20 100644 --- a/src/parser/grammar_advanced.c +++ b/src/parser/grammar_advanced.c @@ -81,3 +81,145 @@ struct ast *parse_prefix(struct lexer_context *ctx) return NULL; } } + +// 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 ast *parse_for(struct lexer_context *ctx) +{ + (void)ctx; + perror("Error: usage of a not implemented function (parse_for)"); + return NULL; +} + +struct ast *parse_while(struct lexer_context *ctx) +{ + struct token *token = PEEK_TOKEN(); + + // 'while' + if (token->type != TOKEN_WHILE) + { + perror( + "Internal error: expected a TOKEN_WHILE but got a different type"); + return NULL; + } + POP_TOKEN(); + + // condition + struct ast *condition = parse_compound_list(ctx); + if (condition == NULL) + return NULL; + token = PEEK_TOKEN(); + + // 'do' + if (token->type != TOKEN_DO) + { + ast_free(&condition); + perror("Syntax error: expected the 'do' keyowrd but got a different " + "token"); + return NULL; + } + POP_TOKEN(); + token = PEEK_TOKEN(); + + // body + struct ast *body = parse_compound_list(ctx); + if (body == NULL) + { + ast_free(&condition); + return NULL; + } + token = PEEK_TOKEN(); + + // 'done' + if (token->type != TOKEN_DONE) + { + ast_free(&condition); + perror("Syntax error: expected the 'done' keyowrd but got a different " + "token"); + return NULL; + } + POP_TOKEN(); + + struct ast *result = ast_create_loop(condition, body); + if (result == NULL) + { + ast_free(&condition); + ast_free(&body); + perror("Internal error: could not create ast node (is your memory full " + "?)"); + return NULL; + } + + return result; +} + +struct ast *parse_until(struct lexer_context *ctx) +{ + struct token *token = PEEK_TOKEN(); + + // 'while' + if (token->type != TOKEN_UNTIL) + { + perror( + "Internal error: expected a TOKEN_WHILE but got a different type"); + return NULL; + } + POP_TOKEN(); + + // condition + struct ast *condition = parse_compound_list(ctx); + if (condition == NULL) + return NULL; + condition = + ast_create_neg(true, condition); // TODO check result (beware to not + // exceed function lines limit) + token = PEEK_TOKEN(); + + // 'do' + if (token->type != TOKEN_DO) + { + ast_free(&condition); + perror("Syntax error: expected the 'do' keyowrd but got a different " + "token"); + return NULL; + } + POP_TOKEN(); + token = PEEK_TOKEN(); + + // body + struct ast *body = parse_compound_list(ctx); + if (body == NULL) + { + ast_free(&condition); + return NULL; + } + token = PEEK_TOKEN(); + + // 'done' + if (token->type != TOKEN_DONE) + { + ast_free(&condition); + perror("Syntax error: expected the 'done' keyowrd but got a different " + "token"); + return NULL; + } + POP_TOKEN(); + + struct ast *result = ast_create_loop(condition, body); + if (result == NULL) + { + ast_free(&condition); + ast_free(&body); + perror("Internal error: could not create ast node (is your memory full " + "?)"); + return NULL; + } + + return result; +} diff --git a/src/parser/grammar_advanced.h b/src/parser/grammar_advanced.h index f3c27ae..2b9cc63 100644 --- a/src/parser/grammar_advanced.h +++ b/src/parser/grammar_advanced.h @@ -24,4 +24,46 @@ struct ast *parse_redirection(struct lexer_context *ctx); */ struct ast *parse_prefix(struct lexer_context *ctx); +/* + * @brief parses a funcdec rule + * @warning NOT IMPLEMENTED + * + * @code funcdec = WORD '(' ')' {'\n'} shell_command ; + * + * @first WORD + */ +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' ) ] ) + * {'\n'} 'do' compound_list 'done' ; + * + * @first TOKEN_FOR + */ +struct ast *parse_for(struct lexer_context *ctx); + +/* + * @brief parses a while rule + * @warning NOT IMPLEMENTED + * + * @code rule_while = 'while' compound_list 'do' compound_list 'done' ; + * + * @first TOKEN_WHILE + */ +struct ast *parse_while(struct lexer_context *ctx); + +/* + * @brief parses an until rule + * @warning NOT IMPLEMENTED + * + * @code rule_until = 'until' compound_list 'do' compound_list 'done' ; + * + * @first TOKEN_UNTIL + */ +struct ast *parse_until(struct lexer_context *ctx); + #endif /* ! GRAMMAR_ADVANCED_H */ diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index 86ed089..c5a5ee3 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -26,6 +26,29 @@ static enum ast_and_or_type and_or_tok_to_ast(enum token_type tok_type) } } +/* @brief: frees command_elements and redirections lists (helper func) + * @return: NULL + */ +static void *err_simple_command(struct list *command_elements, + struct list *redirections) +{ + list_deep_destroy(command_elements); + list_deep_destroy(redirections); + return NULL; +} + +/* @brief: frees all the arguments. (helper func) + * @return: NULL. + */ +static void *err_if_rule(struct ast **cond, struct ast **then_clause, + struct ast **else_clause) +{ + ast_free(cond); + ast_free(then_clause); + ast_free(else_clause); + return NULL; +} + // === Functions struct ast *parse_list(struct lexer_context *ctx) @@ -73,16 +96,11 @@ struct ast *parse_and_or(struct lexer_context *ctx) while (token->type == TOKEN_AND || token->type == TOKEN_OR) { - // Set left part - + // Build AST (left part) + enum ast_and_or_type type = and_or_tok_to_ast(token->type); struct ast *left = result; - // eat and_or token - token = POP_TOKEN(); - - // Set type - enum ast_and_or_type type = and_or_tok_to_ast(token->type); - + POP_TOKEN(); token = PEEK_TOKEN(); // Skip newlines @@ -94,6 +112,12 @@ struct ast *parse_and_or(struct lexer_context *ctx) // Right part struct ast *right = parse_pipeline(ctx); + if (right == NULL) + { + ast_free(&left); + return NULL; + } + token = PEEK_TOKEN(); result = ast_create_and_or(left, right, type); if (result == NULL) @@ -120,30 +144,33 @@ struct ast *parse_pipeline(struct lexer_context *ctx) token = PEEK_TOKEN(); } + // command rule struct ast *left = parse_command(ctx); + token = PEEK_TOKEN(); if (negation) { left = ast_create_neg(negation, left); } - token = PEEK_TOKEN(); + // Pipes while (token->type == TOKEN_PIPE) { POP_TOKEN(); + token = PEEK_TOKEN(); // skip newlines - token = PEEK_TOKEN(); while (token->type == TOKEN_NEWLINE) { POP_TOKEN(); token = PEEK_TOKEN(); } + // command rule struct ast *right = parse_command(ctx); + token = PEEK_TOKEN(); // Create AST left = ast_create_pipe(left, right); - token = PEEK_TOKEN(); } return left; @@ -162,6 +189,11 @@ 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); + } else { perror("Syntax error: unexpected token"); @@ -171,17 +203,6 @@ struct ast *parse_command(struct lexer_context *ctx) return result; } -/* @brief: frees command_elements and redirections lists (helper func) - * @return: NULL - */ -static void *err_simple_command(struct list *command_elements, - struct list *redirections) -{ - list_deep_destroy(command_elements); - list_deep_destroy(redirections); - return NULL; -} - struct ast *parse_simple_command(struct lexer_context *ctx) { struct list *command_elements = NULL; @@ -299,19 +320,41 @@ struct ast *parse_element(struct lexer_context *ctx) struct ast *parse_shell_command(struct lexer_context *ctx) { - return parse_if_rule(ctx); -} + struct token *token = PEEK_TOKEN(); + struct ast *result = NULL; -/* @brief: frees all the arguments. (helper func) - * @return: NULL. - */ -static void *err_if_rule(struct ast **cond, struct ast **then_clause, - struct ast **else_clause) -{ - ast_free(cond); - ast_free(then_clause); - ast_free(else_clause); - return NULL; + // Grouping + // '(' or '{' + if (token->type == TOKEN_LEFT_BRACKET || token->type == TOKEN_LEFT_PAREN) + { + 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) + { + ast_free(&result); + perror("Syntax error: bracket/parenthesis mismatch"); + return NULL; + } + + POP_TOKEN(); + return result; + } + else if (is_first(*token, RULE_IF)) + { + return parse_if_rule(ctx); + } + // TODO loops and case + else + { + perror("Syntax error: unexpected token in parse_shell_command"); + return NULL; + } } struct ast *parse_if_rule(struct lexer_context *ctx) diff --git a/src/parser/grammar_basic.h b/src/parser/grammar_basic.h index 46f7b4f..3ee5355 100644 --- a/src/parser/grammar_basic.h +++ b/src/parser/grammar_basic.h @@ -43,6 +43,7 @@ struct ast *parse_pipeline(struct lexer_context *ctx); * * @code command = simple_command * | shell_command + * * ; * @first first(simple_command), first(shell_command) */ @@ -72,7 +73,10 @@ struct ast *parse_element(struct lexer_context *ctx); /* * @brief Only parses if rules for the moment * - * @code shell_command = if_rule ; + * @code shell_command = '{' compound_list '}' + * | '(' compound_list ')' + * | if_rule + * ; * * @first first(if_rule) */ diff --git a/src/utils/ast/ast.h b/src/utils/ast/ast.h index 9827d8d..1ba949b 100644 --- a/src/utils/ast/ast.h +++ b/src/utils/ast/ast.h @@ -8,6 +8,7 @@ #include "ast_end.h" #include "ast_if.h" #include "ast_list.h" +#include "ast_loop.h" #include "ast_neg.h" #include "ast_pipe.h" #include "ast_redir.h" diff --git a/src/utils/ast/ast_base.h b/src/utils/ast/ast_base.h index ae99a39..71e4a38 100644 --- a/src/utils/ast/ast_base.h +++ b/src/utils/ast/ast_base.h @@ -16,6 +16,7 @@ enum ast_type AST_WORD, AST_PIPE, AST_NEG, + AST_LOOP, AST_ASSIGNMENT }; diff --git a/src/utils/ast/ast_loop.c b/src/utils/ast/ast_loop.c new file mode 100644 index 0000000..fded922 --- /dev/null +++ b/src/utils/ast/ast_loop.c @@ -0,0 +1,37 @@ +#include "ast_loop.h" + +#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) + return NULL; + + node_data->condition = condition; + node_data->body = body; + + 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 (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) +{ + if (loop_data == NULL) + return; + ast_free(&loop_data->condition); + ast_free(&loop_data->body); + free(loop_data); +} diff --git a/src/utils/ast/ast_loop.h b/src/utils/ast/ast_loop.h new file mode 100644 index 0000000..7c5ba6e --- /dev/null +++ b/src/utils/ast/ast_loop.h @@ -0,0 +1,34 @@ +#ifndef AST_LOOP_H +#define AST_LOOP_H + +#include "ast_base.h" + +struct ast_loop +{ + // Repeat body while condition is true + struct ast *condition; + 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. + */ +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); + +/* + * @brief: frees the given ast_loop and sets the pointer to NULL. + */ +void ast_free_loop(struct ast_loop *loop_node); + +#endif /* ! AST_LOOP_H */ From 32c35c4bf7254515bcd55839b4b9cfc3709e2023 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Fri, 30 Jan 2026 19:55:09 +0100 Subject: [PATCH 065/104] fix: add dependencies in Makefile for new ast --- src/utils/Makefile.am | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am index 7876682..085ae1b 100644 --- a/src/utils/Makefile.am +++ b/src/utils/Makefile.am @@ -17,6 +17,7 @@ libutils_a_SOURCES = \ ast/ast_word.c \ ast/ast_neg.c \ ast/ast_pipe.c \ + ast/ast_loop.c \ args/args.c \ vars/vars.c \ ast/ast_assignment.c From 8ca7a92e7db39c26325dfbff6ad3ea6a9ce57268 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Fri, 30 Jan 2026 20:00:09 +0100 Subject: [PATCH 066/104] fix(parser): removed unused old version of static function --- src/parser/grammar_basic.c | 91 +++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 51 deletions(-) diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index 8be9ea3..17a3fb3 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -26,17 +26,6 @@ static enum ast_and_or_type and_or_tok_to_ast(enum token_type tok_type) } } -/* @brief: frees command_elements and redirections lists (helper func) - * @return: NULL - */ -static void *err_simple_command(struct list *command_elements, - struct list *redirections) -{ - list_deep_destroy(command_elements); - list_deep_destroy(redirections); - return NULL; -} - /* @brief: frees all the arguments. (helper func) * @return: NULL. */ @@ -49,6 +38,46 @@ static void *err_if_rule(struct ast **cond, struct ast **then_clause, return NULL; } +/* @brief: frees command_elements and redirections lists (helper func) + * @return: NULL + */ +static void *err_s_com(struct list *command_elements, struct list *redirections, + struct list *assignments) +{ + list_deep_destroy(command_elements); + list_deep_destroy(redirections); + list_deep_destroy(assignments); + return NULL; +} + +/* @brief: used when export keyword is found, and expects an assignment after. + * @return: an ast_assignment with the field [global] set to true. + */ +static struct ast *parse_export(struct lexer_context *ctx) +{ + struct token *token = PEEK_TOKEN(); + if (token->type != TOKEN_EXPORT) + { + fprintf(stderr, "expected the export keyword in parse_export"); + return NULL; + } + // export + POP_TOKEN(); + + token = PEEK_TOKEN(); + + if (token->type != TOKEN_ASSIGNMENT_WORD) + { + fprintf(stderr, "in parser: export must be followed by 'x=y'"); + return NULL; + } + + // assignment + POP_TOKEN(); + + return ast_create_assignment(token->data, true); +} + // === Functions struct ast *parse_list(struct lexer_context *ctx) @@ -203,46 +232,6 @@ struct ast *parse_command(struct lexer_context *ctx) return result; } -/* @brief: frees command_elements and redirections lists (helper func) - * @return: NULL - */ -static void *err_s_com(struct list *command_elements, struct list *redirections, - struct list *assignments) -{ - list_deep_destroy(command_elements); - list_deep_destroy(redirections); - list_deep_destroy(assignments); - return NULL; -} - -/* @brief: used when export keyword is found, and expects an assignment after. - * @return: an ast_assignment with the field [global] set to true. - */ -static struct ast *parse_export(struct lexer_context *ctx) -{ - struct token *token = PEEK_TOKEN(); - if (token->type != TOKEN_EXPORT) - { - fprintf(stderr, "expected the export keyword in parse_export"); - return NULL; - } - // export - POP_TOKEN(); - - token = PEEK_TOKEN(); - - if (token->type != TOKEN_ASSIGNMENT_WORD) - { - fprintf(stderr, "in parser: export must be followed by 'x=y'"); - return NULL; - } - - // assignment - POP_TOKEN(); - - return ast_create_assignment(token->data, true); -} - struct ast *parse_simple_command(struct lexer_context *ctx) { struct list *command_elements = NULL; From 9f967dc9b47965a12ac665715c26342c71702535 Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Fri, 30 Jan 2026 20:00:44 +0100 Subject: [PATCH 067/104] refactor: removed duplicate code for while/until loops in parser --- src/parser/grammar_advanced.c | 166 ++++++++++++++-------------------- src/parser/grammar_basic.c | 10 +- 2 files changed, 78 insertions(+), 98 deletions(-) diff --git a/src/parser/grammar_advanced.c b/src/parser/grammar_advanced.c index c1e9d20..a1235c1 100644 --- a/src/parser/grammar_advanced.c +++ b/src/parser/grammar_advanced.c @@ -9,6 +9,8 @@ #include "grammar.h" #include "grammar_basic.h" +// === Static functions + static enum ast_redir_type redir_tok_to_ast_type(enum token_type tok_type) { switch (tok_type) @@ -32,6 +34,71 @@ static enum ast_redir_type redir_tok_to_ast_type(enum token_type tok_type) } } +/* + * @brief parses a while/until loop starting with the condition + * (after the while/until keyword) + * @arg negate_condition Set to true for until loops, false for while loops + */ +static struct ast *parse_loop(struct lexer_context *ctx, bool negate_condition) +{ + // condition + struct ast *condition = parse_compound_list(ctx); + if (condition == NULL) + return NULL; + if (negate_condition) + { + condition = + ast_create_neg(true, condition); // TODO check result (beware to not + // exceed the function lines limit) + } + + struct token *token = PEEK_TOKEN(); + + // 'do' + if (token->type != TOKEN_DO) + { + ast_free(&condition); + perror("Syntax error: expected the 'do' keyowrd but got a different " + "token"); + return NULL; + } + POP_TOKEN(); + token = PEEK_TOKEN(); + + // body + struct ast *body = parse_compound_list(ctx); + if (body == NULL) + { + ast_free(&condition); + return NULL; + } + token = PEEK_TOKEN(); + + // 'done' + if (token->type != TOKEN_DONE) + { + ast_free(&condition); + perror("Syntax error: expected the 'done' keyowrd but got a different " + "token"); + return NULL; + } + POP_TOKEN(); + + struct ast *result = ast_create_loop(condition, body); + if (result == NULL) + { + ast_free(&condition); + ast_free(&body); + perror("Internal error: could not create ast node (is your memory full " + "?)"); + return NULL; + } + + return result; +} + +// === Functions + struct ast *parse_redirection(struct lexer_context *ctx) { struct token *token = PEEK_TOKEN(); @@ -110,53 +177,7 @@ struct ast *parse_while(struct lexer_context *ctx) } POP_TOKEN(); - // condition - struct ast *condition = parse_compound_list(ctx); - if (condition == NULL) - return NULL; - token = PEEK_TOKEN(); - - // 'do' - if (token->type != TOKEN_DO) - { - ast_free(&condition); - perror("Syntax error: expected the 'do' keyowrd but got a different " - "token"); - return NULL; - } - POP_TOKEN(); - token = PEEK_TOKEN(); - - // body - struct ast *body = parse_compound_list(ctx); - if (body == NULL) - { - ast_free(&condition); - return NULL; - } - token = PEEK_TOKEN(); - - // 'done' - if (token->type != TOKEN_DONE) - { - ast_free(&condition); - perror("Syntax error: expected the 'done' keyowrd but got a different " - "token"); - return NULL; - } - POP_TOKEN(); - - struct ast *result = ast_create_loop(condition, body); - if (result == NULL) - { - ast_free(&condition); - ast_free(&body); - perror("Internal error: could not create ast node (is your memory full " - "?)"); - return NULL; - } - - return result; + return parse_loop(ctx, true); } struct ast *parse_until(struct lexer_context *ctx) @@ -172,54 +193,5 @@ struct ast *parse_until(struct lexer_context *ctx) } POP_TOKEN(); - // condition - struct ast *condition = parse_compound_list(ctx); - if (condition == NULL) - return NULL; - condition = - ast_create_neg(true, condition); // TODO check result (beware to not - // exceed function lines limit) - token = PEEK_TOKEN(); - - // 'do' - if (token->type != TOKEN_DO) - { - ast_free(&condition); - perror("Syntax error: expected the 'do' keyowrd but got a different " - "token"); - return NULL; - } - POP_TOKEN(); - token = PEEK_TOKEN(); - - // body - struct ast *body = parse_compound_list(ctx); - if (body == NULL) - { - ast_free(&condition); - return NULL; - } - token = PEEK_TOKEN(); - - // 'done' - if (token->type != TOKEN_DONE) - { - ast_free(&condition); - perror("Syntax error: expected the 'done' keyowrd but got a different " - "token"); - return NULL; - } - POP_TOKEN(); - - struct ast *result = ast_create_loop(condition, body); - if (result == NULL) - { - ast_free(&condition); - ast_free(&body); - perror("Internal error: could not create ast node (is your memory full " - "?)"); - return NULL; - } - - return result; + return parse_loop(ctx, true); } diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index c5a5ee3..b7f58bd 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -349,7 +349,15 @@ struct ast *parse_shell_command(struct lexer_context *ctx) { return parse_if_rule(ctx); } - // TODO loops and case + else if (is_first(*token, RULE_WHILE)) + { + return parse_while(ctx); + } + else if (is_first(*token, RULE_UNTIL)) + { + return parse_until(ctx); + } + // TODO for and case else { perror("Syntax error: unexpected token in parse_shell_command"); From df367b0a3c9345cb34bd39656b53547d4fcd5092 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Fri, 30 Jan 2026 20:05:48 +0100 Subject: [PATCH 068/104] feat(tests): .sh file to test and_ors --- tests/test.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100755 tests/test.sh diff --git a/tests/test.sh b/tests/test.sh new file mode 100755 index 0000000..c2f4cf0 --- /dev/null +++ b/tests/test.sh @@ -0,0 +1,17 @@ +true +echo 'true =' $? + +false +echo 'false =' $? + +false && true +echo 'false && true =' $? + +true && false +echo 'true && false =' $? + +true || false +echo 'true || false =' $? + +false || true +echo 'false || true =' $? From 00fe1a471789433e0b9322e9d33ccf31256638ed Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Fri, 30 Jan 2026 20:06:40 +0100 Subject: [PATCH 069/104] feat(tests): and_ors.sh renamed --- tests/{test.sh => and_ors.sh} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{test.sh => and_ors.sh} (100%) diff --git a/tests/test.sh b/tests/and_ors.sh similarity index 100% rename from tests/test.sh rename to tests/and_ors.sh From 4870caa45914ecd5086fa2f364b61d784df70853 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Fri, 30 Jan 2026 20:18:10 +0100 Subject: [PATCH 070/104] fix(lexer): and is now detected properly --- src/lexer/lexer_utils.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lexer/lexer_utils.c b/src/lexer/lexer_utils.c index 02f0a3f..3c6c361 100644 --- a/src/lexer/lexer_utils.c +++ b/src/lexer/lexer_utils.c @@ -298,7 +298,7 @@ ssize_t len_op_sepchar(char *stream, ssize_t i) return 2; // AND - if (stream[i] == '|' && stream[i + 1] == '|') + if (stream[i] == '&' && stream[i + 1] == '&') return 2; // special chars From 828b0e8be30104cbcca4638840d125ac358805e8 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Fri, 30 Jan 2026 20:28:38 +0100 Subject: [PATCH 071/104] fix: coverage tests and hash_map_free on NULL --- src/Makefile.am | 2 +- src/utils/hash_map/hash_map.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 3015877..210cac1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -36,13 +36,13 @@ check_PROGRAMS = testsuite testsuite_SOURCES = ../tests/unit/utils/utils_tests.c \ ../tests/unit/expansion/parse_var.c \ - ../tests/unit/io_backend/io_backend.c \ ../tests/unit/utils/args.c \ ../tests/unit/utils/hash_map.c \ ../tests/unit/utils/insert_into.c # ../tests/unit/lexer/lexer_tests.c # ../tests/unit/expansion/expand.c +# ../tests/unit/io_backend/io_backend.c testsuite_CPPFLAGS = $(42sh_CPPFLAGS) diff --git a/src/utils/hash_map/hash_map.c b/src/utils/hash_map/hash_map.c index 6f9a513..b07b63d 100644 --- a/src/utils/hash_map/hash_map.c +++ b/src/utils/hash_map/hash_map.c @@ -116,8 +116,8 @@ void hash_map_free(struct hash_map **hash_map) } free((*hash_map)->data); free(*hash_map); + *hash_map = NULL; } - *hash_map = NULL; } void hash_map_foreach(struct hash_map *hash_map, From 49eca6f2df31672e5b557bf5c420c3c0f0fdb8f4 Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Fri, 30 Jan 2026 20:58:10 +0100 Subject: [PATCH 072/104] fix: random parser fixes --- src/parser/grammar_basic.c | 85 ++++++++++++++++++++++++++++---------- 1 file changed, 63 insertions(+), 22 deletions(-) diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index b7f58bd..f0f96f9 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -68,8 +68,13 @@ struct ast *parse_list(struct lexer_context *ctx) token = PEEK_TOKEN(); while (token->type == TOKEN_SEMICOLON) { - token = POP_TOKEN(); + // Forward + POP_TOKEN(); token = PEEK_TOKEN(); + + // TODO seems a little akward (not fully compliant with the grammar) + // but it's time consuming to rewrite to only cover edge cases. + // So it'll probably stay like that for now if (is_first(*token, RULE_AND_OR)) { current_node = parse_and_or(ctx); @@ -304,8 +309,26 @@ struct ast *parse_element(struct lexer_context *ctx) struct token *token = PEEK_TOKEN(); if (token->type == TOKEN_WORD || token->type == TOKEN_ASSIGNMENT_WORD) { - token = POP_TOKEN(); - return ast_create_word(token->data); + POP_TOKEN(); + + char *word = strdup(token->data); + if (word == NULL) + { + perror("Internal error: could not copy token data (is your memory " + "full ?)"); + return NULL; + } + + struct ast *result = ast_create_word(word); + if (result == NULL) + { + perror("Internal error: could not create ast node (is your memory " + "full ?)"); + free(word); + return NULL; + } + + return result; } else if (token->type == TOKEN_IONUMBER || is_token_redir(token)) { @@ -378,12 +401,15 @@ struct ast *parse_if_rule(struct lexer_context *ctx) // Condition content struct ast *condition_content = parse_compound_list(ctx); + if (condition_content == NULL) + return NULL; + token = PEEK_TOKEN(); // Then keyword - token = PEEK_TOKEN(); if (token->type != TOKEN_THEN) { - perror("Expected the 'then' keyword but token has different type"); + perror("Syntax error: Expected the 'then' keyword but token has " + "different type"); return err_if_rule(&condition_content, NULL, NULL); } POP_TOKEN(); @@ -394,9 +420,9 @@ struct ast *parse_if_rule(struct lexer_context *ctx) { return err_if_rule(&condition_content, &then_content, NULL); } + token = PEEK_TOKEN(); struct ast *else_content = NULL; - token = PEEK_TOKEN(); // Eventual else/elif clause(s) if (is_first(*token, RULE_ELSE_CLAUSE)) { @@ -405,10 +431,10 @@ struct ast *parse_if_rule(struct lexer_context *ctx) { return err_if_rule(&condition_content, &then_content, NULL); } + token = PEEK_TOKEN(); } // Fi keyword - token = PEEK_TOKEN(); if (token->type != TOKEN_FI) { perror("Expected the 'fi' keyword but token has different type"); @@ -437,7 +463,7 @@ struct ast *parse_compound_list(struct lexer_context *ctx) // Skip newlines while (token->type == TOKEN_NEWLINE) { - token = POP_TOKEN(); + POP_TOKEN(); token = PEEK_TOKEN(); } @@ -446,9 +472,9 @@ struct ast *parse_compound_list(struct lexer_context *ctx) if (current_cmd == NULL) return NULL; result_list = list_append(result_list, current_cmd); + token = PEEK_TOKEN(); // Following commands - token = PEEK_TOKEN(); while (token->type == TOKEN_SEMICOLON || token->type == TOKEN_NEWLINE) { POP_TOKEN(); @@ -457,7 +483,7 @@ struct ast *parse_compound_list(struct lexer_context *ctx) // Skip newlines while (token->type == TOKEN_NEWLINE) { - token = POP_TOKEN(); + POP_TOKEN(); token = PEEK_TOKEN(); } @@ -468,22 +494,21 @@ struct ast *parse_compound_list(struct lexer_context *ctx) if (current_cmd == NULL) return NULL; result_list = list_append(result_list, current_cmd); + token = PEEK_TOKEN(); } - - token = PEEK_TOKEN(); } - // Eventual semicolons + // Eventual semicolon if (token->type == TOKEN_SEMICOLON) { - token = POP_TOKEN(); + POP_TOKEN(); token = PEEK_TOKEN(); } // Skip newlines while (token->type == TOKEN_NEWLINE) { - token = POP_TOKEN(); + POP_TOKEN(); token = PEEK_TOKEN(); } @@ -513,18 +538,31 @@ struct ast *parse_else_clause(struct lexer_context *ctx) // Then clause struct ast *then_content = parse_compound_list(ctx); + if (then_content == NULL) + { + ast_free(&condition); + return NULL; + } + token = PEEK_TOKEN(); // Eventual else clause (recursive) struct ast *else_content = NULL; - token = PEEK_TOKEN(); if (token->type == TOKEN_ELSE || token->type == TOKEN_ELIF) { else_content = parse_else_clause(ctx); + if (else_content == NULL) + { + ast_free(&then_content); + ast_free(&condition); + return NULL; + } + } + else + { + else_content = ast_create_void(); } - struct ast *result = - ast_create_if(condition, then_content, else_content); - return result; + return ast_create_if(condition, then_content, else_content); } // Eventual else content @@ -533,12 +571,15 @@ struct ast *parse_else_clause(struct lexer_context *ctx) if (token->type == TOKEN_ELSE) { - token = POP_TOKEN(); // eat else + token = POP_TOKEN(); result = parse_compound_list(ctx); + if (result == NULL) + return NULL; } - - if (result == NULL) + else + { result = ast_create_void(); + } return result; } From b663655d53da3e1abf0815d3153aa12bd99ccfd8 Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Fri, 30 Jan 2026 21:27:23 +0100 Subject: [PATCH 073/104] fix: strdup a la con --- src/parser/grammar_basic.c | 18 ++++-------------- src/utils/ast/ast_word.c | 5 +++-- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index dcd6b31..df103fc 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -304,13 +304,12 @@ struct ast *parse_simple_command(struct lexer_context *ctx) // Get element type if (ast_is_word(element)) { + // Extract word struct ast_word *element_word = ast_get_word(element); - - // TODO test this fix for the memory leaks - char *word = strdup(element_word->word); + char *word = element_word->word; + element_word->word = NULL; // Prevents word to be freed ast_free(&element); command_elements = list_append(command_elements, word); - // end of fix } else if (ast_is_redir(element)) { @@ -344,20 +343,11 @@ struct ast *parse_element(struct lexer_context *ctx) { POP_TOKEN(); - char *word = strdup(token->data); - if (word == NULL) - { - perror("Internal error: could not copy token data (is your memory " - "full ?)"); - return NULL; - } - - struct ast *result = ast_create_word(word); + struct ast *result = ast_create_word(token->data); if (result == NULL) { perror("Internal error: could not create ast node (is your memory " "full ?)"); - free(word); return NULL; } diff --git a/src/utils/ast/ast_word.c b/src/utils/ast/ast_word.c index 6870c50..d83489c 100644 --- a/src/utils/ast/ast_word.c +++ b/src/utils/ast/ast_word.c @@ -2,7 +2,6 @@ #include "ast_word.h" #include -#include #include #include @@ -43,6 +42,8 @@ void ast_free_word(struct ast_word *ast_node) if (ast_node == NULL) return; - free(ast_node->word); + if (ast_node->word != NULL) + free(ast_node->word); + free(ast_node); } From 3794ee0253b68a556889cc327ca57cea891fefb3 Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Fri, 30 Jan 2026 21:29:39 +0100 Subject: [PATCH 074/104] fix: pourcent --- tests/functional/run-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/run-tests.sh b/tests/functional/run-tests.sh index c21ee88..5119aad 100755 --- a/tests/functional/run-tests.sh +++ b/tests/functional/run-tests.sh @@ -251,7 +251,7 @@ summarize() { # Print echo -e $BWhite "\n\n""===========" $UWhite"Summary"$Color_Off "\n" - echo -e " Passed $coverage_color$passed_tests/$total_tests$Color_Off tests ($coverage_color$tests_percentage$Color_Off%)" + echo -e " Passed $coverage_color$passed_tests/$total_tests$Color_Off tests ($coverage_color$tests_percentage%$Color_Off)" echo -e " Got $timeouts_color$timeouts_count timeout(s)$Color_Off" if [ "$OUTPUT_FILE" != "" ]; then echo $tests_percentage > "$OUTPUT_FILE"; From 9e522b2a68247f54c49dca5177a8ee3fc407009c Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Fri, 30 Jan 2026 22:55:07 +0100 Subject: [PATCH 075/104] feat: lexer support for loops keywords --- src/lexer/lexer_utils.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/lexer/lexer_utils.c b/src/lexer/lexer_utils.c index 3c6c361..babf9da 100644 --- a/src/lexer/lexer_utils.c +++ b/src/lexer/lexer_utils.c @@ -72,6 +72,16 @@ static void set_token_keyword(struct token *tok, char *begin, ssize_t size) tok->type = TOKEN_ELSE; else if (strncmp(begin, "elif", size) == 0 && size == 4) tok->type = TOKEN_ELIF; + else if (strncmp(begin, "for", size) == 0 && size == 3) + 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) + tok->type = TOKEN_UNTIL; + else if (strncmp(begin, "do", size) == 0 && size == 2) + tok->type = TOKEN_DO; + else if (strncmp(begin, "done", size) == 0 && size == 4) + tok->type = TOKEN_DONE; else if (strncmp(begin, "export", size) == 0 && size == 6) tok->type = TOKEN_EXPORT; From f31fca4204c5036b843687bc7091b46bada06494 Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Fri, 30 Jan 2026 23:43:49 +0100 Subject: [PATCH 076/104] feat: full while/until loops support, important bug fixes and more tests --- src/execution/execution.c | 6 +-- src/execution/execution_helpers.c | 14 ++++-- src/execution/execution_helpers.h | 1 + src/parser/grammar.c | 84 +++++++++++++++++++++---------- src/utils/ast/ast.c | 6 +++ tests/functional/run-tests.sh | 11 ++-- 6 files changed, 85 insertions(+), 37 deletions(-) diff --git a/src/execution/execution.c b/src/execution/execution.c index cf751f7..46bbfcb 100644 --- a/src/execution/execution.c +++ b/src/execution/execution.c @@ -1,17 +1,13 @@ #define _POSIX_C_SOURCE 200809L #include "execution.h" -#include #include #include -#include -#include #include #include #include #include "../expansion/expansion.h" -#include "../utils/ast/ast.h" #include "../utils/hash_map/hash_map.h" // Refactored: delegates to helpers in execution_helpers.c @@ -40,6 +36,8 @@ int execution(struct ast *ast, struct hash_map *vars) return exec_ast_list(ast_get_list(ast), vars); case AST_AND_OR: return exec_ast_and_or(ast_get_and_or(ast), vars); + case AST_LOOP: + return exec_ast_loop(ast_get_loop(ast), vars); default: return 127; } diff --git a/src/execution/execution_helpers.c b/src/execution/execution_helpers.c index 9a72c10..e65c0d1 100644 --- a/src/execution/execution_helpers.c +++ b/src/execution/execution_helpers.c @@ -1,7 +1,6 @@ #define _POSIX_C_SOURCE 200809L #include "execution_helpers.h" -#include #include #include #include @@ -9,8 +8,6 @@ #include #include -#include "../expansion/expansion.h" -#include "../utils/ast/ast.h" #include "../utils/hash_map/hash_map.h" #include "../utils/lists/lists.h" #include "../utils/vars/vars.h" @@ -275,6 +272,17 @@ void unset_all_redir(struct list *redir_list) } } +int exec_ast_loop(struct ast_loop *loop_node, struct hash_map *vars) +{ + int res = 0; + while (execution(loop_node->condition, vars) == 0) + { + res = execution(loop_node->body, vars); + } + + return res; +} + // --- Builtins --- static int builtin_echo(char **argv) diff --git a/src/execution/execution_helpers.h b/src/execution/execution_helpers.h index ebf6858..1716179 100644 --- a/src/execution/execution_helpers.h +++ b/src/execution/execution_helpers.h @@ -8,6 +8,7 @@ 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); void unset_all_redir(struct list *redir_list); #endif // EXECUTION_HELPERS_H diff --git a/src/parser/grammar.c b/src/parser/grammar.c index 0680a79..e2a4887 100644 --- a/src/parser/grammar.c +++ b/src/parser/grammar.c @@ -101,17 +101,13 @@ static void add_first_redir(void) add_first(RULE_REDIRECTION, TOKEN_REDIR_RIGHT_PIPE); } -// === Functions - -int grammar_init(void) +// Adds only direct tokens to rules firsts into the firsts map +static void add_firsts_tokens(void) { - // Initialize the firsts map - bool success = init_firsts_map(); - if (success != true) - return false; - - // Populate the firsts map - // TODO CHECK ORDER + // Redirection + add_first_redir(); + // %RIP Matteo 30/01/2026 + // %RAX Guillem 30/01/2026 hehe // If add_first(RULE_IF, TOKEN_IF); @@ -127,7 +123,7 @@ int grammar_init(void) add_first(RULE_WHILE, TOKEN_WHILE); // Until - add_first(RULE_WHILE, TOKEN_UNTIL); + add_first(RULE_UNTIL, TOKEN_UNTIL); // Case add_first(RULE_CASE, TOKEN_CASE); @@ -136,33 +132,57 @@ int grammar_init(void) add_first(RULE_CASE_ITEM, TOKEN_LEFT_PAREN); add_first(RULE_CASE_ITEM, TOKEN_WORD); - // Case clause - add_firsts(RULE_CASE_CLAUSE, first(RULE_CASE_ITEM)); + // Shell command + add_first(RULE_SHELL_COMMAND, TOKEN_LEFT_BRACKET); + add_first(RULE_SHELL_COMMAND, TOKEN_LEFT_PAREN); - // Redirection - add_first_redir(); - // %RIP Matteo 30/01/2026 - // %RAX Guillem 30/01/2026 hehe + // Simple command + add_first(RULE_SIMPLE_COMMAND, TOKEN_WORD); + add_first(RULE_SIMPLE_COMMAND, TOKEN_EXPORT); // Element add_first(RULE_ELEMENT, TOKEN_WORD); add_first(RULE_ELEMENT, TOKEN_ASSIGNMENT_WORD); - add_firsts(RULE_ELEMENT, first(RULE_REDIRECTION)); // Prefix add_first(RULE_PREFIX, TOKEN_ASSIGNMENT_WORD); + + // Pipeline + add_first(RULE_PIPELINE, TOKEN_WORD); + + // Compound list + add_first(RULE_COMPOUND_LIST, TOKEN_NEWLINE); + + // Input + add_first(RULE_INPUT, TOKEN_NEWLINE); + add_first(RULE_INPUT, TOKEN_EOF); + + // Funcdec + add_first(RULE_FUNCDEC, TOKEN_WORD); +} + +// Adds only firsts that depend on other rules to the firsts map +// WARNING order matters +static void add_firsts_rec(void) +{ + // Case clause + add_firsts(RULE_CASE_CLAUSE, first(RULE_CASE_ITEM)); + + // Element + add_firsts(RULE_ELEMENT, first(RULE_REDIRECTION)); + + // Prefix add_firsts(RULE_PREFIX, first(RULE_REDIRECTION)); // Shell command add_firsts(RULE_SHELL_COMMAND, first(RULE_IF)); + add_firsts(RULE_SHELL_COMMAND, first(RULE_FOR)); + add_firsts(RULE_SHELL_COMMAND, first(RULE_WHILE)); + add_firsts(RULE_SHELL_COMMAND, first(RULE_UNTIL)); + add_firsts(RULE_SHELL_COMMAND, first(RULE_CASE)); // Simple command add_firsts(RULE_SIMPLE_COMMAND, first(RULE_PREFIX)); - add_first(RULE_SIMPLE_COMMAND, TOKEN_WORD); - add_first(RULE_SIMPLE_COMMAND, TOKEN_EXPORT); - - // Funcdec - add_first(RULE_FUNCDEC, TOKEN_WORD); // Command add_firsts(RULE_COMMAND, first(RULE_SIMPLE_COMMAND)); @@ -170,23 +190,33 @@ int grammar_init(void) add_firsts(RULE_COMMAND, first(RULE_FUNCDEC)); // Pipeline - add_first(RULE_PIPELINE, TOKEN_WORD); add_firsts(RULE_PIPELINE, first(RULE_COMMAND)); // And Or add_firsts(RULE_AND_OR, first(RULE_PIPELINE)); // Compound list - add_first(RULE_COMPOUND_LIST, TOKEN_NEWLINE); add_firsts(RULE_COMPOUND_LIST, first(RULE_AND_OR)); // List add_firsts(RULE_LIST, first(RULE_AND_OR)); // Input - add_first(RULE_INPUT, TOKEN_NEWLINE); - add_first(RULE_INPUT, TOKEN_EOF); add_firsts(RULE_INPUT, first(RULE_LIST)); +} + +// === Functions + +int grammar_init(void) +{ + // Initialize the firsts map + bool success = init_firsts_map(); + if (success != true) + return false; + + // Populate the firsts map + add_firsts_tokens(); + add_firsts_rec(); return true; } diff --git a/src/utils/ast/ast.c b/src/utils/ast/ast.c index 95a2f15..5d64f7d 100644 --- a/src/utils/ast/ast.c +++ b/src/utils/ast/ast.c @@ -42,6 +42,12 @@ void ast_free(struct ast **node) case AST_ASSIGNMENT: ast_free_assignment(ast_get_assignment(*node)); break; + case AST_NEG: + ast_free_neg(ast_get_neg(*node)); + break; + case AST_LOOP: + ast_free_loop(ast_get_loop(*node)); + break; case AST_VOID: case AST_END: break; diff --git a/tests/functional/run-tests.sh b/tests/functional/run-tests.sh index 5119aad..42654da 100755 --- a/tests/functional/run-tests.sh +++ b/tests/functional/run-tests.sh @@ -377,9 +377,14 @@ test_str "If with negation" "if ! false; then echo Yes; fi" test_str "If faut aller niquer sa mere" "if false; ! false; then echo Embrasse moi; fi" -echo -e "\n$BBlue=== For/While ===$Color_Off" -test_str "While loop" "i=0; while [ \$i -lt 3 ]; do echo \$i; i=\$((i+1)); done" -test_str "Until loop" "i=0; until [ \$i -ge 3 ]; do echo \$i; i=\$((i+1)); done" +echo -e "\n$BBlue=== Loops ===$Color_Off" +test_str "While false" "while false; do false; done" +test_str "While(false) true" "while false; do true; done" +test_str "Until(true) false" "until true; do false; done" +test_str "Until true" "until true; do true; done" +# test_str "While var" "a=2; while [ \$a -eq 2 ]; do \$a=3; done" +test_str "While arithmetic" "i=0; while [ \$i -lt 3 ]; do echo \$i; i=\$((i+1)); done" +test_str "Until arithmetic" "i=0; until [ \$i -ge 3 ]; do echo \$i; i=\$((i+1)); done" test_str "While break" "while true; do echo break; break; done" test_str "While continue" "i=0; while [ \$i -lt 3 ]; do i=\$((i+1)); if [ \$i -eq 2 ]; then continue; fi; echo \$i; done" test_str "For loop basic" "for i in a b c; do echo \$i; done" From 64e3fb26c329c927a088d3be2339273f2115b62b Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Sat, 31 Jan 2026 10:48:58 +0100 Subject: [PATCH 077/104] feat(tests): sh file for function testing --- tests/func.sh | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 tests/func.sh diff --git a/tests/func.sh b/tests/func.sh new file mode 100644 index 0000000..dbd0590 --- /dev/null +++ b/tests/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 From d95b0fd220fa8f395b8945b82dadf4f95fa69dff Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 31 Jan 2026 10:59:45 +0100 Subject: [PATCH 078/104] fix: lexer_context is now freed on errors --- src/lexer/lexer_utils.c | 15 +++++++-------- src/lexer/lexer_utils.h | 2 +- src/main.c | 15 ++++++++------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/lexer/lexer_utils.c b/src/lexer/lexer_utils.c index babf9da..6c88a0c 100644 --- a/src/lexer/lexer_utils.c +++ b/src/lexer/lexer_utils.c @@ -257,16 +257,15 @@ 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) + 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 (ctx->previous_token != NULL) + free(ctx->previous_token); + if (ctx->current_token != NULL) + free(ctx->current_token); + 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..1f0fc93 100644 --- a/src/main.c +++ b/src/main.c @@ -21,9 +21,10 @@ /* @brief: frees the hash map. * @return: always ERR_INPUT_PROCESSING. */ -static int err_input(struct hash_map **vars) +static int err_input(struct hash_map **vars, struct lexer_context *ctx) { hash_map_free(vars); + destroy_lexer_context(ctx); return ERR_INPUT_PROCESSING; } @@ -64,7 +65,7 @@ static int main_loop(struct lexer_context *ctx, struct args_options *options, } if (command_ast == NULL) - return err_input(&vars); + return err_input(&vars, ctx); // === free @@ -90,7 +91,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 +106,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 +116,13 @@ 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); + return_code = main_loop(ctx, &options, vars); return return_code; } From 5de6ef2bb58ed15c906b1beee0b4310e3f4e4d8c Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Sat, 31 Jan 2026 11:02:19 +0100 Subject: [PATCH 079/104] fix: lexer_context also freed after normal execution... -_- --- src/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.c b/src/main.c index 1f0fc93..1b86854 100644 --- a/src/main.c +++ b/src/main.c @@ -72,6 +72,7 @@ static int main_loop(struct lexer_context *ctx, struct args_options *options, ast_free(&command_ast); parser_close(); hash_map_free(&vars); + destroy_lexer_context(ctx); return return_code; } From 4289700e2ef3c494021b7aff91e6be9f6def6615 Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 31 Jan 2026 11:09:08 +0100 Subject: [PATCH 080/104] fix(lexer): destroy_lexer_context now fully functional --- src/lexer/lexer_utils.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/lexer/lexer_utils.c b/src/lexer/lexer_utils.c index 6c88a0c..1f40a69 100644 --- a/src/lexer/lexer_utils.c +++ b/src/lexer/lexer_utils.c @@ -259,12 +259,14 @@ struct token *new_token(char *begin, ssize_t size, struct token_info *info) void destroy_lexer_context(struct lexer_context *ctx) { + 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); + if (prev != NULL) + free_token(&prev); + if (cur != NULL) + free_token(&cur); free(ctx); } From 93f280f59d1e091d9f2f752b7bb12bd1eff16925 Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 31 Jan 2026 11:34:24 +0100 Subject: [PATCH 081/104] sh file to test loops --- tests/loops.sh | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100755 tests/loops.sh diff --git a/tests/loops.sh b/tests/loops.sh new file mode 100755 index 0000000..b3d0b69 --- /dev/null +++ b/tests/loops.sh @@ -0,0 +1,15 @@ +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; + +echo "tests done" From a550f9747ea8a67188f7533e10bbf62a277df1f7 Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 31 Jan 2026 11:57:51 +0100 Subject: [PATCH 082/104] feat(ast): ast_function --- src/utils/ast/ast.c | 7 +++++-- src/utils/ast/ast_base.h | 3 ++- src/utils/ast/ast_function.c | 40 ++++++++++++++++++++++++++++++++++++ src/utils/ast/ast_function.h | 31 ++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 src/utils/ast/ast_function.c create mode 100644 src/utils/ast/ast_function.h diff --git a/src/utils/ast/ast.c b/src/utils/ast/ast.c index 5d64f7d..ee0f031 100644 --- a/src/utils/ast/ast.c +++ b/src/utils/ast/ast.c @@ -11,7 +11,7 @@ void ast_free(struct ast **node) { if (node == NULL || *node == NULL) { - perror( + fprintf(stderr, "WARNING: Internal error: failed to free AST node (NULL argument)"); return; } @@ -48,12 +48,15 @@ void ast_free(struct ast **node) case AST_LOOP: ast_free_loop(ast_get_loop(*node)); break; + case AST_FUNCTION: + ast_free_function(ast_get_function(*node)); + break; case AST_VOID: case AST_END: break; default: - perror("WARNING: Internal error: failed to free an AST node (Unknown " + fprintf(stderr, "WARNING: Internal error: failed to free an AST node (Unknown " "type)"); return; } diff --git a/src/utils/ast/ast_base.h b/src/utils/ast/ast_base.h index 71e4a38..e1c7b07 100644 --- a/src/utils/ast/ast_base.h +++ b/src/utils/ast/ast_base.h @@ -17,7 +17,8 @@ enum ast_type AST_PIPE, AST_NEG, AST_LOOP, - AST_ASSIGNMENT + AST_ASSIGNMENT, + AST_FUNCTION }; struct ast diff --git a/src/utils/ast/ast_function.c b/src/utils/ast/ast_function.c new file mode 100644 index 0000000..edd53e0 --- /dev/null +++ b/src/utils/ast/ast_function.c @@ -0,0 +1,40 @@ +#include "ast_function.h" + +#include +#include + +#include "ast_base.h" + +bool ast_is_function(struct ast *node) +{ + return node != NULL && node->type == AST_FUNCTION; +} + +struct ast_function *ast_get_function(struct ast *node) +{ + if (!ast_is_function(node)) + return NULL; + return (struct ast_function *)node->data; +} + +struct ast *ast_create_function(char *name, struct ast *value) +{ + struct ast_function *function_data = malloc(sizeof(struct ast_function)); + if (!function_data) + return NULL; + + function_data->name = strdup(name); + function_data->value = value; + + return ast_create(AST_FUNCTION, function_data); +} + +void ast_free_function(struct ast_function *function_data) +{ + if (function_data) + { + free(function_data->name); + ast_free(&function_data->value); + free(function_data); + } +} diff --git a/src/utils/ast/ast_function.h b/src/utils/ast/ast_function.h new file mode 100644 index 0000000..9294295 --- /dev/null +++ b/src/utils/ast/ast_function.h @@ -0,0 +1,31 @@ +#ifndef AST_FUNCTION_H +#define AST_FUNCTION_H + +struct ast_function +{ + char *name; + struct ast *value; +}; + + +/** + * 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. + */ +struct ast_function *ast_get_function(struct ast *node); + +/** + * Creates a new AST node representing an AST_function + */ +struct ast *ast_create_function(char *name, struct ast *value); +/* + * @brief: frees the given ast_function and sets the pointer to NULL. + */ +void ast_free_function(struct ast_function *function_data); + +#endif /* AST_FUNCTION_H */ From 3cd231f031a99cee823f4ab3a8e724abfff35db1 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Sat, 31 Jan 2026 12:53:23 +0100 Subject: [PATCH 083/104] fix: build and .sh test file for functions --- src/utils/Makefile.am | 3 ++- src/utils/ast/ast.c | 8 +++++--- src/utils/ast/ast.h | 1 + src/utils/ast/ast_function.c | 2 +- src/utils/ast/ast_function.h | 3 ++- src/utils/ast/ast_loop.h | 2 +- tests/func.sh | 0 7 files changed, 12 insertions(+), 7 deletions(-) mode change 100644 => 100755 tests/func.sh diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am index 085ae1b..17454d2 100644 --- a/src/utils/Makefile.am +++ b/src/utils/Makefile.am @@ -20,7 +20,8 @@ libutils_a_SOURCES = \ ast/ast_loop.c \ args/args.c \ vars/vars.c \ - ast/ast_assignment.c + ast/ast_assignment.c \ + ast/ast_function.c libutils_a_CPPFLAGS = -I$(top_srcdir)/src diff --git a/src/utils/ast/ast.c b/src/utils/ast/ast.c index ee0f031..5f5f65b 100644 --- a/src/utils/ast/ast.c +++ b/src/utils/ast/ast.c @@ -11,7 +11,8 @@ void ast_free(struct ast **node) { if (node == NULL || *node == NULL) { - fprintf(stderr, + fprintf( + stderr, "WARNING: Internal error: failed to free AST node (NULL argument)"); return; } @@ -56,8 +57,9 @@ void ast_free(struct ast **node) 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; } diff --git a/src/utils/ast/ast.h b/src/utils/ast/ast.h index 1ba949b..f0023f3 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" diff --git a/src/utils/ast/ast_function.c b/src/utils/ast/ast_function.c index edd53e0..b0356c1 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); diff --git a/src/utils/ast/ast_function.h b/src/utils/ast/ast_function.h index 9294295..a16dc17 100644 --- a/src/utils/ast/ast_function.h +++ b/src/utils/ast/ast_function.h @@ -1,13 +1,14 @@ #ifndef AST_FUNCTION_H #define AST_FUNCTION_H +#include + struct ast_function { char *name; struct ast *value; }; - /** * Checks if the given AST node is an ast_function */ diff --git a/src/utils/ast/ast_loop.h b/src/utils/ast/ast_loop.h index 7c5ba6e..9718db1 100644 --- a/src/utils/ast/ast_loop.h +++ b/src/utils/ast/ast_loop.h @@ -24,7 +24,7 @@ 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. diff --git a/tests/func.sh b/tests/func.sh old mode 100644 new mode 100755 From 132f4f3a539c012f4345d8c5a97947c524be3ba0 Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 31 Jan 2026 13:24:44 +0100 Subject: [PATCH 084/104] feat(parser + ast): function parsing support --- src/parser/grammar_advanced.c | 51 ++++++++++++++++++++++++++++++++--- src/parser/grammar_advanced.h | 2 +- src/parser/grammar_basic.c | 1 - src/utils/ast/ast_function.c | 2 +- src/utils/ast/ast_function.h | 9 ++++--- 5 files changed, 54 insertions(+), 11 deletions(-) diff --git a/src/parser/grammar_advanced.c b/src/parser/grammar_advanced.c index 22e3b9b..9968ba6 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(name); + return NULL; + } + return ast_create_function(name, value); } struct ast *parse_for(struct lexer_context *ctx) diff --git a/src/parser/grammar_advanced.h b/src/parser/grammar_advanced.h index 2b9cc63..1b074ad 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 ; * diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index df103fc..d86abb6 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); diff --git a/src/utils/ast/ast_function.c b/src/utils/ast/ast_function.c index edd53e0..b0356c1 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); diff --git a/src/utils/ast/ast_function.h b/src/utils/ast/ast_function.h index 9294295..f6171dc 100644 --- a/src/utils/ast/ast_function.h +++ b/src/utils/ast/ast_function.h @@ -9,18 +9,19 @@ struct ast_function /** - * 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); /* From 567bd44fce12c9b5f7f3c039f142104f68bff9cf Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 31 Jan 2026 13:26:02 +0100 Subject: [PATCH 085/104] fix(parser): typo --- src/parser/grammar_advanced.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/parser/grammar_advanced.c b/src/parser/grammar_advanced.c index 9968ba6..ae652a1 100644 --- a/src/parser/grammar_advanced.c +++ b/src/parser/grammar_advanced.c @@ -194,10 +194,10 @@ struct ast *parse_funcdec(struct lexer_context *ctx) value = parse_shell_command(ctx); if (value == NULL) { - free(name); + free(func_name); return NULL; } - return ast_create_function(name, value); + return ast_create_function(func_name, value); } struct ast *parse_for(struct lexer_context *ctx) From 182e31b42ea5666f3fb522a08f681dfaff6332fa Mon Sep 17 00:00:00 2001 From: Jean <47366872+jean-voila@users.noreply.github.com> Date: Sat, 31 Jan 2026 15:40:46 +0100 Subject: [PATCH 086/104] fix(chaipa): Remove pretty print to avoid clang-tidy / added the options in echo --- src/execution/execution_helpers.c | 82 +++++++++++++++++++++++++++++-- src/main.c | 10 +--- src/utils/args/args.c | 13 ++--- src/utils/args/args.h | 2 +- src/utils/ast/ast.c | 3 +- src/utils/ast/ast.h | 5 -- tests/unit/utils/args.c | 25 ++++++---- 7 files changed, 106 insertions(+), 34 deletions(-) diff --git a/src/execution/execution_helpers.c b/src/execution/execution_helpers.c index e65c0d1..ebb833d 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 @@ -285,20 +286,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(" "); } diff --git a/src/main.c b/src/main.c index 1b86854..02320b9 100644 --- a/src/main.c +++ b/src/main.c @@ -28,8 +28,7 @@ static int err_input(struct hash_map **vars, struct lexer_context *ctx) return ERR_INPUT_PROCESSING; } -static int main_loop(struct lexer_context *ctx, struct args_options *options, - struct hash_map *vars) +static int main_loop(struct lexer_context *ctx, struct hash_map *vars) { int return_code = SUCCESS; // init parser @@ -41,11 +40,6 @@ static int main_loop(struct lexer_context *ctx, struct args_options *options, // 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) { @@ -123,7 +117,7 @@ int main(int argc, char **argv) // init lexer context struct lexer_context *ctx = calloc(1, sizeof(struct lexer_context)); - return_code = main_loop(ctx, &options, vars); + return_code = main_loop(ctx, vars); return return_code; } 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 5f5f65b..5baaf6a 100644 --- a/src/utils/ast/ast.c +++ b/src/utils/ast/ast.c @@ -79,7 +79,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) @@ -170,3 +170,4 @@ void ast_print_dot(struct ast *ast) fprintf(dot_pipe, "}\n"); pclose(dot_pipe); } + */ \ No newline at end of file diff --git a/src/utils/ast/ast.h b/src/utils/ast/ast.h index f0023f3..12abee5 100644 --- a/src/utils/ast/ast.h +++ b/src/utils/ast/ast.h @@ -16,9 +16,4 @@ #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); - #endif /* ! AST_H */ 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); From 37e430a234e750179ab9d563c5a51c3ebbd0826a Mon Sep 17 00:00:00 2001 From: Jean <47366872+jean-voila@users.noreply.github.com> Date: Sat, 31 Jan 2026 15:47:23 +0100 Subject: [PATCH 087/104] removed the warning --- src/parser/grammar_advanced.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/parser/grammar_advanced.h b/src/parser/grammar_advanced.h index 1b074ad..3829c49 100644 --- a/src/parser/grammar_advanced.h +++ b/src/parser/grammar_advanced.h @@ -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' ) ] ) From b6305c245330866c87b773c7d4b934253627e64c Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Sat, 31 Jan 2026 16:35:34 +0100 Subject: [PATCH 088/104] fix: moved test scripts to functionnal testing --- tests/{ => functional}/and_ors.sh | 0 tests/{ => functional}/func.sh | 0 tests/{ => functional}/loops.sh | 0 tests/functional/run-tests.sh | 3 --- 4 files changed, 3 deletions(-) rename tests/{ => functional}/and_ors.sh (100%) rename tests/{ => functional}/func.sh (100%) rename tests/{ => functional}/loops.sh (100%) 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/func.sh b/tests/functional/func.sh similarity index 100% rename from tests/func.sh rename to tests/functional/func.sh diff --git a/tests/loops.sh b/tests/functional/loops.sh similarity index 100% rename from tests/loops.sh rename to tests/functional/loops.sh 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" From 5df65f41c94dd0ac993861089a9a8716c3fbf6ec Mon Sep 17 00:00:00 2001 From: Jean <47366872+jean-voila@users.noreply.github.com> Date: Sat, 31 Jan 2026 16:43:52 +0100 Subject: [PATCH 089/104] feat(execution): Implemented break and continue for while --- src/execution/execution_helpers.c | 71 ++++++++++++++++++++++++++++--- src/execution/execution_helpers.h | 4 ++ 2 files changed, 70 insertions(+), 5 deletions(-) diff --git a/src/execution/execution_helpers.c b/src/execution/execution_helpers.c index ebb833d..5157290 100644 --- a/src/execution/execution_helpers.c +++ b/src/execution/execution_helpers.c @@ -123,6 +123,9 @@ static char **list_to_argv(struct list *command_list) static int try_builtin(char **argv, struct hash_map *vars); +static int builtin_break(char **argv); +static int builtin_continue(char **argv); + static int exec_assignment(struct list *assignment_list, struct hash_map *vars) { while (assignment_list != NULL) @@ -202,10 +205,22 @@ 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) + return cond; if (cond == 0) - return execution(if_node->then_clause, vars); + { + int r = execution(if_node->then_clause, vars); + if (r == EXEC_SIGNAL_BREAK || r == EXEC_SIGNAL_CONTINUE) + return r; + return r; + } else - return execution(if_node->else_clause, vars); + { + int r = execution(if_node->else_clause, vars); + if (r == EXEC_SIGNAL_BREAK || r == EXEC_SIGNAL_CONTINUE) + return r; + return r; + } } int exec_ast_list(struct ast_list *list_node, struct hash_map *vars) @@ -216,7 +231,13 @@ int exec_ast_list(struct ast_list *list_node, struct hash_map *vars) { struct ast *child = (struct ast *)cur->data; if (!ast_is_void(child)) - ret = execution(child, vars); + { + int child_ret = execution(child, vars); + if (child_ret == EXEC_SIGNAL_BREAK + || child_ret == EXEC_SIGNAL_CONTINUE) + return child_ret; + ret = child_ret; + } cur = cur->next; } return ret; @@ -225,16 +246,30 @@ 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) + 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); + if (right_ret == EXEC_SIGNAL_BREAK + || right_ret == EXEC_SIGNAL_CONTINUE) + return right_ret; + 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); + if (right_ret == EXEC_SIGNAL_BREAK + || right_ret == EXEC_SIGNAL_CONTINUE) + return right_ret; + return right_ret; + } return left_ret; } } @@ -279,6 +314,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; @@ -385,6 +430,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; @@ -447,6 +504,10 @@ static int try_builtin(char **argv, struct hash_map *vars) 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); if (strcmp(argv[0], "cd") == 0) diff --git a/src/execution/execution_helpers.h b/src/execution/execution_helpers.h index 1716179..530b4dc 100644 --- a/src/execution/execution_helpers.h +++ b/src/execution/execution_helpers.h @@ -4,6 +4,10 @@ #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) + 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); From 4d01199e21920b18a064da71a508322694362e12 Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 31 Jan 2026 17:06:27 +0100 Subject: [PATCH 090/104] fix(ast_function): ast_function_free does not free the ast data --- src/utils/ast/ast_function.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils/ast/ast_function.c b/src/utils/ast/ast_function.c index b0356c1..62b5016 100644 --- a/src/utils/ast/ast_function.c +++ b/src/utils/ast/ast_function.c @@ -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); } } From 17f8b918c81d086de3b1b2439e98ec1205b1995e Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 31 Jan 2026 17:29:21 +0100 Subject: [PATCH 091/104] feat(testing): more loops tests --- tests/functional/loops.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/functional/loops.sh b/tests/functional/loops.sh index b3d0b69..c4d9896 100755 --- a/tests/functional/loops.sh +++ b/tests/functional/loops.sh @@ -12,4 +12,9 @@ do echo "should be printed only once" done; +while true; +do + echo "yes" +done; + echo "tests done" From 1f4742e17b10fd099b77bec2623d6062760a2550 Mon Sep 17 00:00:00 2001 From: Jean <47366872+jean-voila@users.noreply.github.com> Date: Sat, 31 Jan 2026 18:17:43 +0100 Subject: [PATCH 092/104] feat(exec): Added the unset builtin --- src/execution/execution_helpers.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/execution/execution_helpers.c b/src/execution/execution_helpers.c index 5157290..4d1848a 100644 --- a/src/execution/execution_helpers.c +++ b/src/execution/execution_helpers.c @@ -125,6 +125,7 @@ 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) { @@ -487,6 +488,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 * @@ -500,6 +518,8 @@ 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) From 3b62c56756f465103a5784f35fb3ef168071c424 Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Sat, 31 Jan 2026 18:27:48 +0100 Subject: [PATCH 093/104] feat: exit builtin --- src/execution/execution.c | 40 +++++++++++++++---- src/execution/execution_helpers.c | 65 ++++++++++++++++++++++--------- src/execution/execution_helpers.h | 1 + 3 files changed, 81 insertions(+), 25 deletions(-) diff --git a/src/execution/execution.c b/src/execution/execution.c index 46bbfcb..fff9be2 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,52 @@ 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; 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 5157290..fb48968 100644 --- a/src/execution/execution_helpers.c +++ b/src/execution/execution_helpers.c @@ -14,6 +14,8 @@ #include "../utils/vars/vars.h" #include "execution.h" +// === Static functions + static int open_redir_file(const struct ast_redir *redir, int *flags, int *mode) { *mode = 0644; @@ -121,6 +123,30 @@ 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); @@ -144,6 +170,8 @@ static int exec_assignment(struct list *assignment_list, struct hash_map *vars) return 0; } +// === Functions + int exec_ast_command(struct ast_command *command, struct hash_map *vars) { exec_assignment(command->assignments, vars); @@ -205,20 +233,17 @@ 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) + if (cond == EXEC_SIGNAL_BREAK || cond == EXEC_SIGNAL_CONTINUE + || cond == EXEC_SIGNAL_EXIT) return cond; if (cond == 0) { int r = execution(if_node->then_clause, vars); - if (r == EXEC_SIGNAL_BREAK || r == EXEC_SIGNAL_CONTINUE) - return r; return r; } else { int r = execution(if_node->else_clause, vars); - if (r == EXEC_SIGNAL_BREAK || r == EXEC_SIGNAL_CONTINUE) - return r; return r; } } @@ -234,7 +259,8 @@ int exec_ast_list(struct ast_list *list_node, struct hash_map *vars) { int child_ret = execution(child, vars); if (child_ret == EXEC_SIGNAL_BREAK - || child_ret == EXEC_SIGNAL_CONTINUE) + || child_ret == EXEC_SIGNAL_CONTINUE + || child_ret == EXEC_SIGNAL_EXIT) return child_ret; ret = child_ret; } @@ -246,16 +272,14 @@ 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) + 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) { int right_ret = execution(ao_node->right, vars); - if (right_ret == EXEC_SIGNAL_BREAK - || right_ret == EXEC_SIGNAL_CONTINUE) - return right_ret; return right_ret; } return left_ret; @@ -265,9 +289,6 @@ int exec_ast_and_or(struct ast_and_or *ao_node, struct hash_map *vars) if (left_ret != 0) { int right_ret = execution(ao_node->right, vars); - if (right_ret == EXEC_SIGNAL_BREAK - || right_ret == EXEC_SIGNAL_CONTINUE) - return right_ret; return right_ret; } return left_ret; @@ -454,13 +475,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) @@ -509,7 +538,7 @@ static int try_builtin(char **argv, struct hash_map *vars) 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 530b4dc..ee28c3d 100644 --- a/src/execution/execution_helpers.h +++ b/src/execution/execution_helpers.h @@ -7,6 +7,7 @@ // 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); From bb7d4b772e7aa3c1af1c297860d454d1021fec20 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Sat, 31 Jan 2026 17:31:30 +0000 Subject: [PATCH 094/104] feat(expansion): parse_subshell_str and tests --- src/expansion/expansion.c | 83 ++++++++++++++++++++------- src/expansion/expansion.h | 16 ++++++ tests/unit/expansion/parse_subshell.c | 72 +++++++++++++++++++++++ tests/unit/expansion/parse_var.c | 1 - 4 files changed, 151 insertions(+), 21 deletions(-) create mode 100644 tests/unit/expansion/parse_subshell.c 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/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" From 5eed5fa65f7a965c7af6d77602693ac2d66922b0 Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 31 Jan 2026 19:25:42 +0100 Subject: [PATCH 095/104] feat: ast_subshell --- src/utils/Makefile.am | 1 + src/utils/ast/ast.h | 1 + src/utils/ast/ast_base.h | 3 ++- src/utils/ast/ast_subshell.c | 31 +++++++++++++++++++++++++++++++ src/utils/ast/ast_subshell.h | 19 +++++++++++++++++++ 5 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 src/utils/ast/ast_subshell.c create mode 100644 src/utils/ast/ast_subshell.h diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am index 17454d2..ddb862c 100644 --- a/src/utils/Makefile.am +++ b/src/utils/Makefile.am @@ -21,6 +21,7 @@ libutils_a_SOURCES = \ args/args.c \ vars/vars.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/ast/ast.h b/src/utils/ast/ast.h index 12abee5..2eac62f 100644 --- a/src/utils/ast/ast.h +++ b/src/utils/ast/ast.h @@ -15,5 +15,6 @@ #include "ast_redir.h" #include "ast_void.h" #include "ast_word.h" +#include "ast_subshell.h" #endif /* ! AST_H */ 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_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 From 45d97fcc3f852437f56dd21f8aaf3641c61c5c1f Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Sat, 31 Jan 2026 18:30:15 +0000 Subject: [PATCH 096/104] feat(utils): hash_map_free_ast --- src/utils/hash_map/hash_map.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) 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 *)) { From 19addf8e6ffbb8918b421cf968545efe1b6c409e Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Sat, 31 Jan 2026 18:51:03 +0000 Subject: [PATCH 097/104] fix: expand tests ast_create_command --- tests/unit/expansion/expand.c | 65 +++++++++++++++++------------------ 1 file changed, 32 insertions(+), 33 deletions(-) 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(); From 3e42b6fd00bb84bfc8461a92b16cbb7d1d82479e Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 31 Jan 2026 19:45:40 +0100 Subject: [PATCH 098/104] feat(parser): ast_subshell --- src/parser/grammar_basic.c | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index d86abb6..92113fe 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -372,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); From a7065a1d9f7fbd942b7c5c2870aa6a4f7b3e0851 Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 31 Jan 2026 19:54:56 +0100 Subject: [PATCH 099/104] feat: subshell total support --- src/execution/execution.c | 3 +++ src/execution/execution_helpers.c | 27 +++++++++++++++++++++++++++ src/execution/execution_helpers.h | 2 ++ src/utils/ast/ast.c | 13 +++++++------ 4 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/execution/execution.c b/src/execution/execution.c index fff9be2..28726e5 100644 --- a/src/execution/execution.c +++ b/src/execution/execution.c @@ -46,6 +46,9 @@ int execution(struct ast *ast, struct hash_map *vars) case AST_LOOP: res = exec_ast_loop(ast_get_loop(ast), vars); break; + case AST_SUBSHELL: + res = exec_ast_subshell(ast_get_subshell(ast), vars); + break; default: res = 127; break; diff --git a/src/execution/execution_helpers.c b/src/execution/execution_helpers.c index 59580e8..65054df 100644 --- a/src/execution/execution_helpers.c +++ b/src/execution/execution_helpers.c @@ -249,6 +249,33 @@ int exec_ast_if(struct ast_if *if_node, struct hash_map *vars) } } +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) { struct list *cur = list_node->children; diff --git a/src/execution/execution_helpers.h b/src/execution/execution_helpers.h index ee28c3d..8a56e18 100644 --- a/src/execution/execution_helpers.h +++ b/src/execution/execution_helpers.h @@ -14,6 +14,8 @@ 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/utils/ast/ast.c b/src/utils/ast/ast.c index 5baaf6a..d16a9cb 100644 --- a/src/utils/ast/ast.c +++ b/src/utils/ast/ast.c @@ -11,8 +11,7 @@ void ast_free(struct ast **node) { if (node == NULL || *node == NULL) { - fprintf( - stderr, + fprintf(stderr, "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; } @@ -170,4 +171,4 @@ void ast_print_dot(struct ast *ast) fprintf(dot_pipe, "}\n"); pclose(dot_pipe); } - */ \ No newline at end of file + */ From c8d028544787d57ae715b87cc5e6a24a45287498 Mon Sep 17 00:00:00 2001 From: Jean <47366872+jean-voila@users.noreply.github.com> Date: Sat, 31 Jan 2026 20:04:48 +0100 Subject: [PATCH 100/104] fix(while/until) --- src/lexer/lexer_utils.c | 2 +- src/parser/grammar_advanced.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lexer/lexer_utils.c b/src/lexer/lexer_utils.c index 1f40a69..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; diff --git a/src/parser/grammar_advanced.c b/src/parser/grammar_advanced.c index ae652a1..d1b7efa 100644 --- a/src/parser/grammar_advanced.c +++ b/src/parser/grammar_advanced.c @@ -220,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(); From c7822a2534b1635113be292594c09c18ce61941f Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 31 Jan 2026 18:31:00 +0100 Subject: [PATCH 101/104] feat(main_loop): subshell start --- src/main.c | 72 +++------------------ src/utils/Makefile.am | 1 + src/utils/main_loop/main_loop.c | 111 ++++++++++++++++++++++++++++++++ src/utils/main_loop/main_loop.h | 31 +++++++++ 4 files changed, 153 insertions(+), 62 deletions(-) create mode 100644 src/utils/main_loop/main_loop.c create mode 100644 src/utils/main_loop/main_loop.h diff --git a/src/main.c b/src/main.c index 02320b9..cf3b791 100644 --- a/src/main.c +++ b/src/main.c @@ -7,70 +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, struct lexer_context *ctx) -{ - hash_map_free(vars); - destroy_lexer_context(ctx); - return ERR_INPUT_PROCESSING; -} - -static int main_loop(struct lexer_context *ctx, 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); - - // 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); - parser_close(); - hash_map_free(&vars); - destroy_lexer_context(ctx); - - return return_code; -} - int main(int argc, char **argv) { struct hash_map *vars = vars_init(); @@ -117,7 +56,16 @@ int main(int argc, char **argv) // init lexer context struct lexer_context *ctx = calloc(1, sizeof(struct lexer_context)); + // 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/utils/Makefile.am b/src/utils/Makefile.am index ddb862c..e40612e 100644 --- a/src/utils/Makefile.am +++ b/src/utils/Makefile.am @@ -20,6 +20,7 @@ libutils_a_SOURCES = \ ast/ast_loop.c \ args/args.c \ vars/vars.c \ + main_loop/main_loop.c \ ast/ast_assignment.c \ ast/ast_subshell.c \ ast/ast_function.c 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 From 4bb3ee85cf623bd98247db2f20808c21b4114026 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Sat, 31 Jan 2026 17:35:29 +0000 Subject: [PATCH 102/104] fix: utils build error --- src/Makefile.am | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 ================ From b12733cad4ef36e5e4a5e7bc39be477f42692d8e Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 31 Jan 2026 20:52:03 +0100 Subject: [PATCH 103/104] feat(exec): export --- src/execution/execution_helpers.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/execution/execution_helpers.c b/src/execution/execution_helpers.c index 65054df..12d4e1f 100644 --- a/src/execution/execution_helpers.c +++ b/src/execution/execution_helpers.c @@ -165,6 +165,10 @@ static int exec_assignment(struct list *assignment_list, struct hash_map *vars) 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; } From cab8b0c51c01d0cde056ffeb50c37c544df616ba Mon Sep 17 00:00:00 2001 From: guillm Date: Fri, 24 Apr 2026 21:17:10 +0200 Subject: [PATCH 104/104] doc: Update README --- README.md | 47 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 15 deletions(-) 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.