From 919ea7b1ba8c90cbec6b2ddc2caafa99d6847ba5 Mon Sep 17 00:00:00 2001 From: Guillem George Date: Wed, 7 Jan 2026 20:18:11 +0100 Subject: [PATCH 001/282] fix: added header guards, and some testing purpose autotools rules --- src/Makefile.am | 49 ++++++++++++++++++++++++++----------- src/ast/ast.h | 22 +++++++++++++++++ src/execution/execution.h | 4 +++ src/expansion/expansion.h | 4 +++ src/io_backend/io_backend.h | 4 +++ src/lexer/lexer.h | 4 +++ src/{42sh.c => main.c} | 0 src/parser/parser.h | 4 +++ src/utils/Makefile.am | 11 +++++++++ 9 files changed, 88 insertions(+), 14 deletions(-) rename src/{42sh.c => main.c} (100%) create mode 100644 src/utils/Makefile.am diff --git a/src/Makefile.am b/src/Makefile.am index 564dfaa..8ecd21f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,25 +1,46 @@ # define the subdirectories SUBDIRS = \ - ast \ - parser \ - lexer \ - io_backend \ - execution \ - expansion -# + utils if needed + ast \ + parser \ + lexer \ + io_backend \ + execution \ + expansin \ + utils bin_PROGRAMS = 42sh -42sh_SOURCES = 42sh.c +42sh_SOURCES = main.c 42sh_CPPFLAGS = -I%D% 42sh_CFLAGS = -std=c99 -pedantic -Werror -Wall -Wextra -Wvla 42sh_LDADD = \ - ast/libast.a \ - parser/libparser.a \ - lexer/liblexer.a \ - io_backend/libio_backend.a \ - expansion/libexpansion.a \ - execution/libexecution.a + ast/libast.a \ + parser/libparser.a \ + lexer/liblexer.a \ + io_backend/libio_backend.a \ + expansion/libexpansion.a \ + execution/libexecution.a \ + utils/libutils.a + + +################################################# Test + +bin_PROGRAMS = 42sh_asan + +42sh_asan_SOURCES = main.c + +42sh_asan_CPPFLAGS = -I%D% + +42sh_asan_CFLAGS = -std=c99 -pedantic -Werror -Wall -Wextra -Wvla -g -fsanitize=address + +42sh_asan_LDADD = \ + ast/libast.a \ + parser/libparser.a \ + lexer/liblexer.a \ + io_backend/libio_backend.a \ + expansion/libexpansion.a \ + execution/libexecution.a \ + utils/libutils.a diff --git a/src/ast/ast.h b/src/ast/ast.h index e69de29..4c14043 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -0,0 +1,22 @@ +#ifndef AST_H +#define AST_H + + +enum ast_type +{ + AST_NULL = 0, + ATS_IF, + AST_CMD +}; + +union ast_union +{ +}; + +struct ast +{ + enum ast_type type; + union ast_union data; +}; + +#endif /* ! AST_H */ diff --git a/src/execution/execution.h b/src/execution/execution.h index e69de29..e773de0 100644 --- a/src/execution/execution.h +++ b/src/execution/execution.h @@ -0,0 +1,4 @@ +#ifndef EXECUTION_H +#define EXECUTION_H + +#endif /* ! EXECUTION_H */ diff --git a/src/expansion/expansion.h b/src/expansion/expansion.h index e69de29..b10b198 100644 --- a/src/expansion/expansion.h +++ b/src/expansion/expansion.h @@ -0,0 +1,4 @@ +#ifndef EXPANSION_H +#define EXPANSION_H + +#endif /* ! EXPANSION_H */ diff --git a/src/io_backend/io_backend.h b/src/io_backend/io_backend.h index e69de29..d202b1a 100644 --- a/src/io_backend/io_backend.h +++ b/src/io_backend/io_backend.h @@ -0,0 +1,4 @@ +#ifndef IO_BACKEND_H +#define IO_BACKEND_H + +#endif /* ! IO_BACKEND_H */ diff --git a/src/lexer/lexer.h b/src/lexer/lexer.h index e69de29..9e7cd67 100644 --- a/src/lexer/lexer.h +++ b/src/lexer/lexer.h @@ -0,0 +1,4 @@ +#ifndef LEXER_H +#define LEXER_H + +#endif /* ! LEXER_H */ diff --git a/src/42sh.c b/src/main.c similarity index 100% rename from src/42sh.c rename to src/main.c diff --git a/src/parser/parser.h b/src/parser/parser.h index e69de29..ef5a449 100644 --- a/src/parser/parser.h +++ b/src/parser/parser.h @@ -0,0 +1,4 @@ +#ifndef PARSER_H +#define PARSER_H + +#endif /* ! PARSER_H */ diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am new file mode 100644 index 0000000..dba2e51 --- /dev/null +++ b/src/utils/Makefile.am @@ -0,0 +1,11 @@ +lib_LIBRARIES = libutils.a + +# libutils_a_SOURCES = \ +# utils.c \ +# utils.h + +# libutils_a_CPPFLAGS = -I$(top_srcdir)/src + +# libutils_a_CFLAGS = -std=c99 -pedantic -Werror -Wall -Wextra -Wvla + +# noinst_LIBRARIES = libutils.a From 3bcd741d568453676643eac46ecf6749022fbc42 Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Thu, 8 Jan 2026 15:02:33 +0100 Subject: [PATCH 002/282] doc: finished --- src/io_backend/io_backend.h | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/io_backend/io_backend.h b/src/io_backend/io_backend.h index 34d85f3..0bcfdca 100644 --- a/src/io_backend/io_backend.h +++ b/src/io_backend/io_backend.h @@ -29,17 +29,14 @@ struct iob_context { */ int iob_init(struct iob_context *context); -/* TODO - * - * - * +/* @brief Closes the opened buffers and exits the modules gracefully */ -void iob_close(); +void iob_close(void); -/*i TODO - * - * +/* @brief reads at most one line of the input and stores it into *stream * + * @param stream is a pointer that will be set to a string to parse + * @return the number of read characters if positive, the error code otherwise */ ssize_t stream_read(char** stream); From e7784b8df1350f55ab3413186deb54b9f44c911c Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Thu, 8 Jan 2026 15:58:24 +0100 Subject: [PATCH 003/282] feat(tests): mini testsuite for token creation --- tests/unit/lexer/lexer_tests.c | 61 ++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 tests/unit/lexer/lexer_tests.c diff --git a/tests/unit/lexer/lexer_tests.c b/tests/unit/lexer/lexer_tests.c new file mode 100644 index 0000000..0a3ccf7 --- /dev/null +++ b/tests/unit/lexer/lexer_tests.c @@ -0,0 +1,61 @@ +#include +#include +#include +#include +#include + +#include "lexer/lexer.h" + +TestSuite(token_creation); + +Test(token_creation, basic) +{ + char *input = "Hello World"; + + char *actual = new_token(input, 5); + char *expected = "Hello"; + cr_expect(eq(str, actual, expected)); + free(actual); +} + +Test(token_creation, nul) +{ + char *input = NULL; + + char *actual = new_token(input, 5); + char *expected = NULL; + cr_expect(eq(str, actual, expected)); + free(actual); +} + +Test(token_creation, too_large) +{ + char *input = "Hel"; + + char *actual = new_token(input, 5); + char *expected = NULL; + cr_expect(eq(str, actual, expected)); + free(actual); +} + +Test(token_creation, empty) +{ + char *input = ""; + + char *actual = new_token(input, 5); + char *expected = NULL; + cr_expect(eq(str, actual, expected)); + free(actual); +} + +Test(token_creation, basic_long) +{ + char *input = "Hello World! This project is a mini shell, I love BIG G."; + + char *actual = new_token(input, 42); + char *expected = calloc(42 + 1, sizeof(char)); + strncpy(input, expected, 42); + cr_expect(eq(str, actual, expected)); + free(actual); + free(expected); +} From 464dbe8e1797e35c2e8649e5e28bae0bde426d20 Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Thu, 8 Jan 2026 16:15:29 +0100 Subject: [PATCH 004/282] feat: finished io_backend --- src/io_backend/io_backend.c | 88 ++++++++++++++++++++++++++++++++----- src/io_backend/io_backend.h | 24 ++++++++-- 2 files changed, 98 insertions(+), 14 deletions(-) diff --git a/src/io_backend/io_backend.c b/src/io_backend/io_backend.c index ee6b09b..7e7c1c3 100644 --- a/src/io_backend/io_backend.c +++ b/src/io_backend/io_backend.c @@ -1,34 +1,102 @@ #include "io_backend.h" #include +#include +#include + +// === Static variables static struct iob_context context; -static FILE *input; +static FILE *input = NULL; +static char *stream_buf = NULL; +static size_t stream_buf_size = 0; +static enum iob_state state = IOB_STATE_NOT_INITIALIZED; + +// === Functions int iob_init(struct iob_context *ctx) { + if (state != IOB_STATE_NOT_INITIALIZED) + return IOB_ERROR_MODULE_NOT_INITIALIZED; + context = *ctx; switch (context.mode) { - IOB_MODE_STDIN: + case IOB_MODE_STDIN: input = stdin; + state = IOB_STATE_READY; return 0; - IOB_MODE_SCRIPT: + case IOB_MODE_SCRIPT: if (context.args == NULL) - return -2; + return IOB_ERROR_BAD_ARG; input = fopen(context.args, "r"); if (input == NULL) - return -4; + return IOB_ERROR_CANNOT_OPEN_FILE; + state = IOB_STATE_READY; + return 0; - IOB_MODE_CMD: + case IOB_MODE_CMD: if (context.args != NULL) - return -2; - else - return 0; + return IOB_ERROR_BAD_ARG; + state = IOB_STATE_READY; + return 0; default: - return -1; + return IOB_ERROR_BAD_ARG; + } +} + +void iob_close(void) +{ + fclose(input); + if ((context.mode == IOB_MODE_STDIN || context.mode == IOB_MODE_SCRIPT) + && stream_buf != NULL) + { + free(stream_buf); + stream_buf_size = 0; + } + state = IOB_STATE_NOT_INITIALIZED; +} + +ssize_t stream_read(char **stream) +{ + // Check args + if (stream == NULL) + return IOB_ERROR_BAD_ARG; + + // Check env + if (state == IOB_STATE_NOT_INITIALIZED) + return IOB_ERROR_MODULE_NOT_INITIALIZED; + if (state == IOB_STATE_FINISHED) + return 0; + if (state == IOB_STATE_ERROR) + return IOB_ERROR_GENERIC; + + // Use input + if (context.mode == IOB_MODE_STDIN || context.mode == IOB_MODE_SCRIPT) + { + ssize_t nread = getline(&stream_buf, &stream_buf_size, input); + if (nread == -1) + { + state = IOB_STATE_FINISHED; + return 0; + } + else if (nread < 0) + state = IOB_STATE_ERROR; + + return nread; + } + // Use args + else if (context.mode == IOB_MODE_CMD) + { + *stream = context.args; + return strlen(context.args); + } + else + { + *stream = NULL; + return IOB_ERROR_GENERIC; } } diff --git a/src/io_backend/io_backend.h b/src/io_backend/io_backend.h index 0bcfdca..c2079f0 100644 --- a/src/io_backend/io_backend.h +++ b/src/io_backend/io_backend.h @@ -3,6 +3,13 @@ #include +// Error codes +#define IOB_ERROR_GENERIC -1 +#define IOB_ERROR_BAD_ARG -2 +#define IOB_ERROR_MODULE_NOT_INITIALIZED -3 +#define IOB_ERROR_MODULE_ALREADY_INITALIZED -4 +#define IOB_ERROR_CANNOT_OPEN_FILE -5 + enum iob_mode { IOB_MODE_NULL = 0, IOB_MODE_STDIN, @@ -10,11 +17,18 @@ enum iob_mode { IOB_MODE_CMD }; +enum iob_state { + IOB_STATE_NOT_INITIALIZED, + IOB_STATE_READY, + IOB_STATE_FINISHED, + IOB_STATE_ERROR +}; + /* @struct iob_context * @var mode * @var args contains - * the script name when mode is set to IOB_SCRIPT, - * the command to execute when mode is set to IOB_CMD, + * the script name when mode is set to IOB_MODE_SCRIPT, + * the command to execute when mode is set to IOB_MODE_CMD */ struct iob_context { enum iob_mode mode; @@ -29,14 +43,16 @@ struct iob_context { */ int iob_init(struct iob_context *context); -/* @brief Closes the opened buffers and exits the modules gracefully +/* @brief Closes the opened buffers and the module gracefully */ void iob_close(void); /* @brief reads at most one line of the input and stores it into *stream * * @param stream is a pointer that will be set to a string to parse - * @return the number of read characters if positive, the error code otherwise + * @return the number of read characters if positive, + * zero if finished (reached EOF), + * the error code otherwise */ ssize_t stream_read(char** stream); From 453a8ab0da0b9b29e95d8efc638bb25aca61a64d Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Thu, 8 Jan 2026 16:32:48 +0100 Subject: [PATCH 005/282] feat(lexer): usefull functions --- src/lexer/lexer.c | 67 +++++++++++++++++++++++++++++++++++++++++++++++ src/lexer/lexer.h | 36 +++++++++++++++++++++++++ 2 files changed, 103 insertions(+) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index e69de29..5b03c18 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -0,0 +1,67 @@ +#include "lexer.h" + +#include + +#include "io_backend/io_backend.h" + +static char *end_last_token; +static ssize_t remaining_chars; + +char *new_token(char *begin, size_t size) +{ + char *res = calloc(size + 1, sizeof(char)); + if (res == NULL) + return NULL; + strncpy(res, begin, size); + return res; +} + +char *stream_init(void) +{ + char *stream; + + if (remaining_chars == 0) + { + remaining_chars = stream_read(&stream); + } + else + { + stream = end_last_token; + } + + return stream; +} + +char *get_token(void) +{ + char *stream = stream_init(); + + bool inquotes = false; + ssize_t i = 0; + + while (i < remaining_chars) + { + switch (stream[i]) + { + case '\'': + inquotes = !inquotes; + break; + + case ' ' | '\n' | '\t': + if (inquotes) + break; + else + { + // token creation + // skip blank char + // exit from loop + char *token = new_token(stream, i); + } + default: + break; + } + i++; + } + + remaining_chars -= i; +} diff --git a/src/lexer/lexer.h b/src/lexer/lexer.h index e69de29..d9a484c 100644 --- a/src/lexer/lexer.h +++ b/src/lexer/lexer.h @@ -0,0 +1,36 @@ +#ifndef LEXER_H +#define LEXER_H + +/* @return: char*, the next token + * + */ +char *get_token(void); + +/* + * @warning: NOT IMPLEMENTED. + * + * @note: maybe usefull for subshells. + */ + +char *get_token_str(void); + +/* + * @brief: return a newly allocated token. + * This token contains [size] chars, starting from [begin]. + * + * @return: NULL on error, null-terminated char* otherwise. + * + */ +char *new_token(char *begin, ssize_t size); + +/* + * @brief: checks if the stream used for the last token creation is empty. + * If it is, it calls stream_read() from IO_backend, + * and sets [remaing_chars]. + * If not, it starts from the end of the last token. + * + * @return: char* stream from which we tokenise. + */ +char *stream_init(void); + +#endif /* LEXER_H */ From 78967f3ef62438e9a3abaaa69704338e3b01757b Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Thu, 8 Jan 2026 16:59:37 +0100 Subject: [PATCH 006/282] test: iob_init and iob_close tests (+ small typo fix in src) --- src/io_backend/io_backend.c | 2 +- src/io_backend/io_backend.h | 2 +- tests/unit/io_backend/io_backend.c | 122 +++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 tests/unit/io_backend/io_backend.c diff --git a/src/io_backend/io_backend.c b/src/io_backend/io_backend.c index 7e7c1c3..97fe974 100644 --- a/src/io_backend/io_backend.c +++ b/src/io_backend/io_backend.c @@ -17,7 +17,7 @@ static enum iob_state state = IOB_STATE_NOT_INITIALIZED; int iob_init(struct iob_context *ctx) { if (state != IOB_STATE_NOT_INITIALIZED) - return IOB_ERROR_MODULE_NOT_INITIALIZED; + return IOB_ERROR_MODULE_ALREADY_INITIALIZED; context = *ctx; diff --git a/src/io_backend/io_backend.h b/src/io_backend/io_backend.h index c2079f0..cd5ec8a 100644 --- a/src/io_backend/io_backend.h +++ b/src/io_backend/io_backend.h @@ -7,7 +7,7 @@ #define IOB_ERROR_GENERIC -1 #define IOB_ERROR_BAD_ARG -2 #define IOB_ERROR_MODULE_NOT_INITIALIZED -3 -#define IOB_ERROR_MODULE_ALREADY_INITALIZED -4 +#define IOB_ERROR_MODULE_ALREADY_INITIALIZED -4 #define IOB_ERROR_CANNOT_OPEN_FILE -5 enum iob_mode { diff --git a/tests/unit/io_backend/io_backend.c b/tests/unit/io_backend/io_backend.c new file mode 100644 index 0000000..8640cdb --- /dev/null +++ b/tests/unit/io_backend/io_backend.c @@ -0,0 +1,122 @@ +#include +#include + +#include + +#include + +TestSuite(IO_Backend); + +// IOB Init + +Test(IO_Backend, init_null) +{ + struct iob_context ctx = { + .iob_mode = IOB_MODE_NULL; + .args = NULL; + }; + int actual = iob_init(ctx); + int expected = IOB_ERROR_BAD_ARG; + cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); +} + +Test(IO_Backend, init_stdin) +{ + struct iob_context ctx = { + .iob_mode = IOB_MODE_STDIN; + .args = NULL; + }; + int actual = iob_init(ctx); + int expected = 0; + cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); + iob_close(); +} + +// WARNING: this one could fail because of iob_close in the previous test +// Same applies for other tests +Test(IO_Backend, init_script) +{ + char* script_name = "script.tmp" + struct iob_context ctx = { + .iob_mode = IOB_MODE_SCRIPT; + .args = script_name; + }; + // Create file + FILE* f = fopen(script_name, "w"); + fclose(f); + + int actual = iob_init(ctx); + int expected = 0; + cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); + iob_close(); + remove(script_name); +} + +Test(IO_Backend, init_script_not_a_file) +{ + char* script_name = "not_a_file.tmp" + struct iob_context ctx = { + .iob_mode = IOB_MODE_SCRIPT; + .args = script_name; + }; + int actual = iob_init(ctx); + int expected = IOB_ERROR_CANNOT_OPEN_FILE; + cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); +} + +Test(IO_Backend, init_script_null) +{ + struct iob_context ctx = { + .iob_mode = IOB_MODE_SCRIPT; + .args = NULL; + }; + int actual = iob_init(ctx); + int expected = IOB_ERROR_CANNOT_OPEN_FILE; + cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); +} + +Test(IO_Backend, init_cmd) +{ + char* cmd = "iamacommand --yesido" + struct iob_context ctx = { + .iob_mode = IOB_MODE_CMD; + .args = cmd; + }; + int actual = iob_init(ctx); + int expected = 0; + cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); + iob_close(); +} + +Test(IO_Backend, init_cmd_null) +{ + struct iob_context ctx = { + .iob_mode = IOB_MODE_CMD; + .args = NULL; + }; + int actual = iob_init(ctx); + int expected = IOB_ERROR_BAD_ARG; + cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); +} + +Test(IO_Backend, init_already_init) +{ + char* cmd = "iamacommand --yesido" + struct iob_context ctx = { + .iob_mode = IOB_MODE_CMD; + .args = cmd; + }; + iob_init(ctx); + int actual = iob_init(ctx); + int expected = IOB_ERROR_ALREADY_INITIALIZED; + cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); + iob_close(); +} + +Test(IO_Backend, close_not_init) +{ + iob_close(); // Shouldn't do anything +} + +// IOB Stream +// TODO From af1af49ae177e60d04abda494589bdefc0ef04d8 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Thu, 8 Jan 2026 17:03:52 +0100 Subject: [PATCH 007/282] feat(utils): string utils + corresponding tests --- src/utils/string_utils/string_utils.c | 16 +++++ src/utils/string_utils/string_utils.h | 10 +++ tests/unit/lexer/lexer_tests.c | 39 +++++------ tests/unit/utils/utils_tests.c | 97 +++++++++++++++++++++++++++ 4 files changed, 140 insertions(+), 22 deletions(-) create mode 100644 src/utils/string_utils/string_utils.c create mode 100644 src/utils/string_utils/string_utils.h create mode 100644 tests/unit/utils/utils_tests.c diff --git a/src/utils/string_utils/string_utils.c b/src/utils/string_utils/string_utils.c new file mode 100644 index 0000000..6e07f7e --- /dev/null +++ b/src/utils/string_utils/string_utils.c @@ -0,0 +1,16 @@ +#include "utils/string_utils/string_utils.h" + +ssize_t skip_blanks(char **str) +{ + if (str == NULL || *str == NULL) + { + return 0; + } + ssize_t skipped = 0; + while (str[skipped] != '\0' && !isblank(str[skipped])) + { + skipped++; + } + *str += skipped; + return skipped; +} diff --git a/src/utils/string_utils/string_utils.h b/src/utils/string_utils/string_utils.h new file mode 100644 index 0000000..3fee923 --- /dev/null +++ b/src/utils/string_utils/string_utils.h @@ -0,0 +1,10 @@ +#ifndef STRING_UTILS_H +#define STRING_UTILS_H + +/* + * @brief: skips blank characters at the beginning of [str]. + * @return: number of characters skipped. + */ +ssize_t skip_blanks(char **str); + +#endif /* STRING_UTILS_H */ diff --git a/tests/unit/lexer/lexer_tests.c b/tests/unit/lexer/lexer_tests.c index 0a3ccf7..d9046d4 100644 --- a/tests/unit/lexer/lexer_tests.c +++ b/tests/unit/lexer/lexer_tests.c @@ -1,8 +1,9 @@ #include #include -#include +#include #include #include +#include #include "lexer/lexer.h" @@ -10,50 +11,44 @@ TestSuite(token_creation); Test(token_creation, basic) { - char *input = "Hello World"; + char input[] = "Hello World"; - char *actual = new_token(input, 5); - char *expected = "Hello"; + char actual[] = new_token(input, 5); + char expected[] = "Hello"; cr_expect(eq(str, actual, expected)); free(actual); } Test(token_creation, nul) { - char *input = NULL; + char input[] = NULL; - char *actual = new_token(input, 5); - char *expected = NULL; - cr_expect(eq(str, actual, expected)); - free(actual); + char actual[] = new_token(input, 5); + cr_expect(actual == NULL); } Test(token_creation, too_large) { - char *input = "Hel"; + char input[] = "Hel"; - char *actual = new_token(input, 5); - char *expected = NULL; - cr_expect(eq(str, actual, expected)); - free(actual); + char actual[] = new_token(input, 5); + cr_expect(actual == NULL); } Test(token_creation, empty) { - char *input = ""; + char input[] = ""; - char *actual = new_token(input, 5); - char *expected = NULL; - cr_expect(eq(str, actual, expected)); - free(actual); + char actual[] = new_token(input, 5); + cr_expect(actual == NULL); } Test(token_creation, basic_long) { - char *input = "Hello World! This project is a mini shell, I love BIG G."; + char input[] = "Hello World! This project is a mini shell, I love BIG G."; - char *actual = new_token(input, 42); - char *expected = calloc(42 + 1, sizeof(char)); + char actual[] = new_token(input, 42); + char expected[] = calloc(42 + 1, sizeof(char)); strncpy(input, expected, 42); cr_expect(eq(str, actual, expected)); free(actual); diff --git a/tests/unit/utils/utils_tests.c b/tests/unit/utils/utils_tests.c new file mode 100644 index 0000000..1076aab --- /dev/null +++ b/tests/unit/utils/utils_tests.c @@ -0,0 +1,97 @@ +#include +#include +#include +#include +#include +#include + +#include "utils/string_utils/string_utils.h" + +TestSuite(string_utils); + +Test(string_utils, skipblank_basic) +{ + char input[] = " Hello World"; + char expected_str[] = "Hello World"; + + ssize_t actual = skip_blanks(input); + ssize_t expected = 2; + cr_expect(eq(str, input, expected_str)); + cr_expect(actual == expected); +} + +Test(string_utils, skipblank_noblank) +{ + char input[] = "Hello World"; + char expected_str[] = "Hello World"; + + ssize_t actual = skip_blanks(input); + ssize_t expected = 0; + cr_expect(eq(str, input, expected_str)); + cr_expect(actual == expected); +} + +Test(string_utils, skipblank_tab) +{ + char input[] = "\tHello World"; + char expected_str[] = "Hello World"; + + ssize_t actual = skip_blanks(input); + ssize_t expected = 1; + cr_expect(eq(str, input, expected_str)); + cr_expect(actual == expected); +} + +Test(string_utils, skipblank_space_tab) +{ + char input[] = " \tHello World"; + char expected_str[] = "Hello World"; + + ssize_t actual = skip_blanks(input); + ssize_t expected = 2; + cr_expect(eq(str, input, expected_str)); + cr_expect(actual == expected); +} + +Test(string_utils, skipblank_2tab_1space) +{ + char input[] = "\t \tHello World"; + char expected_str[] = "Hello World"; + + ssize_t actual = skip_blanks(input); + ssize_t expected = 3; + cr_expect(eq(str, input, expected_str)); + cr_expect(actual == expected); +} + +Test(string_utils, skipblank_a_lot) +{ + char input[] = "\t \t \tHello World"; + char expected_str[] = "Hello World"; + + ssize_t actual = skip_blanks(input); + ssize_t expected = 8; + cr_expect(eq(str, input, expected_str)); + cr_expect(actual == expected); +} + +Test(string_utils, skipblank_newline) +{ + char input[] = "\nHello World"; + char expected_str[] = "\nHello World"; + + ssize_t actual = skip_blanks(input); + ssize_t expected = 0; + cr_expect(eq(str, input, expected_str)); + cr_expect(actual == expected); +} + +Test(string_utils, skipblank_nul) +{ + char *input = NULL; + + ssize_t actual = skip_blanks(input); + ssize_t expected = 0; + cr_expect(input == NULL); + cr_expect(actual == expected); +} From 7d7acbea67b3d093ceddb02dd4a66ef079c17f3b Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Thu, 8 Jan 2026 18:17:02 +0100 Subject: [PATCH 008/282] feat: ast structs deinitions --- src/ast/ast.h | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/ast/ast.h b/src/ast/ast.h index 4c14043..8bc510e 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -1,22 +1,37 @@ #ifndef AST_H #define AST_H +#include "lists.h" enum ast_type { - AST_NULL = 0, + AST_END = 0, ATS_IF, AST_CMD }; -union ast_union +union ast_node { + struct ast_if; + struct ast_cmd; }; struct ast { enum ast_type type; - union ast_union data; + union ast_node data; +}; + +struct ast_if +{ + struct ast* condition; + struct ast* then_clause; + struct ast* else_clause; +}; + +struct ast_cmd +{ + struct list* cmd; }; #endif /* ! AST_H */ From 65a033c5f65ab9bb49b2e6f0a9f1b99182a9b6a4 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Thu, 8 Jan 2026 18:34:08 +0100 Subject: [PATCH 009/282] feat(autotools): fucking autotools meme aurelien galere --- configure.ac | 9 ++++++++- src/Makefile.am | 22 +++++++++------------- src/lexer/Makefile.am | 2 +- src/parser/Makefile.am | 2 +- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/configure.ac b/configure.ac index 4a89a36..d5a6154 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,9 @@ # Init the 42sh project AC_INIT([42sh], [1.0], [matteo.flebus@epita.fr]) +FLAGS="-std=c99 -pedantic -Werror -Wall -Wextra -Wvla" +AC_SUBST([FLAGS]) + # Setup Automake AM_INIT_AUTOMAKE([subdir-objects] [foreign]) @@ -21,10 +24,14 @@ AC_PROG_CC # List Makefiles in subdirectories AC_CONFIG_FILES([ - Makefile src/Makefile src/ast/Makefile src/parser/Makefile src/lexer/Makefile + src/io_backend/Makefile + src/execution/Makefile + src/expansion/Makefile + src/utils/Makefile ]) + # TODO add tests Makefile here AC_OUTPUT diff --git a/src/Makefile.am b/src/Makefile.am index 8ecd21f..3e0ecf7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,14 +1,13 @@ # define the subdirectories SUBDIRS = \ - ast \ parser \ lexer \ io_backend \ execution \ - expansin \ + expansion \ utils -bin_PROGRAMS = 42sh +bin_PROGRAMS = 42sh 42sh_asan 42sh_SOURCES = main.c @@ -17,7 +16,6 @@ bin_PROGRAMS = 42sh 42sh_CFLAGS = -std=c99 -pedantic -Werror -Wall -Wextra -Wvla 42sh_LDADD = \ - ast/libast.a \ parser/libparser.a \ lexer/liblexer.a \ io_backend/libio_backend.a \ @@ -28,8 +26,6 @@ bin_PROGRAMS = 42sh ################################################# Test -bin_PROGRAMS = 42sh_asan - 42sh_asan_SOURCES = main.c 42sh_asan_CPPFLAGS = -I%D% @@ -37,10 +33,10 @@ bin_PROGRAMS = 42sh_asan 42sh_asan_CFLAGS = -std=c99 -pedantic -Werror -Wall -Wextra -Wvla -g -fsanitize=address 42sh_asan_LDADD = \ - ast/libast.a \ - parser/libparser.a \ - lexer/liblexer.a \ - io_backend/libio_backend.a \ - expansion/libexpansion.a \ - execution/libexecution.a \ - utils/libutils.a + ast/lib_asan_ast.a \ + parser/lib_asan_parser.a \ + lexer/lib_asan_lexer.a \ + io_backend/lib_asan_io_backend.a \ + expansion/lib_asan_expansion.a \ + execution/lib_asan_execution.a \ + utils/lib_asan_utils.a diff --git a/src/lexer/Makefile.am b/src/lexer/Makefile.am index a113221..26f7797 100644 --- a/src/lexer/Makefile.am +++ b/src/lexer/Makefile.am @@ -1,4 +1,4 @@ -lib_LIBRARIES = liblexer.a +lib_LIBRARIES = liblexer.a lib_asan_lexer.a liblexer_a_SOURCES = \ lexer.c \ diff --git a/src/parser/Makefile.am b/src/parser/Makefile.am index 51c8cb6..4263c5a 100644 --- a/src/parser/Makefile.am +++ b/src/parser/Makefile.am @@ -6,6 +6,6 @@ libparser_a_SOURCES = \ libparser_a_CPPFLAGS = -I$(top_srcdir)/src -libparser_a_CFLAGS = -std=c99 -pedantic -Werror -Wall -Wextra -Wvla +libparser_a_CFLAGS = $(FLAGS) noinst_LIBRARIES = libparser.a From 0f484b371bd04101081e5c5896395b6df03040e2 Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Thu, 8 Jan 2026 18:36:07 +0100 Subject: [PATCH 010/282] fix: typo --- src/ast/ast.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ast/ast.h b/src/ast/ast.h index 8bc510e..5bfd8df 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -6,7 +6,7 @@ enum ast_type { AST_END = 0, - ATS_IF, + AST_IF, AST_CMD }; From b54488c17f912de99a17d279795f0219b097e65f Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Fri, 9 Jan 2026 14:27:27 +0100 Subject: [PATCH 011/282] feat(autotools): explainations in README --- README.md | 14 +++++++++++--- configure.ac | 7 +++++-- src/Makefile.am | 32 ++++++++++++++------------------ src/ast/Makefile.am | 2 -- src/execution/Makefile.am | 2 -- src/expansion/Makefile.am | 2 -- src/io_backend/Makefile.am | 2 -- src/lexer/Makefile.am | 4 +--- src/parser/Makefile.am | 2 -- src/utils/Makefile.am | 11 ++++------- 10 files changed, 35 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 87fe6e2..bbab8c7 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,20 @@ TODO ### Build - -TODO +run this command: + autoreconf --force --verbose --install ### Test +run this command: + ./configure CFLAGS='-std=c99 -Werror -Wall -Wextra -Wvla' +then: + make -TODO +#### asan +run this command: + ./configure CFLAGS='-std=c99 -Werror -Wall -Wextra -Wvla -g -fsanitize=address' +then: + make check ## Authors diff --git a/configure.ac b/configure.ac index d5a6154..f48e3de 100644 --- a/configure.ac +++ b/configure.ac @@ -1,8 +1,8 @@ # Init the 42sh project AC_INIT([42sh], [1.0], [matteo.flebus@epita.fr]) -FLAGS="-std=c99 -pedantic -Werror -Wall -Wextra -Wvla" -AC_SUBST([FLAGS]) +# FLAGS="-std=c99 -pedantic -Werror -Wall -Wextra -Wvla" +# AC_SUBST([FLAGS]) # Setup Automake AM_INIT_AUTOMAKE([subdir-objects] [foreign]) @@ -32,6 +32,9 @@ AC_CONFIG_FILES([ src/execution/Makefile src/expansion/Makefile src/utils/Makefile + tests/Makefile + tests/unit/Makefile + tests/unit/utils/Makefile ]) # TODO add tests Makefile here AC_OUTPUT diff --git a/src/Makefile.am b/src/Makefile.am index 3e0ecf7..411f4cb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -7,14 +7,12 @@ SUBDIRS = \ expansion \ utils -bin_PROGRAMS = 42sh 42sh_asan +bin_PROGRAMS = 42sh 42sh_SOURCES = main.c 42sh_CPPFLAGS = -I%D% -42sh_CFLAGS = -std=c99 -pedantic -Werror -Wall -Wextra -Wvla - 42sh_LDADD = \ parser/libparser.a \ lexer/liblexer.a \ @@ -25,18 +23,16 @@ bin_PROGRAMS = 42sh 42sh_asan ################################################# Test - -42sh_asan_SOURCES = main.c - -42sh_asan_CPPFLAGS = -I%D% - -42sh_asan_CFLAGS = -std=c99 -pedantic -Werror -Wall -Wextra -Wvla -g -fsanitize=address - -42sh_asan_LDADD = \ - ast/lib_asan_ast.a \ - parser/lib_asan_parser.a \ - lexer/lib_asan_lexer.a \ - io_backend/lib_asan_io_backend.a \ - expansion/lib_asan_expansion.a \ - execution/lib_asan_execution.a \ - utils/lib_asan_utils.a +# +#42sh_asan_SOURCES = main.c +# +#42sh_asan_CPPFLAGS = -I%D% +# +#42sh_asan_LDADD = \ +# ast/lib_asan_ast.a \ +# parser/lib_asan_parser.a \ +# lexer/lib_asan_lexer.a \ +# io_backend/lib_asan_io_backend.a \ +# expansion/lib_asan_expansion.a \ +# execution/lib_asan_execution.a \ +# utils/lib_asan_utils.a diff --git a/src/ast/Makefile.am b/src/ast/Makefile.am index 9df484e..fa41e3d 100644 --- a/src/ast/Makefile.am +++ b/src/ast/Makefile.am @@ -6,6 +6,4 @@ libast_a_SOURCES = \ libast_a_CPPFLAGS = -I$(top_srcdir)/src -libast_a_CFLAGS = -std=c99 -pedantic -Werror -Wall -Wextra -Wvla - noinst_LIBRARIES = libast.a diff --git a/src/execution/Makefile.am b/src/execution/Makefile.am index f83e3ba..5a71cbb 100644 --- a/src/execution/Makefile.am +++ b/src/execution/Makefile.am @@ -6,6 +6,4 @@ libexecution_a_SOURCES = \ libexecution_a_CPPFLAGS = -I$(top_srcdir)/src -libexecution_a_CFLAGS = -std=c99 -pedantic -Werror -Wall -Wextra -Wvla - noinst_LIBRARIES = libexecution.a diff --git a/src/expansion/Makefile.am b/src/expansion/Makefile.am index 5074622..065f225 100644 --- a/src/expansion/Makefile.am +++ b/src/expansion/Makefile.am @@ -6,6 +6,4 @@ libexpansion_a_SOURCES = \ libexpansion_a_CPPFLAGS = -I$(top_srcdir)/src -libexpansion_a_CFLAGS = -std=c99 -pedantic -Werror -Wall -Wextra -Wvla - noinst_LIBRARIES = libexpansion.a diff --git a/src/io_backend/Makefile.am b/src/io_backend/Makefile.am index 7f056b2..0cfadff 100644 --- a/src/io_backend/Makefile.am +++ b/src/io_backend/Makefile.am @@ -6,6 +6,4 @@ libio_backend_a_SOURCES = \ libio_backend_a_CPPFLAGS = -I$(top_srcdir)/src -libio_backend_a_CFLAGS = -std=c99 -pedantic -Werror -Wall -Wextra -Wvla - noinst_LIBRARIES = libio_backend.a diff --git a/src/lexer/Makefile.am b/src/lexer/Makefile.am index 26f7797..dd11411 100644 --- a/src/lexer/Makefile.am +++ b/src/lexer/Makefile.am @@ -1,4 +1,4 @@ -lib_LIBRARIES = liblexer.a lib_asan_lexer.a +lib_LIBRARIES = liblexer.a liblexer_a_SOURCES = \ lexer.c \ @@ -6,6 +6,4 @@ liblexer_a_SOURCES = \ liblexer_a_CPPFLAGS = -I$(top_srcdir)/src -liblexer_a_CFLAGS = -std=c99 -pedantic -Werror -Wall -Wextra -Wvla - noinst_LIBRARIES = liblexer.a diff --git a/src/parser/Makefile.am b/src/parser/Makefile.am index 4263c5a..dd13901 100644 --- a/src/parser/Makefile.am +++ b/src/parser/Makefile.am @@ -6,6 +6,4 @@ libparser_a_SOURCES = \ libparser_a_CPPFLAGS = -I$(top_srcdir)/src -libparser_a_CFLAGS = $(FLAGS) - noinst_LIBRARIES = libparser.a diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am index dba2e51..5b72df3 100644 --- a/src/utils/Makefile.am +++ b/src/utils/Makefile.am @@ -1,11 +1,8 @@ lib_LIBRARIES = libutils.a -# libutils_a_SOURCES = \ -# utils.c \ -# utils.h +libutils_a_SOURCES = \ + string_utils.c -# libutils_a_CPPFLAGS = -I$(top_srcdir)/src +libutils_a_CPPFLAGS = -I$(top_srcdir)/src -# libutils_a_CFLAGS = -std=c99 -pedantic -Werror -Wall -Wextra -Wvla - -# noinst_LIBRARIES = libutils.a +noinst_LIBRARIES = libutils.a From c341b62ec6ed1bffc93d5471ccabaa2e242ed2d5 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Fri, 9 Jan 2026 14:34:22 +0100 Subject: [PATCH 012/282] fix(configure.ac): commented Makefiles for testing --- configure.ac | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/configure.ac b/configure.ac index f48e3de..f9684ed 100644 --- a/configure.ac +++ b/configure.ac @@ -32,9 +32,9 @@ AC_CONFIG_FILES([ src/execution/Makefile src/expansion/Makefile src/utils/Makefile - tests/Makefile - tests/unit/Makefile - tests/unit/utils/Makefile ]) # TODO add tests Makefile here + # tests/Makefile + # tests/unit/Makefile + # tests/unit/utils/Makefile AC_OUTPUT From e8d6ac902cfa498d066ddc8c4281d9bf6dee761a Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Fri, 9 Jan 2026 13:46:37 +0000 Subject: [PATCH 013/282] feat: single quotes expansion --- src/ast/ast.h | 22 +++++++------ src/expansion/expansion.c | 68 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 10 deletions(-) diff --git a/src/ast/ast.h b/src/ast/ast.h index 5bfd8df..b3be544 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -1,7 +1,7 @@ #ifndef AST_H #define AST_H -#include "lists.h" +#include "../utils/lists/lists.h" enum ast_type { @@ -10,28 +10,30 @@ enum ast_type AST_CMD }; -union ast_node +struct ast_cmd { - struct ast_if; - struct ast_cmd; + struct list *cmd; }; +union ast_node; + struct ast { enum ast_type type; - union ast_node data; + union ast_node *data; }; struct ast_if { - struct ast* condition; - struct ast* then_clause; - struct ast* else_clause; + struct ast *condition; + struct ast *then_clause; + struct ast *else_clause; }; -struct ast_cmd +union ast_node { - struct list* cmd; + struct ast_if ast_if; + struct ast_cmd ast_cmd; }; #endif /* ! AST_H */ diff --git a/src/expansion/expansion.c b/src/expansion/expansion.c index e69de29..01fa248 100644 --- a/src/expansion/expansion.c +++ b/src/expansion/expansion.c @@ -0,0 +1,68 @@ +#include +#include +#include +#include +#include + +#include "../ast/ast.h" + +struct ast_cmd *expand(struct ast_cmd *cmd) +{ + if (cmd == NULL) + return NULL; + + bool in_quotes = false; + char *str; + size_t len; + struct list *l = cmd->cmd; + + while (l != NULL) + { + in_quotes = false; + str = (char *)l->data; + len = strlen(str); + + for (size_t i = 0; str[i] != '\0'; i++) + { + if (in_quotes) + { + // do nothing + } + else if (str[i] == '\'') + { + in_quotes = !in_quotes; + memmove(&str[i], &str[i + 1], strlen(&str[i + 1]) + 1); + } + // else if (str[i] == '$' && str[i + 1] != '\0' && str[i + 1] != '$' + // && str[i + 1] != ' ') + else if (str[i] == '$' && isalnum(str[i + 1])) + { + // variable expansion + } + } + + if (in_quotes) + { + // error: quote not closed + } + + if (len != strlen(str)) + { + char *new_str = realloc(str, strlen(str) + 1); + if (new_str == NULL) + { + // error: realloc fail + } + l->data = new_str; + } + + l = l->next; + } + return cmd; +} + +int main() +{ + printf("Expansion module test\n"); + return 0; +} From 3e66d4b959b8801ea27340ddd7e56ceaea3ebaee Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Fri, 9 Jan 2026 16:05:41 +0100 Subject: [PATCH 014/282] refactor: moved ast module to utils --- src/{ => utils}/ast/Makefile.am | 0 src/{ => utils}/ast/ast.c | 0 src/{ => utils}/ast/ast.h | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename src/{ => utils}/ast/Makefile.am (100%) rename src/{ => utils}/ast/ast.c (100%) rename src/{ => utils}/ast/ast.h (100%) diff --git a/src/ast/Makefile.am b/src/utils/ast/Makefile.am similarity index 100% rename from src/ast/Makefile.am rename to src/utils/ast/Makefile.am diff --git a/src/ast/ast.c b/src/utils/ast/ast.c similarity index 100% rename from src/ast/ast.c rename to src/utils/ast/ast.c diff --git a/src/ast/ast.h b/src/utils/ast/ast.h similarity index 100% rename from src/ast/ast.h rename to src/utils/ast/ast.h From d36b5c9fc8bb80a53a9b753736cc5918565fbd24 Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Fri, 9 Jan 2026 16:32:15 +0100 Subject: [PATCH 015/282] docs: Signature and doc of the parser functions --- src/parser/parser.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/parser/parser.h b/src/parser/parser.h index ef5a449..759d4fd 100644 --- a/src/parser/parser.h +++ b/src/parser/parser.h @@ -1,4 +1,20 @@ #ifndef PARSER_H #define PARSER_H +#include "utils/ast/ast.h" + +/* @brief Builds the AST representation of the next command to execute. + * + * @return Returns the AST representation of the next command to execute. + * If there is no command left to execute, retuns an AST_END node. + */ +struct ast* get_ast(); + +/* @brief Builds the AST representation of the given command string. + * + * @return Returns the AST representation of the given command string. + * Returns an AST_END node if the given command is empty. + */ +struct ast* get_ast_str(char* command); + #endif /* ! PARSER_H */ From 2f2cdda801f4c98987ad55f53632193965a6c2a9 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Fri, 9 Jan 2026 16:17:38 +0000 Subject: [PATCH 016/282] fix: removed temp main --- src/expansion/expansion.c | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/expansion/expansion.c b/src/expansion/expansion.c index 01fa248..dccf7b8 100644 --- a/src/expansion/expansion.c +++ b/src/expansion/expansion.c @@ -6,6 +6,14 @@ #include "../ast/ast.h" +// static size_t var_len(char *start) +// { +// char *iter = start; +// while (*iter != ' ' && *iter != 0) +// *iter++; +// return iter - start; +// } + struct ast_cmd *expand(struct ast_cmd *cmd) { if (cmd == NULL) @@ -33,11 +41,14 @@ struct ast_cmd *expand(struct ast_cmd *cmd) in_quotes = !in_quotes; memmove(&str[i], &str[i + 1], strlen(&str[i + 1]) + 1); } - // else if (str[i] == '$' && str[i + 1] != '\0' && str[i + 1] != '$' - // && str[i + 1] != ' ') - else if (str[i] == '$' && isalnum(str[i + 1])) + else if (str[i] == '$' && str[i + 1] != 0 && !isspace(str[i + 1])) { - // variable expansion + // size_t len = var_len(str + i + 1); + // char *end = str + i + len + 1; + // char c = *end; + // *end = 0; + // printf("var: %s\n", str + i + 1); + // *end = c; } } @@ -61,8 +72,16 @@ struct ast_cmd *expand(struct ast_cmd *cmd) return cmd; } -int main() -{ - printf("Expansion module test\n"); - return 0; -} +// int main() +// { +// printf("Expansion module test\n"); +// struct ast_cmd ast_cmd; +// // char str[] = "echo Hello $?"; +// char str[] = "echo Hello $AE86"; +// ast_cmd.cmd = list_append(NULL, str); + +// struct ast_cmd *cmd2 = expand(&ast_cmd); +// printf("cmd2: %s\n", (char *)cmd2->cmd->data); + +// return 0; +// } From 62e44b8101fdac007b5bbd1c13020daa0f70c725 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Fri, 9 Jan 2026 17:21:23 +0000 Subject: [PATCH 017/282] fix: ast circular dep --- src/utils/ast/ast.h | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/utils/ast/ast.h b/src/utils/ast/ast.h index 5bfd8df..8e01551 100644 --- a/src/utils/ast/ast.h +++ b/src/utils/ast/ast.h @@ -1,7 +1,7 @@ #ifndef AST_H #define AST_H -#include "lists.h" +#include "../lists/lists.h" enum ast_type { @@ -10,28 +10,30 @@ enum ast_type AST_CMD }; -union ast_node +struct ast_cmd { - struct ast_if; - struct ast_cmd; + struct list *cmd; }; +union ast_node; + struct ast { enum ast_type type; - union ast_node data; + union ast_node *data; }; struct ast_if { - struct ast* condition; - struct ast* then_clause; - struct ast* else_clause; + struct ast *condition; + struct ast *then_clause; + struct ast *else_clause; }; -struct ast_cmd +union ast_node { - struct list* cmd; + struct ast_if *ast_if; + struct ast_cmd *ast_cmd; }; #endif /* ! AST_H */ From 807416940c25728e236041f0e2f30ee0a98f36fb Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Fri, 9 Jan 2026 22:36:48 +0000 Subject: [PATCH 018/282] feat(ast): data as void* and basic helpers --- src/utils/ast/ast.c | 23 +++++++++++++++++++++++ src/utils/ast/ast.h | 23 +++++++++++------------ 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/utils/ast/ast.c b/src/utils/ast/ast.c index e69de29..93fa402 100644 --- a/src/utils/ast/ast.c +++ b/src/utils/ast/ast.c @@ -0,0 +1,23 @@ +#include "ast.h" + +#include + +bool ast_is_if(struct ast *node) +{ + return node->type == AST_IF; +} + +bool ast_is_cmd(struct ast *node) +{ + return node->type == AST_CMD; +} + +struct ast_if *ast_get_if(struct ast *node) +{ + return (struct ast_if *)node->data; +} + +struct ast_cmd *ast_get_cmd(struct ast *node) +{ + return (struct ast_cmd *)node->data; +} diff --git a/src/utils/ast/ast.h b/src/utils/ast/ast.h index 8e01551..c68b143 100644 --- a/src/utils/ast/ast.h +++ b/src/utils/ast/ast.h @@ -5,7 +5,7 @@ enum ast_type { - AST_END = 0, + AST_END, AST_IF, AST_CMD }; @@ -15,14 +15,6 @@ struct ast_cmd struct list *cmd; }; -union ast_node; - -struct ast -{ - enum ast_type type; - union ast_node *data; -}; - struct ast_if { struct ast *condition; @@ -30,10 +22,17 @@ struct ast_if struct ast *else_clause; }; -union ast_node +struct ast { - struct ast_if *ast_if; - struct ast_cmd *ast_cmd; + enum ast_type type; + + /** + * Data associated with this AST node. It can be one of the following: + * - NULL (AST_END) + * - struct ast_if* (AST_IF) + * - struct ast_cmd* (AST_CMD) + */ + void *data; }; #endif /* ! AST_H */ From 13bed60c09e264d25df8a08c09ed0adfc719fa5c Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Fri, 9 Jan 2026 22:50:09 +0000 Subject: [PATCH 019/282] feat(ast): ast_create_if, ast_create_cmd and header doc --- src/utils/ast/ast.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ src/utils/ast/ast.h | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/src/utils/ast/ast.c b/src/utils/ast/ast.c index 93fa402..40b42d7 100644 --- a/src/utils/ast/ast.c +++ b/src/utils/ast/ast.c @@ -1,23 +1,68 @@ #include "ast.h" +#include #include +#include bool ast_is_if(struct ast *node) { + assert(node != NULL); return node->type == AST_IF; } bool ast_is_cmd(struct ast *node) { + assert(node != NULL); return node->type == AST_CMD; } struct ast_if *ast_get_if(struct ast *node) { + assert(node != NULL); + assert(node->type == AST_IF); return (struct ast_if *)node->data; } struct ast_cmd *ast_get_cmd(struct ast *node) { + assert(node != NULL); + assert(node->type == AST_CMD); return (struct ast_cmd *)node->data; } + +static struct ast *ast_create(enum ast_type type, void *data) +{ + struct ast *node = malloc(sizeof(struct ast)); + if (!node) + return NULL; + + node->type = type; + node->data = data; + + return node; +} + +struct ast *ast_create_if(struct ast *condition, struct ast *then_clause, + struct ast *else_clause) +{ + struct ast_if *if_data = malloc(sizeof(struct ast_if)); + if (!if_data) + return NULL; + + if_data->condition = condition; + if_data->then_clause = then_clause; + if_data->else_clause = else_clause; + + return ast_create(AST_IF, if_data); +} + +struct ast *ast_create_cmd(struct list *cmd) +{ + struct ast_cmd *cmd_data = malloc(sizeof(struct ast_cmd)); + if (!cmd_data) + return NULL; + + cmd_data->cmd = cmd; + + return ast_create(AST_CMD, cmd_data); +} diff --git a/src/utils/ast/ast.h b/src/utils/ast/ast.h index c68b143..3247ba5 100644 --- a/src/utils/ast/ast.h +++ b/src/utils/ast/ast.h @@ -1,6 +1,8 @@ #ifndef AST_H #define AST_H +#include + #include "../lists/lists.h" enum ast_type @@ -35,4 +37,37 @@ struct ast void *data; }; +/** + * Checks if the given AST node is an if statement. + */ +bool ast_is_if(struct ast *node); + +/** + * Checks if the given AST node is a command. + */ +bool ast_is_cmd(struct ast *node); + +/** + * Retrieves the if statement data from the given AST node. + * Assumes that the node is of type AST_IF. + */ +struct ast_if *ast_get_if(struct ast *node); + +/** + * Retrieves the command data from the given AST node. + * Assumes that the node is of type AST_CMD. + */ +struct ast_cmd *ast_get_cmd(struct ast *node); + +/** + * Creates a new AST node representing an if statement. + */ +struct ast *ast_create_if(struct ast *condition, struct ast *then_clause, + struct ast *else_clause); + +/** + * Creates a new AST node representing a command. + */ +struct ast *ast_create_cmd(struct list *cmd); + #endif /* ! AST_H */ From 1097e36a47eff61f5bb676e9e9dcd827ed648505 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Fri, 9 Jan 2026 23:12:03 +0000 Subject: [PATCH 020/282] fix:(expansion) ast include path --- src/expansion/expansion.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/expansion/expansion.c b/src/expansion/expansion.c index dccf7b8..406c00b 100644 --- a/src/expansion/expansion.c +++ b/src/expansion/expansion.c @@ -4,7 +4,7 @@ #include #include -#include "../ast/ast.h" +#include "../utils/ast/ast.h" // static size_t var_len(char *start) // { From 4c237c53d023e0db149375fb0a68f88acf6b5935 Mon Sep 17 00:00:00 2001 From: Jean Herail Date: Sat, 10 Jan 2026 17:15:16 +0100 Subject: [PATCH 021/282] feat(execution): Basic AST execution, with forks and all --- src/execution/execution.c | 135 ++++++++++++++++++++++++++++++++++++++ src/execution/execution.h | 11 ++++ 2 files changed, 146 insertions(+) diff --git a/src/execution/execution.c b/src/execution/execution.c index e69de29..3f6b1a3 100644 --- a/src/execution/execution.c +++ b/src/execution/execution.c @@ -0,0 +1,135 @@ +#include "execution.h" + +#include +#include +#include +#include +#include + +#include "../utils/ast/ast.h" + +/** + * @brief converts a linked list of command arguments to an argv array. Don't + * forget to free the result + * + * @param cmd_list Linked list of command arguments + * @return char** Array of command arguments suitable for execvp. Terminated by + * NULL + */ +static char **list_to_argv(struct list *cmd_list) +{ + size_t len = 0; + struct list *cur = cmd_list; + + while (cur) + { + len++; + cur = cur->next; + } + + char **argv = calloc(len + 1, sizeof(char *)); + if (!argv) + { + return NULL; + } + cur = cmd_list; + + for (size_t i = 0; i < len; i++) + { + argv[i] = (char *)cur->data; + cur = cur->next; + } + + argv[len] = NULL; + return argv; +} + +/** + * @brief Executes a command represented by an ast_cmd structure + * + * @param cmd The command to execute + * @return int The exit status of the command + */ +static int exec_command(struct ast_cmd *cmd) +{ + if (!cmd || !(cmd->cmd)) + { + return -1; + } + + char **argv = list_to_argv(cmd->cmd); + + if (!argv || !(argv[0])) + { + free(argv); + return -1; + } + + pid_t pid = fork(); + + if (pid < 0) // Fork failed + { + perror("fork"); + free(argv); + return -1; + } + + if (pid == 0) // If child process + { + execvp(argv[0], argv); + perror("execvp"); // If execvp returns, there was an error + exit(EXIT_FAILURE); // Exit child process + } + + int status = 0; + waitpid(pid, &status, 0); + free(argv); + + if (WIFEXITED(status)) + { + return WEXITSTATUS(status); + } + + return -1; // Should not happen +} + +/** + * @brief Executes the AST returned by the parser + * + * @param ast The AST to execute + * @return int The exit status of the last executed command. + */ +int execution(struct ast *ast) +{ + if (!ast) + { + return 0; + } + + switch (ast->type) + { + case AST_END: { + return 0; + } + case AST_CMD: { + struct ast_cmd *cmd = ast->data->ast_cmd; // Cast implicit + return exec_command(cmd); // It's recursive + } + case AST_IF: { + struct ast_if *if_node = + ast->data->ast_if; // We cast the union to ast_if + int cond = execution(if_node->condition); + if (cond == 0) // True + { + return execution(if_node->then_clause); + } + else // False + { + return execution(if_node->else_clause); + } + } + default: { + return -1; // Should not happen + } + } +} diff --git a/src/execution/execution.h b/src/execution/execution.h index e773de0..aacacc3 100644 --- a/src/execution/execution.h +++ b/src/execution/execution.h @@ -1,4 +1,15 @@ #ifndef EXECUTION_H #define EXECUTION_H +#include "../utils/ast/ast.h" +#include "../utils/lists/lists.h" + +/** + * @brief Execute the AST + * + * @param ast Pointer to the AST structure + * @return int Execution status code of the last command + */ +int execution(struct ast *ast); + #endif /* ! EXECUTION_H */ From 417a518d1b37c06015979044d04db8c4a83e051e Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Sat, 10 Jan 2026 19:16:36 +0100 Subject: [PATCH 022/282] fix(parser): functions body placebo so code compiles --- src/parser/parser.c | 13 +++++++++++++ src/parser/parser.h | 4 ++++ 2 files changed, 17 insertions(+) diff --git a/src/parser/parser.c b/src/parser/parser.c index e69de29..6651e53 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -0,0 +1,13 @@ +#include "parser.h" + +#include + +struct ast *get_ast() +{ + return NULL; +} + +struct ast *get_ast_str(char *command) +{ + return NULL; +} diff --git a/src/parser/parser.h b/src/parser/parser.h index 759d4fd..8dccb31 100644 --- a/src/parser/parser.h +++ b/src/parser/parser.h @@ -7,6 +7,8 @@ * * @return Returns the AST representation of the next command to execute. * If there is no command left to execute, retuns an AST_END node. + * + * @warning NOT IMPLEMENTED */ struct ast* get_ast(); @@ -14,6 +16,8 @@ struct ast* get_ast(); * * @return Returns the AST representation of the given command string. * Returns an AST_END node if the given command is empty. + * + * @warning NOT IMPLEMENTED */ struct ast* get_ast_str(char* command); From 97b7240286475031377c6c170c0525d185f4717a Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Sat, 10 Jan 2026 19:57:36 +0100 Subject: [PATCH 023/282] fix: Fixed ALL the code --- .gitignore | 43 +++++++++++++++++++++++++++ configure.ac | 1 - src/execution/execution.c | 5 ++-- src/io_backend/io_backend.c | 7 +++-- src/lexer/lexer.c | 7 ++++- src/main.c | 2 ++ src/parser/parser.c | 1 + src/utils/Makefile.am | 4 ++- src/utils/string_utils/string_utils.c | 7 +++-- src/utils/string_utils/string_utils.h | 2 ++ 10 files changed, 68 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index ccbc1e7..64c4a76 100644 --- a/.gitignore +++ b/.gitignore @@ -132,3 +132,46 @@ $RECYCLE.BIN/ *.msm *.msp *.lnk + +# Autotools (since it disappeared for some reason) + +Makefile.in +/ar-lib +/mdate-sh +/py-compile +/test-driver +/ylwrap +.deps/ +.dirstamp + +autom4te.cache +/autoscan.log +/autoscan-*.log +/aclocal.m4 +/compile +/config.cache +/config.guess +/config.h.in +/config.log +/config.status +/config.sub +/configure +/configure.scan +/depcomp +/install-sh +/missing +/stamp-h1 + +/libtool +/ltmain.sh +.libs/ + +/texinfo.tex + +m4/libtool.m4 +m4/ltoptions.m4 +m4/ltsugar.m4 +m4/ltversion.m4 +m4/lt~obsolete.m4 + +Makefile diff --git a/configure.ac b/configure.ac index f9684ed..f8638ca 100644 --- a/configure.ac +++ b/configure.ac @@ -25,7 +25,6 @@ AC_PROG_CC # List Makefiles in subdirectories AC_CONFIG_FILES([ src/Makefile - src/ast/Makefile src/parser/Makefile src/lexer/Makefile src/io_backend/Makefile diff --git a/src/execution/execution.c b/src/execution/execution.c index 3f6b1a3..855fd37 100644 --- a/src/execution/execution.c +++ b/src/execution/execution.c @@ -112,12 +112,11 @@ int execution(struct ast *ast) return 0; } case AST_CMD: { - struct ast_cmd *cmd = ast->data->ast_cmd; // Cast implicit + struct ast_cmd *cmd = ast_get_cmd(ast); return exec_command(cmd); // It's recursive } case AST_IF: { - struct ast_if *if_node = - ast->data->ast_if; // We cast the union to ast_if + struct ast_if *if_node = ast_get_if(ast); int cond = execution(if_node->condition); if (cond == 0) // True { diff --git a/src/io_backend/io_backend.c b/src/io_backend/io_backend.c index ee6b09b..176585f 100644 --- a/src/io_backend/io_backend.c +++ b/src/io_backend/io_backend.c @@ -11,18 +11,19 @@ int iob_init(struct iob_context *ctx) switch (context.mode) { - IOB_MODE_STDIN: + case IOB_MODE_STDIN: input = stdin; return 0; - IOB_MODE_SCRIPT: + case IOB_MODE_SCRIPT: if (context.args == NULL) return -2; input = fopen(context.args, "r"); if (input == NULL) return -4; + return 0; - IOB_MODE_CMD: + case IOB_MODE_CMD: if (context.args != NULL) return -2; else diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index 5b03c18..def814c 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -1,13 +1,16 @@ #include "lexer.h" #include +#include +#include +#include #include "io_backend/io_backend.h" static char *end_last_token; static ssize_t remaining_chars; -char *new_token(char *begin, size_t size) +char *new_token(char *begin, ssize_t size) { char *res = calloc(size + 1, sizeof(char)); if (res == NULL) @@ -56,6 +59,7 @@ char *get_token(void) // skip blank char // exit from loop char *token = new_token(stream, i); + return token; } default: break; @@ -64,4 +68,5 @@ char *get_token(void) } remaining_chars -= i; + return NULL; } diff --git a/src/main.c b/src/main.c index e05c6f6..f20378e 100644 --- a/src/main.c +++ b/src/main.c @@ -2,5 +2,7 @@ int main(int argc, char **argv) { + (void)argc; + (void)argv; return 0; } diff --git a/src/parser/parser.c b/src/parser/parser.c index 6651e53..30cecdd 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -9,5 +9,6 @@ struct ast *get_ast() struct ast *get_ast_str(char *command) { + (void)command; return NULL; } diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am index 5b72df3..b876698 100644 --- a/src/utils/Makefile.am +++ b/src/utils/Makefile.am @@ -1,7 +1,9 @@ lib_LIBRARIES = libutils.a libutils_a_SOURCES = \ - string_utils.c + string_utils/string_utils.c \ + ast/ast.c \ + lists/lists.c libutils_a_CPPFLAGS = -I$(top_srcdir)/src diff --git a/src/utils/string_utils/string_utils.c b/src/utils/string_utils/string_utils.c index 6e07f7e..6762b07 100644 --- a/src/utils/string_utils/string_utils.c +++ b/src/utils/string_utils/string_utils.c @@ -1,4 +1,7 @@ -#include "utils/string_utils/string_utils.h" +#include "string_utils.h" + +#include +#include ssize_t skip_blanks(char **str) { @@ -7,7 +10,7 @@ ssize_t skip_blanks(char **str) return 0; } ssize_t skipped = 0; - while (str[skipped] != '\0' && !isblank(str[skipped])) + while (*str[skipped] != '\0' && !isblank(str[skipped])) { skipped++; } diff --git a/src/utils/string_utils/string_utils.h b/src/utils/string_utils/string_utils.h index 3fee923..5e6df94 100644 --- a/src/utils/string_utils/string_utils.h +++ b/src/utils/string_utils/string_utils.h @@ -1,6 +1,8 @@ #ifndef STRING_UTILS_H #define STRING_UTILS_H +#include + /* * @brief: skips blank characters at the beginning of [str]. * @return: number of characters skipped. From b7dbf57dfbc8b527b502a80b538cdf2aaa326e01 Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Mon, 12 Jan 2026 17:29:00 +0100 Subject: [PATCH 024/282] fix(autotools): missing Makefile in configure.ac --- configure.ac | 1 + 1 file changed, 1 insertion(+) diff --git a/configure.ac b/configure.ac index f8638ca..14a03fd 100644 --- a/configure.ac +++ b/configure.ac @@ -24,6 +24,7 @@ AC_PROG_CC # List Makefiles in subdirectories AC_CONFIG_FILES([ + Makefile src/Makefile src/parser/Makefile src/lexer/Makefile From d1f4e0e88d27044336bad9dad72ce49c2e8ac6c8 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Mon, 12 Jan 2026 17:41:24 +0000 Subject: [PATCH 025/282] refactor(string_utils)!: rename skip_blanks to trim_blank_left and update signature --- src/utils/string_utils/string_utils.c | 20 ++++------ src/utils/string_utils/string_utils.h | 9 +++-- tests/unit/utils/utils_tests.c | 56 +++++++++++++++------------ 3 files changed, 46 insertions(+), 39 deletions(-) diff --git a/src/utils/string_utils/string_utils.c b/src/utils/string_utils/string_utils.c index 6762b07..8a8176a 100644 --- a/src/utils/string_utils/string_utils.c +++ b/src/utils/string_utils/string_utils.c @@ -3,17 +3,13 @@ #include #include -ssize_t skip_blanks(char **str) +char *trim_blank_left(char *str) { - if (str == NULL || *str == NULL) - { - return 0; - } - ssize_t skipped = 0; - while (*str[skipped] != '\0' && !isblank(str[skipped])) - { - skipped++; - } - *str += skipped; - return skipped; + if (str == NULL) + return NULL; + + while (*str != '\0' && isblank(*str)) + str++; + + return str; } diff --git a/src/utils/string_utils/string_utils.h b/src/utils/string_utils/string_utils.h index 5e6df94..496c1d5 100644 --- a/src/utils/string_utils/string_utils.h +++ b/src/utils/string_utils/string_utils.h @@ -4,9 +4,12 @@ #include /* - * @brief: skips blank characters at the beginning of [str]. - * @return: number of characters skipped. + * @brief trims leading blank characters (space and tab) from the input string. + * @param str input string to be trimmed. + * @return pointer to the first non-blank character in the string. If the + * string consists entirely of blank characters, returns a pointer to the null + * terminator at the end of the string. */ -ssize_t skip_blanks(char **str); +char *trim_blank_left(char *str); #endif /* STRING_UTILS_H */ diff --git a/tests/unit/utils/utils_tests.c b/tests/unit/utils/utils_tests.c index 1076aab..b7c326f 100644 --- a/tests/unit/utils/utils_tests.c +++ b/tests/unit/utils/utils_tests.c @@ -5,7 +5,7 @@ #include #include -#include "utils/string_utils/string_utils.h" +#include "../../../src/utils/string_utils/string_utils.h" TestSuite(string_utils); @@ -14,10 +14,11 @@ Test(string_utils, skipblank_basic) char input[] = " Hello World"; char expected_str[] = "Hello World"; - ssize_t actual = skip_blanks(input); + char *trimmed = trim_blank_left(input); + ssize_t offset = trimmed - input; ssize_t expected = 2; - cr_expect(eq(str, input, expected_str)); - cr_expect(actual == expected); + cr_expect(eq(str, trimmed, expected_str)); + cr_expect(offset == expected); } Test(string_utils, skipblank_noblank) @@ -25,10 +26,11 @@ Test(string_utils, skipblank_noblank) char input[] = "Hello World"; char expected_str[] = "Hello World"; - ssize_t actual = skip_blanks(input); + char *trimmed = trim_blank_left(input); + ssize_t offset = trimmed - input; ssize_t expected = 0; - cr_expect(eq(str, input, expected_str)); - cr_expect(actual == expected); + cr_expect(eq(str, trimmed, expected_str)); + cr_expect(offset == expected); } Test(string_utils, skipblank_tab) @@ -36,10 +38,11 @@ Test(string_utils, skipblank_tab) char input[] = "\tHello World"; char expected_str[] = "Hello World"; - ssize_t actual = skip_blanks(input); + char *trimmed = trim_blank_left(input); + ssize_t offset = trimmed - input; ssize_t expected = 1; - cr_expect(eq(str, input, expected_str)); - cr_expect(actual == expected); + cr_expect(eq(str, trimmed, expected_str)); + cr_expect(offset == expected); } Test(string_utils, skipblank_space_tab) @@ -47,10 +50,11 @@ Test(string_utils, skipblank_space_tab) char input[] = " \tHello World"; char expected_str[] = "Hello World"; - ssize_t actual = skip_blanks(input); + char *trimmed = trim_blank_left(input); + ssize_t offset = trimmed - input; ssize_t expected = 2; - cr_expect(eq(str, input, expected_str)); - cr_expect(actual == expected); + cr_expect(eq(str, trimmed, expected_str)); + cr_expect(offset == expected); } Test(string_utils, skipblank_2tab_1space) @@ -58,10 +62,11 @@ Test(string_utils, skipblank_2tab_1space) char input[] = "\t \tHello World"; char expected_str[] = "Hello World"; - ssize_t actual = skip_blanks(input); + char *trimmed = trim_blank_left(input); + ssize_t offset = trimmed - input; ssize_t expected = 3; - cr_expect(eq(str, input, expected_str)); - cr_expect(actual == expected); + cr_expect(eq(str, trimmed, expected_str)); + cr_expect(offset == expected); } Test(string_utils, skipblank_a_lot) @@ -69,10 +74,11 @@ Test(string_utils, skipblank_a_lot) char input[] = "\t \t \tHello World"; char expected_str[] = "Hello World"; - ssize_t actual = skip_blanks(input); + char *trimmed = trim_blank_left(input); + ssize_t offset = trimmed - input; ssize_t expected = 8; - cr_expect(eq(str, input, expected_str)); - cr_expect(actual == expected); + cr_expect(eq(str, trimmed, expected_str)); + cr_expect(offset == expected); } Test(string_utils, skipblank_newline) @@ -80,18 +86,20 @@ Test(string_utils, skipblank_newline) char input[] = "\nHello World"; char expected_str[] = "\nHello World"; - ssize_t actual = skip_blanks(input); + char *trimmed = trim_blank_left(input); + ssize_t offset = trimmed - input; ssize_t expected = 0; - cr_expect(eq(str, input, expected_str)); - cr_expect(actual == expected); + cr_expect(eq(str, trimmed, expected_str)); + cr_expect(offset == expected); } Test(string_utils, skipblank_nul) { char *input = NULL; - ssize_t actual = skip_blanks(input); + char *trimmed = trim_blank_left(input); + ssize_t offset = trimmed - input; ssize_t expected = 0; cr_expect(input == NULL); - cr_expect(actual == expected); + cr_expect(offset == expected); } From 234713b6a0b8a20a65d85fbc8e26fe96e266b9a1 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Mon, 12 Jan 2026 18:42:02 +0000 Subject: [PATCH 026/282] feat(args): args_handler --- src/utils/args/args.c | 64 +++++++++++++++++++++++++++++++++++++++++++ src/utils/args/args.h | 41 +++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 src/utils/args/args.c create mode 100644 src/utils/args/args.h diff --git a/src/utils/args/args.c b/src/utils/args/args.c new file mode 100644 index 0000000..40b5516 --- /dev/null +++ b/src/utils/args/args.c @@ -0,0 +1,64 @@ +#include "./args.h" + +#include +#include +#include + +int args_handler(int argc, char **argv, struct args_options *options) +{ + options->type = INPUT_UNDEFINED; + options->input_source = NULL; + options->pretty_print = false; + options->verbose = false; + + for (int i = 1; i < argc; i++) + { + if (strcmp(argv[i], "--pretty-print") == 0) + { + options->pretty_print = true; + } + else if (strcmp(argv[i], "--verbose") == 0) + { + options->verbose = true; + } + else if (strcmp(argv[i], "-c") == 0 && i + 1 < argc) + { + if (options->type != INPUT_UNDEFINED) + return 1; // Error: multiple input types specified + + options->type = INPUT_CMD; + options->input_source = argv[i + 1]; + i++; + } + else if (argv[i][0] == '-') + { + return 1; // Error: unknown option + } + else + { + if (options->type != INPUT_UNDEFINED) + return 1; // Error: multiple input types specified + + options->type = INPUT_FILE; + options->input_source = argv[i]; + } + } + + if (options->type == INPUT_UNDEFINED) + options->type = INPUT_STDIN; + + return 0; +} + +void args_print(struct args_options *options) +{ + printf("Input type: %s\n", + options->type == INPUT_CMD ? "COMMAND" + : options->type == INPUT_FILE ? "FILE" + : options->type == INPUT_STDIN ? "STDIN" + : "UNDEFINED"); + printf("Input source: %s\n", + options->input_source ? options->input_source : "NULL"); + printf("Pretty print: %s\n", options->pretty_print ? "true" : "false"); + printf("Verbose: %s\n", options->verbose ? "true" : "false"); +} diff --git a/src/utils/args/args.h b/src/utils/args/args.h new file mode 100644 index 0000000..ddb6d8b --- /dev/null +++ b/src/utils/args/args.h @@ -0,0 +1,41 @@ +#ifndef ARGS_H +#define ARGS_H + +#include + +enum input_type +{ + INPUT_UNDEFINED, + INPUT_FILE, + INPUT_CMD, + INPUT_STDIN +}; + +struct args_options +{ + /** Source of the input, filename or command string depending on type, NULL + * if INPUT_STDIN */ + const char *input_source; + /** Type of the input source */ + enum input_type type; + /** Enable or disable pretty printing of outputs */ + bool pretty_print; + /** Enable or disable verbose mode */ + bool verbose; +}; + +/** + * Handles command-line arguments and populates the args_options structure. + * @param argc The argument count. + * @param argv The argument vector. + * @param options Pointer to args_options structure to be populated. + * @return 0 on success, non-zero on failure. + */ +int args_handler(int argc, char **argv, struct args_options *options); + +/** Prints the parsed arguments for debugging purposes. + * @param options Pointer to args_options structure containing parsed options. + */ +void args_print(struct args_options *options); + +#endif /* ARGS_H */ From e3785dbd08abec09a0edc6ccdf0ebc3b7b71ae09 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Mon, 12 Jan 2026 19:30:11 +0000 Subject: [PATCH 027/282] feat(args): args_handler error handling and print_usage --- src/main.c | 15 ++++- src/utils/args/args.c | 33 ++++++++-- src/utils/args/args.h | 7 +++ tests/unit/utils/args.c | 134 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 183 insertions(+), 6 deletions(-) create mode 100644 tests/unit/utils/args.c diff --git a/src/main.c b/src/main.c index f20378e..6ced238 100644 --- a/src/main.c +++ b/src/main.c @@ -1,8 +1,19 @@ // all includes +#include + +#include "utils/args/args.h" + int main(int argc, char **argv) { - (void)argc; - (void)argv; + struct args_options options; + int r = args_handler(argc, argv, &options); + if (r != 0) + { + print_usage(stderr, argv[0]); + return 2; + } + + args_print(&options); return 0; } diff --git a/src/utils/args/args.c b/src/utils/args/args.c index 40b5516..a2dabd8 100644 --- a/src/utils/args/args.c +++ b/src/utils/args/args.c @@ -21,10 +21,19 @@ int args_handler(int argc, char **argv, struct args_options *options) { options->verbose = true; } - else if (strcmp(argv[i], "-c") == 0 && i + 1 < argc) + else if (strcmp(argv[i], "-c") == 0) { if (options->type != INPUT_UNDEFINED) - return 1; // Error: multiple input types specified + { + fprintf(stderr, "Multiple input sources specified: %s\n", + argv[i + 1]); + return 1; + } + else if (i + 1 >= argc) + { + fprintf(stderr, "No command provided after -c\n"); + return 1; + } options->type = INPUT_CMD; options->input_source = argv[i + 1]; @@ -32,12 +41,17 @@ int args_handler(int argc, char **argv, struct args_options *options) } else if (argv[i][0] == '-') { - return 1; // Error: unknown option + fprintf(stderr, "Unknown option: %s\n", argv[i]); + return 1; } else { if (options->type != INPUT_UNDEFINED) - return 1; // Error: multiple input types specified + { + fprintf(stderr, "Multiple input sources specified: %s\n", + argv[i]); + return 1; + } options->type = INPUT_FILE; options->input_source = argv[i]; @@ -62,3 +76,14 @@ void args_print(struct args_options *options) printf("Pretty print: %s\n", options->pretty_print ? "true" : "false"); printf("Verbose: %s\n", options->verbose ? "true" : "false"); } + +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, " --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 ddb6d8b..d19859b 100644 --- a/src/utils/args/args.h +++ b/src/utils/args/args.h @@ -2,6 +2,7 @@ #define ARGS_H #include +#include enum input_type { @@ -38,4 +39,10 @@ int args_handler(int argc, char **argv, struct args_options *options); */ void args_print(struct args_options *options); +/** Prints the usage information for the program. + * @param std The output stream to print to (e.g., stdout or stderr). + * @param program_name The name of the program. + */ +void print_usage(FILE *std, const char *program_name); + #endif /* ARGS_H */ diff --git a/tests/unit/utils/args.c b/tests/unit/utils/args.c new file mode 100644 index 0000000..04dfe26 --- /dev/null +++ b/tests/unit/utils/args.c @@ -0,0 +1,134 @@ +#include "../../../src/utils/args/args.h" + +#include +#include +#include + +TestSuite(utils_args); + +Test(utils_args, basic_command) +{ + int argc = 3; + struct args_options options; + char *input[] = { "program", "-c", "echo Hello, World!" }; + + int r = args_handler(argc, input, &options); + + cr_expect(r == 0); + 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!")); +} + +Test(utils_args, basic_command_with_flags) +{ + int argc = 5; + struct args_options options; + char *input[] = { "program", "--pretty-print", "-c", "echo Hello, World!", + "--verbose" }; + + int r = args_handler(argc, input, &options); + + cr_expect(r == 0); + 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!")); +} + +Test(utils_args, basic_file_input) +{ + int argc = 2; + struct args_options options; + char *input[] = { "program", "input.txt" }; + + int r = args_handler(argc, input, &options); + + cr_expect(r == 0); + 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")); +} + +Test(utils_args, basic_file_input_with_flags) +{ + int argc = 4; + struct args_options options; + char *input[] = { "program", "--verbose", "input.txt", "--pretty-print" }; + + int r = args_handler(argc, input, &options); + + cr_expect(r == 0); + 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")); +} + +Test(utils_args, basic_stdin_input) +{ + int argc = 1; + struct args_options options; + char *input[] = { "program" }; + + int r = args_handler(argc, input, &options); + + cr_expect(r == 0); + cr_expect(options.pretty_print == false); + cr_expect(options.verbose == false); + cr_expect(options.type == INPUT_STDIN); + cr_expect(options.input_source == NULL); +} + +Test(utils_args, pretty_print_and_verbose_flags) +{ + int argc = 3; + struct args_options options; + char *input[] = { "program", "--pretty-print", "--verbose" }; + + int r = args_handler(argc, input, &options); + + cr_expect(r == 0); + cr_expect(options.pretty_print == true); + cr_expect(options.verbose == true); + cr_expect(options.type == INPUT_STDIN); + cr_expect(options.input_source == NULL); +} + +Test(utils_args, missing_command_after_c, .init = cr_redirect_stderr) +{ + int argc = 2; + struct args_options options; + char *input[] = { "program", "-c" }; + + int r = args_handler(argc, input, &options); + + cr_expect(r != 0); + cr_assert_stderr_eq_str("No command provided after -c\n"); +} + +Test(utils_args, unknown_option, .init = cr_redirect_stderr) +{ + int argc = 2; + struct args_options options; + char *input[] = { "program", "--unknown" }; + + int r = args_handler(argc, input, &options); + + cr_expect(r != 0); + cr_assert_stderr_eq_str("Unknown option: --unknown\n"); +} + +Test(utils_args, conflicting_input_types, .init = cr_redirect_stderr) +{ + int argc = 4; + struct args_options options; + char *input[] = { "program", "-c", "echo Hello", "input.txt" }; + + int r = args_handler(argc, input, &options); + + cr_expect(r != 0); + cr_assert_stderr_eq_str("Multiple input sources specified: input.txt\n"); +} From 745649df1c4e5a5d27eafba0c610423476c16b3a Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Mon, 12 Jan 2026 21:31:15 +0100 Subject: [PATCH 028/282] feat(lexer): finished --- src/lexer/lexer.c | 75 +++++++++++++++++++++++++++++++++++------------ src/lexer/lexer.h | 1 + 2 files changed, 57 insertions(+), 19 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index def814c..5d4ba94 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -1,15 +1,53 @@ #include "lexer.h" +#include #include #include #include #include #include "io_backend/io_backend.h" +#include "utils/string_utils/string_utils.h" static char *end_last_token; static ssize_t remaining_chars; +/* @brief: saves state for the next call the the lexer. + * + */ +static void save_state(char *stream, ssize_t i) +{ + remaining_chars -= i; + end_last_token = stream + i; + return; +} + +/* @return: true if a special character from the grammar was found, + * false otherwise. + * + */ +static bool is_special_char(char c) +{ + return c == '\'' || c == '\n' || c == ';'; +} + +/* @return: true if a keyword from the grammar was found, false otherwise. + * + */ +static bool is_keyword(char *stream, ssize_t i) +{ + if (i == 2) + { + return strcmp(stream, "if") == 0 || strcmp(stream, "fi") == 0; + } + if (i == 4) + { + return strcmp(stream, "then") || strcmp(stream, "else") + || strcmp(stream, "elif"); + } + return false; +} + char *new_token(char *begin, ssize_t size) { char *res = calloc(size + 1, sizeof(char)); @@ -32,6 +70,10 @@ char *stream_init(void) stream = end_last_token; } + char *trimed_stream = trim_blanks_left(stream); + remaining_chars -= trimed_stream - stream; + stream = trimed_stream; + return stream; } @@ -39,34 +81,29 @@ char *get_token(void) { char *stream = stream_init(); - bool inquotes = false; ssize_t i = 0; while (i < remaining_chars) { - switch (stream[i]) + if (is_special_char(stream[i])) { - case '\'': - inquotes = !inquotes; + if (i == 0) // where we create spe_char token + i++; break; - - case ' ' | '\n' | '\t': - if (inquotes) - break; - else - { - // token creation - // skip blank char - // exit from loop - char *token = new_token(stream, i); - return token; - } - default: + } + if (isblank(stream[i])) + { + break; + } + else if (is_keyword(stream, i)) + { + i++; break; } i++; } - remaining_chars -= i; - return NULL; + save_state(stream, i); + + return new_token(stream, i); } diff --git a/src/lexer/lexer.h b/src/lexer/lexer.h index 406ca9c..7e7ca10 100644 --- a/src/lexer/lexer.h +++ b/src/lexer/lexer.h @@ -30,6 +30,7 @@ char *new_token(char *begin, ssize_t size); * If it is, it calls stream_read() from IO_backend, * and sets [remaing_chars]. * If not, it starts from the end of the last token. + * Also trims left blanks before returning. * * @return: char* stream from which we tokenise. */ From b9eb7b90cbb2bb316c023e1ec6faa24274cf12ca Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Mon, 12 Jan 2026 17:29:00 +0100 Subject: [PATCH 029/282] fix(autotools): missing Makefile in configure.ac --- configure.ac | 1 + 1 file changed, 1 insertion(+) diff --git a/configure.ac b/configure.ac index f8638ca..14a03fd 100644 --- a/configure.ac +++ b/configure.ac @@ -24,6 +24,7 @@ AC_PROG_CC # List Makefiles in subdirectories AC_CONFIG_FILES([ + Makefile src/Makefile src/parser/Makefile src/lexer/Makefile From 8b75d73d56059f1ce305f68216b9b6db0b933506 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Mon, 12 Jan 2026 17:41:24 +0000 Subject: [PATCH 030/282] refactor(string_utils)!: rename skip_blanks to trim_blank_left and update signature --- src/utils/string_utils/string_utils.c | 20 ++++------ src/utils/string_utils/string_utils.h | 9 +++-- tests/unit/utils/utils_tests.c | 56 +++++++++++++++------------ 3 files changed, 46 insertions(+), 39 deletions(-) diff --git a/src/utils/string_utils/string_utils.c b/src/utils/string_utils/string_utils.c index 6762b07..8a8176a 100644 --- a/src/utils/string_utils/string_utils.c +++ b/src/utils/string_utils/string_utils.c @@ -3,17 +3,13 @@ #include #include -ssize_t skip_blanks(char **str) +char *trim_blank_left(char *str) { - if (str == NULL || *str == NULL) - { - return 0; - } - ssize_t skipped = 0; - while (*str[skipped] != '\0' && !isblank(str[skipped])) - { - skipped++; - } - *str += skipped; - return skipped; + if (str == NULL) + return NULL; + + while (*str != '\0' && isblank(*str)) + str++; + + return str; } diff --git a/src/utils/string_utils/string_utils.h b/src/utils/string_utils/string_utils.h index 5e6df94..496c1d5 100644 --- a/src/utils/string_utils/string_utils.h +++ b/src/utils/string_utils/string_utils.h @@ -4,9 +4,12 @@ #include /* - * @brief: skips blank characters at the beginning of [str]. - * @return: number of characters skipped. + * @brief trims leading blank characters (space and tab) from the input string. + * @param str input string to be trimmed. + * @return pointer to the first non-blank character in the string. If the + * string consists entirely of blank characters, returns a pointer to the null + * terminator at the end of the string. */ -ssize_t skip_blanks(char **str); +char *trim_blank_left(char *str); #endif /* STRING_UTILS_H */ diff --git a/tests/unit/utils/utils_tests.c b/tests/unit/utils/utils_tests.c index 1076aab..b7c326f 100644 --- a/tests/unit/utils/utils_tests.c +++ b/tests/unit/utils/utils_tests.c @@ -5,7 +5,7 @@ #include #include -#include "utils/string_utils/string_utils.h" +#include "../../../src/utils/string_utils/string_utils.h" TestSuite(string_utils); @@ -14,10 +14,11 @@ Test(string_utils, skipblank_basic) char input[] = " Hello World"; char expected_str[] = "Hello World"; - ssize_t actual = skip_blanks(input); + char *trimmed = trim_blank_left(input); + ssize_t offset = trimmed - input; ssize_t expected = 2; - cr_expect(eq(str, input, expected_str)); - cr_expect(actual == expected); + cr_expect(eq(str, trimmed, expected_str)); + cr_expect(offset == expected); } Test(string_utils, skipblank_noblank) @@ -25,10 +26,11 @@ Test(string_utils, skipblank_noblank) char input[] = "Hello World"; char expected_str[] = "Hello World"; - ssize_t actual = skip_blanks(input); + char *trimmed = trim_blank_left(input); + ssize_t offset = trimmed - input; ssize_t expected = 0; - cr_expect(eq(str, input, expected_str)); - cr_expect(actual == expected); + cr_expect(eq(str, trimmed, expected_str)); + cr_expect(offset == expected); } Test(string_utils, skipblank_tab) @@ -36,10 +38,11 @@ Test(string_utils, skipblank_tab) char input[] = "\tHello World"; char expected_str[] = "Hello World"; - ssize_t actual = skip_blanks(input); + char *trimmed = trim_blank_left(input); + ssize_t offset = trimmed - input; ssize_t expected = 1; - cr_expect(eq(str, input, expected_str)); - cr_expect(actual == expected); + cr_expect(eq(str, trimmed, expected_str)); + cr_expect(offset == expected); } Test(string_utils, skipblank_space_tab) @@ -47,10 +50,11 @@ Test(string_utils, skipblank_space_tab) char input[] = " \tHello World"; char expected_str[] = "Hello World"; - ssize_t actual = skip_blanks(input); + char *trimmed = trim_blank_left(input); + ssize_t offset = trimmed - input; ssize_t expected = 2; - cr_expect(eq(str, input, expected_str)); - cr_expect(actual == expected); + cr_expect(eq(str, trimmed, expected_str)); + cr_expect(offset == expected); } Test(string_utils, skipblank_2tab_1space) @@ -58,10 +62,11 @@ Test(string_utils, skipblank_2tab_1space) char input[] = "\t \tHello World"; char expected_str[] = "Hello World"; - ssize_t actual = skip_blanks(input); + char *trimmed = trim_blank_left(input); + ssize_t offset = trimmed - input; ssize_t expected = 3; - cr_expect(eq(str, input, expected_str)); - cr_expect(actual == expected); + cr_expect(eq(str, trimmed, expected_str)); + cr_expect(offset == expected); } Test(string_utils, skipblank_a_lot) @@ -69,10 +74,11 @@ Test(string_utils, skipblank_a_lot) char input[] = "\t \t \tHello World"; char expected_str[] = "Hello World"; - ssize_t actual = skip_blanks(input); + char *trimmed = trim_blank_left(input); + ssize_t offset = trimmed - input; ssize_t expected = 8; - cr_expect(eq(str, input, expected_str)); - cr_expect(actual == expected); + cr_expect(eq(str, trimmed, expected_str)); + cr_expect(offset == expected); } Test(string_utils, skipblank_newline) @@ -80,18 +86,20 @@ Test(string_utils, skipblank_newline) char input[] = "\nHello World"; char expected_str[] = "\nHello World"; - ssize_t actual = skip_blanks(input); + char *trimmed = trim_blank_left(input); + ssize_t offset = trimmed - input; ssize_t expected = 0; - cr_expect(eq(str, input, expected_str)); - cr_expect(actual == expected); + cr_expect(eq(str, trimmed, expected_str)); + cr_expect(offset == expected); } Test(string_utils, skipblank_nul) { char *input = NULL; - ssize_t actual = skip_blanks(input); + char *trimmed = trim_blank_left(input); + ssize_t offset = trimmed - input; ssize_t expected = 0; cr_expect(input == NULL); - cr_expect(actual == expected); + cr_expect(offset == expected); } From 7b7467104287d9bc3665e650d5d21637c67f41d9 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Mon, 12 Jan 2026 18:42:02 +0000 Subject: [PATCH 031/282] feat(args): args_handler --- src/utils/args/args.c | 64 +++++++++++++++++++++++++++++++++++++++++++ src/utils/args/args.h | 41 +++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 src/utils/args/args.c create mode 100644 src/utils/args/args.h diff --git a/src/utils/args/args.c b/src/utils/args/args.c new file mode 100644 index 0000000..40b5516 --- /dev/null +++ b/src/utils/args/args.c @@ -0,0 +1,64 @@ +#include "./args.h" + +#include +#include +#include + +int args_handler(int argc, char **argv, struct args_options *options) +{ + options->type = INPUT_UNDEFINED; + options->input_source = NULL; + options->pretty_print = false; + options->verbose = false; + + for (int i = 1; i < argc; i++) + { + if (strcmp(argv[i], "--pretty-print") == 0) + { + options->pretty_print = true; + } + else if (strcmp(argv[i], "--verbose") == 0) + { + options->verbose = true; + } + else if (strcmp(argv[i], "-c") == 0 && i + 1 < argc) + { + if (options->type != INPUT_UNDEFINED) + return 1; // Error: multiple input types specified + + options->type = INPUT_CMD; + options->input_source = argv[i + 1]; + i++; + } + else if (argv[i][0] == '-') + { + return 1; // Error: unknown option + } + else + { + if (options->type != INPUT_UNDEFINED) + return 1; // Error: multiple input types specified + + options->type = INPUT_FILE; + options->input_source = argv[i]; + } + } + + if (options->type == INPUT_UNDEFINED) + options->type = INPUT_STDIN; + + return 0; +} + +void args_print(struct args_options *options) +{ + printf("Input type: %s\n", + options->type == INPUT_CMD ? "COMMAND" + : options->type == INPUT_FILE ? "FILE" + : options->type == INPUT_STDIN ? "STDIN" + : "UNDEFINED"); + printf("Input source: %s\n", + options->input_source ? options->input_source : "NULL"); + printf("Pretty print: %s\n", options->pretty_print ? "true" : "false"); + printf("Verbose: %s\n", options->verbose ? "true" : "false"); +} diff --git a/src/utils/args/args.h b/src/utils/args/args.h new file mode 100644 index 0000000..ddb6d8b --- /dev/null +++ b/src/utils/args/args.h @@ -0,0 +1,41 @@ +#ifndef ARGS_H +#define ARGS_H + +#include + +enum input_type +{ + INPUT_UNDEFINED, + INPUT_FILE, + INPUT_CMD, + INPUT_STDIN +}; + +struct args_options +{ + /** Source of the input, filename or command string depending on type, NULL + * if INPUT_STDIN */ + const char *input_source; + /** Type of the input source */ + enum input_type type; + /** Enable or disable pretty printing of outputs */ + bool pretty_print; + /** Enable or disable verbose mode */ + bool verbose; +}; + +/** + * Handles command-line arguments and populates the args_options structure. + * @param argc The argument count. + * @param argv The argument vector. + * @param options Pointer to args_options structure to be populated. + * @return 0 on success, non-zero on failure. + */ +int args_handler(int argc, char **argv, struct args_options *options); + +/** Prints the parsed arguments for debugging purposes. + * @param options Pointer to args_options structure containing parsed options. + */ +void args_print(struct args_options *options); + +#endif /* ARGS_H */ From 111f2cf5c23f2df4095c1e0f914519ffbb88770f Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Mon, 12 Jan 2026 19:30:11 +0000 Subject: [PATCH 032/282] feat(args): args_handler error handling and print_usage --- src/main.c | 15 ++++- src/utils/args/args.c | 33 ++++++++-- src/utils/args/args.h | 7 +++ tests/unit/utils/args.c | 134 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 183 insertions(+), 6 deletions(-) create mode 100644 tests/unit/utils/args.c diff --git a/src/main.c b/src/main.c index f20378e..6ced238 100644 --- a/src/main.c +++ b/src/main.c @@ -1,8 +1,19 @@ // all includes +#include + +#include "utils/args/args.h" + int main(int argc, char **argv) { - (void)argc; - (void)argv; + struct args_options options; + int r = args_handler(argc, argv, &options); + if (r != 0) + { + print_usage(stderr, argv[0]); + return 2; + } + + args_print(&options); return 0; } diff --git a/src/utils/args/args.c b/src/utils/args/args.c index 40b5516..a2dabd8 100644 --- a/src/utils/args/args.c +++ b/src/utils/args/args.c @@ -21,10 +21,19 @@ int args_handler(int argc, char **argv, struct args_options *options) { options->verbose = true; } - else if (strcmp(argv[i], "-c") == 0 && i + 1 < argc) + else if (strcmp(argv[i], "-c") == 0) { if (options->type != INPUT_UNDEFINED) - return 1; // Error: multiple input types specified + { + fprintf(stderr, "Multiple input sources specified: %s\n", + argv[i + 1]); + return 1; + } + else if (i + 1 >= argc) + { + fprintf(stderr, "No command provided after -c\n"); + return 1; + } options->type = INPUT_CMD; options->input_source = argv[i + 1]; @@ -32,12 +41,17 @@ int args_handler(int argc, char **argv, struct args_options *options) } else if (argv[i][0] == '-') { - return 1; // Error: unknown option + fprintf(stderr, "Unknown option: %s\n", argv[i]); + return 1; } else { if (options->type != INPUT_UNDEFINED) - return 1; // Error: multiple input types specified + { + fprintf(stderr, "Multiple input sources specified: %s\n", + argv[i]); + return 1; + } options->type = INPUT_FILE; options->input_source = argv[i]; @@ -62,3 +76,14 @@ void args_print(struct args_options *options) printf("Pretty print: %s\n", options->pretty_print ? "true" : "false"); printf("Verbose: %s\n", options->verbose ? "true" : "false"); } + +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, " --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 ddb6d8b..d19859b 100644 --- a/src/utils/args/args.h +++ b/src/utils/args/args.h @@ -2,6 +2,7 @@ #define ARGS_H #include +#include enum input_type { @@ -38,4 +39,10 @@ int args_handler(int argc, char **argv, struct args_options *options); */ void args_print(struct args_options *options); +/** Prints the usage information for the program. + * @param std The output stream to print to (e.g., stdout or stderr). + * @param program_name The name of the program. + */ +void print_usage(FILE *std, const char *program_name); + #endif /* ARGS_H */ diff --git a/tests/unit/utils/args.c b/tests/unit/utils/args.c new file mode 100644 index 0000000..04dfe26 --- /dev/null +++ b/tests/unit/utils/args.c @@ -0,0 +1,134 @@ +#include "../../../src/utils/args/args.h" + +#include +#include +#include + +TestSuite(utils_args); + +Test(utils_args, basic_command) +{ + int argc = 3; + struct args_options options; + char *input[] = { "program", "-c", "echo Hello, World!" }; + + int r = args_handler(argc, input, &options); + + cr_expect(r == 0); + 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!")); +} + +Test(utils_args, basic_command_with_flags) +{ + int argc = 5; + struct args_options options; + char *input[] = { "program", "--pretty-print", "-c", "echo Hello, World!", + "--verbose" }; + + int r = args_handler(argc, input, &options); + + cr_expect(r == 0); + 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!")); +} + +Test(utils_args, basic_file_input) +{ + int argc = 2; + struct args_options options; + char *input[] = { "program", "input.txt" }; + + int r = args_handler(argc, input, &options); + + cr_expect(r == 0); + 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")); +} + +Test(utils_args, basic_file_input_with_flags) +{ + int argc = 4; + struct args_options options; + char *input[] = { "program", "--verbose", "input.txt", "--pretty-print" }; + + int r = args_handler(argc, input, &options); + + cr_expect(r == 0); + 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")); +} + +Test(utils_args, basic_stdin_input) +{ + int argc = 1; + struct args_options options; + char *input[] = { "program" }; + + int r = args_handler(argc, input, &options); + + cr_expect(r == 0); + cr_expect(options.pretty_print == false); + cr_expect(options.verbose == false); + cr_expect(options.type == INPUT_STDIN); + cr_expect(options.input_source == NULL); +} + +Test(utils_args, pretty_print_and_verbose_flags) +{ + int argc = 3; + struct args_options options; + char *input[] = { "program", "--pretty-print", "--verbose" }; + + int r = args_handler(argc, input, &options); + + cr_expect(r == 0); + cr_expect(options.pretty_print == true); + cr_expect(options.verbose == true); + cr_expect(options.type == INPUT_STDIN); + cr_expect(options.input_source == NULL); +} + +Test(utils_args, missing_command_after_c, .init = cr_redirect_stderr) +{ + int argc = 2; + struct args_options options; + char *input[] = { "program", "-c" }; + + int r = args_handler(argc, input, &options); + + cr_expect(r != 0); + cr_assert_stderr_eq_str("No command provided after -c\n"); +} + +Test(utils_args, unknown_option, .init = cr_redirect_stderr) +{ + int argc = 2; + struct args_options options; + char *input[] = { "program", "--unknown" }; + + int r = args_handler(argc, input, &options); + + cr_expect(r != 0); + cr_assert_stderr_eq_str("Unknown option: --unknown\n"); +} + +Test(utils_args, conflicting_input_types, .init = cr_redirect_stderr) +{ + int argc = 4; + struct args_options options; + char *input[] = { "program", "-c", "echo Hello", "input.txt" }; + + int r = args_handler(argc, input, &options); + + cr_expect(r != 0); + cr_assert_stderr_eq_str("Multiple input sources specified: input.txt\n"); +} From 8a727ebd98d3cb09d06236dbe107700c2eaa56cb Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Mon, 12 Jan 2026 21:41:13 +0100 Subject: [PATCH 033/282] fix(lexer): typo in function call --- src/lexer/lexer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index 5d4ba94..0cc237c 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -70,7 +70,7 @@ char *stream_init(void) stream = end_last_token; } - char *trimed_stream = trim_blanks_left(stream); + char *trimed_stream = trim_blank_left(stream); remaining_chars -= trimed_stream - stream; stream = trimed_stream; From 4aa6b439bb8e383106f1def6f284d82c8a55806f Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Mon, 12 Jan 2026 21:46:33 +0100 Subject: [PATCH 034/282] fix(utils): include args module in build system --- src/utils/Makefile.am | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am index b876698..600ee40 100644 --- a/src/utils/Makefile.am +++ b/src/utils/Makefile.am @@ -3,7 +3,8 @@ lib_LIBRARIES = libutils.a libutils_a_SOURCES = \ string_utils/string_utils.c \ ast/ast.c \ - lists/lists.c + lists/lists.c \ + args/args.c libutils_a_CPPFLAGS = -I$(top_srcdir)/src From 9bf3ed068a2b688335273c389f1693fa03aa1abb Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Mon, 12 Jan 2026 21:48:11 +0100 Subject: [PATCH 035/282] style(all): clang format --- src/io_backend/io_backend.h | 12 +++++++----- src/parser/parser.h | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/io_backend/io_backend.h b/src/io_backend/io_backend.h index 34d85f3..fab5cce 100644 --- a/src/io_backend/io_backend.h +++ b/src/io_backend/io_backend.h @@ -3,7 +3,8 @@ #include -enum iob_mode { +enum iob_mode +{ IOB_MODE_NULL = 0, IOB_MODE_STDIN, IOB_MODE_SCRIPT, @@ -16,14 +17,15 @@ enum iob_mode { * the script name when mode is set to IOB_SCRIPT, * the command to execute when mode is set to IOB_CMD, */ -struct iob_context { +struct iob_context +{ enum iob_mode mode; - char* args; + char *args; }; /* * @brief Initializes the IO Backend module - * + * * @param context contains the input mode and the args * @return 0 on success, the corresponding error code otherwise */ @@ -41,6 +43,6 @@ void iob_close(); * * */ -ssize_t stream_read(char** stream); +ssize_t stream_read(char **stream); #endif /* ! IO_BACKEND_H */ diff --git a/src/parser/parser.h b/src/parser/parser.h index 8dccb31..d10cae8 100644 --- a/src/parser/parser.h +++ b/src/parser/parser.h @@ -10,7 +10,7 @@ * * @warning NOT IMPLEMENTED */ -struct ast* get_ast(); +struct ast *get_ast(); /* @brief Builds the AST representation of the given command string. * @@ -19,6 +19,6 @@ struct ast* get_ast(); * * @warning NOT IMPLEMENTED */ -struct ast* get_ast_str(char* command); +struct ast *get_ast_str(char *command); #endif /* ! PARSER_H */ From 83b73745839f503ece272433dd0b6cb5682c8783 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Mon, 12 Jan 2026 21:51:11 +0100 Subject: [PATCH 036/282] fix(autotools): removed useless Makefile.am in utils/ast --- src/utils/ast/Makefile.am | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 src/utils/ast/Makefile.am diff --git a/src/utils/ast/Makefile.am b/src/utils/ast/Makefile.am deleted file mode 100644 index fa41e3d..0000000 --- a/src/utils/ast/Makefile.am +++ /dev/null @@ -1,9 +0,0 @@ -lib_LIBRARIES = libast.a - -libast_a_SOURCES = \ - ast.c \ - ast.h - -libast_a_CPPFLAGS = -I$(top_srcdir)/src - -noinst_LIBRARIES = libast.a From e000d2cf338c3ca619a5446e2480254bdde96022 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Mon, 12 Jan 2026 21:46:33 +0100 Subject: [PATCH 037/282] fix(utils): include args module in build system --- src/utils/Makefile.am | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am index b876698..600ee40 100644 --- a/src/utils/Makefile.am +++ b/src/utils/Makefile.am @@ -3,7 +3,8 @@ lib_LIBRARIES = libutils.a libutils_a_SOURCES = \ string_utils/string_utils.c \ ast/ast.c \ - lists/lists.c + lists/lists.c \ + args/args.c libutils_a_CPPFLAGS = -I$(top_srcdir)/src From 937e935a6e3722001d94039e6d965352a4f0ed3b Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Mon, 12 Jan 2026 21:48:11 +0100 Subject: [PATCH 038/282] style(all): clang format --- src/io_backend/io_backend.h | 12 +++++++----- src/parser/parser.h | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/io_backend/io_backend.h b/src/io_backend/io_backend.h index 34d85f3..fab5cce 100644 --- a/src/io_backend/io_backend.h +++ b/src/io_backend/io_backend.h @@ -3,7 +3,8 @@ #include -enum iob_mode { +enum iob_mode +{ IOB_MODE_NULL = 0, IOB_MODE_STDIN, IOB_MODE_SCRIPT, @@ -16,14 +17,15 @@ enum iob_mode { * the script name when mode is set to IOB_SCRIPT, * the command to execute when mode is set to IOB_CMD, */ -struct iob_context { +struct iob_context +{ enum iob_mode mode; - char* args; + char *args; }; /* * @brief Initializes the IO Backend module - * + * * @param context contains the input mode and the args * @return 0 on success, the corresponding error code otherwise */ @@ -41,6 +43,6 @@ void iob_close(); * * */ -ssize_t stream_read(char** stream); +ssize_t stream_read(char **stream); #endif /* ! IO_BACKEND_H */ diff --git a/src/parser/parser.h b/src/parser/parser.h index 8dccb31..d10cae8 100644 --- a/src/parser/parser.h +++ b/src/parser/parser.h @@ -10,7 +10,7 @@ * * @warning NOT IMPLEMENTED */ -struct ast* get_ast(); +struct ast *get_ast(); /* @brief Builds the AST representation of the given command string. * @@ -19,6 +19,6 @@ struct ast* get_ast(); * * @warning NOT IMPLEMENTED */ -struct ast* get_ast_str(char* command); +struct ast *get_ast_str(char *command); #endif /* ! PARSER_H */ From a11a8ab63556400abcc4ec43c5a5176aa54f0acd Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Mon, 12 Jan 2026 21:51:11 +0100 Subject: [PATCH 039/282] fix(autotools): removed useless Makefile.am in utils/ast --- src/utils/ast/Makefile.am | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 src/utils/ast/Makefile.am diff --git a/src/utils/ast/Makefile.am b/src/utils/ast/Makefile.am deleted file mode 100644 index fa41e3d..0000000 --- a/src/utils/ast/Makefile.am +++ /dev/null @@ -1,9 +0,0 @@ -lib_LIBRARIES = libast.a - -libast_a_SOURCES = \ - ast.c \ - ast.h - -libast_a_CPPFLAGS = -I$(top_srcdir)/src - -noinst_LIBRARIES = libast.a From f71d0a218385609ef2ef60f6b1bdcabb4879a7ed Mon Sep 17 00:00:00 2001 From: Jean Herail Date: Mon, 12 Jan 2026 22:27:26 +0100 Subject: [PATCH 040/282] feat(main_loop): Wrote the base of the main entrypoint. Implemented a helper which converts the args options to iob_context --- src/io_backend/io_backend.c | 27 +++++++++++++++++ src/io_backend/io_backend.h | 8 +++++ src/main.c | 59 +++++++++++++++++++++++++++++++++---- 3 files changed, 89 insertions(+), 5 deletions(-) diff --git a/src/io_backend/io_backend.c b/src/io_backend/io_backend.c index 176585f..9cb6c68 100644 --- a/src/io_backend/io_backend.c +++ b/src/io_backend/io_backend.c @@ -2,6 +2,8 @@ #include +#include "utils/args/args.h" + static struct iob_context context; static FILE *input; @@ -33,3 +35,28 @@ int iob_init(struct iob_context *ctx) return -1; } } + +int iob_config_from_args(struct args_options *args, struct iob_context *ctx) +{ + switch (args->type) + { + case INPUT_STDIN: + ctx->mode = IOB_MODE_STDIN; + ctx->args = NULL; + break; + + case INPUT_FILE: + ctx->mode = IOB_MODE_SCRIPT; + ctx->args = (char *)args->input_source; + break; + + case INPUT_CMD: + ctx->mode = IOB_MODE_CMD; + ctx->args = NULL; + break; + + default: + return -1; + } + return 0; +} \ No newline at end of file diff --git a/src/io_backend/io_backend.h b/src/io_backend/io_backend.h index fab5cce..3f6a07e 100644 --- a/src/io_backend/io_backend.h +++ b/src/io_backend/io_backend.h @@ -23,6 +23,14 @@ struct iob_context char *args; }; +/** + * @brief Converts struct arg_options to iob_context + * + * @param args The arguments options struct + * @param ctx The IO Backend context struct to populate + * @return int 0 on success, negative value on error + */ +int iob_config_from_args(struct args_options *args, struct iob_context *ctx); /* * @brief Initializes the IO Backend module * diff --git a/src/main.c b/src/main.c index 6ced238..b664786 100644 --- a/src/main.c +++ b/src/main.c @@ -1,19 +1,68 @@ -// all includes - +// All the includes #include +#include +#include "execution/execution.h" +#include "io_backend/io_backend.h" +#include "parser/parser.h" #include "utils/args/args.h" +enum return_codes +{ + SUCCESS = 0, + ERR_IO_BACKEND = 2, + ERR_MALLOC = 3 +}; + int main(int argc, char **argv) { + // Create the options struct (with argument handler) struct args_options options; int r = args_handler(argc, argv, &options); if (r != 0) { print_usage(stderr, argv[0]); - return 2; + return ERR_IO_BACKEND; + } + // args_print(&options); + + // Create the IO-Backend context struct + struct iob_context *io_context = malloc(sizeof(struct iob_context)); + if (io_context == NULL) + { + fprintf(stderr, + "Error: Memory allocation failed for IO Backend context\n"); + return ERR_MALLOC; } - args_print(&options); - return 0; + // Convert args_options to iob_context + r = iob_config_from_args(&options, io_context); + if (r != 0) + { + fprintf(stderr, + "Error: Failed to configure IO Backend from arguments\n"); + free(io_context); + return ERR_IO_BACKEND; + } + + // Init IO Backend (with the context struct) + r = iob_init(io_context); + if (r != 0) + { + fprintf(stderr, + "Error: IO Backend initialization failed with code %d\n", r); + free(io_context); + return ERR_IO_BACKEND; + } + + free(io_context); + + // Call the parser to get the AST + struct ast *command_ast = get_ast(); // We'll pass the options later + + // Call the executor with the AST + r = execution(command_ast); + + // Return the execution return code (last command executed) + return r; } From f8018943007c288c64c5f07c3d7e2a3e15707a0e Mon Sep 17 00:00:00 2001 From: Jean Herail Date: Mon, 12 Jan 2026 22:33:43 +0100 Subject: [PATCH 041/282] fix(args): fixed a missing include in args --- src/io_backend/io_backend.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/io_backend/io_backend.h b/src/io_backend/io_backend.h index 3f6a07e..2feb2c4 100644 --- a/src/io_backend/io_backend.h +++ b/src/io_backend/io_backend.h @@ -3,6 +3,8 @@ #include +#include "utils/args/args.h" + enum iob_mode { IOB_MODE_NULL = 0, From 7b773641a148d14ee815f9cb588ca2e639083741 Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Tue, 13 Jan 2026 16:53:25 +0100 Subject: [PATCH 042/282] fix: restored progress from the old parser branch --- src/parser/parser.c | 57 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/src/parser/parser.c b/src/parser/parser.c index 30cecdd..9d2cf10 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -1,12 +1,67 @@ #include "parser.h" +#include #include +#include +#include +#include + +#include "lexer/lexer.h" +#include "utils/lists/lists.h" + +// === Static functions + +/* Returns true if c is a command terminator, false otherwise + */ +static bool isterminator(char c) +{ + switch (c) + { + case '\n': + case ';': + case EOF: + return true; + default: + return false; + } +} + +/* Parses a simple list of words (command and arguments) + * and returns the resulting ast + */ +static struct ast *parse_simple_command(void) +{ + struct list *cmd_elements = NULL; + char *token = get_token(); + if (token == NULL) // just in case ? + return NULL; + + while (token != NULL && !isterminator(token[0])) + { + cmd_elements = list_append(cmd_elements, token); + token = get_token(); + } + + if (token == NULL) + return NULL; // TODO handle error + + struct ast *result = ast_create_cmd(cmd_elements); + return result; +} + +// === Functions struct ast *get_ast() { - return NULL; + struct ast *result = NULL; + struct ast *current_node = NULL; + + // char *token = get_token(); + + return result; } +// TODO struct ast *get_ast_str(char *command) { (void)command; From fe649af2dd205bcb725b9a02919a4cbea983427f Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Thu, 8 Jan 2026 15:02:33 +0100 Subject: [PATCH 043/282] doc: finished --- src/io_backend/io_backend.h | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/io_backend/io_backend.h b/src/io_backend/io_backend.h index fab5cce..437c339 100644 --- a/src/io_backend/io_backend.h +++ b/src/io_backend/io_backend.h @@ -31,17 +31,14 @@ struct iob_context */ int iob_init(struct iob_context *context); -/* TODO - * - * - * +/* @brief Closes the opened buffers and exits the modules gracefully */ -void iob_close(); +void iob_close(void); -/*i TODO - * - * +/* @brief reads at most one line of the input and stores it into *stream * + * @param stream is a pointer that will be set to a string to parse + * @return the number of read characters if positive, the error code otherwise */ ssize_t stream_read(char **stream); From a653c02c9fefb24f277a19d84d96cac1d785cb6c Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Tue, 13 Jan 2026 17:07:23 +0100 Subject: [PATCH 044/282] fix(iobackend): rebasing lexer on dev --- src/io_backend/io_backend.c | 81 +++++++++++++++++++++++++++++++++---- src/io_backend/io_backend.h | 27 ++++++++++--- 2 files changed, 95 insertions(+), 13 deletions(-) diff --git a/src/io_backend/io_backend.c b/src/io_backend/io_backend.c index 176585f..7e7c1c3 100644 --- a/src/io_backend/io_backend.c +++ b/src/io_backend/io_backend.c @@ -1,35 +1,102 @@ #include "io_backend.h" #include +#include +#include + +// === Static variables static struct iob_context context; -static FILE *input; +static FILE *input = NULL; +static char *stream_buf = NULL; +static size_t stream_buf_size = 0; +static enum iob_state state = IOB_STATE_NOT_INITIALIZED; + +// === Functions int iob_init(struct iob_context *ctx) { + if (state != IOB_STATE_NOT_INITIALIZED) + return IOB_ERROR_MODULE_NOT_INITIALIZED; + context = *ctx; switch (context.mode) { case IOB_MODE_STDIN: input = stdin; + state = IOB_STATE_READY; return 0; case IOB_MODE_SCRIPT: if (context.args == NULL) - return -2; + return IOB_ERROR_BAD_ARG; input = fopen(context.args, "r"); if (input == NULL) - return -4; + return IOB_ERROR_CANNOT_OPEN_FILE; + state = IOB_STATE_READY; return 0; case IOB_MODE_CMD: if (context.args != NULL) - return -2; - else - return 0; + return IOB_ERROR_BAD_ARG; + state = IOB_STATE_READY; + return 0; default: - return -1; + return IOB_ERROR_BAD_ARG; + } +} + +void iob_close(void) +{ + fclose(input); + if ((context.mode == IOB_MODE_STDIN || context.mode == IOB_MODE_SCRIPT) + && stream_buf != NULL) + { + free(stream_buf); + stream_buf_size = 0; + } + state = IOB_STATE_NOT_INITIALIZED; +} + +ssize_t stream_read(char **stream) +{ + // Check args + if (stream == NULL) + return IOB_ERROR_BAD_ARG; + + // Check env + if (state == IOB_STATE_NOT_INITIALIZED) + return IOB_ERROR_MODULE_NOT_INITIALIZED; + if (state == IOB_STATE_FINISHED) + return 0; + if (state == IOB_STATE_ERROR) + return IOB_ERROR_GENERIC; + + // Use input + if (context.mode == IOB_MODE_STDIN || context.mode == IOB_MODE_SCRIPT) + { + ssize_t nread = getline(&stream_buf, &stream_buf_size, input); + if (nread == -1) + { + state = IOB_STATE_FINISHED; + return 0; + } + else if (nread < 0) + state = IOB_STATE_ERROR; + + return nread; + } + // Use args + else if (context.mode == IOB_MODE_CMD) + { + *stream = context.args; + return strlen(context.args); + } + else + { + *stream = NULL; + return IOB_ERROR_GENERIC; } } diff --git a/src/io_backend/io_backend.h b/src/io_backend/io_backend.h index 437c339..5aef0f8 100644 --- a/src/io_backend/io_backend.h +++ b/src/io_backend/io_backend.h @@ -3,19 +3,32 @@ #include -enum iob_mode -{ +// Error codes +#define IOB_ERROR_GENERIC -1 +#define IOB_ERROR_BAD_ARG -2 +#define IOB_ERROR_MODULE_NOT_INITIALIZED -3 +#define IOB_ERROR_MODULE_ALREADY_INITALIZED -4 +#define IOB_ERROR_CANNOT_OPEN_FILE -5 + +enum iob_mode { IOB_MODE_NULL = 0, IOB_MODE_STDIN, IOB_MODE_SCRIPT, IOB_MODE_CMD }; +enum iob_state { + IOB_STATE_NOT_INITIALIZED, + IOB_STATE_READY, + IOB_STATE_FINISHED, + IOB_STATE_ERROR +}; + /* @struct iob_context * @var mode * @var args contains - * the script name when mode is set to IOB_SCRIPT, - * the command to execute when mode is set to IOB_CMD, + * the script name when mode is set to IOB_MODE_SCRIPT, + * the command to execute when mode is set to IOB_MODE_CMD */ struct iob_context { @@ -31,14 +44,16 @@ struct iob_context */ int iob_init(struct iob_context *context); -/* @brief Closes the opened buffers and exits the modules gracefully +/* @brief Closes the opened buffers and the module gracefully */ void iob_close(void); /* @brief reads at most one line of the input and stores it into *stream * * @param stream is a pointer that will be set to a string to parse - * @return the number of read characters if positive, the error code otherwise + * @return the number of read characters if positive, + * zero if finished (reached EOF), + * the error code otherwise */ ssize_t stream_read(char **stream); From e5925d69ecb220095a9827e2cf25faf32c3369f5 Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Thu, 8 Jan 2026 16:59:37 +0100 Subject: [PATCH 045/282] test: iob_init and iob_close tests (+ small typo fix in src) --- src/io_backend/io_backend.c | 2 +- src/io_backend/io_backend.h | 2 +- tests/unit/io_backend/io_backend.c | 122 +++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 tests/unit/io_backend/io_backend.c diff --git a/src/io_backend/io_backend.c b/src/io_backend/io_backend.c index 7e7c1c3..97fe974 100644 --- a/src/io_backend/io_backend.c +++ b/src/io_backend/io_backend.c @@ -17,7 +17,7 @@ static enum iob_state state = IOB_STATE_NOT_INITIALIZED; int iob_init(struct iob_context *ctx) { if (state != IOB_STATE_NOT_INITIALIZED) - return IOB_ERROR_MODULE_NOT_INITIALIZED; + return IOB_ERROR_MODULE_ALREADY_INITIALIZED; context = *ctx; diff --git a/src/io_backend/io_backend.h b/src/io_backend/io_backend.h index 5aef0f8..3282016 100644 --- a/src/io_backend/io_backend.h +++ b/src/io_backend/io_backend.h @@ -7,7 +7,7 @@ #define IOB_ERROR_GENERIC -1 #define IOB_ERROR_BAD_ARG -2 #define IOB_ERROR_MODULE_NOT_INITIALIZED -3 -#define IOB_ERROR_MODULE_ALREADY_INITALIZED -4 +#define IOB_ERROR_MODULE_ALREADY_INITIALIZED -4 #define IOB_ERROR_CANNOT_OPEN_FILE -5 enum iob_mode { diff --git a/tests/unit/io_backend/io_backend.c b/tests/unit/io_backend/io_backend.c new file mode 100644 index 0000000..8640cdb --- /dev/null +++ b/tests/unit/io_backend/io_backend.c @@ -0,0 +1,122 @@ +#include +#include + +#include + +#include + +TestSuite(IO_Backend); + +// IOB Init + +Test(IO_Backend, init_null) +{ + struct iob_context ctx = { + .iob_mode = IOB_MODE_NULL; + .args = NULL; + }; + int actual = iob_init(ctx); + int expected = IOB_ERROR_BAD_ARG; + cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); +} + +Test(IO_Backend, init_stdin) +{ + struct iob_context ctx = { + .iob_mode = IOB_MODE_STDIN; + .args = NULL; + }; + int actual = iob_init(ctx); + int expected = 0; + cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); + iob_close(); +} + +// WARNING: this one could fail because of iob_close in the previous test +// Same applies for other tests +Test(IO_Backend, init_script) +{ + char* script_name = "script.tmp" + struct iob_context ctx = { + .iob_mode = IOB_MODE_SCRIPT; + .args = script_name; + }; + // Create file + FILE* f = fopen(script_name, "w"); + fclose(f); + + int actual = iob_init(ctx); + int expected = 0; + cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); + iob_close(); + remove(script_name); +} + +Test(IO_Backend, init_script_not_a_file) +{ + char* script_name = "not_a_file.tmp" + struct iob_context ctx = { + .iob_mode = IOB_MODE_SCRIPT; + .args = script_name; + }; + int actual = iob_init(ctx); + int expected = IOB_ERROR_CANNOT_OPEN_FILE; + cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); +} + +Test(IO_Backend, init_script_null) +{ + struct iob_context ctx = { + .iob_mode = IOB_MODE_SCRIPT; + .args = NULL; + }; + int actual = iob_init(ctx); + int expected = IOB_ERROR_CANNOT_OPEN_FILE; + cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); +} + +Test(IO_Backend, init_cmd) +{ + char* cmd = "iamacommand --yesido" + struct iob_context ctx = { + .iob_mode = IOB_MODE_CMD; + .args = cmd; + }; + int actual = iob_init(ctx); + int expected = 0; + cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); + iob_close(); +} + +Test(IO_Backend, init_cmd_null) +{ + struct iob_context ctx = { + .iob_mode = IOB_MODE_CMD; + .args = NULL; + }; + int actual = iob_init(ctx); + int expected = IOB_ERROR_BAD_ARG; + cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); +} + +Test(IO_Backend, init_already_init) +{ + char* cmd = "iamacommand --yesido" + struct iob_context ctx = { + .iob_mode = IOB_MODE_CMD; + .args = cmd; + }; + iob_init(ctx); + int actual = iob_init(ctx); + int expected = IOB_ERROR_ALREADY_INITIALIZED; + cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); + iob_close(); +} + +Test(IO_Backend, close_not_init) +{ + iob_close(); // Shouldn't do anything +} + +// IOB Stream +// TODO From bfaf7aa47595c816fb957bea5e627ca19d5185aa Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Tue, 13 Jan 2026 17:45:15 +0100 Subject: [PATCH 046/282] fix(lexer): get_token => peek_token and pop_token --- src/lexer/lexer.c | 13 ++++++++++--- src/lexer/lexer.h | 13 +++++++++++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index 0cc237c..b768de5 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -77,7 +77,7 @@ char *stream_init(void) return stream; } -char *get_token(void) +char *peek_token(void) { char *stream = stream_init(); @@ -103,7 +103,14 @@ char *get_token(void) i++; } - save_state(stream, i); - return new_token(stream, i); } + +char *pop_token(void) +{ + char *token = peek_token(); + + save_state(stream, i); + + return token; +} diff --git a/src/lexer/lexer.h b/src/lexer/lexer.h index 7e7ca10..b1b8827 100644 --- a/src/lexer/lexer.h +++ b/src/lexer/lexer.h @@ -3,10 +3,19 @@ #include -/* @return: char*, the next token +/* + * @brief: returns the next (newly allocated) token without consuming it. + * if end of input is reached, returns EOF. * */ -char *get_token(void); +char *peek_token(void); + +/* + * @brief: returns the next (newly allocated) token and consumes it. + * if end of input is reached, returns EOF. + * + */ +char *pop_token(void); /* * @warning: NOT IMPLEMENTED. From ce6507c3a6c4c6cb1142d8da35a499da6dc4fdfb Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Tue, 13 Jan 2026 17:56:30 +0100 Subject: [PATCH 047/282] fix(lexer): pop_token --- src/lexer/lexer.c | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index b768de5..4e246e2 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -108,9 +108,31 @@ char *peek_token(void) char *pop_token(void) { - char *token = peek_token(); + char *stream = stream_init(); + + ssize_t i = 0; + + while (i < remaining_chars) + { + if (is_special_char(stream[i])) + { + if (i == 0) // where we create spe_char token + i++; + break; + } + if (isblank(stream[i])) + { + break; + } + else if (is_keyword(stream, i)) + { + i++; + break; + } + i++; + } save_state(stream, i); - return token; + return new_token(stream, i); } From 4a8c13f9cc3ef889b694bb01a88999f42bb9b4a0 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Tue, 13 Jan 2026 18:01:27 +0100 Subject: [PATCH 048/282] fix(iobackend): define POSIX_C_SOURCE for getline --- src/io_backend/io_backend.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/io_backend/io_backend.c b/src/io_backend/io_backend.c index 97fe974..0fd983e 100644 --- a/src/io_backend/io_backend.c +++ b/src/io_backend/io_backend.c @@ -1,3 +1,5 @@ +#define _POSIX_C_SOURCE 200809L + #include "io_backend.h" #include From 37fcdf99be3ecbcebb50ccb2eab98c59020ac92e Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Tue, 13 Jan 2026 19:41:37 +0100 Subject: [PATCH 049/282] fix: added new ast type and moved parsing functions to a different header --- src/parser/parser.c | 35 ++++++++++++++--------------------- src/parser/parsing_utils.c | 35 +++++++++++++++++++++++++++++++++++ src/parser/parsing_utils.h | 24 ++++++++++++++++++++++++ src/utils/ast/ast.h | 24 +++++++++++++++++++++++- 4 files changed, 96 insertions(+), 22 deletions(-) create mode 100644 src/parser/parsing_utils.c create mode 100644 src/parser/parsing_utils.h diff --git a/src/parser/parser.c b/src/parser/parser.c index 9d2cf10..5b47dda 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -29,35 +29,28 @@ static bool isterminator(char c) /* Parses a simple list of words (command and arguments) * and returns the resulting ast */ -static struct ast *parse_simple_command(void) -{ - struct list *cmd_elements = NULL; - char *token = get_token(); - if (token == NULL) // just in case ? - return NULL; - - while (token != NULL && !isterminator(token[0])) - { - cmd_elements = list_append(cmd_elements, token); - token = get_token(); - } - - if (token == NULL) - return NULL; // TODO handle error - - struct ast *result = ast_create_cmd(cmd_elements); - return result; -} // === Functions struct ast *get_ast() { - struct ast *result = NULL; + struct list *result_list = NULL; struct ast *current_node = NULL; - // char *token = get_token(); + char *token = peek_token(); + if (token != NULL) + { + puts("Internal error: cannot get the following token"); + return NULL; + } + while (token[0] != EOF) + { + struct ast *cmd = parse_simple_command(); + result_list = list_append(result_list, cmd); + } + + struct ast *result = ast_create_list(result_list); return result; } diff --git a/src/parser/parsing_utils.c b/src/parser/parsing_utils.c new file mode 100644 index 0000000..de77af3 --- /dev/null +++ b/src/parser/parsing_utils.c @@ -0,0 +1,35 @@ +struct ast *parse_simple_command(void) +{ + struct list *cmd_elements = NULL; + char *token = pop_token(); + if (token == NULL) // just in case ? + { + puts("Internal error: cannot get the following token"); + return NULL; + } + + while (token != NULL && !isterminator(token[0])) + { + cmd_elements = list_append(cmd_elements, token); + token = pop_token(); + } + + if (token == NULL) + { + puts("Internal error: cannot get the following token"); + return NULL; + } + + struct ast *result = ast_create_cmd(cmd_elements); + return result; +} + +struct ast *parse_if_rule(void) +{ + return NULL; +} + +struct ast *parse_shell_command(void) +{ + return parse_if_rule(); +} diff --git a/src/parser/parsing_utils.h b/src/parser/parsing_utils.h new file mode 100644 index 0000000..0ba9370 --- /dev/null +++ b/src/parser/parsing_utils.h @@ -0,0 +1,24 @@ +/* @brief Parses a simple list of words (command and arguments) + * and returns the resulting ast + */ +struct ast *parse_simple_command(void); + +/* + */ +struct ast *parse_if_rule(void); + +/* + */ +struct ast *parse_shell_command(void); + +/* + */ +struct ast* parse_compound_list(void); + +/* + */ +struct ast* parse_and_or(void); + +/* + */ +struct ast* else_clause(void); diff --git a/src/utils/ast/ast.h b/src/utils/ast/ast.h index 3247ba5..49f0cf4 100644 --- a/src/utils/ast/ast.h +++ b/src/utils/ast/ast.h @@ -8,13 +8,14 @@ enum ast_type { AST_END, + AST_LIST, AST_IF, AST_CMD }; struct ast_cmd { - struct list *cmd; + struct list *cmd; // A list of words (char*) }; struct ast_if @@ -37,6 +38,11 @@ struct ast void *data; }; +struct ast_list +{ + struct list *children; // A list of ASTs (ast*) +}; + /** * Checks if the given AST node is an if statement. */ @@ -47,6 +53,11 @@ bool ast_is_if(struct ast *node); */ bool ast_is_cmd(struct ast *node); +/** + * Checks if the given AST node is a command. + */ +bool ast_is_list(struct ast *node); + /** * Retrieves the if statement data from the given AST node. * Assumes that the node is of type AST_IF. @@ -59,6 +70,12 @@ struct ast_if *ast_get_if(struct ast *node); */ struct ast_cmd *ast_get_cmd(struct ast *node); +/** + * Retrieves the command data from the given AST node. + * Assumes that the node is of type AST_LIST. + */ +struct ast_list *ast_get_list(struct ast *node); + /** * Creates a new AST node representing an if statement. */ @@ -70,4 +87,9 @@ struct ast *ast_create_if(struct ast *condition, struct ast *then_clause, */ struct ast *ast_create_cmd(struct list *cmd); +/** + * Creates a new AST node representing a list of ASTs + */ +struct ast* ast_create_list(struct list* ast_list); + #endif /* ! AST_H */ From 204bb515afab0e337e53140a075eb7c43a7c0109 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Tue, 13 Jan 2026 19:44:56 +0100 Subject: [PATCH 050/282] feat(lexer): rework using a struct token and functions associated - unstable --- src/lexer/lexer.c | 52 ++++++++++++++++++++++++----------------------- src/lexer/lexer.h | 42 ++++++++++++++++++++++++++++++-------- 2 files changed, 60 insertions(+), 34 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index 4e246e2..08dd74a 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -28,31 +28,43 @@ static void save_state(char *stream, ssize_t i) */ static bool is_special_char(char c) { - return c == '\'' || c == '\n' || c == ';'; + return c == '\'' || c == '\n' || c == ';' || c == 'EOF'; } -/* @return: true if a keyword from the grammar was found, false otherwise. - * - */ -static bool is_keyword(char *stream, ssize_t i) +static void new_token_keyword(struct token *tok, char *begin, ssize_t size) { - if (i == 2) + if (strncmp(begin, "if", size) == 0) { - return strcmp(stream, "if") == 0 || strcmp(stream, "fi") == 0; + tok->type = TOKEN_IF; } - if (i == 4) + if (strncmp(begin, "fi", size) == 0) { - return strcmp(stream, "then") || strcmp(stream, "else") - || strcmp(stream, "elif"); + tok->type = TOKEN_FI; + } + if (strncmp(begin, "then", size) == 0) + { + tok->type = TOKEN_THEN; + } + if (strncmp(begin, "else", size) == 0) + { + tok->type = TOKEN_ELSE; } - return false; } -char *new_token(char *begin, ssize_t size) +struct token *new_token(char *begin, ssize_t size) { - char *res = calloc(size + 1, sizeof(char)); + struct token *res = calloc(1, sizeof(struct token)); if (res == NULL) return NULL; + + // checks which type of token + + // is special char + + // is keyword + + // otherwise -> WORD + char *token_data = calloc(size + 1, sizeof(char)); strncpy(res, begin, size); return res; } @@ -77,7 +89,7 @@ char *stream_init(void) return stream; } -char *peek_token(void) +struct token *peek_token(void) { char *stream = stream_init(); @@ -95,18 +107,13 @@ char *peek_token(void) { break; } - else if (is_keyword(stream, i)) - { - i++; - break; - } i++; } return new_token(stream, i); } -char *pop_token(void) +struct token *pop_token(void) { char *stream = stream_init(); @@ -124,11 +131,6 @@ char *pop_token(void) { break; } - else if (is_keyword(stream, i)) - { - i++; - break; - } i++; } diff --git a/src/lexer/lexer.h b/src/lexer/lexer.h index b1b8827..a4d9e13 100644 --- a/src/lexer/lexer.h +++ b/src/lexer/lexer.h @@ -3,19 +3,38 @@ #include +enum token_type +{ + TOKEN_EOF, + TOKEN_WORD, + TOKEN_NEWLINE, + TOKEN_QUOTE, + TOKEN_SEMICOLON, + TOKEN_IF, + TOKEN_THEN, + TOKEN_ELSE, + TOKEN_FI +}; + +struct token +{ + enum token_type type; + char* data; +}; + /* * @brief: returns the next (newly allocated) token without consuming it. - * if end of input is reached, returns EOF. + * if end of input is reached, returns a token of type TOKEN_EOF. * */ -char *peek_token(void); +struct token *peek_token(void); /* * @brief: returns the next (newly allocated) token and consumes it. - * if end of input is reached, returns EOF. + * if end of input is reached, returns a token of type TOKEN_EOF. * */ -char *pop_token(void); +struct token *pop_token(void); /* * @warning: NOT IMPLEMENTED. @@ -23,16 +42,21 @@ char *pop_token(void); * @note: maybe usefull for subshells. */ -char *get_token_str(void); +struct token *get_token_str(void); /* - * @brief: return a newly allocated token. - * This token contains [size] chars, starting from [begin]. + * @brief: return a newly allocated token, with the corresponding type. + * The data contains [size] char, starting from [begin]. * - * @return: NULL on error, null-terminated char* otherwise. + * @return: NULL on error, a token otherwise. * */ -char *new_token(char *begin, ssize_t size); +struct token *new_token(char *begin, ssize_t size); + +/* @brief: frees the token given in argument + * + */ +void free_token(struct token* tok); /* * @brief: checks if the stream used for the last token creation is empty. From 466199ae0de51109737b884174c93ad470e60254 Mon Sep 17 00:00:00 2001 From: Jean Herail Date: Tue, 13 Jan 2026 20:54:51 +0100 Subject: [PATCH 051/282] changed the commands in the readme --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index bbab8c7..be08206 100644 --- a/README.md +++ b/README.md @@ -8,19 +8,20 @@ TODO ### Build run this command: - autoreconf --force --verbose --install + `autoreconf --force --verbose --install` ### Test run this command: - ./configure CFLAGS='-std=c99 -Werror -Wall -Wextra -Wvla' + `./configure CFLAGS='-std=c99 -Werror -Wall -Wextra -Wvla'` then: - make + `make` #### asan run this command: - ./configure CFLAGS='-std=c99 -Werror -Wall -Wextra -Wvla -g -fsanitize=address' + `./configure CFLAGS='-std=c99 -Werror -Wall -Wextra -Wvla -g -fsanitize=address'` + then: - make check + `make check` ## Authors From 8262fdece89edf72eef760a8834b7d269bc40f34 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Tue, 13 Jan 2026 22:00:02 +0100 Subject: [PATCH 052/282] feat(lexer): rework with struct token done --- src/lexer/lexer.c | 78 ++++++++++++++++++++++++++++++++++++++--------- src/lexer/lexer.h | 1 + 2 files changed, 65 insertions(+), 14 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index 08dd74a..d42df48 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -31,42 +31,92 @@ static bool is_special_char(char c) return c == '\'' || c == '\n' || c == ';' || c == 'EOF'; } -static void new_token_keyword(struct token *tok, char *begin, ssize_t size) + +/* @brief: if a special character is found at [begin], + * [tok->token_type] is set accordingly + * + */ +static void set_token_spechar(struct token *tok, char *begin, ssize_t size) { + if (size != 1) + return; + if (begin[0] == 'EOF') + { + tok->type = TOKEN_EOF; + } + else if (begin[0] == ';') + { + tok->type = TOKEN_NEWLINE; + } + else if (begin[0] == '\'') + { + tok->type = TOKEN_QUOTE; + } + else if (begin[0] == ';') + { + tok->type = TOKEN_SEMICOLON; + } +} + + +/* @brief: if a keyword is found at [begin], + * [tok->token_type] is set accordingly + * + */ +static void set_token_keyword(struct token *tok, char *begin, ssize_t size) +{ + if (tok->type != TOKEN_NULL) + return; if (strncmp(begin, "if", size) == 0) { tok->type = TOKEN_IF; } - if (strncmp(begin, "fi", size) == 0) + else if (strncmp(begin, "fi", size) == 0) { tok->type = TOKEN_FI; } - if (strncmp(begin, "then", size) == 0) + else if (strncmp(begin, "then", size) == 0) { tok->type = TOKEN_THEN; } - if (strncmp(begin, "else", size) == 0) + else if (strncmp(begin, "else", size) == 0) { tok->type = TOKEN_ELSE; } } +/* @brief: if token_type has not yet been set, then it is a TOKEN_WORD + * Also allocates the data and fills it. + */ +static void set_token_word(struct token *tok, char *begin, ssize_t size) +{ + if (tok->token_type == TOKEN_NULL) + { + char *token_data = calloc(size + 1, sizeof(char)); + strncpy(res, begin, size); + } +} + struct token *new_token(char *begin, ssize_t size) { - struct token *res = calloc(1, sizeof(struct token)); - if (res == NULL) + struct token *tok = calloc(1, sizeof(struct token)); + if (tok == NULL) return NULL; - // checks which type of token + set_token_spechar(tok, begin, size); + set_token_keyword(tok, begin, size); + set_token_word(tok, begin, size); - // is special char + return tok; +} - // is keyword - - // otherwise -> WORD - char *token_data = calloc(size + 1, sizeof(char)); - strncpy(res, begin, size); - return res; +void free_token(struct token *tok) +{ + if (tok == NULL) + return; + if (tok->data != NULL) + free(tok->data); + free(tok); } char *stream_init(void) diff --git a/src/lexer/lexer.h b/src/lexer/lexer.h index a4d9e13..fe7a024 100644 --- a/src/lexer/lexer.h +++ b/src/lexer/lexer.h @@ -5,6 +5,7 @@ enum token_type { + TOKEN_NULL = 0, TOKEN_EOF, TOKEN_WORD, TOKEN_NEWLINE, From 58fd9d530e64b0abed2660bc398207d7573c8f7c Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Tue, 13 Jan 2026 22:07:40 +0100 Subject: [PATCH 053/282] fix(lexer): includes + typo + clang format --- src/lexer/lexer.c | 15 ++++++++------- src/lexer/lexer.h | 4 ++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index d42df48..00c8fa6 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -28,10 +29,9 @@ static void save_state(char *stream, ssize_t i) */ static bool is_special_char(char c) { - return c == '\'' || c == '\n' || c == ';' || c == 'EOF'; + return c == '\'' || c == '\n' || c == ';' || c == EOF; } - /* @brief: if a special character is found at [begin], * [tok->token_type] is set accordingly * @@ -40,7 +40,7 @@ static void set_token_spechar(struct token *tok, char *begin, ssize_t size) { if (size != 1) return; - if (begin[0] == 'EOF') + if (begin[0] == EOF) { tok->type = TOKEN_EOF; } @@ -58,7 +58,6 @@ static void set_token_spechar(struct token *tok, char *begin, ssize_t size) } } - /* @brief: if a keyword is found at [begin], * [tok->token_type] is set accordingly * @@ -90,10 +89,12 @@ static void set_token_keyword(struct token *tok, char *begin, ssize_t size) */ static void set_token_word(struct token *tok, char *begin, ssize_t size) { - if (tok->token_type == TOKEN_NULL) + if (tok->type == TOKEN_NULL) { - char *token_data = calloc(size + 1, sizeof(char)); - strncpy(res, begin, size); + tok->data = calloc(size + 1, sizeof(char)); + if (tok->data == NULL) + return; + strncpy(tok->data, begin, size); } } diff --git a/src/lexer/lexer.h b/src/lexer/lexer.h index fe7a024..9d9ea06 100644 --- a/src/lexer/lexer.h +++ b/src/lexer/lexer.h @@ -20,7 +20,7 @@ enum token_type struct token { enum token_type type; - char* data; + char *data; }; /* @@ -57,7 +57,7 @@ struct token *new_token(char *begin, ssize_t size); /* @brief: frees the token given in argument * */ -void free_token(struct token* tok); +void free_token(struct token *tok); /* * @brief: checks if the stream used for the last token creation is empty. From 18b0ccee2805283ff7d3d368e264c04adff29921 Mon Sep 17 00:00:00 2001 From: Jean Herail Date: Tue, 13 Jan 2026 22:16:14 +0100 Subject: [PATCH 054/282] fix(io_backend): re-added the iob_config_from_args in the header file --- src/io_backend/io_backend.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/io_backend/io_backend.h b/src/io_backend/io_backend.h index b8eed52..39e6d2e 100644 --- a/src/io_backend/io_backend.h +++ b/src/io_backend/io_backend.h @@ -38,6 +38,15 @@ struct iob_context char *args; }; +/** + * @brief Converts struct arg_options to iob_context + * + * @param args The arguments options struct + * @param ctx The IO Backend context struct to populate + * @return int 0 on success, negative value on error + */ +int iob_config_from_args(struct args_options *args, struct iob_context *ctx); + /* * @brief Initializes the IO Backend module * From ae2ddf07714586e5a661bd821275e62ccd13a65c Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Tue, 13 Jan 2026 22:24:43 +0100 Subject: [PATCH 055/282] fix(iobackend): missing header --- src/io_backend/io_backend.c | 4 ++-- src/io_backend/io_backend.h | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/io_backend/io_backend.c b/src/io_backend/io_backend.c index c6cebdc..eef080a 100644 --- a/src/io_backend/io_backend.c +++ b/src/io_backend/io_backend.c @@ -125,7 +125,7 @@ int iob_config_from_args(struct args_options *args, struct iob_context *ctx) break; default: - return -1; + return IOB_ERROR_GENERIC; } return 0; -} \ No newline at end of file +} diff --git a/src/io_backend/io_backend.h b/src/io_backend/io_backend.h index 39e6d2e..7b9e9b2 100644 --- a/src/io_backend/io_backend.h +++ b/src/io_backend/io_backend.h @@ -2,6 +2,7 @@ #define IO_BACKEND_H #include +#include "utils/args/args.h" // Error codes #define IOB_ERROR_GENERIC -1 From 19c9ce6c5de802697056bad20d1f8df4b1b187ed Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Wed, 14 Jan 2026 18:48:12 +0100 Subject: [PATCH 056/282] fix(lexer): keyword tokens now also contains the char *data --- src/lexer/lexer.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index 00c8fa6..562ab8a 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -82,6 +82,11 @@ static void set_token_keyword(struct token *tok, char *begin, ssize_t size) { tok->type = TOKEN_ELSE; } + + tok->data = calloc(size + 1, sizeof(char)); + if (tok->data == NULL) + return; + strncpy(tok->data, begin, size); } /* @brief: if token_type has not yet been set, then it is a TOKEN_WORD From bc7f8f3e8c0292500d2994898f7964b63edd502a Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Wed, 14 Jan 2026 19:58:59 +0100 Subject: [PATCH 057/282] feat(lexer): elif token type --- src/lexer/lexer.c | 4 ++++ src/lexer/lexer.h | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index 562ab8a..748ad63 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -82,6 +82,10 @@ static void set_token_keyword(struct token *tok, char *begin, ssize_t size) { tok->type = TOKEN_ELSE; } + else if (strncmp(begin, "elif", size) == 0) + { + tok->type = TOKEN_ELIF; + } tok->data = calloc(size + 1, sizeof(char)); if (tok->data == NULL) diff --git a/src/lexer/lexer.h b/src/lexer/lexer.h index 9d9ea06..332355f 100644 --- a/src/lexer/lexer.h +++ b/src/lexer/lexer.h @@ -14,7 +14,8 @@ enum token_type TOKEN_IF, TOKEN_THEN, TOKEN_ELSE, - TOKEN_FI + TOKEN_FI, + TOKEN_ELIF }; struct token From eba6d0f762bbe9b3659debdce56149b17f1cae69 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Wed, 14 Jan 2026 20:32:51 +0100 Subject: [PATCH 058/282] refactor(ast): one file per ast_type -- UNSTABLE --- src/utils/ast/ast.h | 73 ++----------------------------------- src/utils/ast/ast_command.h | 30 +++++++++++++++ src/utils/ast/ast_if.h | 32 ++++++++++++++++ src/utils/ast/ast_list.h | 29 +++++++++++++++ src/utils/ast/ast_void.h | 25 +++++++++++++ 5 files changed, 120 insertions(+), 69 deletions(-) create mode 100644 src/utils/ast/ast_command.h create mode 100644 src/utils/ast/ast_if.h create mode 100644 src/utils/ast/ast_list.h create mode 100644 src/utils/ast/ast_void.h diff --git a/src/utils/ast/ast.h b/src/utils/ast/ast.h index 49f0cf4..0924ab8 100644 --- a/src/utils/ast/ast.h +++ b/src/utils/ast/ast.h @@ -1,9 +1,10 @@ #ifndef AST_H #define AST_H -#include - -#include "../lists/lists.h" +#include "utils/ast/ast_command.h" +#include "utils/ast/ast_if.h" +#include "utils/ast/ast_list.h" +#include "utils/ast/ast_void.h" enum ast_type { @@ -13,18 +14,6 @@ enum ast_type AST_CMD }; -struct ast_cmd -{ - struct list *cmd; // A list of words (char*) -}; - -struct ast_if -{ - struct ast *condition; - struct ast *then_clause; - struct ast *else_clause; -}; - struct ast { enum ast_type type; @@ -38,58 +27,4 @@ struct ast void *data; }; -struct ast_list -{ - struct list *children; // A list of ASTs (ast*) -}; - -/** - * Checks if the given AST node is an if statement. - */ -bool ast_is_if(struct ast *node); - -/** - * Checks if the given AST node is a command. - */ -bool ast_is_cmd(struct ast *node); - -/** - * Checks if the given AST node is a command. - */ -bool ast_is_list(struct ast *node); - -/** - * Retrieves the if statement data from the given AST node. - * Assumes that the node is of type AST_IF. - */ -struct ast_if *ast_get_if(struct ast *node); - -/** - * Retrieves the command data from the given AST node. - * Assumes that the node is of type AST_CMD. - */ -struct ast_cmd *ast_get_cmd(struct ast *node); - -/** - * Retrieves the command data from the given AST node. - * Assumes that the node is of type AST_LIST. - */ -struct ast_list *ast_get_list(struct ast *node); - -/** - * Creates a new AST node representing an if statement. - */ -struct ast *ast_create_if(struct ast *condition, struct ast *then_clause, - struct ast *else_clause); - -/** - * Creates a new AST node representing a command. - */ -struct ast *ast_create_cmd(struct list *cmd); - -/** - * Creates a new AST node representing a list of ASTs - */ -struct ast* ast_create_list(struct list* ast_list); - #endif /* ! AST_H */ diff --git a/src/utils/ast/ast_command.h b/src/utils/ast/ast_command.h new file mode 100644 index 0000000..d95ad4c --- /dev/null +++ b/src/utils/ast/ast_command.h @@ -0,0 +1,30 @@ +#ifndef AST_COMMAND_H +#define AST_COMMAND_H + +#include + +#include "../lists/lists.h" +#include "utils/ast/ast.h" + +struct ast_cmd +{ + struct list *cmd; // A list of words (char*) +}; + +/** + * Checks if the given AST node is a command. + */ +bool ast_is_cmd(struct ast *node); + +/** + * Retrieves the command data from the given AST node. + * Assumes that the node is of type AST_CMD. + */ +struct ast_cmd *ast_get_cmd(struct ast *node); + +/** + * Creates a new AST node representing a command. + */ +struct ast *ast_create_cmd(struct list *cmd); + +#endif /* ! AST_COMMAND_H */ diff --git a/src/utils/ast/ast_if.h b/src/utils/ast/ast_if.h new file mode 100644 index 0000000..d470a92 --- /dev/null +++ b/src/utils/ast/ast_if.h @@ -0,0 +1,32 @@ +#ifndef AST_IF_H +#define AST_IF_H + +#include + +#include "utils/ast/ast.h" + +struct ast_if +{ + struct ast *condition; + struct ast *then_clause; + struct ast *else_clause; +}; + +/** + * Checks if the given AST node is an if statement. + */ +bool ast_is_if(struct ast *node); + +/** + * Retrieves the if statement data from the given AST node. + * Assumes that the node is of type AST_IF. + */ +struct ast_if *ast_get_if(struct ast *node); + +/** + * Creates a new AST node representing an if statement. + */ +struct ast *ast_create_if(struct ast *condition, struct ast *then_clause, + struct ast *else_clause); + +#endif /* ! AST_IF_H */ diff --git a/src/utils/ast/ast_list.h b/src/utils/ast/ast_list.h new file mode 100644 index 0000000..b733c54 --- /dev/null +++ b/src/utils/ast/ast_list.h @@ -0,0 +1,29 @@ +#ifndef AST_LIST_H +#define AST_LIST_H + +#include + +#include "utils/ast/ast.h" + +/** + * Creates a new AST node representing a list of ASTs + */ +struct ast *ast_create_list(struct list *ast_list); + +/** + * Retrieves the command data from the given AST node. + * Assumes that the node is of type AST_LIST. + */ +struct ast_list *ast_get_list(struct ast *node); + +/** + * Checks if the given AST node is a command. + */ +bool ast_is_list(struct ast *node); + +struct ast_list +{ + struct list *children; // A list of ASTs (ast*) +}; + +#endif /* ! AST_LIST_H */ diff --git a/src/utils/ast/ast_void.h b/src/utils/ast/ast_void.h new file mode 100644 index 0000000..62996fb --- /dev/null +++ b/src/utils/ast/ast_void.h @@ -0,0 +1,25 @@ +#ifndef AST_VOID_H +#define AST_VOID_H + +#include + +#include "utils/ast/ast.h" + +/** + * Checks if the given AST node is of type AST_VOID. + */ +bool ast_is_void(struct ast *node); + +/** + * Retrieves the if statement data from the given AST node. + * Assumes that the node is of type AST_VOID. + */ +struct ast_void *ast_get_void(struct ast *node); + +/** + * Creates a new AST node representing NOTHING + * WARNING: data will be a NULL pointer + */ +struct ast *ast_create_void(struct list *ast_list); + +#endif /* ! AST_VOID_H */ From f6573d81f0e0b531baebad1be5f21188b501088d Mon Sep 17 00:00:00 2001 From: Jean Herail Date: Wed, 14 Jan 2026 20:42:31 +0100 Subject: [PATCH 059/282] feat(pretty-print): Just casually implemented the graphviz representation of parsed AST --- .gitignore | 1 + src/utils/ast/ast.c | 91 ++++++++++++++++++++++++++++++++++++++++++++- src/utils/ast/ast.h | 5 +++ 3 files changed, 95 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 64c4a76..ab7c85c 100644 --- a/.gitignore +++ b/.gitignore @@ -175,3 +175,4 @@ m4/ltversion.m4 m4/lt~obsolete.m4 Makefile +*.svg diff --git a/src/utils/ast/ast.c b/src/utils/ast/ast.c index 40b42d7..4019e44 100644 --- a/src/utils/ast/ast.c +++ b/src/utils/ast/ast.c @@ -1,9 +1,96 @@ -#include "ast.h" - #include #include +#include #include +#include "ast.h" + +static void ast_print_dot_recursive(struct ast *node, FILE *out) +{ + if (!node) + return; + + switch (node->type) + { + case AST_IF: + { + struct ast_if *if_data = ast_get_if(node); + fprintf(out, " node%p [label=\"IF\"];\n", (void *)node); + + if (if_data->condition) + { + fprintf(out, " node%p -> node%p;\n", (void *)node, + (void *)if_data->condition); + fprintf(out, " node%p [fillcolor=\"lightyellow\", style=\"filled\"];\n", + (void *)if_data->condition); + ast_print_dot_recursive(if_data->condition, out); + + if (if_data->then_clause) + { + fprintf(out, " node%p -> node%p [label=\"true\"];\n", + (void *)if_data->condition, (void *)if_data->then_clause); + ast_print_dot_recursive(if_data->then_clause, out); + } + + if (if_data->else_clause) + { + fprintf(out, " node%p -> node%p [label=\"false\"];\n", + (void *)if_data->condition, (void *)if_data->else_clause); + ast_print_dot_recursive(if_data->else_clause, out); + } + } + break; + } + case AST_CMD: + { + struct ast_cmd *cmd_data = ast_get_cmd(node); + fprintf(out, " node%p [label=\"", (void *)node); + struct list *l = cmd_data->cmd; + while (l) + { + fprintf(out, "%s", (char *)l->data); + if (l->next) + fprintf(out, " "); + l = l->next; + } + fprintf(out, "\"];\n"); + break; + } + case AST_END: + fprintf(out, " node%p [label=\"END\"];\n", (void *)node); + break; + default: + break; + } +} + +void ast_print_dot(struct ast *ast) +{ + // Always print to stdout so the user can see it in the terminal + printf("digraph AST {\n"); + if (ast) + { + ast_print_dot_recursive(ast, stdout); + } + printf("}\n"); + + if (!ast) + return; + + FILE *dot_pipe = popen("dot -Tsvg -o ast.svg", "w"); + if (!dot_pipe) + { + return; + } + + fprintf(dot_pipe, "digraph AST {\n"); + ast_print_dot_recursive(ast, dot_pipe); + fprintf(dot_pipe, "}\n"); + pclose(dot_pipe); + + system("open ast.svg"); +} + bool ast_is_if(struct ast *node) { assert(node != NULL); diff --git a/src/utils/ast/ast.h b/src/utils/ast/ast.h index 3247ba5..54fef4b 100644 --- a/src/utils/ast/ast.h +++ b/src/utils/ast/ast.h @@ -70,4 +70,9 @@ struct ast *ast_create_if(struct ast *condition, struct ast *then_clause, */ struct ast *ast_create_cmd(struct list *cmd); +/** + * Prints the Graphviz DOT representation of the given AST to stdout. + */ +void ast_print_dot(struct ast *ast); + #endif /* ! AST_H */ From 80e4b6c2fdf13c0ebf2ec227bc1ebb5d6a6fee3a Mon Sep 17 00:00:00 2001 From: Guillem George Date: Wed, 14 Jan 2026 20:53:47 +0100 Subject: [PATCH 060/282] feat: suport for the new lexer and if/else structures --- src/parser/parser.c | 46 ++++----- src/parser/parsing_utils.c | 189 +++++++++++++++++++++++++++++++++---- src/parser/parsing_utils.h | 5 +- 3 files changed, 194 insertions(+), 46 deletions(-) diff --git a/src/parser/parser.c b/src/parser/parser.c index 5b47dda..2918445 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -10,25 +10,7 @@ #include "utils/lists/lists.h" // === Static functions - -/* Returns true if c is a command terminator, false otherwise - */ -static bool isterminator(char c) -{ - switch (c) - { - case '\n': - case ';': - case EOF: - return true; - default: - return false; - } -} - -/* Parses a simple list of words (command and arguments) - * and returns the resulting ast - */ +// ... // === Functions @@ -37,19 +19,29 @@ struct ast *get_ast() struct list *result_list = NULL; struct ast *current_node = NULL; - char *token = peek_token(); - if (token != NULL) + struct token *token = peek_token(); + + while (token != NULL && token->type != TOKEN_EOF) + { + switch (token->type) + { + case TOKEN_WORD: + struct ast *cmd = parse_simple_command(); + result_list = list_append(result_list, cmd); + break; + default: + // Forward + token = pop_token(); + break; + } + } + + if (token == NULL) { puts("Internal error: cannot get the following token"); return NULL; } - while (token[0] != EOF) - { - struct ast *cmd = parse_simple_command(); - result_list = list_append(result_list, cmd); - } - struct ast *result = ast_create_list(result_list); return result; } diff --git a/src/parser/parsing_utils.c b/src/parser/parsing_utils.c index de77af3..74ec36d 100644 --- a/src/parser/parsing_utils.c +++ b/src/parser/parsing_utils.c @@ -1,23 +1,65 @@ +// === Includes +#include "parsing_utils.h" + +#include +#include +#include + +#include "lexer/lexer.h" +#include "utils/ast/ast.h" + +// === Macros + +#define PEEK_TOKEN() \ + peek_token(); \ + if (token == NULL) \ + { \ + puts("Internal error: cannot get the following token"); \ + return NULL; \ + } + +#define POP_TOKEN() \ + pop_token(); \ + if (token == NULL) \ + { \ + puts("Internal error: cannot get the following token"); \ + return NULL; \ + } + +// === Static functions + +/* Returns true if c is a command terminator, false otherwise + */ +static bool isterminator(struct token *token) +{ + if (token == NULL) + return false; + + switch (token->type) + { + case TOKEN_NEWLINE: + case TOKEN_SEMICOLON: + case TOKEN_EOF: + return true; + default: + return false; + } +} + +// === Functions + +/* Parses a simple list of words (command and arguments) + * and returns the resulting ast + */ struct ast *parse_simple_command(void) { struct list *cmd_elements = NULL; - char *token = pop_token(); - if (token == NULL) // just in case ? - { - puts("Internal error: cannot get the following token"); - return NULL; - } + struct token *token = POP_TOKEN(); - while (token != NULL && !isterminator(token[0])) + while (!isterminator(token)) { - cmd_elements = list_append(cmd_elements, token); - token = pop_token(); - } - - if (token == NULL) - { - puts("Internal error: cannot get the following token"); - return NULL; + cmd_elements = list_append(cmd_elements, token->data); + token = POP_TOKEN(); } struct ast *result = ast_create_cmd(cmd_elements); @@ -26,10 +68,123 @@ struct ast *parse_simple_command(void) struct ast *parse_if_rule(void) { - return NULL; + // If condition + struct token *token = POP_TOKEN(); + + if (token->type != TOKEN_IF) + { + puts("Internal error: expected a if rule but token has different " + "type"); + return NULL; + } + + struct ast *condition_content = parse_compound_list(); + + // Then content + token = POP_TOKEN(); + + if (token->type != TOKEN_THEN) + { + puts("Expected the 'then' keyword but token has different type"); + return NULL; + } + + struct ast *then_content = parse_compound_list(); + + // Eventual else/elif clause(s) + struct ast *else_content = parse_else_clause(); + + token = POP_TOKEN(); + if (token->type != TOKEN_FI) + { + puts("Expected the 'fi' keyword but token has different type"); + // TODO free previous asts + return NULL; + } + + struct ast *result = + ast_create_if(condition_content, then_content, else_content); + if (result == NULL) + { + puts("Internal error: could not create a new AST (AST_IF)"); + // TODO free previous asts + return NULL; + } + + return result; +} + +struct ast *parse_compound_list(void) +{ + struct list *result_list = NULL; // ast* list + struct list *cmd_elements = NULL; // token* list + struct token *token = PEEK_TOKEN(); + + while (token->type != TOKEN_THEN || token->type != TOKEN_ELIF + || TOKEN_TYPE != TOKEN_ELSE) + { + // Parse simple command + if (token->type == TOKEN_SEMICOLON || token->type == TOKEN_NEWLINE) + { + // Stage (-> next command) + struct ast *command = ast_create_cmd(cmd_elements); + result_list = list_append(result_list, command); + cmd_elements = NULL; + } + + if (token->type == TOKEN_EOF) + { + puts("Syntax error: Unexpected end of stream"); // TODO pas très + // bien dit + return NULL; + } + + cmd_elements = list_append(cmd_elements, token->data); + token = POP_TOKEN(); + } + + struct ast *result = ast_create_list(result_list); + return result; +} + +struct ast *parse_else_clause(void) +{ + // Eventual elif content + token = PEEK_TOKEN(); + + struct ast *result = NULL; + + // TODO handle ELIF + // while (token->type == TOKEN_ELIF) + // { + // puts("ABORTING ELIF: Not implemented ma gueule"); + // token = POP_TOKEN(); // Forward + // } + + // Eventual else content + if (token->type == TOKEN_ELSE) + { + result = parse_compound_list(); + token = POP_TOKEN(); // Forward + } + + if (result == NULL) + result == ast_create_void(); + + return result; } struct ast *parse_shell_command(void) { - return parse_if_rule(); + struct token *token = PEEK_TOKEN(); + + switch (token->type) + { + case TOKEN_IF: + return parse_if_rule(); + + default: + puts("I think it's not implemented yet"); + return NULL; + } } diff --git a/src/parser/parsing_utils.h b/src/parser/parsing_utils.h index 0ba9370..6981a13 100644 --- a/src/parser/parsing_utils.h +++ b/src/parser/parsing_utils.h @@ -11,7 +11,8 @@ struct ast *parse_if_rule(void); */ struct ast *parse_shell_command(void); -/* +/* @brief parses commands inside if/else clauses and returns the corresponding + * AST list */ struct ast* parse_compound_list(void); @@ -21,4 +22,4 @@ struct ast* parse_and_or(void); /* */ -struct ast* else_clause(void); +struct ast* parse_else_clause(void); From 68d8a1fd1375f3d7964796d351cbb9fa0d379a53 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Wed, 14 Jan 2026 20:53:57 +0100 Subject: [PATCH 061/282] refactor(ast): one file per ast_type -- UNSTABLE (2) --- src/utils/ast/ast.c | 53 ------------------------------------- src/utils/ast/ast_command.c | 29 ++++++++++++++++++++ src/utils/ast/ast_if.c | 32 ++++++++++++++++++++++ src/utils/ast/ast_void.c | 1 + 4 files changed, 62 insertions(+), 53 deletions(-) create mode 100644 src/utils/ast/ast_command.c create mode 100644 src/utils/ast/ast_if.c create mode 100644 src/utils/ast/ast_void.c diff --git a/src/utils/ast/ast.c b/src/utils/ast/ast.c index 40b42d7..ed931e1 100644 --- a/src/utils/ast/ast.c +++ b/src/utils/ast/ast.c @@ -1,34 +1,6 @@ #include "ast.h" -#include #include -#include - -bool ast_is_if(struct ast *node) -{ - assert(node != NULL); - return node->type == AST_IF; -} - -bool ast_is_cmd(struct ast *node) -{ - assert(node != NULL); - return node->type == AST_CMD; -} - -struct ast_if *ast_get_if(struct ast *node) -{ - assert(node != NULL); - assert(node->type == AST_IF); - return (struct ast_if *)node->data; -} - -struct ast_cmd *ast_get_cmd(struct ast *node) -{ - assert(node != NULL); - assert(node->type == AST_CMD); - return (struct ast_cmd *)node->data; -} static struct ast *ast_create(enum ast_type type, void *data) { @@ -41,28 +13,3 @@ static struct ast *ast_create(enum ast_type type, void *data) return node; } - -struct ast *ast_create_if(struct ast *condition, struct ast *then_clause, - struct ast *else_clause) -{ - struct ast_if *if_data = malloc(sizeof(struct ast_if)); - if (!if_data) - return NULL; - - if_data->condition = condition; - if_data->then_clause = then_clause; - if_data->else_clause = else_clause; - - return ast_create(AST_IF, if_data); -} - -struct ast *ast_create_cmd(struct list *cmd) -{ - struct ast_cmd *cmd_data = malloc(sizeof(struct ast_cmd)); - if (!cmd_data) - return NULL; - - cmd_data->cmd = cmd; - - return ast_create(AST_CMD, cmd_data); -} diff --git a/src/utils/ast/ast_command.c b/src/utils/ast/ast_command.c new file mode 100644 index 0000000..4e4af66 --- /dev/null +++ b/src/utils/ast/ast_command.c @@ -0,0 +1,29 @@ +#include "utils/ast/ast_command.h" + +#include +#include +#include + +struct ast *ast_create_cmd(struct list *cmd) +{ + struct ast_cmd *cmd_data = malloc(sizeof(struct ast_cmd)); + if (!cmd_data) + return NULL; + + cmd_data->cmd = cmd; + + return ast_create(AST_CMD, cmd_data); +} + +struct ast_cmd *ast_get_cmd(struct ast *node) +{ + assert(node != NULL); + assert(node->type == AST_CMD); + return (struct ast_cmd *)node->data; +} + +bool ast_is_cmd(struct ast *node) +{ + assert(node != NULL); + return node->type == AST_CMD; +} diff --git a/src/utils/ast/ast_if.c b/src/utils/ast/ast_if.c new file mode 100644 index 0000000..746fd9d --- /dev/null +++ b/src/utils/ast/ast_if.c @@ -0,0 +1,32 @@ +#include "utils/ast/ast_if.h" + +#include +#include +#include + +struct ast *ast_create_if(struct ast *condition, struct ast *then_clause, + struct ast *else_clause) +{ + struct ast_if *if_data = malloc(sizeof(struct ast_if)); + if (!if_data) + return NULL; + + if_data->condition = condition; + if_data->then_clause = then_clause; + if_data->else_clause = else_clause; + + return ast_create(AST_IF, if_data); +} + +struct ast_if *ast_get_if(struct ast *node) +{ + assert(node != NULL); + assert(node->type == AST_IF); + return (struct ast_if *)node->data; +} + +bool ast_is_if(struct ast *node) +{ + assert(node != NULL); + return node->type == AST_IF; +} diff --git a/src/utils/ast/ast_void.c b/src/utils/ast/ast_void.c new file mode 100644 index 0000000..3a2ae7b --- /dev/null +++ b/src/utils/ast/ast_void.c @@ -0,0 +1 @@ +#include "utils/ast/ast_void.h" From e3ec4846219cb1c395adbbb922438364033bc4f6 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Thu, 15 Jan 2026 15:53:56 +0100 Subject: [PATCH 062/282] refactor(ast): one file per ast_type -- UNSTABLE (3) --- src/utils/ast/ast.c | 17 ++++++++++++++++- src/utils/ast/ast.h | 11 +++++++++++ src/utils/ast/ast_command.c | 8 ++++++++ src/utils/ast/ast_command.h | 7 ++++++- src/utils/ast/ast_if.c | 12 ++++++++++++ src/utils/ast/ast_if.h | 4 ++++ src/utils/ast/ast_list.h | 2 ++ src/utils/ast/ast_void.c | 15 +++++++++++++++ src/utils/ast/ast_void.h | 9 ++------- 9 files changed, 76 insertions(+), 9 deletions(-) diff --git a/src/utils/ast/ast.c b/src/utils/ast/ast.c index ed931e1..e04e170 100644 --- a/src/utils/ast/ast.c +++ b/src/utils/ast/ast.c @@ -2,7 +2,22 @@ #include -static struct ast *ast_create(enum ast_type type, void *data) +void ast_free(struct ast *node) +{ + if (node == NULL) + return; + // ast void does not need to be freed. + if (ast_is_if(node)) + ast_free_if(ast_get_if(node)); + else if (ast_is_command(node)) + ast_free_command(ast_get_command(node)); + else if (ast_is_list(node)) + ast_free_list(ast_get_list(node)); + + free(node); +} + +struct ast *ast_create(enum ast_type type, void *data) { struct ast *node = malloc(sizeof(struct ast)); if (!node) diff --git a/src/utils/ast/ast.h b/src/utils/ast/ast.h index 0924ab8..64ee195 100644 --- a/src/utils/ast/ast.h +++ b/src/utils/ast/ast.h @@ -27,4 +27,15 @@ struct ast void *data; }; +/* @brief: returns an ast* with corresponding data and type. + * + * @note: this function should only be called by ast_create_[TYPE] functions. + */ +struct ast *ast_create(enum ast_type type, void *data); + +/* @brief: frees the given ast. If ast is NULL, does nothing. + * + */ +void ast_free(struct ast *node) + #endif /* ! AST_H */ diff --git a/src/utils/ast/ast_command.c b/src/utils/ast/ast_command.c index 4e4af66..a1eafcb 100644 --- a/src/utils/ast/ast_command.c +++ b/src/utils/ast/ast_command.c @@ -4,6 +4,8 @@ #include #include +#include "utils/lists/lists.h" + struct ast *ast_create_cmd(struct list *cmd) { struct ast_cmd *cmd_data = malloc(sizeof(struct ast_cmd)); @@ -27,3 +29,9 @@ bool ast_is_cmd(struct ast *node) assert(node != NULL); return node->type == AST_CMD; } + +void ast_free_cmd(struct ast_cmd *cmd_data) +{ + list_deep_destroy(cmd_data->cmd); + free(cmd_data); +} diff --git a/src/utils/ast/ast_command.h b/src/utils/ast/ast_command.h index d95ad4c..9a623a8 100644 --- a/src/utils/ast/ast_command.h +++ b/src/utils/ast/ast_command.h @@ -3,7 +3,7 @@ #include -#include "../lists/lists.h" +#include "utils/lists/lists.h" #include "utils/ast/ast.h" struct ast_cmd @@ -27,4 +27,9 @@ struct ast_cmd *ast_get_cmd(struct ast *node); */ struct ast *ast_create_cmd(struct list *cmd); +/* + * @brief: frees the given ast_cmd. + */ +void ast_free_cmd(struct ast_cmd *cmd_data) + #endif /* ! AST_COMMAND_H */ diff --git a/src/utils/ast/ast_if.c b/src/utils/ast/ast_if.c index 746fd9d..50d4c5e 100644 --- a/src/utils/ast/ast_if.c +++ b/src/utils/ast/ast_if.c @@ -30,3 +30,15 @@ bool ast_is_if(struct ast *node) assert(node != NULL); return node->type == AST_IF; } + +void ast_free_if(struct ast_if *if_data) +{ + if (if_data == NULL) + return; + + ast_free(if_data->condition); + ast_free(if_data->then_clause); + ast_free(if_data->else_clause); + + free(if_data); +} diff --git a/src/utils/ast/ast_if.h b/src/utils/ast/ast_if.h index d470a92..69d97ec 100644 --- a/src/utils/ast/ast_if.h +++ b/src/utils/ast/ast_if.h @@ -28,5 +28,9 @@ struct ast_if *ast_get_if(struct ast *node); */ struct ast *ast_create_if(struct ast *condition, struct ast *then_clause, struct ast *else_clause); +/* + * @brief: frees the given ast_if. + */ +void ast_free_if(struct ast_if *if_data) #endif /* ! AST_IF_H */ diff --git a/src/utils/ast/ast_list.h b/src/utils/ast/ast_list.h index b733c54..af7be28 100644 --- a/src/utils/ast/ast_list.h +++ b/src/utils/ast/ast_list.h @@ -26,4 +26,6 @@ struct ast_list struct list *children; // A list of ASTs (ast*) }; +void ast_free_list(struct ast_list *ast_list); + #endif /* ! AST_LIST_H */ diff --git a/src/utils/ast/ast_void.c b/src/utils/ast/ast_void.c index 3a2ae7b..1396121 100644 --- a/src/utils/ast/ast_void.c +++ b/src/utils/ast/ast_void.c @@ -1 +1,16 @@ #include "utils/ast/ast_void.h" + +#include +#include +#include + +bool ast_is_void(struct ast *node) +{ + assert(node != NULL); + return node->type == AST_VOID; +} + +struct ast *ast_create_void(void) +{ + return ast_create(AST_VOID, NULL); +} diff --git a/src/utils/ast/ast_void.h b/src/utils/ast/ast_void.h index 62996fb..c3fb123 100644 --- a/src/utils/ast/ast_void.h +++ b/src/utils/ast/ast_void.h @@ -3,6 +3,7 @@ #include +#include "utils/lists/lists.h" #include "utils/ast/ast.h" /** @@ -10,16 +11,10 @@ */ bool ast_is_void(struct ast *node); -/** - * Retrieves the if statement data from the given AST node. - * Assumes that the node is of type AST_VOID. - */ -struct ast_void *ast_get_void(struct ast *node); - /** * Creates a new AST node representing NOTHING * WARNING: data will be a NULL pointer */ -struct ast *ast_create_void(struct list *ast_list); +struct ast *ast_create_void(void); #endif /* ! AST_VOID_H */ From 38f5a358850ab5cb328800140956fa20ec455838 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Thu, 15 Jan 2026 15:59:42 +0100 Subject: [PATCH 063/282] refactor(ast): one file per ast_type -- UNSTABLE (4) --- src/utils/ast/ast_list.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/utils/ast/ast_list.c diff --git a/src/utils/ast/ast_list.c b/src/utils/ast/ast_list.c new file mode 100644 index 0000000..de178ee --- /dev/null +++ b/src/utils/ast/ast_list.c @@ -0,0 +1,16 @@ +#include "utils/ast/ast.h" + +struct ast *ast_create_list(struct list *ast_list); + +struct ast_list *ast_get_list(struct ast *node); + +bool ast_is_list(struct ast *node) + +void ast_free_list(struct ast_list *ast_list) +{ + if (ast_list == NULL) + return; + + list_deep_destroy(ast_list->children); + free(ast_list); +} From 30df2993ee2b3ef5dc798f4975f0e5a89339baa1 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Thu, 15 Jan 2026 17:12:06 +0100 Subject: [PATCH 064/282] refactor(ast): one file per ast_type -- FINISHED --- src/utils/Makefile.am | 4 ++++ src/utils/ast/ast.h | 24 ++---------------------- src/utils/ast/ast_base.h | 25 +++++++++++++++++++++++++ src/utils/ast/ast_command.c | 7 ++++--- src/utils/ast/ast_command.h | 6 +++--- src/utils/ast/ast_if.c | 13 +++++++------ src/utils/ast/ast_if.h | 6 +++--- src/utils/ast/ast_list.c | 31 ++++++++++++++++++++++++------- src/utils/ast/ast_list.h | 18 ++++++++++++------ src/utils/ast/ast_void.h | 2 +- 10 files changed, 85 insertions(+), 51 deletions(-) create mode 100644 src/utils/ast/ast_base.h diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am index 600ee40..9fd1be1 100644 --- a/src/utils/Makefile.am +++ b/src/utils/Makefile.am @@ -3,6 +3,10 @@ lib_LIBRARIES = libutils.a libutils_a_SOURCES = \ string_utils/string_utils.c \ ast/ast.c \ + ast/ast_if.c \ + ast/ast_command.c \ + ast/ast_list.c \ + ast/ast_void.c \ lists/lists.c \ args/args.c diff --git a/src/utils/ast/ast.h b/src/utils/ast/ast.h index 64ee195..79eeff3 100644 --- a/src/utils/ast/ast.h +++ b/src/utils/ast/ast.h @@ -1,32 +1,12 @@ #ifndef AST_H #define AST_H +#include "utils/ast/ast_base.h" #include "utils/ast/ast_command.h" #include "utils/ast/ast_if.h" #include "utils/ast/ast_list.h" #include "utils/ast/ast_void.h" -enum ast_type -{ - AST_END, - AST_LIST, - AST_IF, - AST_CMD -}; - -struct ast -{ - enum ast_type type; - - /** - * Data associated with this AST node. It can be one of the following: - * - NULL (AST_END) - * - struct ast_if* (AST_IF) - * - struct ast_cmd* (AST_CMD) - */ - void *data; -}; - /* @brief: returns an ast* with corresponding data and type. * * @note: this function should only be called by ast_create_[TYPE] functions. @@ -36,6 +16,6 @@ struct ast *ast_create(enum ast_type type, void *data); /* @brief: frees the given ast. If ast is NULL, does nothing. * */ -void ast_free(struct ast *node) +void ast_free(struct ast *node); #endif /* ! AST_H */ diff --git a/src/utils/ast/ast_base.h b/src/utils/ast/ast_base.h new file mode 100644 index 0000000..aa20b76 --- /dev/null +++ b/src/utils/ast/ast_base.h @@ -0,0 +1,25 @@ +#ifndef AST_BASE_H +#define AST_BASE_H + +enum ast_type +{ + AST_END, + AST_LIST, + AST_IF, + AST_CMD +}; + +struct ast +{ + enum ast_type type; + + /** + * Data associated with this AST node. It can be one of the following: + * - NULL (AST_END) + * - struct ast_if* (AST_IF) + * - struct ast_cmd* (AST_CMD) + */ + void *data; +}; + +#endif /* ! AST_BASE_H */ diff --git a/src/utils/ast/ast_command.c b/src/utils/ast/ast_command.c index a1eafcb..9eb2dd7 100644 --- a/src/utils/ast/ast_command.c +++ b/src/utils/ast/ast_command.c @@ -30,8 +30,9 @@ bool ast_is_cmd(struct ast *node) return node->type == AST_CMD; } -void ast_free_cmd(struct ast_cmd *cmd_data) +void ast_free_cmd(struct ast_cmd **cmd_data) { - list_deep_destroy(cmd_data->cmd); - free(cmd_data); + list_deep_destroy((*cmd_data)->cmd); + free(*cmd_data); + *cmd_data = NULL; } diff --git a/src/utils/ast/ast_command.h b/src/utils/ast/ast_command.h index 9a623a8..f7d22a1 100644 --- a/src/utils/ast/ast_command.h +++ b/src/utils/ast/ast_command.h @@ -4,7 +4,7 @@ #include #include "utils/lists/lists.h" -#include "utils/ast/ast.h" +#include "utils/ast/ast_base.h" struct ast_cmd { @@ -28,8 +28,8 @@ struct ast_cmd *ast_get_cmd(struct ast *node); struct ast *ast_create_cmd(struct list *cmd); /* - * @brief: frees the given ast_cmd. + * @brief: frees the given ast_cmd and sets the pointer to NULL. */ -void ast_free_cmd(struct ast_cmd *cmd_data) +void ast_free_cmd(struct ast_cmd **cmd_data); #endif /* ! AST_COMMAND_H */ diff --git a/src/utils/ast/ast_if.c b/src/utils/ast/ast_if.c index 50d4c5e..242d5fd 100644 --- a/src/utils/ast/ast_if.c +++ b/src/utils/ast/ast_if.c @@ -31,14 +31,15 @@ bool ast_is_if(struct ast *node) return node->type == AST_IF; } -void ast_free_if(struct ast_if *if_data) +void ast_free_if(struct ast_if **if_data) { - if (if_data == NULL) + if (if_data == NULL || *if_data == NULL) return; - ast_free(if_data->condition); - ast_free(if_data->then_clause); - ast_free(if_data->else_clause); + ast_free((*if_data)->condition); + ast_free((*if_data)->then_clause); + ast_free((*if_data)->else_clause); - free(if_data); + free(*if_data); + *if_data = NULL; } diff --git a/src/utils/ast/ast_if.h b/src/utils/ast/ast_if.h index 69d97ec..2036eb7 100644 --- a/src/utils/ast/ast_if.h +++ b/src/utils/ast/ast_if.h @@ -3,7 +3,7 @@ #include -#include "utils/ast/ast.h" +#include "utils/ast/ast_base.h" struct ast_if { @@ -29,8 +29,8 @@ struct ast_if *ast_get_if(struct ast *node); struct ast *ast_create_if(struct ast *condition, struct ast *then_clause, struct ast *else_clause); /* - * @brief: frees the given ast_if. + * @brief: frees the given ast_if and sets the pointer to NULL. */ -void ast_free_if(struct ast_if *if_data) +void ast_free_if(struct ast_if **if_data); #endif /* ! AST_IF_H */ diff --git a/src/utils/ast/ast_list.c b/src/utils/ast/ast_list.c index de178ee..fae1f1a 100644 --- a/src/utils/ast/ast_list.c +++ b/src/utils/ast/ast_list.c @@ -1,16 +1,33 @@ #include "utils/ast/ast.h" -struct ast *ast_create_list(struct list *ast_list); +struct ast *ast_create_list(struct list *list) +{ + struct ast_list *ast_list = malloc(sizeof(struct ast_list)); + if (ast_list == NULL) + return NULL; -struct ast_list *ast_get_list(struct ast *node); + ast_list->list = list; + + return ast_create(AST_LIST, ast_list); +} + +struct ast_list *ast_get_list(struct ast *node) +{ + assert(node != NULL); + return (struct ast_list*)node->data; +} bool ast_is_list(struct ast *node) - -void ast_free_list(struct ast_list *ast_list) { - if (ast_list == NULL) + return node->type == AST_LIST; +} + +void ast_free_list(struct ast_list **ast_list) +{ + if (*ast_list == NULL || ast_list == NULL) return; - list_deep_destroy(ast_list->children); - free(ast_list); + list_deep_destroy((*ast_list)->children); + free(*ast_list); + *ast_list = NULL; } diff --git a/src/utils/ast/ast_list.h b/src/utils/ast/ast_list.h index af7be28..6f1cbd9 100644 --- a/src/utils/ast/ast_list.h +++ b/src/utils/ast/ast_list.h @@ -3,7 +3,14 @@ #include -#include "utils/ast/ast.h" +#include "utils/ast/ast_base.h" +#include "utils/lists/lists.h" + +struct ast_list +{ + struct list *children; // A list of ASTs (ast*) +}; + /** * Creates a new AST node representing a list of ASTs @@ -21,11 +28,10 @@ struct ast_list *ast_get_list(struct ast *node); */ bool ast_is_list(struct ast *node); -struct ast_list -{ - struct list *children; // A list of ASTs (ast*) -}; - +/* @brief: frees the given ast list. + * + * @warning: should only be called by ast_free() function. + */ void ast_free_list(struct ast_list *ast_list); #endif /* ! AST_LIST_H */ diff --git a/src/utils/ast/ast_void.h b/src/utils/ast/ast_void.h index c3fb123..866a8d8 100644 --- a/src/utils/ast/ast_void.h +++ b/src/utils/ast/ast_void.h @@ -4,7 +4,7 @@ #include #include "utils/lists/lists.h" -#include "utils/ast/ast.h" +#include "utils/ast/ast_base.h" /** * Checks if the given AST node is of type AST_VOID. From 1116eef08367c241052c4f5d26986db8636b31b5 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Thu, 15 Jan 2026 17:25:21 +0100 Subject: [PATCH 065/282] fix(parser): make it compile --- src/parser/Makefile.am | 2 +- src/parser/parser.c | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/parser/Makefile.am b/src/parser/Makefile.am index dd13901..3b7b6f5 100644 --- a/src/parser/Makefile.am +++ b/src/parser/Makefile.am @@ -2,7 +2,7 @@ lib_LIBRARIES = libparser.a libparser_a_SOURCES = \ parser.c \ - parser.h + parsing_utils.c libparser_a_CPPFLAGS = -I$(top_srcdir)/src diff --git a/src/parser/parser.c b/src/parser/parser.c index 2918445..0123e2f 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -8,6 +8,7 @@ #include "lexer/lexer.h" #include "utils/lists/lists.h" +#include "parser/parsing_utils.h" // === Static functions // ... @@ -26,8 +27,8 @@ struct ast *get_ast() switch (token->type) { case TOKEN_WORD: - struct ast *cmd = parse_simple_command(); - result_list = list_append(result_list, cmd); + current_node = parse_simple_command(); + result_list = list_append(result_list, current_node); break; default: // Forward @@ -39,6 +40,7 @@ struct ast *get_ast() if (token == NULL) { puts("Internal error: cannot get the following token"); + puts("Hint: EOF might be missing"); return NULL; } From 89ff5f4b1e5381f06ce0655292b166670c8ea5a0 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Thu, 15 Jan 2026 17:29:34 +0100 Subject: [PATCH 066/282] fix(parser): making it compile -- UNSTABLE --- src/parser/parsing_utils.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/parser/parsing_utils.c b/src/parser/parsing_utils.c index 74ec36d..359cee2 100644 --- a/src/parser/parsing_utils.c +++ b/src/parser/parsing_utils.c @@ -121,7 +121,7 @@ struct ast *parse_compound_list(void) struct token *token = PEEK_TOKEN(); while (token->type != TOKEN_THEN || token->type != TOKEN_ELIF - || TOKEN_TYPE != TOKEN_ELSE) + || token->type != TOKEN_ELSE) { // Parse simple command if (token->type == TOKEN_SEMICOLON || token->type == TOKEN_NEWLINE) @@ -150,7 +150,7 @@ struct ast *parse_compound_list(void) struct ast *parse_else_clause(void) { // Eventual elif content - token = PEEK_TOKEN(); + struct token *token = PEEK_TOKEN(); struct ast *result = NULL; @@ -169,7 +169,7 @@ struct ast *parse_else_clause(void) } if (result == NULL) - result == ast_create_void(); + result = ast_create_void(); return result; } From f0e3c2f41b195d51e9fbcf5a12824e78d205bbde Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Thu, 15 Jan 2026 17:41:15 +0100 Subject: [PATCH 067/282] fix(ast): rename *_cmd() functions to *_command() --- src/utils/ast/ast_base.h | 2 ++ src/utils/ast/ast_command.c | 24 ++++++++++++------------ src/utils/ast/ast_command.h | 14 +++++++------- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/utils/ast/ast_base.h b/src/utils/ast/ast_base.h index aa20b76..955ccdc 100644 --- a/src/utils/ast/ast_base.h +++ b/src/utils/ast/ast_base.h @@ -1,6 +1,8 @@ #ifndef AST_BASE_H #define AST_BASE_H +#include + enum ast_type { AST_END, diff --git a/src/utils/ast/ast_command.c b/src/utils/ast/ast_command.c index 9eb2dd7..6ad8fcc 100644 --- a/src/utils/ast/ast_command.c +++ b/src/utils/ast/ast_command.c @@ -6,33 +6,33 @@ #include "utils/lists/lists.h" -struct ast *ast_create_cmd(struct list *cmd) +struct ast *ast_create_command(struct list *command) { - struct ast_cmd *cmd_data = malloc(sizeof(struct ast_cmd)); - if (!cmd_data) + struct ast_command *command_data = malloc(sizeof(struct ast_command)); + if (!command_data) return NULL; - cmd_data->cmd = cmd; + command_data->command = command; - return ast_create(AST_CMD, cmd_data); + return ast_create(AST_CMD, command_data); } -struct ast_cmd *ast_get_cmd(struct ast *node) +struct ast_command *ast_get_command(struct ast *node) { assert(node != NULL); assert(node->type == AST_CMD); - return (struct ast_cmd *)node->data; + return (struct ast_command *)node->data; } -bool ast_is_cmd(struct ast *node) +bool ast_is_command(struct ast *node) { assert(node != NULL); return node->type == AST_CMD; } -void ast_free_cmd(struct ast_cmd **cmd_data) +void ast_free_command(struct ast_command **command_data) { - list_deep_destroy((*cmd_data)->cmd); - free(*cmd_data); - *cmd_data = NULL; + list_deep_destroy((*command_data)->command); + free(*command_data); + *command_data = NULL; } diff --git a/src/utils/ast/ast_command.h b/src/utils/ast/ast_command.h index f7d22a1..0e442a5 100644 --- a/src/utils/ast/ast_command.h +++ b/src/utils/ast/ast_command.h @@ -6,30 +6,30 @@ #include "utils/lists/lists.h" #include "utils/ast/ast_base.h" -struct ast_cmd +struct ast_command { - struct list *cmd; // A list of words (char*) + struct list *command; // A list of words (char*) }; /** * Checks if the given AST node is a command. */ -bool ast_is_cmd(struct ast *node); +bool ast_is_command(struct ast *node); /** * Retrieves the command data from the given AST node. * Assumes that the node is of type AST_CMD. */ -struct ast_cmd *ast_get_cmd(struct ast *node); +struct ast_command *ast_get_command(struct ast *node); /** * Creates a new AST node representing a command. */ -struct ast *ast_create_cmd(struct list *cmd); +struct ast *ast_create_command(struct list *command); /* - * @brief: frees the given ast_cmd and sets the pointer to NULL. + * @brief: frees the given ast_command and sets the pointer to NULL. */ -void ast_free_cmd(struct ast_cmd **cmd_data); +void ast_free_command(struct ast_command **command_data); #endif /* ! AST_COMMAND_H */ From eb5f57cbaa1cf4e7b3442bf5f7225024e57e79a3 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Thu, 15 Jan 2026 17:46:24 +0100 Subject: [PATCH 068/282] fix(parser): rename cmd => command --- src/parser/parsing_utils.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/parser/parsing_utils.c b/src/parser/parsing_utils.c index 359cee2..b064743 100644 --- a/src/parser/parsing_utils.c +++ b/src/parser/parsing_utils.c @@ -53,16 +53,16 @@ static bool isterminator(struct token *token) */ struct ast *parse_simple_command(void) { - struct list *cmd_elements = NULL; + struct list *command_elements = NULL; struct token *token = POP_TOKEN(); while (!isterminator(token)) { - cmd_elements = list_append(cmd_elements, token->data); + command_elements = list_append(command_elements, token->data); token = POP_TOKEN(); } - struct ast *result = ast_create_cmd(cmd_elements); + struct ast *result = ast_create_command(command_elements); return result; } @@ -117,7 +117,7 @@ struct ast *parse_if_rule(void) struct ast *parse_compound_list(void) { struct list *result_list = NULL; // ast* list - struct list *cmd_elements = NULL; // token* list + struct list *command_elements = NULL; // token* list struct token *token = PEEK_TOKEN(); while (token->type != TOKEN_THEN || token->type != TOKEN_ELIF @@ -127,9 +127,9 @@ struct ast *parse_compound_list(void) if (token->type == TOKEN_SEMICOLON || token->type == TOKEN_NEWLINE) { // Stage (-> next command) - struct ast *command = ast_create_cmd(cmd_elements); + struct ast *command = ast_create_command(command_elements); result_list = list_append(result_list, command); - cmd_elements = NULL; + command_elements = NULL; } if (token->type == TOKEN_EOF) @@ -139,7 +139,7 @@ struct ast *parse_compound_list(void) return NULL; } - cmd_elements = list_append(cmd_elements, token->data); + command_elements = list_append(command_elements, token->data); token = POP_TOKEN(); } From 1eecb1bd429ea021ef3557da31fca710659ef5b6 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Thu, 15 Jan 2026 18:49:42 +0100 Subject: [PATCH 069/282] fix: merging all the code --- src/execution/execution.c | 22 +++++++++++----------- src/expansion/expansion.c | 16 ++++++++-------- src/io_backend/io_backend.c | 4 ++-- src/utils/ast/ast.c | 21 +++++++++++---------- src/utils/ast/ast.h | 11 ----------- src/utils/ast/ast_base.h | 12 ++++++++++++ src/utils/ast/ast_command.c | 9 +++++---- src/utils/ast/ast_command.h | 2 +- src/utils/ast/ast_if.c | 13 ++++++------- src/utils/ast/ast_if.h | 2 +- src/utils/ast/ast_list.c | 27 +++++++++++++++++++++------ src/utils/ast/ast_list.h | 8 ++++++++ 12 files changed, 86 insertions(+), 61 deletions(-) diff --git a/src/execution/execution.c b/src/execution/execution.c index 855fd37..0066681 100644 --- a/src/execution/execution.c +++ b/src/execution/execution.c @@ -12,14 +12,14 @@ * @brief converts a linked list of command arguments to an argv array. Don't * forget to free the result * - * @param cmd_list Linked list of command arguments + * @param command_list Linked list of command arguments * @return char** Array of command arguments suitable for execvp. Terminated by * NULL */ -static char **list_to_argv(struct list *cmd_list) +static char **list_to_argv(struct list *command_list) { size_t len = 0; - struct list *cur = cmd_list; + struct list *cur = command_list; while (cur) { @@ -32,7 +32,7 @@ static char **list_to_argv(struct list *cmd_list) { return NULL; } - cur = cmd_list; + cur = command_list; for (size_t i = 0; i < len; i++) { @@ -45,19 +45,19 @@ static char **list_to_argv(struct list *cmd_list) } /** - * @brief Executes a command represented by an ast_cmd structure + * @brief Executes a command represented by an ast_command structure * - * @param cmd The command to execute + * @param command The command to execute * @return int The exit status of the command */ -static int exec_command(struct ast_cmd *cmd) +static int exec_command(struct ast_command *command) { - if (!cmd || !(cmd->cmd)) + if (!command || !(command->command)) { return -1; } - char **argv = list_to_argv(cmd->cmd); + char **argv = list_to_argv(command->command); if (!argv || !(argv[0])) { @@ -112,8 +112,8 @@ int execution(struct ast *ast) return 0; } case AST_CMD: { - struct ast_cmd *cmd = ast_get_cmd(ast); - return exec_command(cmd); // It's recursive + struct ast_command *command = ast_get_command(ast); + return exec_command(command); // It's recursive } case AST_IF: { struct ast_if *if_node = ast_get_if(ast); diff --git a/src/expansion/expansion.c b/src/expansion/expansion.c index 406c00b..fe28dd0 100644 --- a/src/expansion/expansion.c +++ b/src/expansion/expansion.c @@ -14,15 +14,15 @@ // return iter - start; // } -struct ast_cmd *expand(struct ast_cmd *cmd) +struct ast_command *expand(struct ast_command *command) { - if (cmd == NULL) + if (command == NULL) return NULL; bool in_quotes = false; char *str; size_t len; - struct list *l = cmd->cmd; + struct list *l = command->command; while (l != NULL) { @@ -69,19 +69,19 @@ struct ast_cmd *expand(struct ast_cmd *cmd) l = l->next; } - return cmd; + return command; } // int main() // { // printf("Expansion module test\n"); -// struct ast_cmd ast_cmd; +// struct ast_command ast_command; // // char str[] = "echo Hello $?"; // char str[] = "echo Hello $AE86"; -// ast_cmd.cmd = list_append(NULL, str); +// ast_command.command = list_append(NULL, str); -// struct ast_cmd *cmd2 = expand(&ast_cmd); -// printf("cmd2: %s\n", (char *)cmd2->cmd->data); +// struct ast_command *command2 = expand(&ast_command); +// printf("command2: %s\n", (char *)command2->command->data); // return 0; // } diff --git a/src/io_backend/io_backend.c b/src/io_backend/io_backend.c index eef080a..c3c38e3 100644 --- a/src/io_backend/io_backend.c +++ b/src/io_backend/io_backend.c @@ -42,7 +42,7 @@ int iob_init(struct iob_context *ctx) return 0; case IOB_MODE_CMD: - if (context.args != NULL) + if (context.args == NULL) return IOB_ERROR_BAD_ARG; state = IOB_STATE_READY; return 0; @@ -121,7 +121,7 @@ int iob_config_from_args(struct args_options *args, struct iob_context *ctx) case INPUT_CMD: ctx->mode = IOB_MODE_CMD; - ctx->args = NULL; + ctx->args = (char *)args->input_source; break; default: diff --git a/src/utils/ast/ast.c b/src/utils/ast/ast.c index e04e170..2d0ce84 100644 --- a/src/utils/ast/ast.c +++ b/src/utils/ast/ast.c @@ -2,25 +2,26 @@ #include -void ast_free(struct ast *node) +void ast_free(struct ast **node) { - if (node == NULL) + if (*node == NULL) return; // ast void does not need to be freed. - if (ast_is_if(node)) - ast_free_if(ast_get_if(node)); - else if (ast_is_command(node)) - ast_free_command(ast_get_command(node)); - else if (ast_is_list(node)) - ast_free_list(ast_get_list(node)); + if (ast_is_if(*node)) + ast_free_if(ast_get_if(*node)); + else if (ast_is_command(*node)) + ast_free_command(ast_get_command(*node)); + else if (ast_is_list(*node)) + ast_free_list(ast_get_list(*node)); - free(node); + free(*node); + *node = NULL; } struct ast *ast_create(enum ast_type type, void *data) { struct ast *node = malloc(sizeof(struct ast)); - if (!node) + if (node == NULL) return NULL; node->type = type; diff --git a/src/utils/ast/ast.h b/src/utils/ast/ast.h index 79eeff3..8ac85a0 100644 --- a/src/utils/ast/ast.h +++ b/src/utils/ast/ast.h @@ -7,15 +7,4 @@ #include "utils/ast/ast_list.h" #include "utils/ast/ast_void.h" -/* @brief: returns an ast* with corresponding data and type. - * - * @note: this function should only be called by ast_create_[TYPE] functions. - */ -struct ast *ast_create(enum ast_type type, void *data); - -/* @brief: frees the given ast. If ast is NULL, does nothing. - * - */ -void ast_free(struct ast *node); - #endif /* ! AST_H */ diff --git a/src/utils/ast/ast_base.h b/src/utils/ast/ast_base.h index 955ccdc..432eb2c 100644 --- a/src/utils/ast/ast_base.h +++ b/src/utils/ast/ast_base.h @@ -8,6 +8,7 @@ enum ast_type AST_END, AST_LIST, AST_IF, + AST_VOID, AST_CMD }; @@ -24,4 +25,15 @@ struct ast void *data; }; +/* @brief: returns an ast* with corresponding data and type. + * + * @note: this function should only be called by ast_create_[TYPE] functions. + */ +struct ast *ast_create(enum ast_type type, void *data); + +/* @brief: frees the given ast. If ast is NULL, does nothing. + * + */ +void ast_free(struct ast **node); + #endif /* ! AST_BASE_H */ diff --git a/src/utils/ast/ast_command.c b/src/utils/ast/ast_command.c index 6ad8fcc..920b1ff 100644 --- a/src/utils/ast/ast_command.c +++ b/src/utils/ast/ast_command.c @@ -30,9 +30,10 @@ bool ast_is_command(struct ast *node) return node->type == AST_CMD; } -void ast_free_command(struct ast_command **command_data) +void ast_free_command(struct ast_command *command_data) { - list_deep_destroy((*command_data)->command); - free(*command_data); - *command_data = NULL; + if (command_data == NULL) + return; + list_deep_destroy(command_data->command); + free(command_data); } diff --git a/src/utils/ast/ast_command.h b/src/utils/ast/ast_command.h index 0e442a5..08c62ba 100644 --- a/src/utils/ast/ast_command.h +++ b/src/utils/ast/ast_command.h @@ -30,6 +30,6 @@ struct ast *ast_create_command(struct list *command); /* * @brief: frees the given ast_command and sets the pointer to NULL. */ -void ast_free_command(struct ast_command **command_data); +void ast_free_command(struct ast_command *command_data); #endif /* ! AST_COMMAND_H */ diff --git a/src/utils/ast/ast_if.c b/src/utils/ast/ast_if.c index 242d5fd..6a25a9c 100644 --- a/src/utils/ast/ast_if.c +++ b/src/utils/ast/ast_if.c @@ -31,15 +31,14 @@ bool ast_is_if(struct ast *node) return node->type == AST_IF; } -void ast_free_if(struct ast_if **if_data) +void ast_free_if(struct ast_if *if_data) { - if (if_data == NULL || *if_data == NULL) + if (if_data == NULL) return; - ast_free((*if_data)->condition); - ast_free((*if_data)->then_clause); - ast_free((*if_data)->else_clause); + ast_free(&if_data->condition); + ast_free(&if_data->then_clause); + ast_free(&if_data->else_clause); - free(*if_data); - *if_data = NULL; + free(if_data); } diff --git a/src/utils/ast/ast_if.h b/src/utils/ast/ast_if.h index 2036eb7..51c1844 100644 --- a/src/utils/ast/ast_if.h +++ b/src/utils/ast/ast_if.h @@ -31,6 +31,6 @@ struct ast *ast_create_if(struct ast *condition, struct ast *then_clause, /* * @brief: frees the given ast_if and sets the pointer to NULL. */ -void ast_free_if(struct ast_if **if_data); +void ast_free_if(struct ast_if *if_data); #endif /* ! AST_IF_H */ diff --git a/src/utils/ast/ast_list.c b/src/utils/ast/ast_list.c index fae1f1a..95e7047 100644 --- a/src/utils/ast/ast_list.c +++ b/src/utils/ast/ast_list.c @@ -1,12 +1,14 @@ #include "utils/ast/ast.h" +#include + struct ast *ast_create_list(struct list *list) { struct ast_list *ast_list = malloc(sizeof(struct ast_list)); if (ast_list == NULL) return NULL; - ast_list->list = list; + ast_list->children = list; return ast_create(AST_LIST, ast_list); } @@ -22,12 +24,25 @@ bool ast_is_list(struct ast *node) return node->type == AST_LIST; } -void ast_free_list(struct ast_list **ast_list) +void ast_free_list(struct ast_list *ast_list) { - if (*ast_list == NULL || ast_list == NULL) + if (ast_list == NULL) return; - list_deep_destroy((*ast_list)->children); - free(*ast_list); - *ast_list = NULL; + list_deep_destroy(ast_list->children); + free(ast_list); +} + +void ast_list_deep_destroy(struct list *l) +{ + struct list *elt = l; + struct list *next_elt; + while (elt != NULL) + { + next_elt = elt->next; + + ast_free(elt->data); + free(elt); + elt = next_elt; + } } diff --git a/src/utils/ast/ast_list.h b/src/utils/ast/ast_list.h index 6f1cbd9..591b045 100644 --- a/src/utils/ast/ast_list.h +++ b/src/utils/ast/ast_list.h @@ -11,6 +11,14 @@ struct ast_list struct list *children; // A list of ASTs (ast*) }; +/* +** Release the memory used by the AST list and its AST children +** Does nothing if `list` is `NULL`. +* +* @warning: this function should NEVER be used on a list containing +* anything else than ASTs. +*/ +void ast_list_deep_destroy(struct list *l); /** * Creates a new AST node representing a list of ASTs From 04529f858cb298f1f15403fbbb1d54f9dea6cf02 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Thu, 15 Jan 2026 20:42:28 +0100 Subject: [PATCH 070/282] fix: compiling but not working -- need debug --- src/io_backend/io_backend.c | 4 +++- src/lexer/lexer.c | 7 ++++++- src/parser/parser.c | 1 + src/parser/parsing_utils.c | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/io_backend/io_backend.c b/src/io_backend/io_backend.c index c3c38e3..7919e14 100644 --- a/src/io_backend/io_backend.c +++ b/src/io_backend/io_backend.c @@ -96,7 +96,9 @@ ssize_t stream_read(char **stream) else if (context.mode == IOB_MODE_CMD) { *stream = context.args; - return strlen(context.args); + size_t len = strlen(context.args); + context.args[len] = EOF; + return len; } else { diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index 748ad63..852bd2d 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -12,6 +12,7 @@ static char *end_last_token; static ssize_t remaining_chars; +static bool at_beginning = true; /* @brief: saves state for the next call the the lexer. * @@ -20,6 +21,7 @@ static void save_state(char *stream, ssize_t i) { remaining_chars -= i; end_last_token = stream + i; + at_beginning = false; return; } @@ -43,6 +45,7 @@ static void set_token_spechar(struct token *tok, char *begin, ssize_t size) if (begin[0] == EOF) { tok->type = TOKEN_EOF; + remaining_chars = 0; } else if (begin[0] == ';') { @@ -100,6 +103,7 @@ static void set_token_word(struct token *tok, char *begin, ssize_t size) { if (tok->type == TOKEN_NULL) { + tok->type = TOKEN_WORD; tok->data = calloc(size + 1, sizeof(char)); if (tok->data == NULL) return; @@ -133,9 +137,10 @@ char *stream_init(void) { char *stream; - if (remaining_chars == 0) + if (at_beginning) { remaining_chars = stream_read(&stream); + // at_beginning = true; } else { diff --git a/src/parser/parser.c b/src/parser/parser.c index 0123e2f..9d05701 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -35,6 +35,7 @@ struct ast *get_ast() token = pop_token(); break; } + token = peek_token(); } if (token == NULL) diff --git a/src/parser/parsing_utils.c b/src/parser/parsing_utils.c index b064743..b7c487b 100644 --- a/src/parser/parsing_utils.c +++ b/src/parser/parsing_utils.c @@ -54,7 +54,7 @@ static bool isterminator(struct token *token) struct ast *parse_simple_command(void) { struct list *command_elements = NULL; - struct token *token = POP_TOKEN(); + struct token *token = PEEK_TOKEN(); while (!isterminator(token)) { From 10ce140e37838bcfa1278434ae8675d5598e9a5e Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Fri, 16 Jan 2026 19:31:58 +0100 Subject: [PATCH 071/282] fix(parser + lexer): interaction -- WIP --- src/lexer/lexer.c | 65 ++++++++++++++++++++++++++++++-------- src/lexer/lexer.h | 9 ++++-- src/parser/parser.c | 34 ++++++++++++-------- src/parser/parser.h | 18 +++++++++++ src/parser/parsing_utils.c | 21 ++---------- 5 files changed, 99 insertions(+), 48 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index 852bd2d..68366cf 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -13,16 +13,40 @@ static char *end_last_token; static ssize_t remaining_chars; static bool at_beginning = true; +static struct token *last_token; +static struct token *current_token; -/* @brief: saves state for the next call the the lexer. + +/* @brief: sets the current_token to [tok]. + * this function is called by token_peek(). + */ +static void update_current_token(struct token* tok) +{ + current_token = tok; +} + +/* @brief: frees the last token and sets it to [tok]. + * Also sets current_token to NULL. + * this function is called by token_pop(). * */ -static void save_state(char *stream, ssize_t i) +static void update_last_token(struct token* tok) +{ + free_token(&last_token); + last_token = tok; +} + +/* @brief: saves state for the next call to the the lexer. + * this function is called by token_pop(). + * + */ +static void save_state(char *stream, ssize_t i, struct token *tok) { remaining_chars -= i; end_last_token = stream + i; at_beginning = false; - return; + + update_last_token(tok); } /* @return: true if a special character from the grammar was found, @@ -67,7 +91,7 @@ static void set_token_spechar(struct token *tok, char *begin, ssize_t size) */ static void set_token_keyword(struct token *tok, char *begin, ssize_t size) { - if (tok->type != TOKEN_NULL) + if (tok->type != TOKEN_NULL || size == 0) return; if (strncmp(begin, "if", size) == 0) { @@ -101,7 +125,7 @@ static void set_token_keyword(struct token *tok, char *begin, ssize_t size) */ static void set_token_word(struct token *tok, char *begin, ssize_t size) { - if (tok->type == TOKEN_NULL) + if (tok->type == TOKEN_NULL && size != 0) { tok->type = TOKEN_WORD; tok->data = calloc(size + 1, sizeof(char)); @@ -124,13 +148,14 @@ struct token *new_token(char *begin, ssize_t size) return tok; } -void free_token(struct token *tok) +void free_token(struct token **tok) { - if (tok == NULL) + if (tok == NULL || *tok == NULL) return; - if (tok->data != NULL) - free(tok->data); - free(tok); + if ((*tok)->data != NULL) + free((*tok)->data); + free(*tok); + *tok = NULL; } char *stream_init(void) @@ -156,6 +181,12 @@ char *stream_init(void) struct token *peek_token(void) { + // EOF looping mode + if (current_token != NULL && current_token->type == TOKEN_EOF) + { + return current_token; + } + char *stream = stream_init(); ssize_t i = 0; @@ -175,11 +206,18 @@ struct token *peek_token(void) i++; } - return new_token(stream, i); + struct token *tok = new_token(stream, i); + update_current_token(tok); + return tok; } struct token *pop_token(void) { + if (last_token != NULL && last_token->type == TOKEN_EOF) + { + free_token(&last_token); + return NULL; + } char *stream = stream_init(); ssize_t i = 0; @@ -199,7 +237,8 @@ struct token *pop_token(void) i++; } - save_state(stream, i); + struct token *tok = new_token(stream, i); + save_state(stream, i, tok); - return new_token(stream, i); + return tok; } diff --git a/src/lexer/lexer.h b/src/lexer/lexer.h index 332355f..1b46523 100644 --- a/src/lexer/lexer.h +++ b/src/lexer/lexer.h @@ -26,7 +26,8 @@ struct token /* * @brief: returns the next (newly allocated) token without consuming it. - * if end of input is reached, returns a token of type TOKEN_EOF. + * if end of input is reached, enters in EOF looping node, + * returning only the same token of type TOKEN_EOF. * */ struct token *peek_token(void); @@ -34,6 +35,10 @@ struct token *peek_token(void); /* * @brief: returns the next (newly allocated) token and consumes it. * if end of input is reached, returns a token of type TOKEN_EOF. + * It also frees the last token created if there was one. + * @warning: if the last returned token was a token EOF, it frees it + * and returns NULL. This means that after peeking a token EOF + * in the parser, there must be EXACTLY ONE call to pop_token(). * */ struct token *pop_token(void); @@ -58,7 +63,7 @@ struct token *new_token(char *begin, ssize_t size); /* @brief: frees the token given in argument * */ -void free_token(struct token *tok); +void free_token(struct token **tok); /* * @brief: checks if the stream used for the last token creation is empty. diff --git a/src/parser/parser.c b/src/parser/parser.c index 9d05701..b9c07e7 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -17,33 +17,39 @@ struct ast *get_ast() { - struct list *result_list = NULL; - struct ast *current_node = NULL; + // struct list *result_list = NULL; + // struct ast *current_node = NULL; - struct token *token = peek_token(); + struct token *token = PEEK_TOKEN(); - while (token != NULL && token->type != TOKEN_EOF) + if (token->type == TOKEN_EOF) { - switch (token->type) - { - case TOKEN_WORD: + token = pop_token(); + // TODO + // return ast END. + } + else if (token->type == TOKEN_NEWLINE) + { + token = pop_token(); + // TODO + // return ast EMPTY. + } + else // TOKEN WORD + { + // TODO + // call parse_list current_node = parse_simple_command(); result_list = list_append(result_list, current_node); - break; - default: - // Forward - token = pop_token(); - break; - } - token = peek_token(); } + /* if (token == NULL) { puts("Internal error: cannot get the following token"); puts("Hint: EOF might be missing"); return NULL; } + */ struct ast *result = ast_create_list(result_list); return result; diff --git a/src/parser/parser.h b/src/parser/parser.h index d10cae8..bf88adc 100644 --- a/src/parser/parser.h +++ b/src/parser/parser.h @@ -3,6 +3,24 @@ #include "utils/ast/ast.h" +// === Macros + +#define PEEK_TOKEN() \ + peek_token(); \ + if (token == NULL) \ + { \ + puts("Internal error: cannot get the following token"); \ + return NULL; \ + } + +#define POP_TOKEN() \ + pop_token(); \ + if (token == NULL) \ + { \ + puts("Internal error: cannot get the following token"); \ + return NULL; \ + } + /* @brief Builds the AST representation of the next command to execute. * * @return Returns the AST representation of the next command to execute. diff --git a/src/parser/parsing_utils.c b/src/parser/parsing_utils.c index b7c487b..b0c6618 100644 --- a/src/parser/parsing_utils.c +++ b/src/parser/parsing_utils.c @@ -8,24 +8,6 @@ #include "lexer/lexer.h" #include "utils/ast/ast.h" -// === Macros - -#define PEEK_TOKEN() \ - peek_token(); \ - if (token == NULL) \ - { \ - puts("Internal error: cannot get the following token"); \ - return NULL; \ - } - -#define POP_TOKEN() \ - pop_token(); \ - if (token == NULL) \ - { \ - puts("Internal error: cannot get the following token"); \ - return NULL; \ - } - // === Static functions /* Returns true if c is a command terminator, false otherwise @@ -58,8 +40,9 @@ struct ast *parse_simple_command(void) while (!isterminator(token)) { - command_elements = list_append(command_elements, token->data); token = POP_TOKEN(); + command_elements = list_append(command_elements, token->data); + token = PEEK_TOKEN(); } struct ast *result = ast_create_command(command_elements); From bf59c5717eb8a860600423d364fed7363c303ee0 Mon Sep 17 00:00:00 2001 From: Guillem George Date: Fri, 16 Jan 2026 19:39:17 +0100 Subject: [PATCH 072/282] new token types --- src/lexer/lexer.c | 75 ++++++++++++++++++++++++++++++++++++----------- src/lexer/lexer.h | 26 +++++++++++----- 2 files changed, 77 insertions(+), 24 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index 748ad63..5249633 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -14,7 +14,6 @@ static char *end_last_token; static ssize_t remaining_chars; /* @brief: saves state for the next call the the lexer. - * */ static void save_state(char *stream, ssize_t i) { @@ -25,42 +24,84 @@ static void save_state(char *stream, ssize_t i) /* @return: true if a special character from the grammar was found, * false otherwise. - * */ static bool is_special_char(char c) { - return c == '\'' || c == '\n' || c == ';' || c == EOF; + if (c == EOF) + return true; + + char special_chars[] = "\n'\"`;#|&\\$(){}<>*"; + return strchr(special_chars, c) != NULL; } /* @brief: if a special character is found at [begin], * [tok->token_type] is set accordingly - * */ static void set_token_spechar(struct token *tok, char *begin, ssize_t size) { if (size != 1) return; - if (begin[0] == EOF) + switch (begin[0]) { + case EOF: tok->type = TOKEN_EOF; - } - else if (begin[0] == ';') - { - tok->type = TOKEN_NEWLINE; - } - else if (begin[0] == '\'') - { - tok->type = TOKEN_QUOTE; - } - else if (begin[0] == ';') - { + break; + case ';': tok->type = TOKEN_SEMICOLON; + break; + case '\n': + tok->type = TOKEN_NEWLINE; + break; + case '\'': + tok->type = TOKEN_QUOTE; + break; + case '"': + tok->type = TOKEN_DOUBLE_QUOTE; + break; + case '`': + tok->type = TOKEN_GRAVE; + break; + case '#': + tok->type = TOKEN_COMMENT; + break; + case '|': + tok->type = TOKEN_PIPE; + break; + case '&': + tok->type = TOKEN_AMPERSAND; + break; + case '\\': + tok->type = TOKEN_BACKSLASH; + break; + case '$': + tok->type = TOKEN_DOLLAR; + break; + case '(': + tok->type = TOKEN_LEFT_PAREN; + break; + case ')': + tok->type = TOKEN_RIGHT_PAREN; + break; + case '{': + tok->type = TOKEN_LEFT_BRACKET; + break; + case '}': + tok->type = TOKEN_RIGHT_BRACKET; + break; + case '<': + tok->type = TOKEN_LESS; + break; + case '>': + tok->type = TOKEN_GREATER; + break; + case '*': + tok->type = TOKEN_STAR; + break; } } /* @brief: if a keyword is found at [begin], * [tok->token_type] is set accordingly - * */ static void set_token_keyword(struct token *tok, char *begin, ssize_t size) { diff --git a/src/lexer/lexer.h b/src/lexer/lexer.h index 332355f..3c06d35 100644 --- a/src/lexer/lexer.h +++ b/src/lexer/lexer.h @@ -5,12 +5,29 @@ enum token_type { + // Special characters TOKEN_NULL = 0, TOKEN_EOF, TOKEN_WORD, TOKEN_NEWLINE, TOKEN_QUOTE, + TOKEN_DOUBLE_QUOTE, + TOKEN_GRAVE, TOKEN_SEMICOLON, + TOKEN_COMMENT, + TOKEN_PIPE, + TOKEN_AMPERSAND, + TOKEN_BACKSLASH, + TOKEN_DOLLAR, + TOKEN_LEFT_PAREN, + TOKEN_RIGHT_PAREN, + TOKEN_LEFT_BRACKET, + TOKEN_RIGHT_BRACKET, + TOKEN_LESS, + TOKEN_GREATER, + TOKEN_STAR, + + // Keywords TOKEN_IF, TOKEN_THEN, TOKEN_ELSE, @@ -27,21 +44,18 @@ struct token /* * @brief: returns the next (newly allocated) token without consuming it. * if end of input is reached, returns a token of type TOKEN_EOF. - * */ struct token *peek_token(void); /* * @brief: returns the next (newly allocated) token and consumes it. * if end of input is reached, returns a token of type TOKEN_EOF. - * */ struct token *pop_token(void); -/* - * @warning: NOT IMPLEMENTED. +/* @note: maybe usefull for subshells. * - * @note: maybe usefull for subshells. + * @warning: NOT IMPLEMENTED. */ struct token *get_token_str(void); @@ -51,12 +65,10 @@ struct token *get_token_str(void); * The data contains [size] char, starting from [begin]. * * @return: NULL on error, a token otherwise. - * */ struct token *new_token(char *begin, ssize_t size); /* @brief: frees the token given in argument - * */ void free_token(struct token *tok); From f33498fb1345f1e00d81dc5830325dd61717bdae Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Fri, 16 Jan 2026 19:45:15 +0100 Subject: [PATCH 073/282] fix(parser): moved macro declaration from parser.h to parsing_utils.h --- src/parser/parser.h | 18 ------------------ src/parser/parsing_utils.h | 22 ++++++++++++++++++++++ 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/parser/parser.h b/src/parser/parser.h index bf88adc..d10cae8 100644 --- a/src/parser/parser.h +++ b/src/parser/parser.h @@ -3,24 +3,6 @@ #include "utils/ast/ast.h" -// === Macros - -#define PEEK_TOKEN() \ - peek_token(); \ - if (token == NULL) \ - { \ - puts("Internal error: cannot get the following token"); \ - return NULL; \ - } - -#define POP_TOKEN() \ - pop_token(); \ - if (token == NULL) \ - { \ - puts("Internal error: cannot get the following token"); \ - return NULL; \ - } - /* @brief Builds the AST representation of the next command to execute. * * @return Returns the AST representation of the next command to execute. diff --git a/src/parser/parsing_utils.h b/src/parser/parsing_utils.h index 6981a13..a133ef0 100644 --- a/src/parser/parsing_utils.h +++ b/src/parser/parsing_utils.h @@ -1,3 +1,23 @@ +#ifndef PARSING_UTILS_H +#define PARSING_UTILS_H + +// === Macros + +#define PEEK_TOKEN() \ + peek_token(); \ + if (token == NULL) \ + { \ + puts("Internal error: cannot get the following token"); \ + return NULL; \ + } + +#define POP_TOKEN() \ + pop_token(); \ + if (token == NULL) \ + { \ + puts("Internal error: cannot get the following token"); \ + return NULL; \ + } /* @brief Parses a simple list of words (command and arguments) * and returns the resulting ast */ @@ -23,3 +43,5 @@ struct ast* parse_and_or(void); /* */ struct ast* parse_else_clause(void); + +#define /* ! PARSING_UTILS_H */ From b3d44317b03f73a0db68e65af79f3d00788a370e Mon Sep 17 00:00:00 2001 From: Guillem George Date: Fri, 16 Jan 2026 20:05:49 +0100 Subject: [PATCH 074/282] fix: updated main to a parse-execute loop and changed error codes from an enum to macros --- src/main.c | 58 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/src/main.c b/src/main.c index b664786..399b874 100644 --- a/src/main.c +++ b/src/main.c @@ -1,4 +1,5 @@ -// All the includes +// === Includes +#include #include #include @@ -7,22 +8,24 @@ #include "parser/parser.h" #include "utils/args/args.h" -enum return_codes -{ - SUCCESS = 0, - ERR_IO_BACKEND = 2, - ERR_MALLOC = 3 -}; +// === Error codes + +#define SUCCESS 0 +#define ERR_INPUT_PROCESSING 2 +#define ERR_MALLOC 3 +#define ERR_GENERIC 4 + +// === Functions int main(int argc, char **argv) { // Create the options struct (with argument handler) struct args_options options; - int r = args_handler(argc, argv, &options); - if (r != 0) + int return_code = args_handler(argc, argv, &options); + if (return_code != 0) { print_usage(stderr, argv[0]); - return ERR_IO_BACKEND; + return ERR_INPUT_PROCESSING; } // args_print(&options); @@ -36,33 +39,42 @@ int main(int argc, char **argv) } // Convert args_options to iob_context - r = iob_config_from_args(&options, io_context); - if (r != 0) + return_code = iob_config_from_args(&options, io_context); + if (return_code != 0) { fprintf(stderr, "Error: Failed to configure IO Backend from arguments\n"); free(io_context); - return ERR_IO_BACKEND; + return ERR_INPUT_PROCESSING; } // Init IO Backend (with the context struct) - r = iob_init(io_context); - if (r != 0) + return_code = iob_init(io_context); + if (return_code != 0) { fprintf(stderr, - "Error: IO Backend initialization failed with code %d\n", r); + "Error: IO Backend initialization failed with code %d\n", + return_code); free(io_context); - return ERR_IO_BACKEND; + return ERR_INPUT_PROCESSING; } free(io_context); - // Call the parser to get the AST - struct ast *command_ast = get_ast(); // We'll pass the options later + // Retrieve and build first AST + struct ast *command_ast = get_ast(); - // Call the executor with the AST - r = execution(command_ast); + // Main parse-execute loop + while (command_ast != NULL && command_ast->type != AST_END) + { + // Execute AST + return_code = execution(command_ast); - // Return the execution return code (last command executed) - return r; + // Retrieve and build next AST + command_ast = get_ast(); + } + if (command_ast == NULL) + return ERR_INPUT_PROCESSING; + + return return_code; } From ed42f0b93d28361f7ab6872e202df560ae146700 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Fri, 16 Jan 2026 20:12:29 +0100 Subject: [PATCH 075/282] feat(parser): parse_list() implemented --- src/parser/parser.c | 25 +++++++++-------------- src/parser/parsing_utils.c | 41 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/src/parser/parser.c b/src/parser/parser.c index b9c07e7..5edae88 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -17,9 +17,6 @@ struct ast *get_ast() { - // struct list *result_list = NULL; - // struct ast *current_node = NULL; - struct token *token = PEEK_TOKEN(); if (token->type == TOKEN_EOF) @@ -31,25 +28,21 @@ struct ast *get_ast() else if (token->type == TOKEN_NEWLINE) { token = pop_token(); - // TODO - // return ast EMPTY. + return ast_create_void(); } else // TOKEN WORD { - // TODO - // call parse_list - current_node = parse_simple_command(); - result_list = list_append(result_list, current_node); + current_node = parse_list(); } /* - if (token == NULL) - { - puts("Internal error: cannot get the following token"); - puts("Hint: EOF might be missing"); - return NULL; - } - */ + if (token == NULL) + { + puts("Internal error: cannot get the following token"); + puts("Hint: EOF might be missing"); + return NULL; + } + */ struct ast *result = ast_create_list(result_list); return result; diff --git a/src/parser/parsing_utils.c b/src/parser/parsing_utils.c index b0c6618..c07653e 100644 --- a/src/parser/parsing_utils.c +++ b/src/parser/parsing_utils.c @@ -28,6 +28,23 @@ static bool isterminator(struct token *token) } } +/* @brief: returns true if token is an end of list indicator. + */ +static bool is_end_of_list(struct token *token) +{ + if (token == NULL) + return false; + + switch (token->type) + { + case TOKEN_NEWLINE: + case TOKEN_EOF: + return true; + default: + return false; + } +} + // === Functions /* Parses a simple list of words (command and arguments) @@ -49,6 +66,30 @@ struct ast *parse_simple_command(void) return result; } +struct ast *parse_list(void) +{ + struct list *result_list = NULL; + struct ast *current_node = NULL; + + struct token *token = PEEK_TOKEN(); + + while (!is_end_of_list(token)) + { + if (token->type == TOKEN_SEMICOLON) + { + result_list = list_append(result_list, current_node); + } + else + { + // TODO use parse_and_or() instead. + current_node = parse_simple_command(); + } + token = PEEK_TOKEN(); + } + + return ast_create_list(result_list); +} + struct ast *parse_if_rule(void) { // If condition From 9e56654d313973c6bbe0f3154d7371de66bf0093 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Fri, 16 Jan 2026 20:19:12 +0100 Subject: [PATCH 076/282] feat(ast): AST_END type implemented --- src/utils/ast/ast_end.c | 16 ++++++++++++++++ src/utils/ast/ast_end.h | 20 ++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 src/utils/ast/ast_end.c create mode 100644 src/utils/ast/ast_end.h diff --git a/src/utils/ast/ast_end.c b/src/utils/ast/ast_end.c new file mode 100644 index 0000000..b2db692 --- /dev/null +++ b/src/utils/ast/ast_end.c @@ -0,0 +1,16 @@ +#include "utils/ast/ast_end.h" + +#include +#include +#include + +bool ast_is_end(struct ast *node) +{ + assert(node != NULL); + return node->type == AST_END; +} + +struct ast *ast_create_end(end) +{ + return ast_create(AST_END, NULL); +} diff --git a/src/utils/ast/ast_end.h b/src/utils/ast/ast_end.h new file mode 100644 index 0000000..e4e16c8 --- /dev/null +++ b/src/utils/ast/ast_end.h @@ -0,0 +1,20 @@ +#ifndef AST_END_H +#define AST_END_H + +#include + +#include "utils/lists/lists.h" +#include "utils/ast/ast_base.h" + +/** + * Checks if the given AST node is of type AST_END. + */ +bool ast_is_end(struct ast *node); + +/** + * Creates a new AST node representing the end of input. + * WARNING: data will be a NULL pointer + */ +struct ast *ast_create_end(end); + +#endif /* ! AST_END_H */ From ce60a95471f894fc15028777b9fff11a84458677 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Fri, 16 Jan 2026 20:27:57 +0100 Subject: [PATCH 077/282] fix(parser): made it compile --- src/parser/parser.c | 9 ++++----- src/parser/parsing_utils.h | 7 ++++++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/parser/parser.c b/src/parser/parser.c index 5edae88..0cce2fb 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -18,12 +18,12 @@ struct ast *get_ast() { struct token *token = PEEK_TOKEN(); + struct ast *res; if (token->type == TOKEN_EOF) { token = pop_token(); - // TODO - // return ast END. + return ast_create_end(); } else if (token->type == TOKEN_NEWLINE) { @@ -32,7 +32,7 @@ struct ast *get_ast() } else // TOKEN WORD { - current_node = parse_list(); + res = parse_list(); } /* @@ -44,8 +44,7 @@ struct ast *get_ast() } */ - struct ast *result = ast_create_list(result_list); - return result; + return res; } // TODO diff --git a/src/parser/parsing_utils.h b/src/parser/parsing_utils.h index a133ef0..a380a85 100644 --- a/src/parser/parsing_utils.h +++ b/src/parser/parsing_utils.h @@ -18,6 +18,11 @@ puts("Internal error: cannot get the following token"); \ return NULL; \ } + +/* @brief: parses a list of [and_or] rules, separated by semicolons. + */ +struct ast *parse_list(void); + /* @brief Parses a simple list of words (command and arguments) * and returns the resulting ast */ @@ -44,4 +49,4 @@ struct ast* parse_and_or(void); */ struct ast* parse_else_clause(void); -#define /* ! PARSING_UTILS_H */ +#endif /* ! PARSING_UTILS_H */ From 1cd827180a299e081be754e0403433af402fa498 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Fri, 16 Jan 2026 20:30:04 +0100 Subject: [PATCH 078/282] fix(ast): add ast_end in building process --- src/utils/Makefile.am | 1 + src/utils/ast/ast.h | 1 + 2 files changed, 2 insertions(+) diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am index 9fd1be1..d1aa675 100644 --- a/src/utils/Makefile.am +++ b/src/utils/Makefile.am @@ -7,6 +7,7 @@ libutils_a_SOURCES = \ ast/ast_command.c \ ast/ast_list.c \ ast/ast_void.c \ + ast/ast_end.c \ lists/lists.c \ args/args.c diff --git a/src/utils/ast/ast.h b/src/utils/ast/ast.h index 8ac85a0..3984667 100644 --- a/src/utils/ast/ast.h +++ b/src/utils/ast/ast.h @@ -5,6 +5,7 @@ #include "utils/ast/ast_command.h" #include "utils/ast/ast_if.h" #include "utils/ast/ast_list.h" +#include "utils/ast/ast_end.h" #include "utils/ast/ast_void.h" #endif /* ! AST_H */ From 67d76ac8952c8f5cc3735c931254a3fc18708084 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Fri, 16 Jan 2026 20:31:30 +0100 Subject: [PATCH 079/282] fix(ast): typo --- src/utils/ast/ast_end.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/ast/ast_end.c b/src/utils/ast/ast_end.c index b2db692..3be1eeb 100644 --- a/src/utils/ast/ast_end.c +++ b/src/utils/ast/ast_end.c @@ -10,7 +10,7 @@ bool ast_is_end(struct ast *node) return node->type == AST_END; } -struct ast *ast_create_end(end) +struct ast *ast_create_end(void) { return ast_create(AST_END, NULL); } From ccb9438d6983d57924a3731f97fd28c2bebccee8 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Fri, 16 Jan 2026 20:32:56 +0100 Subject: [PATCH 080/282] fix(ast): the same typo --- src/utils/ast/ast_end.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/ast/ast_end.h b/src/utils/ast/ast_end.h index e4e16c8..f86e477 100644 --- a/src/utils/ast/ast_end.h +++ b/src/utils/ast/ast_end.h @@ -15,6 +15,6 @@ bool ast_is_end(struct ast *node); * Creates a new AST node representing the end of input. * WARNING: data will be a NULL pointer */ -struct ast *ast_create_end(end); +struct ast *ast_create_end(void); #endif /* ! AST_END_H */ From dadbebfceba8c00f7a9f5710ffe1339977bcd0a6 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Fri, 16 Jan 2026 21:48:27 +0000 Subject: [PATCH 081/282] feat(expansion): parse_var_name and tests --- src/expansion/expansion.c | 102 ++++++++++++++++------- src/expansion/expansion.h | 11 +++ tests/unit/expansion/parse_var.c | 138 +++++++++++++++++++++++++++++++ 3 files changed, 223 insertions(+), 28 deletions(-) create mode 100644 tests/unit/expansion/parse_var.c diff --git a/src/expansion/expansion.c b/src/expansion/expansion.c index fe28dd0..81867a1 100644 --- a/src/expansion/expansion.c +++ b/src/expansion/expansion.c @@ -1,3 +1,4 @@ +#define _POSIX_C_SOURCE 200809L #include #include #include @@ -6,13 +7,75 @@ #include "../utils/ast/ast.h" -// static size_t var_len(char *start) -// { -// char *iter = start; -// while (*iter != ' ' && *iter != 0) -// *iter++; -// return iter - start; -// } +static bool is_var_start_char(char c) +{ + return isalpha(c) || c == '_'; +} + +static bool is_var_char(char c) +{ + return isalnum(c) || c == '_'; +} + +static bool is_special_var_char(char c) +{ + return c == '@' || c == '*' || c == '?' || c == '$' || isdigit(c) + || c == '#'; +} + +size_t parse_var_name(char *str, char **res) +{ + char *brace = NULL; + size_t i = 1; // skip the '$' + + if (str[i] == '{' && str[i + 1] != 0 && str[i + 1] != '}') + { + if (is_special_var_char(str[i + 1]) && str[i + 2] == '}') + { + // Special variable like ${1}, ${?} + *res = strndup(str + i + 1, 1); + return 4; // length of ${X} + } + + brace = str + i; + i++; // skip the '{' + } + else if (is_special_var_char(str[i])) + { + *res = strndup(str + i, 1); + return 2; // length of $X + } + + if (!is_var_start_char(str[i])) + { + // Not a valid variable start + *res = NULL; + return 0; + } + + while (1) + { + if (str[i] == '}' && *brace == '{') + { + *res = strndup(str + 2, i - 2); + return i + 1; + } + else if (!is_var_char(str[i])) + { + if (brace != NULL) + { + // Missing closing '}' + *res = NULL; + return 0; + } + break; + } + i++; + } + + *res = strndup(str + 1, i - 1); + return i; +} struct ast_command *expand(struct ast_command *command) { @@ -34,21 +97,18 @@ struct ast_command *expand(struct ast_command *command) { if (in_quotes) { - // do nothing + continue; // do nothing } else if (str[i] == '\'') { + // remove quote in_quotes = !in_quotes; memmove(&str[i], &str[i + 1], strlen(&str[i + 1]) + 1); + i--; } else if (str[i] == '$' && str[i + 1] != 0 && !isspace(str[i + 1])) { - // size_t len = var_len(str + i + 1); - // char *end = str + i + len + 1; - // char c = *end; - // *end = 0; - // printf("var: %s\n", str + i + 1); - // *end = c; + // variable expansion } } @@ -71,17 +131,3 @@ struct ast_command *expand(struct ast_command *command) } return command; } - -// int main() -// { -// printf("Expansion module test\n"); -// struct ast_command ast_command; -// // char str[] = "echo Hello $?"; -// char str[] = "echo Hello $AE86"; -// ast_command.command = list_append(NULL, str); - -// struct ast_command *command2 = expand(&ast_command); -// printf("command2: %s\n", (char *)command2->command->data); - -// return 0; -// } diff --git a/src/expansion/expansion.h b/src/expansion/expansion.h index b10b198..756e75e 100644 --- a/src/expansion/expansion.h +++ b/src/expansion/expansion.h @@ -1,4 +1,15 @@ #ifndef EXPANSION_H #define EXPANSION_H +#include + +/** + * Parse a variable from a string starting with '$'. + * @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 + * variable name. + * @return The number of characters processed in the input string. + */ +size_t parse_var_name(char *str, char **res); + #endif /* ! EXPANSION_H */ diff --git a/tests/unit/expansion/parse_var.c b/tests/unit/expansion/parse_var.c new file mode 100644 index 0000000..f588415 --- /dev/null +++ b/tests/unit/expansion/parse_var.c @@ -0,0 +1,138 @@ +#include +#include +#include + +#include "../../../src/expansion/expansion.h" + +TestSuite(parse_var_name); + +// char *input = "$MY$VAR"; +// char *input = "$MY$VAR$"; +// char *input = "$MY$VAR${}"; +// char *input = "$MY$VAR${1}"; + +Test(parse_var_name, basic_variable) +{ + char *input = "$MY_VAR"; + char *extracted_var = NULL; + size_t r = parse_var_name(input, &extracted_var); + + cr_expect(r == 7); + cr_expect_str_eq(extracted_var, "MY_VAR"); + free(extracted_var); +} + +Test(parse_var_name, multi_basic_variable) +{ + char *input = "$MY$VAR"; + char *extracted_var = NULL; + size_t r = parse_var_name(input, &extracted_var); + + cr_expect(r == 3); + cr_expect_str_eq(extracted_var, "MY"); + free(extracted_var); + + input += r; + r = parse_var_name(input, &extracted_var); + + cr_expect(r == 4); + cr_expect_str_eq(extracted_var, "VAR"); + free(extracted_var); +} + +Test(parse_var_name, variable_with_braces) +{ + char *input = "${MY_VAR}"; + char *extracted_var = NULL; + size_t r = parse_var_name(input, &extracted_var); + + cr_expect(r == 9); + cr_expect_str_eq(extracted_var, "MY_VAR"); + free(extracted_var); +} + +Test(parse_var_name, special_variable) +{ + char *input = "$1"; + char *extracted_var = NULL; + size_t r = parse_var_name(input, &extracted_var); + + cr_expect(r == 2); + cr_expect_str_eq(extracted_var, "1"); + free(extracted_var); +} + +Test(parse_var_name, incomplete_braces) +{ + char *input = "${MY_VAR"; + char *extracted_var = NULL; + size_t r = parse_var_name(input, &extracted_var); + + cr_expect(r == 0); + cr_expect(extracted_var == NULL); +} + +Test(parse_var_name, empty_braces) +{ + char *input = "${}"; + char *extracted_var = NULL; + size_t r = parse_var_name(input, &extracted_var); + + cr_expect(r == 0); + cr_expect(extracted_var == NULL); +} + +Test(parse_var_name, dollar_sign_only) +{ + char *input = "$"; + char *extracted_var = NULL; + size_t r = parse_var_name(input, &extracted_var); + + cr_expect(r == 0); + cr_expect(extracted_var == NULL); +} + +Test(parse_var_name, variable_followed_by_dollar) +{ + char *input = "$MY$VAR$"; + char *extracted_var = NULL; + size_t r = parse_var_name(input, &extracted_var); + + cr_expect(r == 3); + cr_expect_str_eq(extracted_var, "MY"); + free(extracted_var); + + input += r; + r = parse_var_name(input, &extracted_var); + + cr_expect(r == 4); + cr_expect_str_eq(extracted_var, "VAR"); + free(extracted_var); + + input += r; + r = parse_var_name(input, &extracted_var); + + cr_expect(r == 0); + cr_expect(extracted_var == NULL); +} + +Test(parse_var_name, special_variable_followed_by_text) +{ + char *input = "$1VAR"; + char *extracted_var = NULL; + size_t r = parse_var_name(input, &extracted_var); + + cr_expect(r == 2); + cr_expect_str_eq(extracted_var, "1"); + free(extracted_var); +} + +Test(parse_var_name, bad_variable_with_braces) +{ + char *input = "${1VAR}"; + char *extracted_var = NULL; + size_t r = parse_var_name(input, &extracted_var); + + cr_expect(r == 0); + cr_expect(extracted_var == NULL); +} From d269e82df6c064bf6972487ab856f1c4e41a40f3 Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 17 Jan 2026 10:43:23 +0100 Subject: [PATCH 082/282] rebased lexer on dev --- src/lexer/lexer.c | 82 +++++++++++++++++++++++++++++++++++------------ src/lexer/lexer.h | 26 +++++++++++---- 2 files changed, 82 insertions(+), 26 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index 68366cf..1df0ec5 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -16,11 +16,10 @@ static bool at_beginning = true; static struct token *last_token; static struct token *current_token; - /* @brief: sets the current_token to [tok]. * this function is called by token_peek(). */ -static void update_current_token(struct token* tok) +static void update_current_token(struct token *tok) { current_token = tok; } @@ -30,7 +29,7 @@ static void update_current_token(struct token* tok) * this function is called by token_pop(). * */ -static void update_last_token(struct token* tok) +static void update_last_token(struct token *tok) { free_token(&last_token); last_token = tok; @@ -51,43 +50,86 @@ static void save_state(char *stream, ssize_t i, struct token *tok) /* @return: true if a special character from the grammar was found, * false otherwise. - * */ static bool is_special_char(char c) { - return c == '\'' || c == '\n' || c == ';' || c == EOF; + if (c == EOF) + return true; + + char special_chars[] = "\n'\"`;#|&\\$(){}<>*"; + return strchr(special_chars, c) != NULL; } /* @brief: if a special character is found at [begin], * [tok->token_type] is set accordingly - * */ static void set_token_spechar(struct token *tok, char *begin, ssize_t size) { if (size != 1) return; - if (begin[0] == EOF) + switch (begin[0]) { + case EOF: tok->type = TOKEN_EOF; - remaining_chars = 0; - } - else if (begin[0] == ';') - { - tok->type = TOKEN_NEWLINE; - } - else if (begin[0] == '\'') - { - tok->type = TOKEN_QUOTE; - } - else if (begin[0] == ';') - { + break; + case ';': tok->type = TOKEN_SEMICOLON; + break; + case '\n': + tok->type = TOKEN_NEWLINE; + break; + case '\'': + tok->type = TOKEN_QUOTE; + break; + case '"': + tok->type = TOKEN_DOUBLE_QUOTE; + break; + case '`': + tok->type = TOKEN_GRAVE; + break; + case '#': + tok->type = TOKEN_COMMENT; + break; + case '|': + tok->type = TOKEN_PIPE; + break; + case '&': + tok->type = TOKEN_AMPERSAND; + break; + case '\\': + tok->type = TOKEN_BACKSLASH; + break; + case '$': + tok->type = TOKEN_DOLLAR; + break; + case '(': + tok->type = TOKEN_LEFT_PAREN; + break; + case ')': + tok->type = TOKEN_RIGHT_PAREN; + break; + case '{': + tok->type = TOKEN_LEFT_BRACKET; + break; + case '}': + tok->type = TOKEN_RIGHT_BRACKET; + break; + case '<': + tok->type = TOKEN_LESS; + break; + case '>': + tok->type = TOKEN_GREATER; + break; + case '*': + tok->type = TOKEN_STAR; + break; + default: + break; } } /* @brief: if a keyword is found at [begin], * [tok->token_type] is set accordingly - * */ static void set_token_keyword(struct token *tok, char *begin, ssize_t size) { diff --git a/src/lexer/lexer.h b/src/lexer/lexer.h index 1b46523..31a329a 100644 --- a/src/lexer/lexer.h +++ b/src/lexer/lexer.h @@ -5,12 +5,29 @@ enum token_type { + // Special characters TOKEN_NULL = 0, TOKEN_EOF, TOKEN_WORD, TOKEN_NEWLINE, TOKEN_QUOTE, + TOKEN_DOUBLE_QUOTE, + TOKEN_GRAVE, TOKEN_SEMICOLON, + TOKEN_COMMENT, + TOKEN_PIPE, + TOKEN_AMPERSAND, + TOKEN_BACKSLASH, + TOKEN_DOLLAR, + TOKEN_LEFT_PAREN, + TOKEN_RIGHT_PAREN, + TOKEN_LEFT_BRACKET, + TOKEN_RIGHT_BRACKET, + TOKEN_LESS, + TOKEN_GREATER, + TOKEN_STAR, + + // Keywords TOKEN_IF, TOKEN_THEN, TOKEN_ELSE, @@ -28,7 +45,6 @@ struct token * @brief: returns the next (newly allocated) token without consuming it. * if end of input is reached, enters in EOF looping node, * returning only the same token of type TOKEN_EOF. - * */ struct token *peek_token(void); @@ -36,6 +52,7 @@ struct token *peek_token(void); * @brief: returns the next (newly allocated) token and consumes it. * if end of input is reached, returns a token of type TOKEN_EOF. * It also frees the last token created if there was one. + * * @warning: if the last returned token was a token EOF, it frees it * and returns NULL. This means that after peeking a token EOF * in the parser, there must be EXACTLY ONE call to pop_token(). @@ -43,10 +60,9 @@ struct token *peek_token(void); */ struct token *pop_token(void); -/* - * @warning: NOT IMPLEMENTED. +/* @note: maybe usefull for subshells. * - * @note: maybe usefull for subshells. + * @warning: NOT IMPLEMENTED. */ struct token *get_token_str(void); @@ -56,12 +72,10 @@ struct token *get_token_str(void); * The data contains [size] char, starting from [begin]. * * @return: NULL on error, a token otherwise. - * */ struct token *new_token(char *begin, ssize_t size); /* @brief: frees the token given in argument - * */ void free_token(struct token **tok); From 6715d72cd7912fee070ee114b0279c03e7dbd2a5 Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 17 Jan 2026 11:06:50 +0100 Subject: [PATCH 083/282] fix(lexer): some memory leaks + removed useless [at_begining] variable --- src/lexer/lexer.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index a432de8..acb8402 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -12,7 +12,6 @@ static char *end_last_token; static ssize_t remaining_chars; -static bool at_beginning = true; static struct token *last_token; static struct token *current_token; @@ -34,17 +33,19 @@ static void update_last_token(struct token *tok) last_token = tok; } -/* @brief: saves state for the next call to the the lexer. - * this function is called by token_pop(). - * +/* @brief: updates the current position in the stream. + * [stream] += [i] + * Also saves the last token sent (so we can free it afterwards). + * Also sets the current token to NULL. + * This function is called by token_pop(). */ static void save_state(char *stream, ssize_t i, struct token *tok) { remaining_chars -= i; end_last_token = stream + i; - at_beginning = false; update_last_token(tok); + update_current_token(NULL); } /* @return: true if a special character from the grammar was found, @@ -203,10 +204,9 @@ char *stream_init(void) { char *stream; - if (at_beginning) + if (last_token == NULL) // at the begining { remaining_chars = stream_read(&stream); - // at_beginning = true; } else { @@ -222,8 +222,8 @@ char *stream_init(void) struct token *peek_token(void) { - // EOF looping mode - if (current_token != NULL && current_token->type == TOKEN_EOF) + // we already created the upcoming token during the previous call to peek() + if (current_token != NULL) { return current_token; } From f4d7f58ef5b115dea4e0a278dc5ed552236e8814 Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 17 Jan 2026 11:38:23 +0100 Subject: [PATCH 084/282] fix(lexer): memory leaks --- src/lexer/lexer.c | 139 +++++++++++++++++++++++++--------------------- 1 file changed, 75 insertions(+), 64 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index acb8402..ef5a478 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -35,16 +35,16 @@ static void update_last_token(struct token *tok) /* @brief: updates the current position in the stream. * [stream] += [i] - * Also saves the last token sent (so we can free it afterwards). - * Also sets the current token to NULL. + * Also frees the last sent token, and sets it to current_token. + * Current token is then set to NULL. * This function is called by token_pop(). */ -static void save_state(char *stream, ssize_t i, struct token *tok) +static void save_state(char *stream, ssize_t i) { remaining_chars -= i; end_last_token = stream + i; - update_last_token(tok); + update_last_token(current_token); update_current_token(NULL); } @@ -69,62 +69,62 @@ static void set_token_spechar(struct token *tok, char *begin, ssize_t size) return; switch (begin[0]) { - case EOF: - tok->type = TOKEN_EOF; - break; - case ';': - tok->type = TOKEN_SEMICOLON; - break; - case '\n': - tok->type = TOKEN_NEWLINE; - break; - case '\'': - tok->type = TOKEN_QUOTE; - break; - case '"': - tok->type = TOKEN_DOUBLE_QUOTE; - break; - case '`': - tok->type = TOKEN_GRAVE; - break; - case '#': - tok->type = TOKEN_COMMENT; - break; - case '|': - tok->type = TOKEN_PIPE; - break; - case '&': - tok->type = TOKEN_AMPERSAND; - break; - case '\\': - tok->type = TOKEN_BACKSLASH; - break; - case '$': - tok->type = TOKEN_DOLLAR; - break; - case '(': - tok->type = TOKEN_LEFT_PAREN; - break; - case ')': - tok->type = TOKEN_RIGHT_PAREN; - break; - case '{': - tok->type = TOKEN_LEFT_BRACKET; - break; - case '}': - tok->type = TOKEN_RIGHT_BRACKET; - break; - case '<': - tok->type = TOKEN_LESS; - break; - case '>': - tok->type = TOKEN_GREATER; - break; - case '*': - tok->type = TOKEN_STAR; - break; - default: - break; + case EOF: + tok->type = TOKEN_EOF; + break; + case ';': + tok->type = TOKEN_SEMICOLON; + break; + case '\n': + tok->type = TOKEN_NEWLINE; + break; + case '\'': + tok->type = TOKEN_QUOTE; + break; + case '"': + tok->type = TOKEN_DOUBLE_QUOTE; + break; + case '`': + tok->type = TOKEN_GRAVE; + break; + case '#': + tok->type = TOKEN_COMMENT; + break; + case '|': + tok->type = TOKEN_PIPE; + break; + case '&': + tok->type = TOKEN_AMPERSAND; + break; + case '\\': + tok->type = TOKEN_BACKSLASH; + break; + case '$': + tok->type = TOKEN_DOLLAR; + break; + case '(': + tok->type = TOKEN_LEFT_PAREN; + break; + case ')': + tok->type = TOKEN_RIGHT_PAREN; + break; + case '{': + tok->type = TOKEN_LEFT_BRACKET; + break; + case '}': + tok->type = TOKEN_RIGHT_BRACKET; + break; + case '<': + tok->type = TOKEN_LESS; + break; + case '>': + tok->type = TOKEN_GREATER; + break; + case '*': + tok->type = TOKEN_STAR; + break; + default: + break; } } @@ -156,6 +156,10 @@ static void set_token_keyword(struct token *tok, char *begin, ssize_t size) tok->type = TOKEN_ELIF; } + // no keywords found. + if (tok->type == TOKEN_NULL) + return; + tok->data = calloc(size + 1, sizeof(char)); if (tok->data == NULL) return; @@ -254,9 +258,11 @@ struct token *peek_token(void) struct token *pop_token(void) { - if (last_token != NULL && last_token->type == TOKEN_EOF) + if (current_token != NULL && current_token->type == TOKEN_EOF) { + // we reached end of input, frees all the token still allocated. free_token(&last_token); + free_token(¤t_token); return NULL; } char *stream = stream_init(); @@ -278,8 +284,13 @@ struct token *pop_token(void) i++; } - struct token *tok = new_token(stream, i); - save_state(stream, i, tok); + // just in case peek() was not called before poping. + // (this should never happen) + if (current_token == NULL) + { + current_token = new_token(stream, i); + } + save_state(stream, i); - return tok; + return last_token; } From 346ad17e264bd512d7bc2d6c34cc5653b6adbdbc Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Sat, 17 Jan 2026 16:40:53 +0100 Subject: [PATCH 085/282] docs: reworked parser header to fully comply with the given grammar and added a language representation for each parse_* function --- src/parser/parsing_utils.c | 7 ++- src/parser/parsing_utils.h | 88 +++++++++++++++++++++++++++++--------- 2 files changed, 73 insertions(+), 22 deletions(-) diff --git a/src/parser/parsing_utils.c b/src/parser/parsing_utils.c index c07653e..0d0f43a 100644 --- a/src/parser/parsing_utils.c +++ b/src/parser/parsing_utils.c @@ -47,6 +47,11 @@ static bool is_end_of_list(struct token *token) // === Functions +struct ast *parse_input(void) +{ + return parse_list(); +} + /* Parses a simple list of words (command and arguments) * and returns the resulting ast */ @@ -55,7 +60,7 @@ struct ast *parse_simple_command(void) struct list *command_elements = NULL; struct token *token = PEEK_TOKEN(); - while (!isterminator(token)) + while (token->type == TOKEN_WORD) { token = POP_TOKEN(); command_elements = list_append(command_elements, token->data); diff --git a/src/parser/parsing_utils.h b/src/parser/parsing_utils.h index a380a85..3c9101c 100644 --- a/src/parser/parsing_utils.h +++ b/src/parser/parsing_utils.h @@ -19,33 +19,79 @@ return NULL; \ } -/* @brief: parses a list of [and_or] rules, separated by semicolons. +/* @brief Acts as the entry point of the parser, calls parse_list + */ +struct ast* parse_input(void); + +/* @brief: parses a list of [and_or] rules separated by semicolons and that + * ends by a newline + * + * @code input = list '\n' + * | list EOF + * | '\n' + * | EOF + * ; */ struct ast *parse_list(void); -/* @brief Parses a simple list of words (command and arguments) - * and returns the resulting ast - */ -struct ast *parse_simple_command(void); - -/* - */ -struct ast *parse_if_rule(void); - -/* - */ -struct ast *parse_shell_command(void); - -/* @brief parses commands inside if/else clauses and returns the corresponding - * AST list - */ -struct ast* parse_compound_list(void); - -/* +/* @brief Only parses a pipeline rule for the moment + * + * @code and_or = pipeline ; */ struct ast* parse_and_or(void); -/* +/* @brief Only parses a command rule for the moment + * + * @code pipeline = command ; + */ +struct ast* parse_pipeline(void); + +/* @brief Parses a simple command rule or a shell command rule depending on + * the first token. + * @note + * TOKEN_WORD => simple_command + * TOKEN_IF => shell_command + * + * @code command = simple_command + * | shell_command + * ; + */ +struct ast* parse_command(void); + +/* @brief Parses a simple list of words (command and arguments) + * ending by a separator + * + * @code simple_command = WORD { element } ; + */ +struct ast *parse_simple_command(void); + + +/* @brief Only parses if rules for the moment + * + * @code shell_command = if_rule ; + */ +struct ast *parse_shell_command(void); + + +/* @brief Parses a if rule (condition, then-clause, elif-clause, else-clause) + * + * @code if_rule = 'if' compound_list 'then' compound_list [else_clause] 'fi' ; + */ +struct ast *parse_if_rule(void); + + +/* @brief parses commands inside if/else clauses and returns the corresponding + * AST list + * + * @code compound_list = {'\n'} and_or { ( ';' | '\n' ) {'\n'} and_or } [';'] {'\n'} ; + */ +struct ast* parse_compound_list(void); + +/* @brief + * + * @code else_clause = 'else' compound_list + * | 'elif' compound_list 'then' compound_list [else_clause] + * ; */ struct ast* parse_else_clause(void); From f887c90ec567b05b263ebddbd1630b42d591a28b Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Sat, 17 Jan 2026 17:20:13 +0100 Subject: [PATCH 086/282] feat: elif support, fixed a lot of inconcistencies with the grammar and updated code according to the behaviour described in the header. Also fixed some typos and doc errors --- src/parser/parsing_utils.c | 140 +++++++++++++++++++++++++------------ src/parser/parsing_utils.h | 12 ++-- 2 files changed, 103 insertions(+), 49 deletions(-) diff --git a/src/parser/parsing_utils.c b/src/parser/parsing_utils.c index 0d0f43a..f1408c5 100644 --- a/src/parser/parsing_utils.c +++ b/src/parser/parsing_utils.c @@ -52,9 +52,57 @@ struct ast *parse_input(void) return parse_list(); } -/* Parses a simple list of words (command and arguments) - * and returns the resulting ast - */ +struct ast *parse_list(void) +{ + struct list *result_list = NULL; + struct ast *current_node = NULL; + + struct token *token = PEEK_TOKEN(); + + while (!is_end_of_list(token)) + { + if (token->type == TOKEN_SEMICOLON) + { + result_list = list_append(result_list, current_node); + } + else + { + current_node = parse_and_or(); + } + token = PEEK_TOKEN(); + } + + return ast_create_list(result_list); +} + +struct ast *parse_and_or(void) +{ + return parse_pipeline(); +} + +struct ast *parse_pipeline(void) +{ + return parse_command(); +} + +struct ast *parse_command(void) +{ + struct token *token = PEEK_TOKEN(); + + if (token->type == TOKEN_WORD) + { + return parse_simple_command(); + } + else if (token->type == TOKEN_IF) + { + return parse_shell_command(); + } + else + { + return ast_create_void(); // TODO not sure what to do + } +} + struct ast *parse_simple_command(void) { struct list *command_elements = NULL; @@ -71,30 +119,23 @@ struct ast *parse_simple_command(void) return result; } -struct ast *parse_list(void) +// TODO check compliance with the grammar +struct ast *parse_shell_command(void) { - struct list *result_list = NULL; - struct ast *current_node = NULL; - struct token *token = PEEK_TOKEN(); - while (!is_end_of_list(token)) + switch (token->type) { - if (token->type == TOKEN_SEMICOLON) - { - result_list = list_append(result_list, current_node); - } - else - { - // TODO use parse_and_or() instead. - current_node = parse_simple_command(); - } - token = PEEK_TOKEN(); - } + case TOKEN_IF: + return parse_if_rule(); - return ast_create_list(result_list); + default: + puts("I think it's not implemented yet"); + return NULL; + } } +// TODO check compliance with the grammar struct ast *parse_if_rule(void) { // If condition @@ -143,6 +184,7 @@ struct ast *parse_if_rule(void) return result; } +// TODO comply with header's grammar struct ast *parse_compound_list(void) { struct list *result_list = NULL; // ast* list @@ -178,19 +220,44 @@ struct ast *parse_compound_list(void) struct ast *parse_else_clause(void) { - // Eventual elif content struct token *token = PEEK_TOKEN(); + // TODO handle ELIF + // Eventual elif content + while (token->type == TOKEN_ELIF) + { + // Condition + token = POP_TOKEN(); + struct ast *condition = parse_compound_list(); + + // 'then' + token = POP_TOKEN(); + if (token->type != TOKEN_THEN) + { + puts("Expected the 'then' keyword but got a different token type"); + return NULL; + } + + // Then clause + struct ast *then_content = parse_compound_list(); + + // 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(); + } + + struct ast *result = + ast_create_if(condition, then_content, else_content); + return result; + } + + // Eventual else content + struct ast *result = NULL; - // TODO handle ELIF - // while (token->type == TOKEN_ELIF) - // { - // puts("ABORTING ELIF: Not implemented ma gueule"); - // token = POP_TOKEN(); // Forward - // } - - // Eventual else content if (token->type == TOKEN_ELSE) { result = parse_compound_list(); @@ -202,18 +269,3 @@ struct ast *parse_else_clause(void) return result; } - -struct ast *parse_shell_command(void) -{ - struct token *token = PEEK_TOKEN(); - - switch (token->type) - { - case TOKEN_IF: - return parse_if_rule(); - - default: - puts("I think it's not implemented yet"); - return NULL; - } -} diff --git a/src/parser/parsing_utils.h b/src/parser/parsing_utils.h index 3c9101c..f4c5a68 100644 --- a/src/parser/parsing_utils.h +++ b/src/parser/parsing_utils.h @@ -20,11 +20,6 @@ } /* @brief Acts as the entry point of the parser, calls parse_list - */ -struct ast* parse_input(void); - -/* @brief: parses a list of [and_or] rules separated by semicolons and that - * ends by a newline * * @code input = list '\n' * | list EOF @@ -32,6 +27,13 @@ struct ast* parse_input(void); * | EOF * ; */ +struct ast* parse_input(void); + +/* @brief: parses a list of [and_or] rules separated by semicolons and that + * ends by a newline + * + * @code list = and_or { ';' and_or } [ ';' ] ; + */ struct ast *parse_list(void); /* @brief Only parses a pipeline rule for the moment From bf992f2db49044b1d4185bcf679d287a6e55676b Mon Sep 17 00:00:00 2001 From: Jean Herail Date: Sat, 17 Jan 2026 17:33:22 +0100 Subject: [PATCH 087/282] Implemented some ast handlings... ast lists, and_or, redirection and builtins --- src/execution/execution.c | 231 +++++++++++++++++++++++++++++++++++-- src/utils/Makefile.am | 2 + src/utils/ast/ast.c | 4 + src/utils/ast/ast.h | 2 + src/utils/ast/ast_and_or.c | 35 ++++++ src/utils/ast/ast_and_or.h | 23 ++++ src/utils/ast/ast_base.h | 6 +- src/utils/ast/ast_list.c | 5 +- src/utils/ast/ast_redir.c | 36 ++++++ src/utils/ast/ast_redir.h | 29 +++++ 10 files changed, 359 insertions(+), 14 deletions(-) create mode 100644 src/utils/ast/ast_and_or.c create mode 100644 src/utils/ast/ast_and_or.h create mode 100644 src/utils/ast/ast_redir.c create mode 100644 src/utils/ast/ast_redir.h diff --git a/src/execution/execution.c b/src/execution/execution.c index 0066681..33389cb 100644 --- a/src/execution/execution.c +++ b/src/execution/execution.c @@ -5,9 +5,14 @@ #include #include #include +#include +#include +#include #include "../utils/ast/ast.h" +// --- Helpers --- + /** * @brief converts a linked list of command arguments to an argv array. Don't * forget to free the result @@ -44,6 +49,94 @@ static char **list_to_argv(struct list *command_list) return argv; } +// --- Builtins --- + +static int builtin_echo(char **argv) +{ + bool newline = true; + int i = 1; + + if (argv[1] && strcmp(argv[1], "-n") == 0) + { + newline = false; + i++; + } + + for (; argv[i]; i++) + { + printf("%s", argv[i]); + if (argv[i + 1]) + printf(" "); + } + if (newline) + printf("\n"); + + fflush(stdout); + return 0; +} + +static int builtin_true(char **argv) +{ + (void)argv; + return 0; +} + +static int builtin_false(char **argv) +{ + (void)argv; + return 1; +} + +static int builtin_exit(char **argv) +{ + int exit_val = 0; + if (argv[1]) + exit_val = atoi(argv[1]); + exit(exit_val); + return exit_val; +} + +static int builtin_cd(char **argv) +{ + const char *path = argv[1]; + if (!path) + { + path = getenv("HOME"); + if (!path) + { + fprintf(stderr, "cd: HOME not set\n"); + return 1; + } + } + if (chdir(path) != 0) + { + perror("cd"); + return 1; + } + return 0; +} + +static int try_builtin(char **argv) +{ + if (!argv || !argv[0]) + return 0; + + if (strcmp(argv[0], "echo") == 0) + return builtin_echo(argv); + if (strcmp(argv[0], "true") == 0) + return builtin_true(argv); + if (strcmp(argv[0], "false") == 0) + return builtin_false(argv); + if (strcmp(argv[0], "exit") == 0) + return builtin_exit(argv); + if (strcmp(argv[0], "cd") == 0) + return builtin_cd(argv); + + return -1; +} + +// --- Execution Core --- + /** * @brief Executes a command represented by an ast_command structure * @@ -54,7 +147,7 @@ static int exec_command(struct ast_command *command) { if (!command || !(command->command)) { - return -1; + return 1; } char **argv = list_to_argv(command->command); @@ -62,23 +155,30 @@ static int exec_command(struct ast_command *command) if (!argv || !(argv[0])) { free(argv); - return -1; + return 0; + } + + int builtin_ret = try_builtin(argv); + if (builtin_ret != -1) + { + free(argv); + return builtin_ret; } pid_t pid = fork(); - if (pid < 0) // Fork failed + if (pid < 0) { perror("fork"); free(argv); - return -1; + return 1; } - if (pid == 0) // If child process + if (pid == 0) { execvp(argv[0], argv); - perror("execvp"); // If execvp returns, there was an error - exit(EXIT_FAILURE); // Exit child process + perror("execvp"); + _exit(127); } int status = 0; @@ -90,7 +190,7 @@ static int exec_command(struct ast_command *command) return WEXITSTATUS(status); } - return -1; // Should not happen + return 1; } /** @@ -113,7 +213,7 @@ int execution(struct ast *ast) } case AST_CMD: { struct ast_command *command = ast_get_command(ast); - return exec_command(command); // It's recursive + return exec_command(command); } case AST_IF: { struct ast_if *if_node = ast_get_if(ast); @@ -127,8 +227,117 @@ int execution(struct ast *ast) return execution(if_node->else_clause); } } + case AST_LIST: { + struct ast_list *list_node = ast_get_list(ast); + struct list *cur = list_node->children; + int ret = 0; + while (cur) + { + struct ast *child = (struct ast *)cur->data; + ret = execution(child); + cur = cur->next; + } + return ret; + } + case AST_AND_OR: { + struct ast_and_or *ao_node = ast_get_and_or(ast); + int left_ret = execution(ao_node->left); + + if (ao_node->type == AST_AND_OR_TYPE_AND) + { + if (left_ret == 0) + return execution(ao_node->right); + return left_ret; + } + else // OR + { + if (left_ret != 0) + return execution(ao_node->right); + return left_ret; + } + } + case AST_REDIR: { + struct ast_redir *redir = ast_get_redir(ast); + + int fd_target = redir->io_number; + if (fd_target == -1) + { + if (redir->type == AST_REDIR_TYPE_LESS || redir->type == AST_REDIR_TYPE_DLESS || redir->type == AST_REDIR_TYPE_LESSAND) + fd_target = 0; + else + fd_target = 1; + } + + int saved_fd = dup(fd_target); + + int new_fd = -1; + int flags = 0; + int mode = 0644; + + if (redir->type == AST_REDIR_TYPE_GREAT || redir->type == AST_REDIR_TYPE_CLOBBER) + { + flags = O_WRONLY | O_CREAT | O_TRUNC; + new_fd = open(redir->filename, flags, mode); + } + else if (redir->type == AST_REDIR_TYPE_DGREAT) + { + flags = O_WRONLY | O_CREAT | O_APPEND; + new_fd = open(redir->filename, flags, mode); + } + else if (redir->type == AST_REDIR_TYPE_LESS) + { + flags = O_RDONLY; + new_fd = open(redir->filename, flags); + } + else if (redir->type == AST_REDIR_TYPE_GREATAND || redir->type == AST_REDIR_TYPE_LESSAND) + { + // Simple fd duplication + new_fd = atoi(redir->filename); + // Verify new_fd is valid? dup2 will check. + if (dup2(new_fd, fd_target) == -1) + { + perror("dup2"); + if (saved_fd != -1) close(saved_fd); + return 1; + } + new_fd = -2; // Mark as "already duped" + } + + if (new_fd == -1) + { + perror("open"); + if (saved_fd != -1) close(saved_fd); + return 1; + } + + if (new_fd != -2) + { + if (dup2(new_fd, fd_target) == -1) + { + perror("dup2"); + close(new_fd); + if (saved_fd != -1) close(saved_fd); + return 1; + } + close(new_fd); + } + + int ret = execution(redir->child); + + if (saved_fd != -1) + { + dup2(saved_fd, fd_target); + close(saved_fd); + } + else + { + close(fd_target); + } + + return ret; + } default: { - return -1; // Should not happen + return 127; } } -} +} \ No newline at end of file diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am index d1aa675..6cd107d 100644 --- a/src/utils/Makefile.am +++ b/src/utils/Makefile.am @@ -6,6 +6,8 @@ libutils_a_SOURCES = \ ast/ast_if.c \ ast/ast_command.c \ ast/ast_list.c \ + ast/ast_and_or.c \ + ast/ast_redir.c \ ast/ast_void.c \ ast/ast_end.c \ lists/lists.c \ diff --git a/src/utils/ast/ast.c b/src/utils/ast/ast.c index 2d0ce84..c4bece9 100644 --- a/src/utils/ast/ast.c +++ b/src/utils/ast/ast.c @@ -13,6 +13,10 @@ void ast_free(struct ast **node) ast_free_command(ast_get_command(*node)); else if (ast_is_list(*node)) ast_free_list(ast_get_list(*node)); + else if (ast_is_and_or(*node)) + ast_free_and_or(ast_get_and_or(*node)); + else if (ast_is_redir(*node)) + ast_free_redir(ast_get_redir(*node)); free(*node); *node = NULL; diff --git a/src/utils/ast/ast.h b/src/utils/ast/ast.h index 3984667..1062782 100644 --- a/src/utils/ast/ast.h +++ b/src/utils/ast/ast.h @@ -4,6 +4,8 @@ #include "utils/ast/ast_base.h" #include "utils/ast/ast_command.h" #include "utils/ast/ast_if.h" +#include "utils/ast/ast_and_or.h" +#include "utils/ast/ast_redir.h" #include "utils/ast/ast_list.h" #include "utils/ast/ast_end.h" #include "utils/ast/ast_void.h" diff --git a/src/utils/ast/ast_and_or.c b/src/utils/ast/ast_and_or.c new file mode 100644 index 0000000..d07eaf5 --- /dev/null +++ b/src/utils/ast/ast_and_or.c @@ -0,0 +1,35 @@ +#include "utils/ast/ast_and_or.h" +#include + +bool ast_is_and_or(struct ast *node) +{ + return node != NULL && node->type == AST_AND_OR; +} + +struct ast_and_or *ast_get_and_or(struct ast *node) +{ + if (ast_is_and_or(node)) + return (struct ast_and_or *)node->data; + return NULL; +} + +struct ast *ast_create_and_or(struct ast *left, struct ast *right, enum ast_and_or_type type) +{ + struct ast_and_or *and_or = malloc(sizeof(struct ast_and_or)); + if (!and_or) + return NULL; + and_or->left = left; + and_or->right = right; + and_or->type = type; + + return ast_create(AST_AND_OR, and_or); +} + +void ast_free_and_or(struct ast_and_or *and_or) +{ + if (!and_or) + return; + ast_free(&and_or->left); + ast_free(&and_or->right); + free(and_or); +} diff --git a/src/utils/ast/ast_and_or.h b/src/utils/ast/ast_and_or.h new file mode 100644 index 0000000..57a9668 --- /dev/null +++ b/src/utils/ast/ast_and_or.h @@ -0,0 +1,23 @@ +#ifndef AST_AND_OR_H +#define AST_AND_OR_H + +#include +#include "utils/ast/ast_base.h" + +enum ast_and_or_type { + AST_AND_OR_TYPE_AND, + AST_AND_OR_TYPE_OR +}; + +struct ast_and_or { + struct ast *left; + struct ast *right; + enum ast_and_or_type type; +}; + +bool ast_is_and_or(struct ast *node); +struct ast_and_or *ast_get_and_or(struct ast *node); +struct ast *ast_create_and_or(struct ast *left, struct ast *right, enum ast_and_or_type type); +void ast_free_and_or(struct ast_and_or *and_or); + +#endif /* ! AST_AND_OR_H */ diff --git a/src/utils/ast/ast_base.h b/src/utils/ast/ast_base.h index 432eb2c..e09b25d 100644 --- a/src/utils/ast/ast_base.h +++ b/src/utils/ast/ast_base.h @@ -8,6 +8,8 @@ enum ast_type AST_END, AST_LIST, AST_IF, + AST_AND_OR, + AST_REDIR, AST_VOID, AST_CMD }; @@ -20,7 +22,9 @@ struct ast * Data associated with this AST node. It can be one of the following: * - NULL (AST_END) * - struct ast_if* (AST_IF) - * - struct ast_cmd* (AST_CMD) + * - struct ast_command* (AST_CMD) + * - struct ast_and_or* (AST_AND_OR) + * - struct ast_redir* (AST_REDIR) */ void *data; }; diff --git a/src/utils/ast/ast_list.c b/src/utils/ast/ast_list.c index 95e7047..4fc87f5 100644 --- a/src/utils/ast/ast_list.c +++ b/src/utils/ast/ast_list.c @@ -29,7 +29,7 @@ void ast_free_list(struct ast_list *ast_list) if (ast_list == NULL) return; - list_deep_destroy(ast_list->children); + ast_list_deep_destroy(ast_list->children); free(ast_list); } @@ -41,7 +41,8 @@ void ast_list_deep_destroy(struct list *l) { next_elt = elt->next; - ast_free(elt->data); + struct ast *node = (struct ast *)elt->data; + ast_free(&node); free(elt); elt = next_elt; } diff --git a/src/utils/ast/ast_redir.c b/src/utils/ast/ast_redir.c new file mode 100644 index 0000000..c5eec63 --- /dev/null +++ b/src/utils/ast/ast_redir.c @@ -0,0 +1,36 @@ +#include "utils/ast/ast_redir.h" +#include + +bool ast_is_redir(struct ast *node) +{ + return node != NULL && node->type == AST_REDIR; +} + +struct ast_redir *ast_get_redir(struct ast *node) +{ + if (ast_is_redir(node)) + return (struct ast_redir *)node->data; + return NULL; +} + +struct ast *ast_create_redir(struct ast *child, char *filename, int io_number, enum ast_redir_type type) +{ + struct ast_redir *redir = malloc(sizeof(struct ast_redir)); + if (!redir) + return NULL; + redir->child = child; + redir->filename = filename; // Takes ownership? Usually yes in simple ASTs, or dup. Let's assume pointer copy for now, but user must manage memory. + redir->io_number = io_number; + redir->type = type; + + return ast_create(AST_REDIR, redir); +} + +void ast_free_redir(struct ast_redir *redir) +{ + if (!redir) + return; + ast_free(&redir->child); + free(redir->filename); + free(redir); +} diff --git a/src/utils/ast/ast_redir.h b/src/utils/ast/ast_redir.h new file mode 100644 index 0000000..5f5ffb5 --- /dev/null +++ b/src/utils/ast/ast_redir.h @@ -0,0 +1,29 @@ +#ifndef AST_REDIR_H +#define AST_REDIR_H + +#include +#include "utils/ast/ast_base.h" + +enum ast_redir_type { + AST_REDIR_TYPE_LESS, // < + AST_REDIR_TYPE_GREAT, // > + AST_REDIR_TYPE_DLESS, // << + AST_REDIR_TYPE_DGREAT, // >> + AST_REDIR_TYPE_LESSAND, // <& + AST_REDIR_TYPE_GREATAND, // >& + AST_REDIR_TYPE_CLOBBER // >| +}; + +struct ast_redir { + struct ast *child; + char *filename; + int io_number; // The FD being redirected (default -1 if not specified, implies 0 or 1 based on type) + enum ast_redir_type type; +}; + +bool ast_is_redir(struct ast *node); +struct ast_redir *ast_get_redir(struct ast *node); +struct ast *ast_create_redir(struct ast *child, char *filename, int io_number, enum ast_redir_type type); +void ast_free_redir(struct ast_redir *redir); + +#endif /* ! AST_REDIR_H */ From e7b24d0ed626d398af898d576220bd261dd8b72a Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 17 Jan 2026 17:53:37 +0100 Subject: [PATCH 088/282] feat(testsuite): autotools working, testsuite and debug implemented and working --- README.md | 26 ++++---- src/Makefile.am | 44 ++++++++----- src/main.c | 2 +- tests/unit/lexer/lexer_tests.c | 116 +++++++++++++++++++++++---------- 4 files changed, 125 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index be08206..f9130ce 100644 --- a/README.md +++ b/README.md @@ -7,22 +7,26 @@ TODO ### Build -run this command: - `autoreconf --force --verbose --install` - -### Test -run this command: - `./configure CFLAGS='-std=c99 -Werror -Wall -Wextra -Wvla'` -then: +run theses commands: + `autoreconf --force --verbose --install && ./configure` `make` + `./src/42sh --help` + +### Testing -#### asan run this command: - `./configure CFLAGS='-std=c99 -Werror -Wall -Wextra -Wvla -g -fsanitize=address'` - -then: `make check` +#### debug (asan) + +run this command: + `./src/debug` + +#### testsuite + +run this command: + `./src/testsuite` + ## Authors - Matteo Flebus diff --git a/src/Makefile.am b/src/Makefile.am index 411f4cb..c5c19e7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,4 +1,3 @@ -# define the subdirectories SUBDIRS = \ parser \ lexer \ @@ -9,6 +8,8 @@ SUBDIRS = \ bin_PROGRAMS = 42sh +42sh_CFLAGS = -std=c99 -Werror -Wall -Wextra -Wvla + 42sh_SOURCES = main.c 42sh_CPPFLAGS = -I%D% @@ -21,18 +22,31 @@ bin_PROGRAMS = 42sh execution/libexecution.a \ utils/libutils.a +# ================ TESTS ================ -################################################# Test -# -#42sh_asan_SOURCES = main.c -# -#42sh_asan_CPPFLAGS = -I%D% -# -#42sh_asan_LDADD = \ -# ast/lib_asan_ast.a \ -# parser/lib_asan_parser.a \ -# lexer/lib_asan_lexer.a \ -# io_backend/lib_asan_io_backend.a \ -# expansion/lib_asan_expansion.a \ -# execution/lib_asan_execution.a \ -# utils/lib_asan_utils.a +# ------------- Unit tests -------------- + +check_PROGRAMS = testsuite + +testsuite_CFLAGS = $(42sh_CFLAGS) +testsuite_CFLAGS += $(CRITERION_CFLAGS) + +testsuite_SOURCES = ../tests/unit/lexer/lexer_tests.c \ + ../tests/unit/utils/utils_tests.c + +testsuite_CPPFLAGS = $(42sh_CPPFLAGS) + +testsuite_LDADD = $(42sh_LDADD) $(CRITERION_LIBS) + +# ------------- asan debug -------------- + +check_PROGRAMS += debug + +debug_CFLAGS = $(42sh_CFLAGS) +debug_CFLAGS += -fsanitize=address -g + +debug_SOURCES = $(42sh_SOURCES) + +debug_CPPFLAGS = $(42sh_CPPFLAGS) + +debug_LDADD = $(42sh_LDADD) diff --git a/src/main.c b/src/main.c index 399b874..423a141 100644 --- a/src/main.c +++ b/src/main.c @@ -1,5 +1,5 @@ // === Includes -#include +//#include #include #include diff --git a/tests/unit/lexer/lexer_tests.c b/tests/unit/lexer/lexer_tests.c index d9046d4..910a81a 100644 --- a/tests/unit/lexer/lexer_tests.c +++ b/tests/unit/lexer/lexer_tests.c @@ -6,51 +6,95 @@ #include #include "lexer/lexer.h" +#include "io_backend/io_backend.h" -TestSuite(token_creation); +TestSuite(peek_token); -Test(token_creation, basic) +Test(peek_token, basic_empty) { - char input[] = "Hello World"; + // simulates input + char command[] = ""; + struct iob_context context = + { + IOB_MODE_CMD, + command + }; + iob_init(&context); - char actual[] = new_token(input, 5); - char expected[] = "Hello"; - cr_expect(eq(str, actual, expected)); - free(actual); + // test + struct token *tok = peek_token(); + + // expected + enum token_type type_expected = TOKEN_EOF; + char *string_expected = NULL; + + cr_assert(eq(str, string_expected, tok->data)); + cr_assert(type_expected == tok->type); + + free_token(&tok); } -Test(token_creation, nul) +Test(peek_token, basic_word) { - char input[] = NULL; + // simulates input + char command[] = "hello"; + struct iob_context context = + { + IOB_MODE_CMD, + command + }; + iob_init(&context); - char actual[] = new_token(input, 5); - cr_expect(actual == NULL); + // test + struct token *tok = peek_token(); + + // expected + enum token_type type_expected = TOKEN_WORD; + char string_expected[] = "hello"; + + cr_assert(eq(str, string_expected, tok->data)); + cr_assert(type_expected == tok->type); + free_token(&tok); } - -Test(token_creation, too_large) +Test(peek_token, basic_words) { - char input[] = "Hel"; + // simulates input + char command[] = "echo hello there"; + struct iob_context context = + { + IOB_MODE_CMD, + command + }; + iob_init(&context); - char actual[] = new_token(input, 5); - cr_expect(actual == NULL); -} - -Test(token_creation, empty) -{ - char input[] = ""; - - char actual[] = new_token(input, 5); - cr_expect(actual == NULL); -} - -Test(token_creation, basic_long) -{ - char input[] = "Hello World! This project is a mini shell, I love BIG G."; - - char actual[] = new_token(input, 42); - char expected[] = calloc(42 + 1, sizeof(char)); - strncpy(input, expected, 42); - cr_expect(eq(str, actual, expected)); - free(actual); - free(expected); + // ======= echo + + struct token *tok = peek_token(); + + enum token_type type_expected = TOKEN_WORD; + char string_expected[] = "echo"; + + cr_assert(eq(str, string_expected, tok->data)); + cr_assert(type_expected == tok->type); + free_token(&tok); + + // ======= hello + + tok = peek_token(); + + char string_expected2[] = "hello"; + + cr_assert(eq(str, string_expected2, tok->data)); + cr_assert(type_expected == tok->type); + free_token(&tok); + + // ======= there + + tok = peek_token(); + + char string_expected3[] = "there"; + + cr_assert(eq(str, string_expected3, tok->data)); + cr_assert(type_expected == tok->type); + free_token(&tok); } From 0ef11f866db18aaa92680084a38a470822068a95 Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 17 Jan 2026 18:00:34 +0100 Subject: [PATCH 089/282] fix(main): remove unecessary unknown header --- src/main.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main.c b/src/main.c index 423a141..8a81efb 100644 --- a/src/main.c +++ b/src/main.c @@ -1,5 +1,4 @@ // === Includes -//#include #include #include From f37e7dd217808ae87745739cb99ac87b6846568a Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 17 Jan 2026 18:02:00 +0100 Subject: [PATCH 090/282] feat(gitignore): debug and testsuite --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 64c4a76..9cdb480 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ # Project specific 42sh +testsuite +debug # Prerequisites *.d From 352c122549fa1a69d0a9633ee222483969279c03 Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 17 Jan 2026 19:11:55 +0100 Subject: [PATCH 091/282] fix: now compiles and works for simple commands + clang format --- README.md | 26 ++++---- src/Makefile.am | 8 +-- src/execution/execution.c | 49 ++++++++------- src/io_backend/io_backend.h | 1 + src/lexer/lexer.c | 112 ++++++++++++++++----------------- src/main.c | 5 ++ src/parser/parser.c | 2 +- src/parser/parsing_utils.c | 10 ++- src/parser/parsing_utils.h | 8 +-- src/utils/ast/ast.h | 8 +-- src/utils/ast/ast_and_or.c | 4 +- src/utils/ast/ast_and_or.h | 10 ++- src/utils/ast/ast_command.c | 2 +- src/utils/ast/ast_command.h | 2 +- src/utils/ast/ast_end.c | 2 +- src/utils/ast/ast_end.h | 2 +- src/utils/ast/ast_if.c | 2 +- src/utils/ast/ast_list.c | 6 +- src/utils/ast/ast_redir.c | 8 ++- src/utils/ast/ast_redir.h | 27 ++++---- src/utils/ast/ast_void.c | 2 +- src/utils/ast/ast_void.h | 2 +- tests/unit/lexer/lexer_tests.c | 20 ++---- 23 files changed, 168 insertions(+), 150 deletions(-) diff --git a/README.md b/README.md index f9130ce..be08206 100644 --- a/README.md +++ b/README.md @@ -7,26 +7,22 @@ TODO ### Build -run theses commands: - `autoreconf --force --verbose --install && ./configure` +run this command: + `autoreconf --force --verbose --install` + +### Test +run this command: + `./configure CFLAGS='-std=c99 -Werror -Wall -Wextra -Wvla'` +then: `make` - `./src/42sh --help` - -### Testing +#### asan run this command: + `./configure CFLAGS='-std=c99 -Werror -Wall -Wextra -Wvla -g -fsanitize=address'` + +then: `make check` -#### debug (asan) - -run this command: - `./src/debug` - -#### testsuite - -run this command: - `./src/testsuite` - ## Authors - Matteo Flebus diff --git a/src/Makefile.am b/src/Makefile.am index c5c19e7..bd5d691 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -28,8 +28,8 @@ bin_PROGRAMS = 42sh check_PROGRAMS = testsuite -testsuite_CFLAGS = $(42sh_CFLAGS) -testsuite_CFLAGS += $(CRITERION_CFLAGS) +#testsuite_CFLAGS = $(42sh_CFLAGS) +#testsuite_CFLAGS += $(CRITERION_CFLAGS) testsuite_SOURCES = ../tests/unit/lexer/lexer_tests.c \ ../tests/unit/utils/utils_tests.c @@ -42,8 +42,8 @@ testsuite_LDADD = $(42sh_LDADD) $(CRITERION_LIBS) check_PROGRAMS += debug -debug_CFLAGS = $(42sh_CFLAGS) -debug_CFLAGS += -fsanitize=address -g +#debug_CFLAGS = $(42sh_CFLAGS) +#debug_CFLAGS += -fsanitize=address -g debug_SOURCES = $(42sh_SOURCES) diff --git a/src/execution/execution.c b/src/execution/execution.c index 33389cb..a882397 100644 --- a/src/execution/execution.c +++ b/src/execution/execution.c @@ -1,13 +1,13 @@ #include "execution.h" +#include +#include #include #include +#include #include #include #include -#include -#include -#include #include "../utils/ast/ast.h" @@ -70,7 +70,7 @@ static int builtin_echo(char **argv) } if (newline) printf("\n"); - + fflush(stdout); return 0; } @@ -120,7 +120,7 @@ static int try_builtin(char **argv) { if (!argv || !argv[0]) return 0; - + if (strcmp(argv[0], "echo") == 0) return builtin_echo(argv); if (strcmp(argv[0], "true") == 0) @@ -242,7 +242,7 @@ int execution(struct ast *ast) case AST_AND_OR: { struct ast_and_or *ao_node = ast_get_and_or(ast); int left_ret = execution(ao_node->left); - + if (ao_node->type == AST_AND_OR_TYPE_AND) { if (left_ret == 0) @@ -258,38 +258,42 @@ int execution(struct ast *ast) } case AST_REDIR: { struct ast_redir *redir = ast_get_redir(ast); - + int fd_target = redir->io_number; if (fd_target == -1) { - if (redir->type == AST_REDIR_TYPE_LESS || redir->type == AST_REDIR_TYPE_DLESS || redir->type == AST_REDIR_TYPE_LESSAND) + if (redir->type == AST_REDIR_TYPE_LESS + || redir->type == AST_REDIR_TYPE_DLESS + || redir->type == AST_REDIR_TYPE_LESSAND) fd_target = 0; else fd_target = 1; } - + int saved_fd = dup(fd_target); - + int new_fd = -1; int flags = 0; int mode = 0644; - - if (redir->type == AST_REDIR_TYPE_GREAT || redir->type == AST_REDIR_TYPE_CLOBBER) + + if (redir->type == AST_REDIR_TYPE_GREAT + || redir->type == AST_REDIR_TYPE_CLOBBER) { flags = O_WRONLY | O_CREAT | O_TRUNC; new_fd = open(redir->filename, flags, mode); } else if (redir->type == AST_REDIR_TYPE_DGREAT) { - flags = O_WRONLY | O_CREAT | O_APPEND; - new_fd = open(redir->filename, flags, mode); + flags = O_WRONLY | O_CREAT | O_APPEND; + new_fd = open(redir->filename, flags, mode); } else if (redir->type == AST_REDIR_TYPE_LESS) { - flags = O_RDONLY; - new_fd = open(redir->filename, flags); + flags = O_RDONLY; + new_fd = open(redir->filename, flags); } - else if (redir->type == AST_REDIR_TYPE_GREATAND || redir->type == AST_REDIR_TYPE_LESSAND) + else if (redir->type == AST_REDIR_TYPE_GREATAND + || redir->type == AST_REDIR_TYPE_LESSAND) { // Simple fd duplication new_fd = atoi(redir->filename); @@ -297,16 +301,18 @@ int execution(struct ast *ast) if (dup2(new_fd, fd_target) == -1) { perror("dup2"); - if (saved_fd != -1) close(saved_fd); + if (saved_fd != -1) + close(saved_fd); return 1; } new_fd = -2; // Mark as "already duped" } - + if (new_fd == -1) { perror("open"); - if (saved_fd != -1) close(saved_fd); + if (saved_fd != -1) + close(saved_fd); return 1; } @@ -316,7 +322,8 @@ int execution(struct ast *ast) { perror("dup2"); close(new_fd); - if (saved_fd != -1) close(saved_fd); + if (saved_fd != -1) + close(saved_fd); return 1; } close(new_fd); diff --git a/src/io_backend/io_backend.h b/src/io_backend/io_backend.h index 7b9e9b2..413dced 100644 --- a/src/io_backend/io_backend.h +++ b/src/io_backend/io_backend.h @@ -2,6 +2,7 @@ #define IO_BACKEND_H #include + #include "utils/args/args.h" // Error codes diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index ef5a478..47703f3 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -69,62 +69,62 @@ static void set_token_spechar(struct token *tok, char *begin, ssize_t size) return; switch (begin[0]) { - case EOF: - tok->type = TOKEN_EOF; - break; - case ';': - tok->type = TOKEN_SEMICOLON; - break; - case '\n': - tok->type = TOKEN_NEWLINE; - break; - case '\'': - tok->type = TOKEN_QUOTE; - break; - case '"': - tok->type = TOKEN_DOUBLE_QUOTE; - break; - case '`': - tok->type = TOKEN_GRAVE; - break; - case '#': - tok->type = TOKEN_COMMENT; - break; - case '|': - tok->type = TOKEN_PIPE; - break; - case '&': - tok->type = TOKEN_AMPERSAND; - break; - case '\\': - tok->type = TOKEN_BACKSLASH; - break; - case '$': - tok->type = TOKEN_DOLLAR; - break; - case '(': - tok->type = TOKEN_LEFT_PAREN; - break; - case ')': - tok->type = TOKEN_RIGHT_PAREN; - break; - case '{': - tok->type = TOKEN_LEFT_BRACKET; - break; - case '}': - tok->type = TOKEN_RIGHT_BRACKET; - break; - case '<': - tok->type = TOKEN_LESS; - break; - case '>': - tok->type = TOKEN_GREATER; - break; - case '*': - tok->type = TOKEN_STAR; - break; - default: - break; + case EOF: + tok->type = TOKEN_EOF; + break; + case ';': + tok->type = TOKEN_SEMICOLON; + break; + case '\n': + tok->type = TOKEN_NEWLINE; + break; + case '\'': + tok->type = TOKEN_QUOTE; + break; + case '"': + tok->type = TOKEN_DOUBLE_QUOTE; + break; + case '`': + tok->type = TOKEN_GRAVE; + break; + case '#': + tok->type = TOKEN_COMMENT; + break; + case '|': + tok->type = TOKEN_PIPE; + break; + case '&': + tok->type = TOKEN_AMPERSAND; + break; + case '\\': + tok->type = TOKEN_BACKSLASH; + break; + case '$': + tok->type = TOKEN_DOLLAR; + break; + case '(': + tok->type = TOKEN_LEFT_PAREN; + break; + case ')': + tok->type = TOKEN_RIGHT_PAREN; + break; + case '{': + tok->type = TOKEN_LEFT_BRACKET; + break; + case '}': + tok->type = TOKEN_RIGHT_BRACKET; + break; + case '<': + tok->type = TOKEN_LESS; + break; + case '>': + tok->type = TOKEN_GREATER; + break; + case '*': + tok->type = TOKEN_STAR; + break; + default: + break; } } diff --git a/src/main.c b/src/main.c index 8a81efb..a3e9527 100644 --- a/src/main.c +++ b/src/main.c @@ -69,9 +69,14 @@ int main(int argc, char **argv) // Execute AST return_code = execution(command_ast); + ast_free(&command_ast); + // Retrieve and build next AST command_ast = get_ast(); } + + ast_free(&command_ast); + if (command_ast == NULL) return ERR_INPUT_PROCESSING; diff --git a/src/parser/parser.c b/src/parser/parser.c index 0cce2fb..cf7b052 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -7,8 +7,8 @@ #include #include "lexer/lexer.h" -#include "utils/lists/lists.h" #include "parser/parsing_utils.h" +#include "utils/lists/lists.h" // === Static functions // ... diff --git a/src/parser/parsing_utils.c b/src/parser/parsing_utils.c index c07653e..9ac32d8 100644 --- a/src/parser/parsing_utils.c +++ b/src/parser/parsing_utils.c @@ -1,9 +1,11 @@ +#define _POSIX_C_SOURCE 200809L // === Includes #include "parsing_utils.h" #include #include #include +#include #include "lexer/lexer.h" #include "utils/ast/ast.h" @@ -58,7 +60,8 @@ struct ast *parse_simple_command(void) while (!isterminator(token)) { token = POP_TOKEN(); - command_elements = list_append(command_elements, token->data); + char *word = strdup(token->data); + command_elements = list_append(command_elements, word); token = PEEK_TOKEN(); } @@ -86,6 +89,7 @@ struct ast *parse_list(void) } token = PEEK_TOKEN(); } + result_list = list_append(result_list, current_node); return ast_create_list(result_list); } @@ -163,7 +167,9 @@ struct ast *parse_compound_list(void) return NULL; } - command_elements = list_append(command_elements, token->data); + char *word = strdup(token->data); + + command_elements = list_append(command_elements, word); token = POP_TOKEN(); } diff --git a/src/parser/parsing_utils.h b/src/parser/parsing_utils.h index a380a85..6fe6b89 100644 --- a/src/parser/parsing_utils.h +++ b/src/parser/parsing_utils.h @@ -28,7 +28,7 @@ struct ast *parse_list(void); */ struct ast *parse_simple_command(void); -/* +/* */ struct ast *parse_if_rule(void); @@ -39,14 +39,14 @@ struct ast *parse_shell_command(void); /* @brief parses commands inside if/else clauses and returns the corresponding * AST list */ -struct ast* parse_compound_list(void); +struct ast *parse_compound_list(void); /* */ -struct ast* parse_and_or(void); +struct ast *parse_and_or(void); /* */ -struct ast* parse_else_clause(void); +struct ast *parse_else_clause(void); #endif /* ! PARSING_UTILS_H */ diff --git a/src/utils/ast/ast.h b/src/utils/ast/ast.h index 1062782..5a06cf8 100644 --- a/src/utils/ast/ast.h +++ b/src/utils/ast/ast.h @@ -1,13 +1,13 @@ #ifndef AST_H #define AST_H +#include "utils/ast/ast_and_or.h" #include "utils/ast/ast_base.h" #include "utils/ast/ast_command.h" -#include "utils/ast/ast_if.h" -#include "utils/ast/ast_and_or.h" -#include "utils/ast/ast_redir.h" -#include "utils/ast/ast_list.h" #include "utils/ast/ast_end.h" +#include "utils/ast/ast_if.h" +#include "utils/ast/ast_list.h" +#include "utils/ast/ast_redir.h" #include "utils/ast/ast_void.h" #endif /* ! AST_H */ diff --git a/src/utils/ast/ast_and_or.c b/src/utils/ast/ast_and_or.c index d07eaf5..6b6038b 100644 --- a/src/utils/ast/ast_and_or.c +++ b/src/utils/ast/ast_and_or.c @@ -1,4 +1,5 @@ #include "utils/ast/ast_and_or.h" + #include bool ast_is_and_or(struct ast *node) @@ -13,7 +14,8 @@ struct ast_and_or *ast_get_and_or(struct ast *node) return NULL; } -struct ast *ast_create_and_or(struct ast *left, struct ast *right, enum ast_and_or_type type) +struct ast *ast_create_and_or(struct ast *left, struct ast *right, + enum ast_and_or_type type) { struct ast_and_or *and_or = malloc(sizeof(struct ast_and_or)); if (!and_or) diff --git a/src/utils/ast/ast_and_or.h b/src/utils/ast/ast_and_or.h index 57a9668..e370558 100644 --- a/src/utils/ast/ast_and_or.h +++ b/src/utils/ast/ast_and_or.h @@ -2,14 +2,17 @@ #define AST_AND_OR_H #include + #include "utils/ast/ast_base.h" -enum ast_and_or_type { +enum ast_and_or_type +{ AST_AND_OR_TYPE_AND, AST_AND_OR_TYPE_OR }; -struct ast_and_or { +struct ast_and_or +{ struct ast *left; struct ast *right; enum ast_and_or_type type; @@ -17,7 +20,8 @@ struct ast_and_or { bool ast_is_and_or(struct ast *node); struct ast_and_or *ast_get_and_or(struct ast *node); -struct ast *ast_create_and_or(struct ast *left, struct ast *right, enum ast_and_or_type type); +struct ast *ast_create_and_or(struct ast *left, struct ast *right, + enum ast_and_or_type type); void ast_free_and_or(struct ast_and_or *and_or); #endif /* ! AST_AND_OR_H */ diff --git a/src/utils/ast/ast_command.c b/src/utils/ast/ast_command.c index 920b1ff..89ed379 100644 --- a/src/utils/ast/ast_command.c +++ b/src/utils/ast/ast_command.c @@ -1,8 +1,8 @@ #include "utils/ast/ast_command.h" -#include #include #include +#include #include "utils/lists/lists.h" diff --git a/src/utils/ast/ast_command.h b/src/utils/ast/ast_command.h index 08c62ba..b83bade 100644 --- a/src/utils/ast/ast_command.h +++ b/src/utils/ast/ast_command.h @@ -3,8 +3,8 @@ #include -#include "utils/lists/lists.h" #include "utils/ast/ast_base.h" +#include "utils/lists/lists.h" struct ast_command { diff --git a/src/utils/ast/ast_end.c b/src/utils/ast/ast_end.c index 3be1eeb..a74fdb3 100644 --- a/src/utils/ast/ast_end.c +++ b/src/utils/ast/ast_end.c @@ -1,8 +1,8 @@ #include "utils/ast/ast_end.h" -#include #include #include +#include bool ast_is_end(struct ast *node) { diff --git a/src/utils/ast/ast_end.h b/src/utils/ast/ast_end.h index f86e477..df24d8b 100644 --- a/src/utils/ast/ast_end.h +++ b/src/utils/ast/ast_end.h @@ -3,8 +3,8 @@ #include -#include "utils/lists/lists.h" #include "utils/ast/ast_base.h" +#include "utils/lists/lists.h" /** * Checks if the given AST node is of type AST_END. diff --git a/src/utils/ast/ast_if.c b/src/utils/ast/ast_if.c index 6a25a9c..400a390 100644 --- a/src/utils/ast/ast_if.c +++ b/src/utils/ast/ast_if.c @@ -1,8 +1,8 @@ #include "utils/ast/ast_if.h" -#include #include #include +#include struct ast *ast_create_if(struct ast *condition, struct ast *then_clause, struct ast *else_clause) diff --git a/src/utils/ast/ast_list.c b/src/utils/ast/ast_list.c index 4fc87f5..0455698 100644 --- a/src/utils/ast/ast_list.c +++ b/src/utils/ast/ast_list.c @@ -1,7 +1,7 @@ -#include "utils/ast/ast.h" - #include +#include "utils/ast/ast.h" + struct ast *ast_create_list(struct list *list) { struct ast_list *ast_list = malloc(sizeof(struct ast_list)); @@ -16,7 +16,7 @@ struct ast *ast_create_list(struct list *list) struct ast_list *ast_get_list(struct ast *node) { assert(node != NULL); - return (struct ast_list*)node->data; + return (struct ast_list *)node->data; } bool ast_is_list(struct ast *node) diff --git a/src/utils/ast/ast_redir.c b/src/utils/ast/ast_redir.c index c5eec63..e4c92f3 100644 --- a/src/utils/ast/ast_redir.c +++ b/src/utils/ast/ast_redir.c @@ -1,4 +1,5 @@ #include "utils/ast/ast_redir.h" + #include bool ast_is_redir(struct ast *node) @@ -13,13 +14,16 @@ struct ast_redir *ast_get_redir(struct ast *node) return NULL; } -struct ast *ast_create_redir(struct ast *child, char *filename, int io_number, enum ast_redir_type type) +struct ast *ast_create_redir(struct ast *child, char *filename, int io_number, + enum ast_redir_type type) { struct ast_redir *redir = malloc(sizeof(struct ast_redir)); if (!redir) return NULL; redir->child = child; - redir->filename = filename; // Takes ownership? Usually yes in simple ASTs, or dup. Let's assume pointer copy for now, but user must manage memory. + redir->filename = + filename; // Takes ownership? Usually yes in simple ASTs, or dup. Let's + // assume pointer copy for now, but user must manage memory. redir->io_number = io_number; redir->type = type; diff --git a/src/utils/ast/ast_redir.h b/src/utils/ast/ast_redir.h index 5f5ffb5..65be4a2 100644 --- a/src/utils/ast/ast_redir.h +++ b/src/utils/ast/ast_redir.h @@ -2,28 +2,33 @@ #define AST_REDIR_H #include + #include "utils/ast/ast_base.h" -enum ast_redir_type { - AST_REDIR_TYPE_LESS, // < - AST_REDIR_TYPE_GREAT, // > - AST_REDIR_TYPE_DLESS, // << - AST_REDIR_TYPE_DGREAT, // >> - AST_REDIR_TYPE_LESSAND, // <& - AST_REDIR_TYPE_GREATAND, // >& - AST_REDIR_TYPE_CLOBBER // >| +enum ast_redir_type +{ + AST_REDIR_TYPE_LESS, // < + AST_REDIR_TYPE_GREAT, // > + AST_REDIR_TYPE_DLESS, // << + AST_REDIR_TYPE_DGREAT, // >> + AST_REDIR_TYPE_LESSAND, // <& + AST_REDIR_TYPE_GREATAND, // >& + AST_REDIR_TYPE_CLOBBER // >| }; -struct ast_redir { +struct ast_redir +{ struct ast *child; char *filename; - int io_number; // The FD being redirected (default -1 if not specified, implies 0 or 1 based on type) + int io_number; // The FD being redirected (default -1 if not specified, + // implies 0 or 1 based on type) enum ast_redir_type type; }; bool ast_is_redir(struct ast *node); struct ast_redir *ast_get_redir(struct ast *node); -struct ast *ast_create_redir(struct ast *child, char *filename, int io_number, enum ast_redir_type type); +struct ast *ast_create_redir(struct ast *child, char *filename, int io_number, + enum ast_redir_type type); void ast_free_redir(struct ast_redir *redir); #endif /* ! AST_REDIR_H */ diff --git a/src/utils/ast/ast_void.c b/src/utils/ast/ast_void.c index 1396121..3368c7d 100644 --- a/src/utils/ast/ast_void.c +++ b/src/utils/ast/ast_void.c @@ -1,8 +1,8 @@ #include "utils/ast/ast_void.h" -#include #include #include +#include bool ast_is_void(struct ast *node) { diff --git a/src/utils/ast/ast_void.h b/src/utils/ast/ast_void.h index 866a8d8..6abaef8 100644 --- a/src/utils/ast/ast_void.h +++ b/src/utils/ast/ast_void.h @@ -3,8 +3,8 @@ #include -#include "utils/lists/lists.h" #include "utils/ast/ast_base.h" +#include "utils/lists/lists.h" /** * Checks if the given AST node is of type AST_VOID. diff --git a/tests/unit/lexer/lexer_tests.c b/tests/unit/lexer/lexer_tests.c index 910a81a..7cb84a5 100644 --- a/tests/unit/lexer/lexer_tests.c +++ b/tests/unit/lexer/lexer_tests.c @@ -5,8 +5,8 @@ #include #include -#include "lexer/lexer.h" #include "io_backend/io_backend.h" +#include "lexer/lexer.h" TestSuite(peek_token); @@ -14,11 +14,7 @@ Test(peek_token, basic_empty) { // simulates input char command[] = ""; - struct iob_context context = - { - IOB_MODE_CMD, - command - }; + struct iob_context context = { IOB_MODE_CMD, command }; iob_init(&context); // test @@ -38,11 +34,7 @@ Test(peek_token, basic_word) { // simulates input char command[] = "hello"; - struct iob_context context = - { - IOB_MODE_CMD, - command - }; + struct iob_context context = { IOB_MODE_CMD, command }; iob_init(&context); // test @@ -60,11 +52,7 @@ Test(peek_token, basic_words) { // simulates input char command[] = "echo hello there"; - struct iob_context context = - { - IOB_MODE_CMD, - command - }; + struct iob_context context = { IOB_MODE_CMD, command }; iob_init(&context); // ======= echo From 5d87e87f2ec62c605b60a9cb2f59a57d2b9b97b5 Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Sat, 17 Jan 2026 20:15:27 +0100 Subject: [PATCH 092/282] fix: lot of fixes in parsing. Now code should be fully compliant with the doc grammar. WARNING: not tested yet --- src/parser/parsing_utils.c | 135 +++++++++++++++++++++++-------------- 1 file changed, 86 insertions(+), 49 deletions(-) diff --git a/src/parser/parsing_utils.c b/src/parser/parsing_utils.c index f1408c5..487602d 100644 --- a/src/parser/parsing_utils.c +++ b/src/parser/parsing_utils.c @@ -59,17 +59,29 @@ struct ast *parse_list(void) struct token *token = PEEK_TOKEN(); - while (!is_end_of_list(token)) + // and_or + current_node = parse_and_or(); + if (current_node == NULL) + return NULL; + list_append(result_list, current_node); + + // Following and_or commands + token = PEEK_TOKEN(); + while (token->type == TOKEN_SEMICOLON) { - if (token->type == TOKEN_SEMICOLON) - { - result_list = list_append(result_list, current_node); - } - else + token = POP_TOKEN(); + if (!isterminator(token)) // Follow(list) { current_node = parse_and_or(); + if (current_node == NULL) + { + //TODO free list + // There must be a function for that + return NULL; + } + list_append(result_list, current_node); + token = PEEK_TOKEN(); } - token = PEEK_TOKEN(); } return ast_create_list(result_list); @@ -107,11 +119,17 @@ struct ast *parse_simple_command(void) { struct list *command_elements = NULL; struct token *token = PEEK_TOKEN(); + if (token->type != TOKEN_WORD) + { + puts("Expected a command but got a different token type"); + return NULL; + } while (token->type == TOKEN_WORD) { token = POP_TOKEN(); - command_elements = list_append(command_elements, token->data); + char* word = strdup(token->data); + command_elements = list_append(command_elements, word); token = PEEK_TOKEN(); } @@ -122,25 +140,13 @@ struct ast *parse_simple_command(void) // TODO check compliance with the grammar struct ast *parse_shell_command(void) { - struct token *token = PEEK_TOKEN(); - - switch (token->type) - { - case TOKEN_IF: - return parse_if_rule(); - - default: - puts("I think it's not implemented yet"); - return NULL; - } + return parse_if_rule(); } -// TODO check compliance with the grammar struct ast *parse_if_rule(void) { - // If condition + // If keyword struct token *token = POP_TOKEN(); - if (token->type != TOKEN_IF) { puts("Internal error: expected a if rule but token has different " @@ -148,27 +154,43 @@ struct ast *parse_if_rule(void) return NULL; } + // Condition content struct ast *condition_content = parse_compound_list(); - // Then content + // Then keyword token = POP_TOKEN(); - if (token->type != TOKEN_THEN) { + ast_free(&condition_content); puts("Expected the 'then' keyword but token has different type"); return NULL; } + // Then content struct ast *then_content = parse_compound_list(); + if (then_content == NULL) + { + ast_free(&condition_content); + ast_free(&then_content); + return NULL; + } // Eventual else/elif clause(s) struct ast *else_content = parse_else_clause(); + if (else_content == NULL) + { + ast_free(&condition_content); + ast_free(&then_content); + return NULL; + } token = POP_TOKEN(); if (token->type != TOKEN_FI) { + ast_free(&condition_content); + ast_free(&then_content); + ast_free(&else_content); puts("Expected the 'fi' keyword but token has different type"); - // TODO free previous asts return NULL; } @@ -176,44 +198,60 @@ struct ast *parse_if_rule(void) ast_create_if(condition_content, then_content, else_content); if (result == NULL) { + ast_free(&condition_content); + ast_free(&then_content); + ast_free(&else_content); puts("Internal error: could not create a new AST (AST_IF)"); - // TODO free previous asts return NULL; } return result; } -// TODO comply with header's grammar struct ast *parse_compound_list(void) { struct list *result_list = NULL; // ast* list - struct list *command_elements = NULL; // token* list + struct ast *current_cmd = NULL; struct token *token = PEEK_TOKEN(); - while (token->type != TOKEN_THEN || token->type != TOKEN_ELIF - || token->type != TOKEN_ELSE) - { - // Parse simple command - if (token->type == TOKEN_SEMICOLON || token->type == TOKEN_NEWLINE) - { - // Stage (-> next command) - struct ast *command = ast_create_command(command_elements); - result_list = list_append(result_list, command); - command_elements = NULL; - } - - if (token->type == TOKEN_EOF) - { - puts("Syntax error: Unexpected end of stream"); // TODO pas très - // bien dit - return NULL; - } - - command_elements = list_append(command_elements, token->data); + // Skip newlines + while (token == TOKEN_NEWLINE) token = POP_TOKEN(); + + // and_or + current_cmd = parse_and_or(); + if (current_cmd == NULL) + return NULL; + list_append(result_list, current_cmd); + + // Following commands + token = PEEK_TOKEN(); + while (token->type == TOKEN_SEMICOLON || token->type TOKEN_NEWLINE) + { + POP_TOKEN(); + + // Skip newlines + while (token == TOKEN_NEWLINE) + token = POP_TOKEN(); + + // and_or + current_cmd = parse_and_or(); + if (current_cmd == NULL) + return NULL; + list_append(result_list, current_cmd); + + token = PEEK_TOKEN(); } + // Eventual semicolons + if (token->type == TOKEN_SEMICOLON) + token = POP_TOKEN(); + + // Skip newlines + while (token == TOKEN_NEWLINE) + token = POP_TOKEN(); + + struct ast *result = ast_create_list(result_list); return result; } @@ -222,7 +260,6 @@ struct ast *parse_else_clause(void) { struct token *token = PEEK_TOKEN(); - // TODO handle ELIF // Eventual elif content while (token->type == TOKEN_ELIF) { From b7b6a6a8d5ce3748c07c4e789b954ec9d22813ba Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Sat, 17 Jan 2026 22:45:27 +0000 Subject: [PATCH 093/282] feat(hash_map): hash_map --- src/utils/hash_map/hash_map.c | 173 ++++++++++++++++++++++++++++++++++ src/utils/hash_map/hash_map.h | 34 +++++++ 2 files changed, 207 insertions(+) create mode 100644 src/utils/hash_map/hash_map.c create mode 100644 src/utils/hash_map/hash_map.h diff --git a/src/utils/hash_map/hash_map.c b/src/utils/hash_map/hash_map.c new file mode 100644 index 0000000..dec698c --- /dev/null +++ b/src/utils/hash_map/hash_map.c @@ -0,0 +1,173 @@ +#include "hash_map.h" + +#include +#include +#include +#include +#include +#include + +/* +** Hash the key using FNV-1a 32 bits hash algorithm. +*/ +static size_t hash(const char *key) +{ + if (key == NULL) + return 0; + + uint32_t hash = 2166136261; // FNV offset basis + uint32_t prime = 16777619; // FNV prime + + while (*key != 0) + { + hash ^= *key; + hash *= prime; + key++; + } + + return hash; +} + +struct hash_map *hash_map_init(size_t size) +{ + struct hash_map *p = malloc(sizeof(struct hash_map)); + if (p != NULL) + { + p->data = calloc(size, sizeof(struct pair_list *)); + if (p->data == NULL) + { + free(p); + return NULL; + } + p->size = size; + } + return p; +} + +bool hash_map_insert(struct hash_map *hash_map, const char *key, void *value, + bool *updated) +{ + if (hash_map == NULL || hash_map->size == 0 || key == NULL) + return false; + + size_t h = hash(key); + struct pair_list **l = hash_map->data; + size_t i = h % hash_map->size; + + if (l[i] != NULL) + { + // check if key is in linked list + struct pair_list *iter = l[i]; + while (iter) + { + if (strcmp(iter->key, key) == 0) + { + // update + iter->value = value; + if (updated) + *updated = true; + return true; + } + iter = iter->next; + } + + // if not found => collision + } + struct pair_list *p = malloc(sizeof(struct pair_list)); + if (p == NULL) + return false; + + if (updated) + *updated = false; + + p->key = key; + p->value = value; + p->next = l[i]; + l[i] = p; + l[i] = p; + return true; +} + +void hash_map_free(struct hash_map *hash_map) +{ + struct pair_list *l; + struct pair_list *prev; + + if (hash_map) + { + for (size_t i = 0; i < hash_map->size; i++) + { + l = hash_map->data[i]; + while (l != NULL) + { + prev = l; + l = l->next; + free(prev); + } + } + free(hash_map->data); + free(hash_map); + } +} + +void hash_map_foreach(struct hash_map *hash_map, + void (*fn)(const char *, const void *)) +{ + if (hash_map == NULL) + return; + + struct pair_list *iter; + for (size_t i = 0; i < hash_map->size; i++) + { + iter = hash_map->data[i]; + while (iter != NULL) + { + fn(iter->key, iter->value); + iter = iter->next; + } + } +} + +const void *hash_map_get(const struct hash_map *hash_map, const char *key) +{ + if (hash_map == NULL || hash_map->size == 0) + return NULL; + + size_t h = hash(key); + size_t i = h % hash_map->size; + struct pair_list *l = hash_map->data[i]; + while (l != NULL) + { + if (strcmp(l->key, key) == 0) + return l->value; + l = l->next; + } + return NULL; +} + +bool hash_map_remove(struct hash_map *hash_map, const char *key) +{ + if (hash_map == NULL || hash_map->size == 0) + return false; + + size_t h = hash(key); + size_t i = h % hash_map->size; + struct pair_list *l = hash_map->data[i]; + struct pair_list *p = NULL; + + while (l != NULL) + { + if (strcmp(l->key, key) == 0) + { + if (p != NULL) + p->next = l->next; + else + hash_map->data[i] = l->next; + free(l); + return true; + } + p = l; + l = l->next; + } + return false; +} diff --git a/src/utils/hash_map/hash_map.h b/src/utils/hash_map/hash_map.h new file mode 100644 index 0000000..efdabc2 --- /dev/null +++ b/src/utils/hash_map/hash_map.h @@ -0,0 +1,34 @@ +#ifndef HASH_MAP_H +#define HASH_MAP_H + +#include +#include + +struct pair_list +{ + const char *key; + void *value; + struct pair_list *next; +}; + +struct hash_map +{ + struct pair_list **data; + size_t size; +}; + +struct hash_map *hash_map_init(size_t size); + +bool hash_map_insert(struct hash_map *hash_map, const char *key, void *value, + bool *updated); + +void hash_map_free(struct hash_map *hash_map); + +void hash_map_foreach(struct hash_map *hash_map, + void (*fn)(const char *, const void *)); + +const void *hash_map_get(const struct hash_map *hash_map, const char *key); + +bool hash_map_remove(struct hash_map *hash_map, const char *key); + +#endif /* ! HASH_MAP_H */ From d377b80803b242a9b64c85cb9623ec4a6f84df45 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Sat, 17 Jan 2026 22:46:10 +0000 Subject: [PATCH 094/282] feat(vars): vars --- src/utils/vars/vars.c | 32 ++++++++++++++++++++++++++++++++ src/utils/vars/vars.h | 31 +++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 src/utils/vars/vars.c create mode 100644 src/utils/vars/vars.h diff --git a/src/utils/vars/vars.c b/src/utils/vars/vars.c new file mode 100644 index 0000000..70ab328 --- /dev/null +++ b/src/utils/vars/vars.c @@ -0,0 +1,32 @@ +#define _POSIX_C_SOURCE 200809L +#include "vars.h" + +#include +#include +#include + +#include "../hash_map/hash_map.h" + +#define VARS_INITIAL_SIZE 16 + +struct hash_map *vars_init(void) +{ + return hash_map_init(VARS_INITIAL_SIZE); +} + +char *get_var(const struct hash_map *vars, const char *key) +{ + return (char *)hash_map_get(vars, key); +} + +bool set_var(struct hash_map *vars, const char *key, const char *value) +{ + if (key == NULL || value == NULL) + return false; + return hash_map_insert(vars, key, (void *)value, NULL); +} + +bool set_var_copy(struct hash_map *vars, const char *key, const char *value) +{ + return set_var(vars, strdup(key), strdup(value)); +} diff --git a/src/utils/vars/vars.h b/src/utils/vars/vars.h new file mode 100644 index 0000000..97db704 --- /dev/null +++ b/src/utils/vars/vars.h @@ -0,0 +1,31 @@ +#ifndef VARS_H +#define VARS_H + +#include + +#include "../hash_map/hash_map.h" + +/** + * Initialize a new variables hash map. + */ +struct hash_map *vars_init(void); + +/** + * Get the value of a variable, NULL if not found. + */ +char *get_var(const struct hash_map *vars, const char *key); + +/** + * Set the value of a variable. Key and value ownership are transferred to + * the hash_map and need to be on the heap. Returns true on success, false on + * failure. + */ +bool set_var(struct hash_map *vars, const char *key, const char *value); + +/** + * Same as set_var, but makes copies of key and value so you don't have to worry + * about their memory management. Returns true on success, false on failure. + */ +bool set_var_copy(struct hash_map *vars, const char *key, const char *value); + +#endif /* ! VARS_H */ From a4fcce530cb0153d3eae472826823d195ad491c3 Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 17 Jan 2026 10:43:23 +0100 Subject: [PATCH 095/282] rebased lexer on dev --- src/lexer/lexer.c | 82 +++++++++++++++++++++++++++++++++++------------ src/lexer/lexer.h | 26 +++++++++++---- 2 files changed, 82 insertions(+), 26 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index 68366cf..1df0ec5 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -16,11 +16,10 @@ static bool at_beginning = true; static struct token *last_token; static struct token *current_token; - /* @brief: sets the current_token to [tok]. * this function is called by token_peek(). */ -static void update_current_token(struct token* tok) +static void update_current_token(struct token *tok) { current_token = tok; } @@ -30,7 +29,7 @@ static void update_current_token(struct token* tok) * this function is called by token_pop(). * */ -static void update_last_token(struct token* tok) +static void update_last_token(struct token *tok) { free_token(&last_token); last_token = tok; @@ -51,43 +50,86 @@ static void save_state(char *stream, ssize_t i, struct token *tok) /* @return: true if a special character from the grammar was found, * false otherwise. - * */ static bool is_special_char(char c) { - return c == '\'' || c == '\n' || c == ';' || c == EOF; + if (c == EOF) + return true; + + char special_chars[] = "\n'\"`;#|&\\$(){}<>*"; + return strchr(special_chars, c) != NULL; } /* @brief: if a special character is found at [begin], * [tok->token_type] is set accordingly - * */ static void set_token_spechar(struct token *tok, char *begin, ssize_t size) { if (size != 1) return; - if (begin[0] == EOF) + switch (begin[0]) { + case EOF: tok->type = TOKEN_EOF; - remaining_chars = 0; - } - else if (begin[0] == ';') - { - tok->type = TOKEN_NEWLINE; - } - else if (begin[0] == '\'') - { - tok->type = TOKEN_QUOTE; - } - else if (begin[0] == ';') - { + break; + case ';': tok->type = TOKEN_SEMICOLON; + break; + case '\n': + tok->type = TOKEN_NEWLINE; + break; + case '\'': + tok->type = TOKEN_QUOTE; + break; + case '"': + tok->type = TOKEN_DOUBLE_QUOTE; + break; + case '`': + tok->type = TOKEN_GRAVE; + break; + case '#': + tok->type = TOKEN_COMMENT; + break; + case '|': + tok->type = TOKEN_PIPE; + break; + case '&': + tok->type = TOKEN_AMPERSAND; + break; + case '\\': + tok->type = TOKEN_BACKSLASH; + break; + case '$': + tok->type = TOKEN_DOLLAR; + break; + case '(': + tok->type = TOKEN_LEFT_PAREN; + break; + case ')': + tok->type = TOKEN_RIGHT_PAREN; + break; + case '{': + tok->type = TOKEN_LEFT_BRACKET; + break; + case '}': + tok->type = TOKEN_RIGHT_BRACKET; + break; + case '<': + tok->type = TOKEN_LESS; + break; + case '>': + tok->type = TOKEN_GREATER; + break; + case '*': + tok->type = TOKEN_STAR; + break; + default: + break; } } /* @brief: if a keyword is found at [begin], * [tok->token_type] is set accordingly - * */ static void set_token_keyword(struct token *tok, char *begin, ssize_t size) { diff --git a/src/lexer/lexer.h b/src/lexer/lexer.h index 1b46523..31a329a 100644 --- a/src/lexer/lexer.h +++ b/src/lexer/lexer.h @@ -5,12 +5,29 @@ enum token_type { + // Special characters TOKEN_NULL = 0, TOKEN_EOF, TOKEN_WORD, TOKEN_NEWLINE, TOKEN_QUOTE, + TOKEN_DOUBLE_QUOTE, + TOKEN_GRAVE, TOKEN_SEMICOLON, + TOKEN_COMMENT, + TOKEN_PIPE, + TOKEN_AMPERSAND, + TOKEN_BACKSLASH, + TOKEN_DOLLAR, + TOKEN_LEFT_PAREN, + TOKEN_RIGHT_PAREN, + TOKEN_LEFT_BRACKET, + TOKEN_RIGHT_BRACKET, + TOKEN_LESS, + TOKEN_GREATER, + TOKEN_STAR, + + // Keywords TOKEN_IF, TOKEN_THEN, TOKEN_ELSE, @@ -28,7 +45,6 @@ struct token * @brief: returns the next (newly allocated) token without consuming it. * if end of input is reached, enters in EOF looping node, * returning only the same token of type TOKEN_EOF. - * */ struct token *peek_token(void); @@ -36,6 +52,7 @@ struct token *peek_token(void); * @brief: returns the next (newly allocated) token and consumes it. * if end of input is reached, returns a token of type TOKEN_EOF. * It also frees the last token created if there was one. + * * @warning: if the last returned token was a token EOF, it frees it * and returns NULL. This means that after peeking a token EOF * in the parser, there must be EXACTLY ONE call to pop_token(). @@ -43,10 +60,9 @@ struct token *peek_token(void); */ struct token *pop_token(void); -/* - * @warning: NOT IMPLEMENTED. +/* @note: maybe usefull for subshells. * - * @note: maybe usefull for subshells. + * @warning: NOT IMPLEMENTED. */ struct token *get_token_str(void); @@ -56,12 +72,10 @@ struct token *get_token_str(void); * The data contains [size] char, starting from [begin]. * * @return: NULL on error, a token otherwise. - * */ struct token *new_token(char *begin, ssize_t size); /* @brief: frees the token given in argument - * */ void free_token(struct token **tok); From f475f4d4ec4518f0b43c65f568f70ea23c5245b4 Mon Sep 17 00:00:00 2001 From: Guillem George Date: Fri, 16 Jan 2026 20:05:49 +0100 Subject: [PATCH 096/282] fix: updated main to a parse-execute loop and changed error codes from an enum to macros --- src/main.c | 58 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/src/main.c b/src/main.c index b664786..399b874 100644 --- a/src/main.c +++ b/src/main.c @@ -1,4 +1,5 @@ -// All the includes +// === Includes +#include #include #include @@ -7,22 +8,24 @@ #include "parser/parser.h" #include "utils/args/args.h" -enum return_codes -{ - SUCCESS = 0, - ERR_IO_BACKEND = 2, - ERR_MALLOC = 3 -}; +// === Error codes + +#define SUCCESS 0 +#define ERR_INPUT_PROCESSING 2 +#define ERR_MALLOC 3 +#define ERR_GENERIC 4 + +// === Functions int main(int argc, char **argv) { // Create the options struct (with argument handler) struct args_options options; - int r = args_handler(argc, argv, &options); - if (r != 0) + int return_code = args_handler(argc, argv, &options); + if (return_code != 0) { print_usage(stderr, argv[0]); - return ERR_IO_BACKEND; + return ERR_INPUT_PROCESSING; } // args_print(&options); @@ -36,33 +39,42 @@ int main(int argc, char **argv) } // Convert args_options to iob_context - r = iob_config_from_args(&options, io_context); - if (r != 0) + return_code = iob_config_from_args(&options, io_context); + if (return_code != 0) { fprintf(stderr, "Error: Failed to configure IO Backend from arguments\n"); free(io_context); - return ERR_IO_BACKEND; + return ERR_INPUT_PROCESSING; } // Init IO Backend (with the context struct) - r = iob_init(io_context); - if (r != 0) + return_code = iob_init(io_context); + if (return_code != 0) { fprintf(stderr, - "Error: IO Backend initialization failed with code %d\n", r); + "Error: IO Backend initialization failed with code %d\n", + return_code); free(io_context); - return ERR_IO_BACKEND; + return ERR_INPUT_PROCESSING; } free(io_context); - // Call the parser to get the AST - struct ast *command_ast = get_ast(); // We'll pass the options later + // Retrieve and build first AST + struct ast *command_ast = get_ast(); - // Call the executor with the AST - r = execution(command_ast); + // Main parse-execute loop + while (command_ast != NULL && command_ast->type != AST_END) + { + // Execute AST + return_code = execution(command_ast); - // Return the execution return code (last command executed) - return r; + // Retrieve and build next AST + command_ast = get_ast(); + } + if (command_ast == NULL) + return ERR_INPUT_PROCESSING; + + return return_code; } From 2c1f210d3711170cff4ae7bfc5f55b648018e222 Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 17 Jan 2026 11:06:50 +0100 Subject: [PATCH 097/282] fix(lexer): some memory leaks + removed useless [at_begining] variable --- src/lexer/lexer.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index 1df0ec5..cbb2b1b 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -12,7 +12,6 @@ static char *end_last_token; static ssize_t remaining_chars; -static bool at_beginning = true; static struct token *last_token; static struct token *current_token; @@ -35,17 +34,19 @@ static void update_last_token(struct token *tok) last_token = tok; } -/* @brief: saves state for the next call to the the lexer. - * this function is called by token_pop(). - * +/* @brief: updates the current position in the stream. + * [stream] += [i] + * Also saves the last token sent (so we can free it afterwards). + * Also sets the current token to NULL. + * This function is called by token_pop(). */ static void save_state(char *stream, ssize_t i, struct token *tok) { remaining_chars -= i; end_last_token = stream + i; - at_beginning = false; update_last_token(tok); + update_current_token(NULL); } /* @return: true if a special character from the grammar was found, @@ -204,10 +205,9 @@ char *stream_init(void) { char *stream; - if (at_beginning) + if (last_token == NULL) // at the begining { remaining_chars = stream_read(&stream); - // at_beginning = true; } else { @@ -223,8 +223,8 @@ char *stream_init(void) struct token *peek_token(void) { - // EOF looping mode - if (current_token != NULL && current_token->type == TOKEN_EOF) + // we already created the upcoming token during the previous call to peek() + if (current_token != NULL) { return current_token; } From acbcb860a45ba9cb73d4a1452724b18dda471634 Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 17 Jan 2026 11:38:23 +0100 Subject: [PATCH 098/282] fix(lexer): memory leaks --- src/lexer/lexer.c | 139 +++++++++++++++++++++++++--------------------- 1 file changed, 75 insertions(+), 64 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index cbb2b1b..28f098c 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -36,16 +36,16 @@ static void update_last_token(struct token *tok) /* @brief: updates the current position in the stream. * [stream] += [i] - * Also saves the last token sent (so we can free it afterwards). - * Also sets the current token to NULL. + * Also frees the last sent token, and sets it to current_token. + * Current token is then set to NULL. * This function is called by token_pop(). */ -static void save_state(char *stream, ssize_t i, struct token *tok) +static void save_state(char *stream, ssize_t i) { remaining_chars -= i; end_last_token = stream + i; - update_last_token(tok); + update_last_token(current_token); update_current_token(NULL); } @@ -70,62 +70,62 @@ static void set_token_spechar(struct token *tok, char *begin, ssize_t size) return; switch (begin[0]) { - case EOF: - tok->type = TOKEN_EOF; - break; - case ';': - tok->type = TOKEN_SEMICOLON; - break; - case '\n': - tok->type = TOKEN_NEWLINE; - break; - case '\'': - tok->type = TOKEN_QUOTE; - break; - case '"': - tok->type = TOKEN_DOUBLE_QUOTE; - break; - case '`': - tok->type = TOKEN_GRAVE; - break; - case '#': - tok->type = TOKEN_COMMENT; - break; - case '|': - tok->type = TOKEN_PIPE; - break; - case '&': - tok->type = TOKEN_AMPERSAND; - break; - case '\\': - tok->type = TOKEN_BACKSLASH; - break; - case '$': - tok->type = TOKEN_DOLLAR; - break; - case '(': - tok->type = TOKEN_LEFT_PAREN; - break; - case ')': - tok->type = TOKEN_RIGHT_PAREN; - break; - case '{': - tok->type = TOKEN_LEFT_BRACKET; - break; - case '}': - tok->type = TOKEN_RIGHT_BRACKET; - break; - case '<': - tok->type = TOKEN_LESS; - break; - case '>': - tok->type = TOKEN_GREATER; - break; - case '*': - tok->type = TOKEN_STAR; - break; - default: - break; + case EOF: + tok->type = TOKEN_EOF; + break; + case ';': + tok->type = TOKEN_SEMICOLON; + break; + case '\n': + tok->type = TOKEN_NEWLINE; + break; + case '\'': + tok->type = TOKEN_QUOTE; + break; + case '"': + tok->type = TOKEN_DOUBLE_QUOTE; + break; + case '`': + tok->type = TOKEN_GRAVE; + break; + case '#': + tok->type = TOKEN_COMMENT; + break; + case '|': + tok->type = TOKEN_PIPE; + break; + case '&': + tok->type = TOKEN_AMPERSAND; + break; + case '\\': + tok->type = TOKEN_BACKSLASH; + break; + case '$': + tok->type = TOKEN_DOLLAR; + break; + case '(': + tok->type = TOKEN_LEFT_PAREN; + break; + case ')': + tok->type = TOKEN_RIGHT_PAREN; + break; + case '{': + tok->type = TOKEN_LEFT_BRACKET; + break; + case '}': + tok->type = TOKEN_RIGHT_BRACKET; + break; + case '<': + tok->type = TOKEN_LESS; + break; + case '>': + tok->type = TOKEN_GREATER; + break; + case '*': + tok->type = TOKEN_STAR; + break; + default: + break; } } @@ -157,6 +157,10 @@ static void set_token_keyword(struct token *tok, char *begin, ssize_t size) tok->type = TOKEN_ELIF; } + // no keywords found. + if (tok->type == TOKEN_NULL) + return; + tok->data = calloc(size + 1, sizeof(char)); if (tok->data == NULL) return; @@ -255,9 +259,11 @@ struct token *peek_token(void) struct token *pop_token(void) { - if (last_token != NULL && last_token->type == TOKEN_EOF) + if (current_token != NULL && current_token->type == TOKEN_EOF) { + // we reached end of input, frees all the token still allocated. free_token(&last_token); + free_token(¤t_token); return NULL; } char *stream = stream_init(); @@ -279,8 +285,13 @@ struct token *pop_token(void) i++; } - struct token *tok = new_token(stream, i); - save_state(stream, i, tok); + // just in case peek() was not called before poping. + // (this should never happen) + if (current_token == NULL) + { + current_token = new_token(stream, i); + } + save_state(stream, i); - return tok; + return last_token; } From e8d10523ae0667e7a407c86540e6c34ed6e466d7 Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 17 Jan 2026 17:53:37 +0100 Subject: [PATCH 099/282] feat(testsuite): autotools working, testsuite and debug implemented and working --- README.md | 26 ++++---- src/Makefile.am | 44 ++++++++----- src/main.c | 2 +- tests/unit/lexer/lexer_tests.c | 116 +++++++++++++++++++++++---------- 4 files changed, 125 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index be08206..f9130ce 100644 --- a/README.md +++ b/README.md @@ -7,22 +7,26 @@ TODO ### Build -run this command: - `autoreconf --force --verbose --install` - -### Test -run this command: - `./configure CFLAGS='-std=c99 -Werror -Wall -Wextra -Wvla'` -then: +run theses commands: + `autoreconf --force --verbose --install && ./configure` `make` + `./src/42sh --help` + +### Testing -#### asan run this command: - `./configure CFLAGS='-std=c99 -Werror -Wall -Wextra -Wvla -g -fsanitize=address'` - -then: `make check` +#### debug (asan) + +run this command: + `./src/debug` + +#### testsuite + +run this command: + `./src/testsuite` + ## Authors - Matteo Flebus diff --git a/src/Makefile.am b/src/Makefile.am index 411f4cb..c5c19e7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,4 +1,3 @@ -# define the subdirectories SUBDIRS = \ parser \ lexer \ @@ -9,6 +8,8 @@ SUBDIRS = \ bin_PROGRAMS = 42sh +42sh_CFLAGS = -std=c99 -Werror -Wall -Wextra -Wvla + 42sh_SOURCES = main.c 42sh_CPPFLAGS = -I%D% @@ -21,18 +22,31 @@ bin_PROGRAMS = 42sh execution/libexecution.a \ utils/libutils.a +# ================ TESTS ================ -################################################# Test -# -#42sh_asan_SOURCES = main.c -# -#42sh_asan_CPPFLAGS = -I%D% -# -#42sh_asan_LDADD = \ -# ast/lib_asan_ast.a \ -# parser/lib_asan_parser.a \ -# lexer/lib_asan_lexer.a \ -# io_backend/lib_asan_io_backend.a \ -# expansion/lib_asan_expansion.a \ -# execution/lib_asan_execution.a \ -# utils/lib_asan_utils.a +# ------------- Unit tests -------------- + +check_PROGRAMS = testsuite + +testsuite_CFLAGS = $(42sh_CFLAGS) +testsuite_CFLAGS += $(CRITERION_CFLAGS) + +testsuite_SOURCES = ../tests/unit/lexer/lexer_tests.c \ + ../tests/unit/utils/utils_tests.c + +testsuite_CPPFLAGS = $(42sh_CPPFLAGS) + +testsuite_LDADD = $(42sh_LDADD) $(CRITERION_LIBS) + +# ------------- asan debug -------------- + +check_PROGRAMS += debug + +debug_CFLAGS = $(42sh_CFLAGS) +debug_CFLAGS += -fsanitize=address -g + +debug_SOURCES = $(42sh_SOURCES) + +debug_CPPFLAGS = $(42sh_CPPFLAGS) + +debug_LDADD = $(42sh_LDADD) diff --git a/src/main.c b/src/main.c index 399b874..423a141 100644 --- a/src/main.c +++ b/src/main.c @@ -1,5 +1,5 @@ // === Includes -#include +//#include #include #include diff --git a/tests/unit/lexer/lexer_tests.c b/tests/unit/lexer/lexer_tests.c index d9046d4..910a81a 100644 --- a/tests/unit/lexer/lexer_tests.c +++ b/tests/unit/lexer/lexer_tests.c @@ -6,51 +6,95 @@ #include #include "lexer/lexer.h" +#include "io_backend/io_backend.h" -TestSuite(token_creation); +TestSuite(peek_token); -Test(token_creation, basic) +Test(peek_token, basic_empty) { - char input[] = "Hello World"; + // simulates input + char command[] = ""; + struct iob_context context = + { + IOB_MODE_CMD, + command + }; + iob_init(&context); - char actual[] = new_token(input, 5); - char expected[] = "Hello"; - cr_expect(eq(str, actual, expected)); - free(actual); + // test + struct token *tok = peek_token(); + + // expected + enum token_type type_expected = TOKEN_EOF; + char *string_expected = NULL; + + cr_assert(eq(str, string_expected, tok->data)); + cr_assert(type_expected == tok->type); + + free_token(&tok); } -Test(token_creation, nul) +Test(peek_token, basic_word) { - char input[] = NULL; + // simulates input + char command[] = "hello"; + struct iob_context context = + { + IOB_MODE_CMD, + command + }; + iob_init(&context); - char actual[] = new_token(input, 5); - cr_expect(actual == NULL); + // test + struct token *tok = peek_token(); + + // expected + enum token_type type_expected = TOKEN_WORD; + char string_expected[] = "hello"; + + cr_assert(eq(str, string_expected, tok->data)); + cr_assert(type_expected == tok->type); + free_token(&tok); } - -Test(token_creation, too_large) +Test(peek_token, basic_words) { - char input[] = "Hel"; + // simulates input + char command[] = "echo hello there"; + struct iob_context context = + { + IOB_MODE_CMD, + command + }; + iob_init(&context); - char actual[] = new_token(input, 5); - cr_expect(actual == NULL); -} - -Test(token_creation, empty) -{ - char input[] = ""; - - char actual[] = new_token(input, 5); - cr_expect(actual == NULL); -} - -Test(token_creation, basic_long) -{ - char input[] = "Hello World! This project is a mini shell, I love BIG G."; - - char actual[] = new_token(input, 42); - char expected[] = calloc(42 + 1, sizeof(char)); - strncpy(input, expected, 42); - cr_expect(eq(str, actual, expected)); - free(actual); - free(expected); + // ======= echo + + struct token *tok = peek_token(); + + enum token_type type_expected = TOKEN_WORD; + char string_expected[] = "echo"; + + cr_assert(eq(str, string_expected, tok->data)); + cr_assert(type_expected == tok->type); + free_token(&tok); + + // ======= hello + + tok = peek_token(); + + char string_expected2[] = "hello"; + + cr_assert(eq(str, string_expected2, tok->data)); + cr_assert(type_expected == tok->type); + free_token(&tok); + + // ======= there + + tok = peek_token(); + + char string_expected3[] = "there"; + + cr_assert(eq(str, string_expected3, tok->data)); + cr_assert(type_expected == tok->type); + free_token(&tok); } From 446d1ebd7acd9ebb250347ec728c2c1c4b4e18e4 Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 17 Jan 2026 18:00:34 +0100 Subject: [PATCH 100/282] fix(main): remove unecessary unknown header --- src/main.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main.c b/src/main.c index 423a141..8a81efb 100644 --- a/src/main.c +++ b/src/main.c @@ -1,5 +1,4 @@ // === Includes -//#include #include #include From cf7825aaf017e5a274f4f2dfe80962ff4c088261 Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 17 Jan 2026 18:02:00 +0100 Subject: [PATCH 101/282] feat(gitignore): debug and testsuite --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 64c4a76..9cdb480 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ # Project specific 42sh +testsuite +debug # Prerequisites *.d From e9b6d39760bc96a9c79d208f32d0460d82d5cdc8 Mon Sep 17 00:00:00 2001 From: Jean Herail Date: Sat, 17 Jan 2026 17:33:22 +0100 Subject: [PATCH 102/282] Implemented some ast handlings... ast lists, and_or, redirection and builtins --- src/execution/execution.c | 231 +++++++++++++++++++++++++++++++++++-- src/utils/Makefile.am | 2 + src/utils/ast/ast.c | 4 + src/utils/ast/ast.h | 2 + src/utils/ast/ast_and_or.c | 35 ++++++ src/utils/ast/ast_and_or.h | 23 ++++ src/utils/ast/ast_base.h | 6 +- src/utils/ast/ast_list.c | 5 +- src/utils/ast/ast_redir.c | 36 ++++++ src/utils/ast/ast_redir.h | 29 +++++ 10 files changed, 359 insertions(+), 14 deletions(-) create mode 100644 src/utils/ast/ast_and_or.c create mode 100644 src/utils/ast/ast_and_or.h create mode 100644 src/utils/ast/ast_redir.c create mode 100644 src/utils/ast/ast_redir.h diff --git a/src/execution/execution.c b/src/execution/execution.c index 0066681..33389cb 100644 --- a/src/execution/execution.c +++ b/src/execution/execution.c @@ -5,9 +5,14 @@ #include #include #include +#include +#include +#include #include "../utils/ast/ast.h" +// --- Helpers --- + /** * @brief converts a linked list of command arguments to an argv array. Don't * forget to free the result @@ -44,6 +49,94 @@ static char **list_to_argv(struct list *command_list) return argv; } +// --- Builtins --- + +static int builtin_echo(char **argv) +{ + bool newline = true; + int i = 1; + + if (argv[1] && strcmp(argv[1], "-n") == 0) + { + newline = false; + i++; + } + + for (; argv[i]; i++) + { + printf("%s", argv[i]); + if (argv[i + 1]) + printf(" "); + } + if (newline) + printf("\n"); + + fflush(stdout); + return 0; +} + +static int builtin_true(char **argv) +{ + (void)argv; + return 0; +} + +static int builtin_false(char **argv) +{ + (void)argv; + return 1; +} + +static int builtin_exit(char **argv) +{ + int exit_val = 0; + if (argv[1]) + exit_val = atoi(argv[1]); + exit(exit_val); + return exit_val; +} + +static int builtin_cd(char **argv) +{ + const char *path = argv[1]; + if (!path) + { + path = getenv("HOME"); + if (!path) + { + fprintf(stderr, "cd: HOME not set\n"); + return 1; + } + } + if (chdir(path) != 0) + { + perror("cd"); + return 1; + } + return 0; +} + +static int try_builtin(char **argv) +{ + if (!argv || !argv[0]) + return 0; + + if (strcmp(argv[0], "echo") == 0) + return builtin_echo(argv); + if (strcmp(argv[0], "true") == 0) + return builtin_true(argv); + if (strcmp(argv[0], "false") == 0) + return builtin_false(argv); + if (strcmp(argv[0], "exit") == 0) + return builtin_exit(argv); + if (strcmp(argv[0], "cd") == 0) + return builtin_cd(argv); + + return -1; +} + +// --- Execution Core --- + /** * @brief Executes a command represented by an ast_command structure * @@ -54,7 +147,7 @@ static int exec_command(struct ast_command *command) { if (!command || !(command->command)) { - return -1; + return 1; } char **argv = list_to_argv(command->command); @@ -62,23 +155,30 @@ static int exec_command(struct ast_command *command) if (!argv || !(argv[0])) { free(argv); - return -1; + return 0; + } + + int builtin_ret = try_builtin(argv); + if (builtin_ret != -1) + { + free(argv); + return builtin_ret; } pid_t pid = fork(); - if (pid < 0) // Fork failed + if (pid < 0) { perror("fork"); free(argv); - return -1; + return 1; } - if (pid == 0) // If child process + if (pid == 0) { execvp(argv[0], argv); - perror("execvp"); // If execvp returns, there was an error - exit(EXIT_FAILURE); // Exit child process + perror("execvp"); + _exit(127); } int status = 0; @@ -90,7 +190,7 @@ static int exec_command(struct ast_command *command) return WEXITSTATUS(status); } - return -1; // Should not happen + return 1; } /** @@ -113,7 +213,7 @@ int execution(struct ast *ast) } case AST_CMD: { struct ast_command *command = ast_get_command(ast); - return exec_command(command); // It's recursive + return exec_command(command); } case AST_IF: { struct ast_if *if_node = ast_get_if(ast); @@ -127,8 +227,117 @@ int execution(struct ast *ast) return execution(if_node->else_clause); } } + case AST_LIST: { + struct ast_list *list_node = ast_get_list(ast); + struct list *cur = list_node->children; + int ret = 0; + while (cur) + { + struct ast *child = (struct ast *)cur->data; + ret = execution(child); + cur = cur->next; + } + return ret; + } + case AST_AND_OR: { + struct ast_and_or *ao_node = ast_get_and_or(ast); + int left_ret = execution(ao_node->left); + + if (ao_node->type == AST_AND_OR_TYPE_AND) + { + if (left_ret == 0) + return execution(ao_node->right); + return left_ret; + } + else // OR + { + if (left_ret != 0) + return execution(ao_node->right); + return left_ret; + } + } + case AST_REDIR: { + struct ast_redir *redir = ast_get_redir(ast); + + int fd_target = redir->io_number; + if (fd_target == -1) + { + if (redir->type == AST_REDIR_TYPE_LESS || redir->type == AST_REDIR_TYPE_DLESS || redir->type == AST_REDIR_TYPE_LESSAND) + fd_target = 0; + else + fd_target = 1; + } + + int saved_fd = dup(fd_target); + + int new_fd = -1; + int flags = 0; + int mode = 0644; + + if (redir->type == AST_REDIR_TYPE_GREAT || redir->type == AST_REDIR_TYPE_CLOBBER) + { + flags = O_WRONLY | O_CREAT | O_TRUNC; + new_fd = open(redir->filename, flags, mode); + } + else if (redir->type == AST_REDIR_TYPE_DGREAT) + { + flags = O_WRONLY | O_CREAT | O_APPEND; + new_fd = open(redir->filename, flags, mode); + } + else if (redir->type == AST_REDIR_TYPE_LESS) + { + flags = O_RDONLY; + new_fd = open(redir->filename, flags); + } + else if (redir->type == AST_REDIR_TYPE_GREATAND || redir->type == AST_REDIR_TYPE_LESSAND) + { + // Simple fd duplication + new_fd = atoi(redir->filename); + // Verify new_fd is valid? dup2 will check. + if (dup2(new_fd, fd_target) == -1) + { + perror("dup2"); + if (saved_fd != -1) close(saved_fd); + return 1; + } + new_fd = -2; // Mark as "already duped" + } + + if (new_fd == -1) + { + perror("open"); + if (saved_fd != -1) close(saved_fd); + return 1; + } + + if (new_fd != -2) + { + if (dup2(new_fd, fd_target) == -1) + { + perror("dup2"); + close(new_fd); + if (saved_fd != -1) close(saved_fd); + return 1; + } + close(new_fd); + } + + int ret = execution(redir->child); + + if (saved_fd != -1) + { + dup2(saved_fd, fd_target); + close(saved_fd); + } + else + { + close(fd_target); + } + + return ret; + } default: { - return -1; // Should not happen + return 127; } } -} +} \ No newline at end of file diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am index d1aa675..6cd107d 100644 --- a/src/utils/Makefile.am +++ b/src/utils/Makefile.am @@ -6,6 +6,8 @@ libutils_a_SOURCES = \ ast/ast_if.c \ ast/ast_command.c \ ast/ast_list.c \ + ast/ast_and_or.c \ + ast/ast_redir.c \ ast/ast_void.c \ ast/ast_end.c \ lists/lists.c \ diff --git a/src/utils/ast/ast.c b/src/utils/ast/ast.c index 2d0ce84..c4bece9 100644 --- a/src/utils/ast/ast.c +++ b/src/utils/ast/ast.c @@ -13,6 +13,10 @@ void ast_free(struct ast **node) ast_free_command(ast_get_command(*node)); else if (ast_is_list(*node)) ast_free_list(ast_get_list(*node)); + else if (ast_is_and_or(*node)) + ast_free_and_or(ast_get_and_or(*node)); + else if (ast_is_redir(*node)) + ast_free_redir(ast_get_redir(*node)); free(*node); *node = NULL; diff --git a/src/utils/ast/ast.h b/src/utils/ast/ast.h index 3984667..1062782 100644 --- a/src/utils/ast/ast.h +++ b/src/utils/ast/ast.h @@ -4,6 +4,8 @@ #include "utils/ast/ast_base.h" #include "utils/ast/ast_command.h" #include "utils/ast/ast_if.h" +#include "utils/ast/ast_and_or.h" +#include "utils/ast/ast_redir.h" #include "utils/ast/ast_list.h" #include "utils/ast/ast_end.h" #include "utils/ast/ast_void.h" diff --git a/src/utils/ast/ast_and_or.c b/src/utils/ast/ast_and_or.c new file mode 100644 index 0000000..d07eaf5 --- /dev/null +++ b/src/utils/ast/ast_and_or.c @@ -0,0 +1,35 @@ +#include "utils/ast/ast_and_or.h" +#include + +bool ast_is_and_or(struct ast *node) +{ + return node != NULL && node->type == AST_AND_OR; +} + +struct ast_and_or *ast_get_and_or(struct ast *node) +{ + if (ast_is_and_or(node)) + return (struct ast_and_or *)node->data; + return NULL; +} + +struct ast *ast_create_and_or(struct ast *left, struct ast *right, enum ast_and_or_type type) +{ + struct ast_and_or *and_or = malloc(sizeof(struct ast_and_or)); + if (!and_or) + return NULL; + and_or->left = left; + and_or->right = right; + and_or->type = type; + + return ast_create(AST_AND_OR, and_or); +} + +void ast_free_and_or(struct ast_and_or *and_or) +{ + if (!and_or) + return; + ast_free(&and_or->left); + ast_free(&and_or->right); + free(and_or); +} diff --git a/src/utils/ast/ast_and_or.h b/src/utils/ast/ast_and_or.h new file mode 100644 index 0000000..57a9668 --- /dev/null +++ b/src/utils/ast/ast_and_or.h @@ -0,0 +1,23 @@ +#ifndef AST_AND_OR_H +#define AST_AND_OR_H + +#include +#include "utils/ast/ast_base.h" + +enum ast_and_or_type { + AST_AND_OR_TYPE_AND, + AST_AND_OR_TYPE_OR +}; + +struct ast_and_or { + struct ast *left; + struct ast *right; + enum ast_and_or_type type; +}; + +bool ast_is_and_or(struct ast *node); +struct ast_and_or *ast_get_and_or(struct ast *node); +struct ast *ast_create_and_or(struct ast *left, struct ast *right, enum ast_and_or_type type); +void ast_free_and_or(struct ast_and_or *and_or); + +#endif /* ! AST_AND_OR_H */ diff --git a/src/utils/ast/ast_base.h b/src/utils/ast/ast_base.h index 432eb2c..e09b25d 100644 --- a/src/utils/ast/ast_base.h +++ b/src/utils/ast/ast_base.h @@ -8,6 +8,8 @@ enum ast_type AST_END, AST_LIST, AST_IF, + AST_AND_OR, + AST_REDIR, AST_VOID, AST_CMD }; @@ -20,7 +22,9 @@ struct ast * Data associated with this AST node. It can be one of the following: * - NULL (AST_END) * - struct ast_if* (AST_IF) - * - struct ast_cmd* (AST_CMD) + * - struct ast_command* (AST_CMD) + * - struct ast_and_or* (AST_AND_OR) + * - struct ast_redir* (AST_REDIR) */ void *data; }; diff --git a/src/utils/ast/ast_list.c b/src/utils/ast/ast_list.c index 95e7047..4fc87f5 100644 --- a/src/utils/ast/ast_list.c +++ b/src/utils/ast/ast_list.c @@ -29,7 +29,7 @@ void ast_free_list(struct ast_list *ast_list) if (ast_list == NULL) return; - list_deep_destroy(ast_list->children); + ast_list_deep_destroy(ast_list->children); free(ast_list); } @@ -41,7 +41,8 @@ void ast_list_deep_destroy(struct list *l) { next_elt = elt->next; - ast_free(elt->data); + struct ast *node = (struct ast *)elt->data; + ast_free(&node); free(elt); elt = next_elt; } diff --git a/src/utils/ast/ast_redir.c b/src/utils/ast/ast_redir.c new file mode 100644 index 0000000..c5eec63 --- /dev/null +++ b/src/utils/ast/ast_redir.c @@ -0,0 +1,36 @@ +#include "utils/ast/ast_redir.h" +#include + +bool ast_is_redir(struct ast *node) +{ + return node != NULL && node->type == AST_REDIR; +} + +struct ast_redir *ast_get_redir(struct ast *node) +{ + if (ast_is_redir(node)) + return (struct ast_redir *)node->data; + return NULL; +} + +struct ast *ast_create_redir(struct ast *child, char *filename, int io_number, enum ast_redir_type type) +{ + struct ast_redir *redir = malloc(sizeof(struct ast_redir)); + if (!redir) + return NULL; + redir->child = child; + redir->filename = filename; // Takes ownership? Usually yes in simple ASTs, or dup. Let's assume pointer copy for now, but user must manage memory. + redir->io_number = io_number; + redir->type = type; + + return ast_create(AST_REDIR, redir); +} + +void ast_free_redir(struct ast_redir *redir) +{ + if (!redir) + return; + ast_free(&redir->child); + free(redir->filename); + free(redir); +} diff --git a/src/utils/ast/ast_redir.h b/src/utils/ast/ast_redir.h new file mode 100644 index 0000000..5f5ffb5 --- /dev/null +++ b/src/utils/ast/ast_redir.h @@ -0,0 +1,29 @@ +#ifndef AST_REDIR_H +#define AST_REDIR_H + +#include +#include "utils/ast/ast_base.h" + +enum ast_redir_type { + AST_REDIR_TYPE_LESS, // < + AST_REDIR_TYPE_GREAT, // > + AST_REDIR_TYPE_DLESS, // << + AST_REDIR_TYPE_DGREAT, // >> + AST_REDIR_TYPE_LESSAND, // <& + AST_REDIR_TYPE_GREATAND, // >& + AST_REDIR_TYPE_CLOBBER // >| +}; + +struct ast_redir { + struct ast *child; + char *filename; + int io_number; // The FD being redirected (default -1 if not specified, implies 0 or 1 based on type) + enum ast_redir_type type; +}; + +bool ast_is_redir(struct ast *node); +struct ast_redir *ast_get_redir(struct ast *node); +struct ast *ast_create_redir(struct ast *child, char *filename, int io_number, enum ast_redir_type type); +void ast_free_redir(struct ast_redir *redir); + +#endif /* ! AST_REDIR_H */ From c2998825860180e900a75019e9feefb968824f0c Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 17 Jan 2026 19:11:55 +0100 Subject: [PATCH 103/282] fix: now compiles and works for simple commands + clang format --- README.md | 26 ++++---- src/Makefile.am | 8 +-- src/execution/execution.c | 49 ++++++++------- src/io_backend/io_backend.h | 1 + src/lexer/lexer.c | 112 ++++++++++++++++----------------- src/main.c | 5 ++ src/parser/parser.c | 2 +- src/parser/parsing_utils.c | 10 ++- src/parser/parsing_utils.h | 8 +-- src/utils/ast/ast.h | 8 +-- src/utils/ast/ast_and_or.c | 4 +- src/utils/ast/ast_and_or.h | 10 ++- src/utils/ast/ast_command.c | 2 +- src/utils/ast/ast_command.h | 2 +- src/utils/ast/ast_end.c | 2 +- src/utils/ast/ast_end.h | 2 +- src/utils/ast/ast_if.c | 2 +- src/utils/ast/ast_list.c | 6 +- src/utils/ast/ast_redir.c | 8 ++- src/utils/ast/ast_redir.h | 27 ++++---- src/utils/ast/ast_void.c | 2 +- src/utils/ast/ast_void.h | 2 +- tests/unit/lexer/lexer_tests.c | 20 ++---- 23 files changed, 168 insertions(+), 150 deletions(-) diff --git a/README.md b/README.md index f9130ce..be08206 100644 --- a/README.md +++ b/README.md @@ -7,26 +7,22 @@ TODO ### Build -run theses commands: - `autoreconf --force --verbose --install && ./configure` +run this command: + `autoreconf --force --verbose --install` + +### Test +run this command: + `./configure CFLAGS='-std=c99 -Werror -Wall -Wextra -Wvla'` +then: `make` - `./src/42sh --help` - -### Testing +#### asan run this command: + `./configure CFLAGS='-std=c99 -Werror -Wall -Wextra -Wvla -g -fsanitize=address'` + +then: `make check` -#### debug (asan) - -run this command: - `./src/debug` - -#### testsuite - -run this command: - `./src/testsuite` - ## Authors - Matteo Flebus diff --git a/src/Makefile.am b/src/Makefile.am index c5c19e7..bd5d691 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -28,8 +28,8 @@ bin_PROGRAMS = 42sh check_PROGRAMS = testsuite -testsuite_CFLAGS = $(42sh_CFLAGS) -testsuite_CFLAGS += $(CRITERION_CFLAGS) +#testsuite_CFLAGS = $(42sh_CFLAGS) +#testsuite_CFLAGS += $(CRITERION_CFLAGS) testsuite_SOURCES = ../tests/unit/lexer/lexer_tests.c \ ../tests/unit/utils/utils_tests.c @@ -42,8 +42,8 @@ testsuite_LDADD = $(42sh_LDADD) $(CRITERION_LIBS) check_PROGRAMS += debug -debug_CFLAGS = $(42sh_CFLAGS) -debug_CFLAGS += -fsanitize=address -g +#debug_CFLAGS = $(42sh_CFLAGS) +#debug_CFLAGS += -fsanitize=address -g debug_SOURCES = $(42sh_SOURCES) diff --git a/src/execution/execution.c b/src/execution/execution.c index 33389cb..a882397 100644 --- a/src/execution/execution.c +++ b/src/execution/execution.c @@ -1,13 +1,13 @@ #include "execution.h" +#include +#include #include #include +#include #include #include #include -#include -#include -#include #include "../utils/ast/ast.h" @@ -70,7 +70,7 @@ static int builtin_echo(char **argv) } if (newline) printf("\n"); - + fflush(stdout); return 0; } @@ -120,7 +120,7 @@ static int try_builtin(char **argv) { if (!argv || !argv[0]) return 0; - + if (strcmp(argv[0], "echo") == 0) return builtin_echo(argv); if (strcmp(argv[0], "true") == 0) @@ -242,7 +242,7 @@ int execution(struct ast *ast) case AST_AND_OR: { struct ast_and_or *ao_node = ast_get_and_or(ast); int left_ret = execution(ao_node->left); - + if (ao_node->type == AST_AND_OR_TYPE_AND) { if (left_ret == 0) @@ -258,38 +258,42 @@ int execution(struct ast *ast) } case AST_REDIR: { struct ast_redir *redir = ast_get_redir(ast); - + int fd_target = redir->io_number; if (fd_target == -1) { - if (redir->type == AST_REDIR_TYPE_LESS || redir->type == AST_REDIR_TYPE_DLESS || redir->type == AST_REDIR_TYPE_LESSAND) + if (redir->type == AST_REDIR_TYPE_LESS + || redir->type == AST_REDIR_TYPE_DLESS + || redir->type == AST_REDIR_TYPE_LESSAND) fd_target = 0; else fd_target = 1; } - + int saved_fd = dup(fd_target); - + int new_fd = -1; int flags = 0; int mode = 0644; - - if (redir->type == AST_REDIR_TYPE_GREAT || redir->type == AST_REDIR_TYPE_CLOBBER) + + if (redir->type == AST_REDIR_TYPE_GREAT + || redir->type == AST_REDIR_TYPE_CLOBBER) { flags = O_WRONLY | O_CREAT | O_TRUNC; new_fd = open(redir->filename, flags, mode); } else if (redir->type == AST_REDIR_TYPE_DGREAT) { - flags = O_WRONLY | O_CREAT | O_APPEND; - new_fd = open(redir->filename, flags, mode); + flags = O_WRONLY | O_CREAT | O_APPEND; + new_fd = open(redir->filename, flags, mode); } else if (redir->type == AST_REDIR_TYPE_LESS) { - flags = O_RDONLY; - new_fd = open(redir->filename, flags); + flags = O_RDONLY; + new_fd = open(redir->filename, flags); } - else if (redir->type == AST_REDIR_TYPE_GREATAND || redir->type == AST_REDIR_TYPE_LESSAND) + else if (redir->type == AST_REDIR_TYPE_GREATAND + || redir->type == AST_REDIR_TYPE_LESSAND) { // Simple fd duplication new_fd = atoi(redir->filename); @@ -297,16 +301,18 @@ int execution(struct ast *ast) if (dup2(new_fd, fd_target) == -1) { perror("dup2"); - if (saved_fd != -1) close(saved_fd); + if (saved_fd != -1) + close(saved_fd); return 1; } new_fd = -2; // Mark as "already duped" } - + if (new_fd == -1) { perror("open"); - if (saved_fd != -1) close(saved_fd); + if (saved_fd != -1) + close(saved_fd); return 1; } @@ -316,7 +322,8 @@ int execution(struct ast *ast) { perror("dup2"); close(new_fd); - if (saved_fd != -1) close(saved_fd); + if (saved_fd != -1) + close(saved_fd); return 1; } close(new_fd); diff --git a/src/io_backend/io_backend.h b/src/io_backend/io_backend.h index 7b9e9b2..413dced 100644 --- a/src/io_backend/io_backend.h +++ b/src/io_backend/io_backend.h @@ -2,6 +2,7 @@ #define IO_BACKEND_H #include + #include "utils/args/args.h" // Error codes diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index 28f098c..aae9cf8 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -70,62 +70,62 @@ static void set_token_spechar(struct token *tok, char *begin, ssize_t size) return; switch (begin[0]) { - case EOF: - tok->type = TOKEN_EOF; - break; - case ';': - tok->type = TOKEN_SEMICOLON; - break; - case '\n': - tok->type = TOKEN_NEWLINE; - break; - case '\'': - tok->type = TOKEN_QUOTE; - break; - case '"': - tok->type = TOKEN_DOUBLE_QUOTE; - break; - case '`': - tok->type = TOKEN_GRAVE; - break; - case '#': - tok->type = TOKEN_COMMENT; - break; - case '|': - tok->type = TOKEN_PIPE; - break; - case '&': - tok->type = TOKEN_AMPERSAND; - break; - case '\\': - tok->type = TOKEN_BACKSLASH; - break; - case '$': - tok->type = TOKEN_DOLLAR; - break; - case '(': - tok->type = TOKEN_LEFT_PAREN; - break; - case ')': - tok->type = TOKEN_RIGHT_PAREN; - break; - case '{': - tok->type = TOKEN_LEFT_BRACKET; - break; - case '}': - tok->type = TOKEN_RIGHT_BRACKET; - break; - case '<': - tok->type = TOKEN_LESS; - break; - case '>': - tok->type = TOKEN_GREATER; - break; - case '*': - tok->type = TOKEN_STAR; - break; - default: - break; + case EOF: + tok->type = TOKEN_EOF; + break; + case ';': + tok->type = TOKEN_SEMICOLON; + break; + case '\n': + tok->type = TOKEN_NEWLINE; + break; + case '\'': + tok->type = TOKEN_QUOTE; + break; + case '"': + tok->type = TOKEN_DOUBLE_QUOTE; + break; + case '`': + tok->type = TOKEN_GRAVE; + break; + case '#': + tok->type = TOKEN_COMMENT; + break; + case '|': + tok->type = TOKEN_PIPE; + break; + case '&': + tok->type = TOKEN_AMPERSAND; + break; + case '\\': + tok->type = TOKEN_BACKSLASH; + break; + case '$': + tok->type = TOKEN_DOLLAR; + break; + case '(': + tok->type = TOKEN_LEFT_PAREN; + break; + case ')': + tok->type = TOKEN_RIGHT_PAREN; + break; + case '{': + tok->type = TOKEN_LEFT_BRACKET; + break; + case '}': + tok->type = TOKEN_RIGHT_BRACKET; + break; + case '<': + tok->type = TOKEN_LESS; + break; + case '>': + tok->type = TOKEN_GREATER; + break; + case '*': + tok->type = TOKEN_STAR; + break; + default: + break; } } diff --git a/src/main.c b/src/main.c index 8a81efb..a3e9527 100644 --- a/src/main.c +++ b/src/main.c @@ -69,9 +69,14 @@ int main(int argc, char **argv) // Execute AST return_code = execution(command_ast); + ast_free(&command_ast); + // Retrieve and build next AST command_ast = get_ast(); } + + ast_free(&command_ast); + if (command_ast == NULL) return ERR_INPUT_PROCESSING; diff --git a/src/parser/parser.c b/src/parser/parser.c index 0cce2fb..cf7b052 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -7,8 +7,8 @@ #include #include "lexer/lexer.h" -#include "utils/lists/lists.h" #include "parser/parsing_utils.h" +#include "utils/lists/lists.h" // === Static functions // ... diff --git a/src/parser/parsing_utils.c b/src/parser/parsing_utils.c index c07653e..9ac32d8 100644 --- a/src/parser/parsing_utils.c +++ b/src/parser/parsing_utils.c @@ -1,9 +1,11 @@ +#define _POSIX_C_SOURCE 200809L // === Includes #include "parsing_utils.h" #include #include #include +#include #include "lexer/lexer.h" #include "utils/ast/ast.h" @@ -58,7 +60,8 @@ struct ast *parse_simple_command(void) while (!isterminator(token)) { token = POP_TOKEN(); - command_elements = list_append(command_elements, token->data); + char *word = strdup(token->data); + command_elements = list_append(command_elements, word); token = PEEK_TOKEN(); } @@ -86,6 +89,7 @@ struct ast *parse_list(void) } token = PEEK_TOKEN(); } + result_list = list_append(result_list, current_node); return ast_create_list(result_list); } @@ -163,7 +167,9 @@ struct ast *parse_compound_list(void) return NULL; } - command_elements = list_append(command_elements, token->data); + char *word = strdup(token->data); + + command_elements = list_append(command_elements, word); token = POP_TOKEN(); } diff --git a/src/parser/parsing_utils.h b/src/parser/parsing_utils.h index a380a85..6fe6b89 100644 --- a/src/parser/parsing_utils.h +++ b/src/parser/parsing_utils.h @@ -28,7 +28,7 @@ struct ast *parse_list(void); */ struct ast *parse_simple_command(void); -/* +/* */ struct ast *parse_if_rule(void); @@ -39,14 +39,14 @@ struct ast *parse_shell_command(void); /* @brief parses commands inside if/else clauses and returns the corresponding * AST list */ -struct ast* parse_compound_list(void); +struct ast *parse_compound_list(void); /* */ -struct ast* parse_and_or(void); +struct ast *parse_and_or(void); /* */ -struct ast* parse_else_clause(void); +struct ast *parse_else_clause(void); #endif /* ! PARSING_UTILS_H */ diff --git a/src/utils/ast/ast.h b/src/utils/ast/ast.h index 1062782..5a06cf8 100644 --- a/src/utils/ast/ast.h +++ b/src/utils/ast/ast.h @@ -1,13 +1,13 @@ #ifndef AST_H #define AST_H +#include "utils/ast/ast_and_or.h" #include "utils/ast/ast_base.h" #include "utils/ast/ast_command.h" -#include "utils/ast/ast_if.h" -#include "utils/ast/ast_and_or.h" -#include "utils/ast/ast_redir.h" -#include "utils/ast/ast_list.h" #include "utils/ast/ast_end.h" +#include "utils/ast/ast_if.h" +#include "utils/ast/ast_list.h" +#include "utils/ast/ast_redir.h" #include "utils/ast/ast_void.h" #endif /* ! AST_H */ diff --git a/src/utils/ast/ast_and_or.c b/src/utils/ast/ast_and_or.c index d07eaf5..6b6038b 100644 --- a/src/utils/ast/ast_and_or.c +++ b/src/utils/ast/ast_and_or.c @@ -1,4 +1,5 @@ #include "utils/ast/ast_and_or.h" + #include bool ast_is_and_or(struct ast *node) @@ -13,7 +14,8 @@ struct ast_and_or *ast_get_and_or(struct ast *node) return NULL; } -struct ast *ast_create_and_or(struct ast *left, struct ast *right, enum ast_and_or_type type) +struct ast *ast_create_and_or(struct ast *left, struct ast *right, + enum ast_and_or_type type) { struct ast_and_or *and_or = malloc(sizeof(struct ast_and_or)); if (!and_or) diff --git a/src/utils/ast/ast_and_or.h b/src/utils/ast/ast_and_or.h index 57a9668..e370558 100644 --- a/src/utils/ast/ast_and_or.h +++ b/src/utils/ast/ast_and_or.h @@ -2,14 +2,17 @@ #define AST_AND_OR_H #include + #include "utils/ast/ast_base.h" -enum ast_and_or_type { +enum ast_and_or_type +{ AST_AND_OR_TYPE_AND, AST_AND_OR_TYPE_OR }; -struct ast_and_or { +struct ast_and_or +{ struct ast *left; struct ast *right; enum ast_and_or_type type; @@ -17,7 +20,8 @@ struct ast_and_or { bool ast_is_and_or(struct ast *node); struct ast_and_or *ast_get_and_or(struct ast *node); -struct ast *ast_create_and_or(struct ast *left, struct ast *right, enum ast_and_or_type type); +struct ast *ast_create_and_or(struct ast *left, struct ast *right, + enum ast_and_or_type type); void ast_free_and_or(struct ast_and_or *and_or); #endif /* ! AST_AND_OR_H */ diff --git a/src/utils/ast/ast_command.c b/src/utils/ast/ast_command.c index 920b1ff..89ed379 100644 --- a/src/utils/ast/ast_command.c +++ b/src/utils/ast/ast_command.c @@ -1,8 +1,8 @@ #include "utils/ast/ast_command.h" -#include #include #include +#include #include "utils/lists/lists.h" diff --git a/src/utils/ast/ast_command.h b/src/utils/ast/ast_command.h index 08c62ba..b83bade 100644 --- a/src/utils/ast/ast_command.h +++ b/src/utils/ast/ast_command.h @@ -3,8 +3,8 @@ #include -#include "utils/lists/lists.h" #include "utils/ast/ast_base.h" +#include "utils/lists/lists.h" struct ast_command { diff --git a/src/utils/ast/ast_end.c b/src/utils/ast/ast_end.c index 3be1eeb..a74fdb3 100644 --- a/src/utils/ast/ast_end.c +++ b/src/utils/ast/ast_end.c @@ -1,8 +1,8 @@ #include "utils/ast/ast_end.h" -#include #include #include +#include bool ast_is_end(struct ast *node) { diff --git a/src/utils/ast/ast_end.h b/src/utils/ast/ast_end.h index f86e477..df24d8b 100644 --- a/src/utils/ast/ast_end.h +++ b/src/utils/ast/ast_end.h @@ -3,8 +3,8 @@ #include -#include "utils/lists/lists.h" #include "utils/ast/ast_base.h" +#include "utils/lists/lists.h" /** * Checks if the given AST node is of type AST_END. diff --git a/src/utils/ast/ast_if.c b/src/utils/ast/ast_if.c index 6a25a9c..400a390 100644 --- a/src/utils/ast/ast_if.c +++ b/src/utils/ast/ast_if.c @@ -1,8 +1,8 @@ #include "utils/ast/ast_if.h" -#include #include #include +#include struct ast *ast_create_if(struct ast *condition, struct ast *then_clause, struct ast *else_clause) diff --git a/src/utils/ast/ast_list.c b/src/utils/ast/ast_list.c index 4fc87f5..0455698 100644 --- a/src/utils/ast/ast_list.c +++ b/src/utils/ast/ast_list.c @@ -1,7 +1,7 @@ -#include "utils/ast/ast.h" - #include +#include "utils/ast/ast.h" + struct ast *ast_create_list(struct list *list) { struct ast_list *ast_list = malloc(sizeof(struct ast_list)); @@ -16,7 +16,7 @@ struct ast *ast_create_list(struct list *list) struct ast_list *ast_get_list(struct ast *node) { assert(node != NULL); - return (struct ast_list*)node->data; + return (struct ast_list *)node->data; } bool ast_is_list(struct ast *node) diff --git a/src/utils/ast/ast_redir.c b/src/utils/ast/ast_redir.c index c5eec63..e4c92f3 100644 --- a/src/utils/ast/ast_redir.c +++ b/src/utils/ast/ast_redir.c @@ -1,4 +1,5 @@ #include "utils/ast/ast_redir.h" + #include bool ast_is_redir(struct ast *node) @@ -13,13 +14,16 @@ struct ast_redir *ast_get_redir(struct ast *node) return NULL; } -struct ast *ast_create_redir(struct ast *child, char *filename, int io_number, enum ast_redir_type type) +struct ast *ast_create_redir(struct ast *child, char *filename, int io_number, + enum ast_redir_type type) { struct ast_redir *redir = malloc(sizeof(struct ast_redir)); if (!redir) return NULL; redir->child = child; - redir->filename = filename; // Takes ownership? Usually yes in simple ASTs, or dup. Let's assume pointer copy for now, but user must manage memory. + redir->filename = + filename; // Takes ownership? Usually yes in simple ASTs, or dup. Let's + // assume pointer copy for now, but user must manage memory. redir->io_number = io_number; redir->type = type; diff --git a/src/utils/ast/ast_redir.h b/src/utils/ast/ast_redir.h index 5f5ffb5..65be4a2 100644 --- a/src/utils/ast/ast_redir.h +++ b/src/utils/ast/ast_redir.h @@ -2,28 +2,33 @@ #define AST_REDIR_H #include + #include "utils/ast/ast_base.h" -enum ast_redir_type { - AST_REDIR_TYPE_LESS, // < - AST_REDIR_TYPE_GREAT, // > - AST_REDIR_TYPE_DLESS, // << - AST_REDIR_TYPE_DGREAT, // >> - AST_REDIR_TYPE_LESSAND, // <& - AST_REDIR_TYPE_GREATAND, // >& - AST_REDIR_TYPE_CLOBBER // >| +enum ast_redir_type +{ + AST_REDIR_TYPE_LESS, // < + AST_REDIR_TYPE_GREAT, // > + AST_REDIR_TYPE_DLESS, // << + AST_REDIR_TYPE_DGREAT, // >> + AST_REDIR_TYPE_LESSAND, // <& + AST_REDIR_TYPE_GREATAND, // >& + AST_REDIR_TYPE_CLOBBER // >| }; -struct ast_redir { +struct ast_redir +{ struct ast *child; char *filename; - int io_number; // The FD being redirected (default -1 if not specified, implies 0 or 1 based on type) + int io_number; // The FD being redirected (default -1 if not specified, + // implies 0 or 1 based on type) enum ast_redir_type type; }; bool ast_is_redir(struct ast *node); struct ast_redir *ast_get_redir(struct ast *node); -struct ast *ast_create_redir(struct ast *child, char *filename, int io_number, enum ast_redir_type type); +struct ast *ast_create_redir(struct ast *child, char *filename, int io_number, + enum ast_redir_type type); void ast_free_redir(struct ast_redir *redir); #endif /* ! AST_REDIR_H */ diff --git a/src/utils/ast/ast_void.c b/src/utils/ast/ast_void.c index 1396121..3368c7d 100644 --- a/src/utils/ast/ast_void.c +++ b/src/utils/ast/ast_void.c @@ -1,8 +1,8 @@ #include "utils/ast/ast_void.h" -#include #include #include +#include bool ast_is_void(struct ast *node) { diff --git a/src/utils/ast/ast_void.h b/src/utils/ast/ast_void.h index 866a8d8..6abaef8 100644 --- a/src/utils/ast/ast_void.h +++ b/src/utils/ast/ast_void.h @@ -3,8 +3,8 @@ #include -#include "utils/lists/lists.h" #include "utils/ast/ast_base.h" +#include "utils/lists/lists.h" /** * Checks if the given AST node is of type AST_VOID. diff --git a/tests/unit/lexer/lexer_tests.c b/tests/unit/lexer/lexer_tests.c index 910a81a..7cb84a5 100644 --- a/tests/unit/lexer/lexer_tests.c +++ b/tests/unit/lexer/lexer_tests.c @@ -5,8 +5,8 @@ #include #include -#include "lexer/lexer.h" #include "io_backend/io_backend.h" +#include "lexer/lexer.h" TestSuite(peek_token); @@ -14,11 +14,7 @@ Test(peek_token, basic_empty) { // simulates input char command[] = ""; - struct iob_context context = - { - IOB_MODE_CMD, - command - }; + struct iob_context context = { IOB_MODE_CMD, command }; iob_init(&context); // test @@ -38,11 +34,7 @@ Test(peek_token, basic_word) { // simulates input char command[] = "hello"; - struct iob_context context = - { - IOB_MODE_CMD, - command - }; + struct iob_context context = { IOB_MODE_CMD, command }; iob_init(&context); // test @@ -60,11 +52,7 @@ Test(peek_token, basic_words) { // simulates input char command[] = "echo hello there"; - struct iob_context context = - { - IOB_MODE_CMD, - command - }; + struct iob_context context = { IOB_MODE_CMD, command }; iob_init(&context); // ======= echo From 78068f53bd5a11ee671556bd5d7189bdc1629dea Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Sat, 17 Jan 2026 22:45:27 +0000 Subject: [PATCH 104/282] feat(hash_map): hash_map --- src/utils/hash_map/hash_map.c | 173 ++++++++++++++++++++++++++++++++++ src/utils/hash_map/hash_map.h | 34 +++++++ 2 files changed, 207 insertions(+) create mode 100644 src/utils/hash_map/hash_map.c create mode 100644 src/utils/hash_map/hash_map.h diff --git a/src/utils/hash_map/hash_map.c b/src/utils/hash_map/hash_map.c new file mode 100644 index 0000000..dec698c --- /dev/null +++ b/src/utils/hash_map/hash_map.c @@ -0,0 +1,173 @@ +#include "hash_map.h" + +#include +#include +#include +#include +#include +#include + +/* +** Hash the key using FNV-1a 32 bits hash algorithm. +*/ +static size_t hash(const char *key) +{ + if (key == NULL) + return 0; + + uint32_t hash = 2166136261; // FNV offset basis + uint32_t prime = 16777619; // FNV prime + + while (*key != 0) + { + hash ^= *key; + hash *= prime; + key++; + } + + return hash; +} + +struct hash_map *hash_map_init(size_t size) +{ + struct hash_map *p = malloc(sizeof(struct hash_map)); + if (p != NULL) + { + p->data = calloc(size, sizeof(struct pair_list *)); + if (p->data == NULL) + { + free(p); + return NULL; + } + p->size = size; + } + return p; +} + +bool hash_map_insert(struct hash_map *hash_map, const char *key, void *value, + bool *updated) +{ + if (hash_map == NULL || hash_map->size == 0 || key == NULL) + return false; + + size_t h = hash(key); + struct pair_list **l = hash_map->data; + size_t i = h % hash_map->size; + + if (l[i] != NULL) + { + // check if key is in linked list + struct pair_list *iter = l[i]; + while (iter) + { + if (strcmp(iter->key, key) == 0) + { + // update + iter->value = value; + if (updated) + *updated = true; + return true; + } + iter = iter->next; + } + + // if not found => collision + } + struct pair_list *p = malloc(sizeof(struct pair_list)); + if (p == NULL) + return false; + + if (updated) + *updated = false; + + p->key = key; + p->value = value; + p->next = l[i]; + l[i] = p; + l[i] = p; + return true; +} + +void hash_map_free(struct hash_map *hash_map) +{ + struct pair_list *l; + struct pair_list *prev; + + if (hash_map) + { + for (size_t i = 0; i < hash_map->size; i++) + { + l = hash_map->data[i]; + while (l != NULL) + { + prev = l; + l = l->next; + free(prev); + } + } + free(hash_map->data); + free(hash_map); + } +} + +void hash_map_foreach(struct hash_map *hash_map, + void (*fn)(const char *, const void *)) +{ + if (hash_map == NULL) + return; + + struct pair_list *iter; + for (size_t i = 0; i < hash_map->size; i++) + { + iter = hash_map->data[i]; + while (iter != NULL) + { + fn(iter->key, iter->value); + iter = iter->next; + } + } +} + +const void *hash_map_get(const struct hash_map *hash_map, const char *key) +{ + if (hash_map == NULL || hash_map->size == 0) + return NULL; + + size_t h = hash(key); + size_t i = h % hash_map->size; + struct pair_list *l = hash_map->data[i]; + while (l != NULL) + { + if (strcmp(l->key, key) == 0) + return l->value; + l = l->next; + } + return NULL; +} + +bool hash_map_remove(struct hash_map *hash_map, const char *key) +{ + if (hash_map == NULL || hash_map->size == 0) + return false; + + size_t h = hash(key); + size_t i = h % hash_map->size; + struct pair_list *l = hash_map->data[i]; + struct pair_list *p = NULL; + + while (l != NULL) + { + if (strcmp(l->key, key) == 0) + { + if (p != NULL) + p->next = l->next; + else + hash_map->data[i] = l->next; + free(l); + return true; + } + p = l; + l = l->next; + } + return false; +} diff --git a/src/utils/hash_map/hash_map.h b/src/utils/hash_map/hash_map.h new file mode 100644 index 0000000..efdabc2 --- /dev/null +++ b/src/utils/hash_map/hash_map.h @@ -0,0 +1,34 @@ +#ifndef HASH_MAP_H +#define HASH_MAP_H + +#include +#include + +struct pair_list +{ + const char *key; + void *value; + struct pair_list *next; +}; + +struct hash_map +{ + struct pair_list **data; + size_t size; +}; + +struct hash_map *hash_map_init(size_t size); + +bool hash_map_insert(struct hash_map *hash_map, const char *key, void *value, + bool *updated); + +void hash_map_free(struct hash_map *hash_map); + +void hash_map_foreach(struct hash_map *hash_map, + void (*fn)(const char *, const void *)); + +const void *hash_map_get(const struct hash_map *hash_map, const char *key); + +bool hash_map_remove(struct hash_map *hash_map, const char *key); + +#endif /* ! HASH_MAP_H */ From 0c6c3571614b69a925ea832adecd87a7673d4602 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Sat, 17 Jan 2026 22:46:10 +0000 Subject: [PATCH 105/282] feat(vars): vars --- src/utils/vars/vars.c | 32 ++++++++++++++++++++++++++++++++ src/utils/vars/vars.h | 31 +++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 src/utils/vars/vars.c create mode 100644 src/utils/vars/vars.h diff --git a/src/utils/vars/vars.c b/src/utils/vars/vars.c new file mode 100644 index 0000000..70ab328 --- /dev/null +++ b/src/utils/vars/vars.c @@ -0,0 +1,32 @@ +#define _POSIX_C_SOURCE 200809L +#include "vars.h" + +#include +#include +#include + +#include "../hash_map/hash_map.h" + +#define VARS_INITIAL_SIZE 16 + +struct hash_map *vars_init(void) +{ + return hash_map_init(VARS_INITIAL_SIZE); +} + +char *get_var(const struct hash_map *vars, const char *key) +{ + return (char *)hash_map_get(vars, key); +} + +bool set_var(struct hash_map *vars, const char *key, const char *value) +{ + if (key == NULL || value == NULL) + return false; + return hash_map_insert(vars, key, (void *)value, NULL); +} + +bool set_var_copy(struct hash_map *vars, const char *key, const char *value) +{ + return set_var(vars, strdup(key), strdup(value)); +} diff --git a/src/utils/vars/vars.h b/src/utils/vars/vars.h new file mode 100644 index 0000000..97db704 --- /dev/null +++ b/src/utils/vars/vars.h @@ -0,0 +1,31 @@ +#ifndef VARS_H +#define VARS_H + +#include + +#include "../hash_map/hash_map.h" + +/** + * Initialize a new variables hash map. + */ +struct hash_map *vars_init(void); + +/** + * Get the value of a variable, NULL if not found. + */ +char *get_var(const struct hash_map *vars, const char *key); + +/** + * Set the value of a variable. Key and value ownership are transferred to + * the hash_map and need to be on the heap. Returns true on success, false on + * failure. + */ +bool set_var(struct hash_map *vars, const char *key, const char *value); + +/** + * Same as set_var, but makes copies of key and value so you don't have to worry + * about their memory management. Returns true on success, false on failure. + */ +bool set_var_copy(struct hash_map *vars, const char *key, const char *value); + +#endif /* ! VARS_H */ From cf5da6f231da6bb92ee280dc7bfcfbdc6b3cd152 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Sat, 17 Jan 2026 23:57:59 +0000 Subject: [PATCH 106/282] feat(hash_map): destroy_pair_list --- src/utils/hash_map/hash_map.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/utils/hash_map/hash_map.c b/src/utils/hash_map/hash_map.c index dec698c..46ac9d1 100644 --- a/src/utils/hash_map/hash_map.c +++ b/src/utils/hash_map/hash_map.c @@ -28,6 +28,14 @@ static size_t hash(const char *key) return hash; } +static void destroy_pair_list(struct pair_list **p) +{ + free((char *)(*p)->key); + 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)); @@ -102,7 +110,7 @@ void hash_map_free(struct hash_map *hash_map) { prev = l; l = l->next; - free(prev); + destroy_pair_list(&prev); } } free(hash_map->data); @@ -163,7 +171,7 @@ bool hash_map_remove(struct hash_map *hash_map, const char *key) p->next = l->next; else hash_map->data[i] = l->next; - free(l); + destroy_pair_list(&l); return true; } p = l; From 0c9c5dc1f8b2d89bff689cb2134de3082828e24b Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Sat, 17 Jan 2026 23:58:15 +0000 Subject: [PATCH 107/282] feat(vars): get_var_or_env --- src/utils/vars/vars.c | 10 +++++++++- src/utils/vars/vars.h | 6 ++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/utils/vars/vars.c b/src/utils/vars/vars.c index 70ab328..985e085 100644 --- a/src/utils/vars/vars.c +++ b/src/utils/vars/vars.c @@ -2,7 +2,7 @@ #include "vars.h" #include -#include +#include #include #include "../hash_map/hash_map.h" @@ -19,6 +19,14 @@ char *get_var(const struct hash_map *vars, const char *key) return (char *)hash_map_get(vars, key); } +char *get_var_or_env(const struct hash_map *vars, const char *key) +{ + char *value = (char *)hash_map_get(vars, key); + if (value == NULL) + value = getenv(key); + return value; +} + bool set_var(struct hash_map *vars, const char *key, const char *value) { if (key == NULL || value == NULL) diff --git a/src/utils/vars/vars.h b/src/utils/vars/vars.h index 97db704..de1c4c6 100644 --- a/src/utils/vars/vars.h +++ b/src/utils/vars/vars.h @@ -15,6 +15,12 @@ struct hash_map *vars_init(void); */ char *get_var(const struct hash_map *vars, const char *key); +/** + * Get the value of a variable, from the environment if not found in vars, + * NULL if not found in either. + */ +char *get_var_or_env(const struct hash_map *vars, const char *key); + /** * Set the value of a variable. Key and value ownership are transferred to * the hash_map and need to be on the heap. Returns true on success, false on From df7cc02eb914ddd0f47da136818f451e050fe639 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Sat, 17 Jan 2026 23:59:09 +0000 Subject: [PATCH 108/282] feat(string_utils): insert_into --- src/utils/string_utils/string_utils.c | 31 +++++++++- src/utils/string_utils/string_utils.h | 6 ++ tests/unit/utils/insert_into.c | 85 +++++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 tests/unit/utils/insert_into.c diff --git a/src/utils/string_utils/string_utils.c b/src/utils/string_utils/string_utils.c index 8a8176a..e5b7040 100644 --- a/src/utils/string_utils/string_utils.c +++ b/src/utils/string_utils/string_utils.c @@ -1,7 +1,8 @@ #include "string_utils.h" #include -#include +#include +#include char *trim_blank_left(char *str) { @@ -13,3 +14,31 @@ char *trim_blank_left(char *str) return str; } + +char *insert_into(char *dest, const char *src, size_t pos, size_t len) +{ + size_t res_len = strlen(dest); + size_t prefix_len = pos; + size_t suffix_len = res_len - (pos + len); + size_t src_len = strlen(src); + size_t new_len = prefix_len + src_len + suffix_len; + + if (dest == NULL || src == NULL || pos + len > res_len) + return NULL; + + if (res_len < new_len) + { + char *p = realloc(dest, new_len + 1); + if (p == NULL) + return NULL; // allocation failure + dest = p; + } + + memmove(dest + pos + src_len, dest + pos + len, suffix_len); + memcpy(dest + pos, src, src_len); + dest[new_len] = 0; + + if (res_len > new_len) + return realloc(dest, new_len + 1); + return dest; +} diff --git a/src/utils/string_utils/string_utils.h b/src/utils/string_utils/string_utils.h index 496c1d5..e411f0e 100644 --- a/src/utils/string_utils/string_utils.h +++ b/src/utils/string_utils/string_utils.h @@ -12,4 +12,10 @@ */ char *trim_blank_left(char *str); +/** + * Inserts a substring into a destination string at a specified position, + * replacing a specified length of characters. + */ +char *insert_into(char *dest, const char *src, size_t pos, size_t len); + #endif /* STRING_UTILS_H */ diff --git a/tests/unit/utils/insert_into.c b/tests/unit/utils/insert_into.c new file mode 100644 index 0000000..0bcc833 --- /dev/null +++ b/tests/unit/utils/insert_into.c @@ -0,0 +1,85 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include + +#include "../../../src/utils/string_utils/string_utils.h" + +TestSuite(insert_into); + +Test(insert_into, basic) +{ + char *dest = strdup("The is nice."); + const char *src = "weather"; + size_t pos = 4; + + char *result = insert_into(dest, src, pos, 6); + + cr_expect(result != NULL); + cr_expect(eq(str, result, "The weather is nice.")); + + if (result) + free(result); +} + +Test(insert_into, begin) +{ + char *dest = strdup("Hello World!"); + const char *src = "Hi"; + size_t pos = 0; + + char *result = insert_into(dest, src, pos, 5); + + cr_expect(result != NULL); + cr_expect(eq(str, result, "Hi World!")); + + if (result) + free(result); +} + +Test(insert_into, end) +{ + char *dest = strdup("The number is 1024"); + const char *src = "2048"; + size_t pos = 14; + + char *result = insert_into(dest, src, pos, 4); + + cr_expect(result != NULL); + cr_expect(eq(str, result, "The number is 2048")); + + if (result) + free(result); +} + +Test(insert_into, big) +{ + char *dest = strdup("I could insert [VAR] here."); + const char *src = "a very very long string"; + size_t pos = 15; + + char *result = insert_into(dest, src, pos, 5); + + cr_expect(result != NULL); + cr_expect(eq(str, result, "I could insert a very very long string here.")); + + if (result) + free(result); +} + +Test(insert_into, small) +{ + char *dest = strdup("I could insert [VARNAME_IS_SO_LONG] string here."); + const char *src = "a short"; + size_t pos = 15; + + char *result = insert_into(dest, src, pos, 20); + + cr_expect(result != NULL); + cr_expect(eq(str, result, "I could insert a short string here.")); + + if (result) + free(result); +} From a16712e802d4600afe818c8211569798d767ceda Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Sun, 18 Jan 2026 00:29:31 +0000 Subject: [PATCH 109/282] feat(expansion): special_variable_with_braces test --- tests/unit/expansion/parse_var.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/unit/expansion/parse_var.c b/tests/unit/expansion/parse_var.c index f588415..ce4c11a 100644 --- a/tests/unit/expansion/parse_var.c +++ b/tests/unit/expansion/parse_var.c @@ -62,6 +62,17 @@ Test(parse_var_name, special_variable) free(extracted_var); } +Test(parse_var_name, special_variable_with_braces) +{ + char *input = "${1}"; + char *extracted_var = NULL; + size_t r = parse_var_name(input, &extracted_var); + + cr_expect(r == 4); + cr_expect_str_eq(extracted_var, "1"); + free(extracted_var); +} + Test(parse_var_name, incomplete_braces) { char *input = "${MY_VAR"; From 34c741d86ec531de924b53706aa5b7a9bc4440d2 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Sun, 18 Jan 2026 01:27:31 +0000 Subject: [PATCH 110/282] feat(expansion): expand_var and expand --- src/expansion/expansion.c | 54 ++++++-- src/expansion/expansion.h | 11 ++ tests/unit/expansion/expand.c | 220 +++++++++++++++++++++++++++++++ tests/unit/expansion/parse_var.c | 5 - 4 files changed, 276 insertions(+), 14 deletions(-) create mode 100644 tests/unit/expansion/expand.c diff --git a/src/expansion/expansion.c b/src/expansion/expansion.c index 81867a1..dca13ec 100644 --- a/src/expansion/expansion.c +++ b/src/expansion/expansion.c @@ -6,6 +6,9 @@ #include #include "../utils/ast/ast.h" +#include "../utils/hash_map/hash_map.h" +#include "../utils/string_utils/string_utils.h" +#include "../utils/vars/vars.h" static bool is_var_start_char(char c) { @@ -77,14 +80,39 @@ size_t parse_var_name(char *str, char **res) return i; } -struct ast_command *expand(struct ast_command *command) +static bool expand_var(char **str, size_t pos, const struct hash_map *vars) +{ + char *var_name = NULL; + size_t r = parse_var_name(*str + pos, &var_name); + if (r > 0 && var_name != NULL) + { + char *value = get_var_or_env(vars, var_name); + if (value == NULL) + // Undefined variable: expand to empty string + value = ""; + + char *p = insert_into(*str, value, pos, r); + free(var_name); + if (p == NULL) + { + // error: insertion failed + return false; + } + *str = p; + return true; + } + return false; +} + +struct ast_command *expand(struct ast_command *command, + const struct hash_map *vars) { if (command == NULL) return NULL; - bool in_quotes = false; char *str; size_t len; + bool in_quotes; struct list *l = command->command; while (l != NULL) @@ -93,28 +121,35 @@ struct ast_command *expand(struct ast_command *command) str = (char *)l->data; len = strlen(str); - for (size_t i = 0; str[i] != '\0'; i++) + for (size_t i = 0; str[i] != 0; i++) { - if (in_quotes) - { - continue; // do nothing - } - else if (str[i] == '\'') + if (str[i] == '\'') { // remove quote in_quotes = !in_quotes; - memmove(&str[i], &str[i + 1], strlen(&str[i + 1]) + 1); + memmove(str + i, str + i + 1, strlen(str + i + 1) + 1); i--; } + else if (in_quotes) + { + continue; // do nothing + } else if (str[i] == '$' && str[i + 1] != 0 && !isspace(str[i + 1])) { // variable expansion + bool r = expand_var(&str, i, vars); + if (r == false || str == NULL) + return NULL; + + i--; // -1 because loop will increment i } } if (in_quotes) { // error: quote not closed + fprintf(stderr, "Error: quote not closed in string: %s\n", str); + return NULL; } if (len != strlen(str)) @@ -123,6 +158,7 @@ struct ast_command *expand(struct ast_command *command) if (new_str == NULL) { // error: realloc fail + return NULL; } l->data = new_str; } diff --git a/src/expansion/expansion.h b/src/expansion/expansion.h index 756e75e..7211ae3 100644 --- a/src/expansion/expansion.h +++ b/src/expansion/expansion.h @@ -3,6 +3,8 @@ #include +#include "../utils/hash_map/hash_map.h" + /** * Parse a variable from a string starting with '$'. * @param str The input string starting with '$'. It must start with '$'. @@ -12,4 +14,13 @@ */ size_t parse_var_name(char *str, char **res); +/** + * Expand variables in an AST command using the provided variable map. + * @param command The AST command to expand. + * @param vars The hash map containing variables. + * @return A new AST command with variables expanded, or NULL on error. + */ +struct ast_command *expand(struct ast_command *command, + const struct hash_map *vars); + #endif /* ! EXPANSION_H */ diff --git a/tests/unit/expansion/expand.c b/tests/unit/expansion/expand.c new file mode 100644 index 0000000..1e25d7c --- /dev/null +++ b/tests/unit/expansion/expand.c @@ -0,0 +1,220 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include + +#include "../../../src/expansion/expansion.h" +#include "../../../src/utils/ast/ast.h" +#include "../../../src/utils/hash_map/hash_map.h" +#include "../../../src/utils/vars/vars.h" + +TestSuite(expand); + +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_command *ast_command = ast_get_command(ast); + + struct ast_command *command2 = expand(ast_command, NULL); + cr_assert_not_null(command2, "Expansion returned NULL"); + cr_assert_str_eq((char *)command2->command->data, "echo something", + "String without variables should remain unchanged"); + ast_free(&ast); +} + +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_command *ast_command = ast_get_command(ast); + + struct hash_map *vars = vars_init(); + set_var_copy(vars, "VAR", "expanded"); + + struct ast_command *command2 = expand(ast_command, vars); + cr_assert_not_null(command2, "Expansion returned NULL"); + cr_assert_str_eq((char *)command2->command->data, "echo $VAR", + "Variable should not expand inside single quotes"); + ast_free(&ast); + hash_map_free(vars); +} + +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_command *ast_command = ast_get_command(ast); + + struct hash_map *vars = vars_init(); + set_var_copy(vars, "VAR", "expanded"); + + struct ast_command *command2 = expand(ast_command, vars); + cr_assert_not_null(command2, "Expansion returned NULL"); + cr_assert_str_eq((char *)command2->command->data, "echo $ sign", + "Variable should not expand inside single quotes"); + ast_free(&ast); + hash_map_free(vars); +} + +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_command *ast_command = ast_get_command(ast); + + struct hash_map *vars = vars_init(); + set_var_copy(vars, "VAR", "expanded"); + + struct ast_command *command2 = expand(ast_command, vars); + cr_assert_null(command2, "Expansion should fail on empty braces"); + ast_free(&ast); + hash_map_free(vars); +} + +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_command *ast_command = ast_get_command(ast); + + struct hash_map *vars = vars_init(); + set_var_copy(vars, "VAR", "expanded"); + + struct ast_command *command2 = expand(ast_command, vars); + cr_assert_not_null(command2, "Expansion returned NULL"); + cr_assert_str_eq((char *)command2->command->data, "echo expanded", + "Variable should expand correctly"); + ast_free(&ast); + hash_map_free(vars); +} + +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_command *ast_command = ast_get_command(ast); + + struct hash_map *vars = vars_init(); + set_var_copy(vars, "VAR1", "expanded"); + set_var_copy(vars, "VAR2", "values"); + set_var_copy(vars, "VAR3", "here"); + + struct ast_command *command2 = expand(ast_command, vars); + cr_assert_not_null(command2, "Expansion returned NULL"); + cr_assert_str_eq((char *)command2->command->data, + "echo expanded values here", + "Multiple variables should expand correctly"); + ast_free(&ast); + hash_map_free(vars); +} + +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_command *ast_command = ast_get_command(ast); + + setenv("MY_ENV_VAR", "environment", 0); + + struct ast_command *command2 = expand(ast_command, NULL); + cr_assert_not_null(command2, "Expansion returned NULL"); + cr_assert_str_eq((char *)command2->command->data, "echo environment", + "Environment variable should expand correctly"); + ast_free(&ast); +} + +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_command *ast_command = ast_get_command(ast); + + struct hash_map *vars = vars_init(); + + struct ast_command *command2 = expand(ast_command, vars); + cr_assert_not_null(command2, "Expansion returned NULL"); + cr_assert_str_eq((char *)command2->command->data, "echo ", + "Undefined variable should expand to empty string"); + ast_free(&ast); + hash_map_free(vars); +} + +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_command *ast_command = ast_get_command(ast); + + struct hash_map *vars = vars_init(); + set_var_copy(vars, "A", "expanded"); + set_var_copy(vars, "B", "$A"); + + struct ast_command *command2 = expand(ast_command, vars); + cr_assert_not_null(command2, "Expansion returned NULL"); + cr_assert_str_eq((char *)command2->command->data, "echo expanded", + "Nested variable should expand correctly"); + ast_free(&ast); + 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); + + struct hash_map *vars = vars_init(); + set_var_copy(vars, "VAR1", "expanded"); + set_var_copy(vars, "VAR2", "not_expanded"); + + struct ast_command *command2 = expand(ast_command, vars); + cr_assert_not_null(command2, "Expansion returned NULL"); + cr_assert_str_eq((char *)command2->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_command *ast_command = ast_get_command(ast); + + struct hash_map *vars = vars_init(); + set_var_copy(vars, "VAR1", "hello"); + set_var_copy(vars, "VAR2", "world"); + + struct ast_command *command2 = expand(ast_command, vars); + cr_assert_not_null(command2, "Expansion returned NULL"); + cr_assert_str_eq((char *)command2->command->data, "echo helloworld", + "Adjacent variables should expand correctly"); + ast_free(&ast); + hash_map_free(vars); +} diff --git a/tests/unit/expansion/parse_var.c b/tests/unit/expansion/parse_var.c index ce4c11a..27a4b94 100644 --- a/tests/unit/expansion/parse_var.c +++ b/tests/unit/expansion/parse_var.c @@ -6,11 +6,6 @@ TestSuite(parse_var_name); -// char *input = "$MY$VAR"; -// char *input = "$MY$VAR$"; -// char *input = "$MY$VAR${}"; -// char *input = "$MY$VAR${1}"; - Test(parse_var_name, basic_variable) { char *input = "$MY_VAR"; From 096a7d81a09770c393925b4b324e406faa5cb750 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Sun, 18 Jan 2026 01:36:04 +0000 Subject: [PATCH 111/282] feat: vars initialization --- src/main.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main.c b/src/main.c index a3e9527..ee2f36a 100644 --- a/src/main.c +++ b/src/main.c @@ -6,6 +6,7 @@ #include "io_backend/io_backend.h" #include "parser/parser.h" #include "utils/args/args.h" +#include "utils/vars/vars.h" // === Error codes @@ -28,6 +29,14 @@ int main(int argc, char **argv) } // args_print(&options); + // Initialize variables hash map + struct hash_map *vars = vars_init(); + if (vars == NULL) + { + fprintf(stderr, "Error: Failed to initialize variables hash map\n"); + return ERR_MALLOC; + } + // Create the IO-Backend context struct struct iob_context *io_context = malloc(sizeof(struct iob_context)); if (io_context == NULL) From 3cf1960a00bb5e7c6a901e736705dcf896d8bb00 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Sun, 18 Jan 2026 01:45:27 +0000 Subject: [PATCH 112/282] fix(utils): Makefile.am sources --- src/utils/Makefile.am | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am index 6cd107d..57e6e4d 100644 --- a/src/utils/Makefile.am +++ b/src/utils/Makefile.am @@ -1,7 +1,7 @@ lib_LIBRARIES = libutils.a libutils_a_SOURCES = \ - string_utils/string_utils.c \ + args/args.c \ ast/ast.c \ ast/ast_if.c \ ast/ast_command.c \ @@ -10,8 +10,10 @@ libutils_a_SOURCES = \ ast/ast_redir.c \ ast/ast_void.c \ ast/ast_end.c \ + hash_map/hash_map.c \ lists/lists.c \ - args/args.c + string_utils/string_utils.c \ + vars/vars.c libutils_a_CPPFLAGS = -I$(top_srcdir)/src From 2ebf56dde757164c467b03e0df9b26c3b5c73b41 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Mon, 19 Jan 2026 15:28:35 +0000 Subject: [PATCH 113/282] fix(hash_map): fixed leak on value update --- src/utils/hash_map/hash_map.c | 15 +-- src/utils/hash_map/hash_map.h | 15 ++- src/utils/vars/vars.c | 17 ++- tests/unit/utils/hash_map.c | 217 ++++++++++++++++++++++++++++++++++ 4 files changed, 251 insertions(+), 13 deletions(-) create mode 100644 tests/unit/utils/hash_map.c diff --git a/src/utils/hash_map/hash_map.c b/src/utils/hash_map/hash_map.c index 46ac9d1..3d77734 100644 --- a/src/utils/hash_map/hash_map.c +++ b/src/utils/hash_map/hash_map.c @@ -55,7 +55,7 @@ struct hash_map *hash_map_init(size_t size) bool hash_map_insert(struct hash_map *hash_map, const char *key, void *value, bool *updated) { - if (hash_map == NULL || hash_map->size == 0 || key == NULL) + if (hash_map == NULL || hash_map->size == 0 || key == NULL || value == NULL) return false; size_t h = hash(key); @@ -71,6 +71,7 @@ bool hash_map_insert(struct hash_map *hash_map, const char *key, void *value, if (strcmp(iter->key, key) == 0) { // update + free(iter->value); iter->value = value; if (updated) *updated = true; @@ -96,16 +97,16 @@ bool hash_map_insert(struct hash_map *hash_map, const char *key, void *value, return true; } -void hash_map_free(struct hash_map *hash_map) +void hash_map_free(struct hash_map **hash_map) { struct pair_list *l; struct pair_list *prev; - if (hash_map) + if (hash_map != NULL && *hash_map != NULL) { - for (size_t i = 0; i < hash_map->size; i++) + for (size_t i = 0; i < (*hash_map)->size; i++) { - l = hash_map->data[i]; + l = (*hash_map)->data[i]; while (l != NULL) { prev = l; @@ -113,8 +114,8 @@ void hash_map_free(struct hash_map *hash_map) destroy_pair_list(&prev); } } - free(hash_map->data); - free(hash_map); + free((*hash_map)->data); + free(*hash_map); } } diff --git a/src/utils/hash_map/hash_map.h b/src/utils/hash_map/hash_map.h index efdabc2..048a882 100644 --- a/src/utils/hash_map/hash_map.h +++ b/src/utils/hash_map/hash_map.h @@ -19,10 +19,23 @@ struct hash_map struct hash_map *hash_map_init(size_t size); +/** + * @brief Inserts a key-value pair into the hash map. Key and value are expected + * to be on the heap and will be managed by the hash map. It means they are + * consumed by this function. If the key already exists, its value is updated + * and the key is not consumed so it must be freed by the caller. + * + * @param hash_map The hash map. + * @param key The key to insert. + * @param value The value to insert. + * @param updated If not NULL, set to true if the key was already present and + * updated, false if the key was newly inserted. + * @return true on success, false on failure. + */ bool hash_map_insert(struct hash_map *hash_map, const char *key, void *value, bool *updated); -void hash_map_free(struct hash_map *hash_map); +void hash_map_free(struct hash_map **hash_map); void hash_map_foreach(struct hash_map *hash_map, void (*fn)(const char *, const void *)); diff --git a/src/utils/vars/vars.c b/src/utils/vars/vars.c index 985e085..da3afa2 100644 --- a/src/utils/vars/vars.c +++ b/src/utils/vars/vars.c @@ -27,14 +27,21 @@ char *get_var_or_env(const struct hash_map *vars, const char *key) return value; } -bool set_var(struct hash_map *vars, const char *key, const char *value) +bool set_var(struct hash_map *vars, const char *key, const char *value, + bool *updated) { - if (key == NULL || value == NULL) - return false; - return hash_map_insert(vars, key, (void *)value, NULL); + return hash_map_insert(vars, key, (void *)value, updated); } bool set_var_copy(struct hash_map *vars, const char *key, const char *value) { - return set_var(vars, strdup(key), strdup(value)); + char *key_copy = strdup(key); + char *value_copy = strdup(value); + bool updated; + bool res = set_var(vars, key_copy, value_copy, &updated); + if (updated || !res) + free(key_copy); + if (!res) + free(value_copy); + return res; } diff --git a/tests/unit/utils/hash_map.c b/tests/unit/utils/hash_map.c new file mode 100644 index 0000000..3c3bd04 --- /dev/null +++ b/tests/unit/utils/hash_map.c @@ -0,0 +1,217 @@ +#define _POSIX_C_SOURCE 200809L +#include "../../../src/utils/hash_map/hash_map.h" + +#include +#include +#include +#include + +TestSuite(utils_hash_map); + +Test(utils_hash_map, init_free) +{ + struct hash_map *map = hash_map_init(10); + cr_expect_not_null(map); + cr_expect_eq(map->size, 10); + hash_map_free(&map); +} + +Test(utils_hash_map, insert_basic) +{ + struct hash_map *map = hash_map_init(10); + cr_expect_not_null(map); + cr_expect_eq(map->size, 10); + + bool updated = false; + bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), &updated); + cr_expect_eq(res, true); + cr_expect_eq(updated, false); + + hash_map_free(&map); +} + +Test(utils_hash_map, insert_multiple) +{ + struct hash_map *map = hash_map_init(10); + cr_expect_not_null(map); + cr_expect_eq(map->size, 10); + + bool updated = false; + bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), &updated); + cr_expect_eq(res, true); + cr_expect_eq(updated, false); + res = hash_map_insert(map, strdup("key2"), strdup("value2"), &updated); + cr_expect_eq(res, true); + cr_expect_eq(updated, false); + + hash_map_free(&map); +} + +Test(utils_hash_map, insert_update) +{ + struct hash_map *map = hash_map_init(10); + cr_expect_not_null(map); + cr_expect_eq(map->size, 10); + + bool updated = false; + bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), &updated); + cr_expect_eq(res, true); + cr_expect_eq(updated, false); + res = hash_map_insert(map, "key1", strdup("value2"), &updated); + cr_expect_eq(res, true); + cr_expect_eq(updated, true); + + hash_map_free(&map); +} + +Test(utils_hash_map, insert_update_multiple) +{ + struct hash_map *map = hash_map_init(10); + cr_expect_not_null(map); + cr_expect_eq(map->size, 10); + + bool updated = false; + bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), &updated); + cr_expect_eq(res, true); + cr_expect_eq(updated, false); + res = hash_map_insert(map, strdup("key2"), strdup("value2"), &updated); + cr_expect_eq(res, true); + cr_expect_eq(updated, false); + res = hash_map_insert(map, "key1", strdup("value2"), &updated); + cr_expect_eq(res, true); + cr_expect_eq(updated, true); + res = hash_map_insert(map, "key1", strdup("value3"), &updated); + cr_expect_eq(res, true); + cr_expect_eq(updated, true); + + hash_map_free(&map); +} + +Test(utils_hash_map, get_basic) +{ + struct hash_map *map = hash_map_init(10); + cr_expect_not_null(map); + cr_expect_eq(map->size, 10); + + bool updated = false; + bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), &updated); + cr_expect_eq(res, true); + cr_expect_eq(updated, false); + + char *value = (char *)hash_map_get(map, "key1"); + cr_expect_str_eq(value, "value1"); + + hash_map_free(&map); +} + +Test(utils_hash_map, get_after_update) +{ + struct hash_map *map = hash_map_init(10); + cr_expect_not_null(map); + cr_expect_eq(map->size, 10); + + bool updated = false; + bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), &updated); + cr_expect_eq(res, true); + cr_expect_eq(updated, false); + + res = hash_map_insert(map, "key1", strdup("value2"), &updated); + cr_expect_eq(res, true); + cr_expect_eq(updated, true); + + char *value = (char *)hash_map_get(map, "key1"); + cr_expect_str_eq(value, "value2"); + + hash_map_free(&map); +} + +Test(utils_hash_map, get_unknown_key) +{ + struct hash_map *map = hash_map_init(10); + cr_expect_not_null(map); + cr_expect_eq(map->size, 10); + + char *value = (char *)hash_map_get(map, "unknown_key"); + cr_expect_null(value); + + hash_map_free(&map); +} + +Test(utils_hash_map, delete_key) +{ + struct hash_map *map = hash_map_init(10); + cr_expect_not_null(map); + cr_expect_eq(map->size, 10); + + bool updated = false; + bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), &updated); + cr_expect_eq(res, true); + cr_expect_eq(updated, false); + + res = hash_map_remove(map, "key1"); + cr_expect_eq(res, true); + + char *value = (char *)hash_map_get(map, "key1"); + cr_expect_null(value); + + hash_map_free(&map); +} + +Test(utils_hash_map, delete_unknown_key) +{ + struct hash_map *map = hash_map_init(10); + cr_expect_not_null(map); + cr_expect_eq(map->size, 10); + + bool res = hash_map_remove(map, "unknown_key"); + cr_expect_eq(res, false); + + hash_map_free(&map); +} + +Test(utils_hash_map, free_nonnull_map) +{ + struct hash_map *map = hash_map_init(10); + cr_expect_not_null(map); + cr_expect_eq(map->size, 10); + + bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), NULL); + cr_expect_eq(res, true); + res = hash_map_insert(map, strdup("key2"), strdup("value2"), NULL); + cr_expect_eq(res, true); + + hash_map_free(&map); +} + +Test(utils_hash_map, free_null_map) +{ + hash_map_free(NULL); +} + +static size_t count = 0; +void foreach_fn(const char *key, const void *value) +{ + printf("Key: %s, Value: %s\n", key, (const char *)value); + count++; +} + +Test(utils_hash_map, foreach, .init = cr_redirect_stdout) +{ + struct hash_map *map = hash_map_init(10); + cr_expect_not_null(map); + cr_expect_eq(map->size, 10); + + bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), NULL); + cr_expect_eq(res, true); + res = hash_map_insert(map, strdup("key2"), strdup("value2"), NULL); + cr_expect_eq(res, true); + + count = 0; + hash_map_foreach(map, foreach_fn); + fflush(stdout); + cr_expect_eq(count, 2); + cr_expect_stdout_eq_str( + "Key: key2, Value: value2\nKey: key1, Value: value1\n"); + + hash_map_free(&map); +} From c81afc2c699c119a10ead35585f8742691b77593 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Mon, 19 Jan 2026 17:32:45 +0100 Subject: [PATCH 114/282] feat(lexer): quote handling --- src/lexer/lexer.c | 190 ++++++++++++++++++++++++++++------------------ src/lexer/lexer.h | 14 +++- 2 files changed, 127 insertions(+), 77 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index ef5a478..fd54c98 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -69,62 +69,62 @@ static void set_token_spechar(struct token *tok, char *begin, ssize_t size) return; switch (begin[0]) { - case EOF: - tok->type = TOKEN_EOF; - break; - case ';': - tok->type = TOKEN_SEMICOLON; - break; - case '\n': - tok->type = TOKEN_NEWLINE; - break; - case '\'': - tok->type = TOKEN_QUOTE; - break; - case '"': - tok->type = TOKEN_DOUBLE_QUOTE; - break; - case '`': - tok->type = TOKEN_GRAVE; - break; - case '#': - tok->type = TOKEN_COMMENT; - break; - case '|': - tok->type = TOKEN_PIPE; - break; - case '&': - tok->type = TOKEN_AMPERSAND; - break; - case '\\': - tok->type = TOKEN_BACKSLASH; - break; - case '$': - tok->type = TOKEN_DOLLAR; - break; - case '(': - tok->type = TOKEN_LEFT_PAREN; - break; - case ')': - tok->type = TOKEN_RIGHT_PAREN; - break; - case '{': - tok->type = TOKEN_LEFT_BRACKET; - break; - case '}': - tok->type = TOKEN_RIGHT_BRACKET; - break; - case '<': - tok->type = TOKEN_LESS; - break; - case '>': - tok->type = TOKEN_GREATER; - break; - case '*': - tok->type = TOKEN_STAR; - break; - default: - break; + case EOF: + tok->type = TOKEN_EOF; + break; + case ';': + tok->type = TOKEN_SEMICOLON; + break; + case '\n': + tok->type = TOKEN_NEWLINE; + break; + case '\'': + tok->type = TOKEN_QUOTE; + break; + case '"': + tok->type = TOKEN_DOUBLE_QUOTE; + break; + case '`': + tok->type = TOKEN_GRAVE; + break; + case '#': + tok->type = TOKEN_COMMENT; + break; + case '|': + tok->type = TOKEN_PIPE; + break; + case '&': + tok->type = TOKEN_AMPERSAND; + break; + case '\\': + tok->type = TOKEN_BACKSLASH; + break; + case '$': + tok->type = TOKEN_DOLLAR; + break; + case '(': + tok->type = TOKEN_LEFT_PAREN; + break; + case ')': + tok->type = TOKEN_RIGHT_PAREN; + break; + case '{': + tok->type = TOKEN_LEFT_BRACKET; + break; + case '}': + tok->type = TOKEN_RIGHT_BRACKET; + break; + case '<': + tok->type = TOKEN_LESS; + break; + case '>': + tok->type = TOKEN_GREATER; + break; + case '*': + tok->type = TOKEN_STAR; + break; + default: + break; } } @@ -224,6 +224,36 @@ char *stream_init(void) return stream; } +/* + * @brief: Updates the lexing_mode to LEXER_NORMAL + * if the SECOND quote is found at stream[i]. + * Updates the lexing_mode to the corresponding quote type + * if the FIRST quote of any type is found. + * + * @return: true if an update was done. false otherwise. + */ +static bool update_lexing_mode(char *stream, ssize_t i, + enum lexing_mode *lexing_mode) +{ + enum lexing_mode mode_before_update = *lexing_mode; + // SECOND quote + if (*lexing_mode == LEXER_QUOTE && stream[i] == '\'') + *lexing_mode = LEXER_NORMAL; + if (*lexing_mode == LEXER_DOUBLE_QUOTE && stream[i] == '"') + *lexing_mode = LEXER_NORMAL; + + // FIRST quote + if (*lexing_mode == LEXER_NORMAL) + { + if (stream[i] == '"') + *lexing_mode = LEXER_DOUBLE_QUOTE; + + if (stream[i] == '\'') + *lexing_mode = LEXER_QUOTE; + } + return *lexing_mode != mode_before_update; +} + struct token *peek_token(void) { // we already created the upcoming token during the previous call to peek() @@ -233,20 +263,26 @@ struct token *peek_token(void) } char *stream = stream_init(); - 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 < remaining_chars) { - if (is_special_char(stream[i])) + // true if we didn't encounter a quotes of any type at stream[i] + if (!update_lexing_mode(stream, i, &lexing_mode)) { - if (i == 0) // where we create spe_char token - i++; - break; - } - if (isblank(stream[i])) - { - break; + if (is_special_char(stream[i])) + { + if (i == 0) // where we create spe_char token + i++; + break; + } + if (isblank(stream[i])) + { + break; + } } i++; } @@ -266,20 +302,26 @@ struct token *pop_token(void) return NULL; } char *stream = stream_init(); - 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 < remaining_chars) { - if (is_special_char(stream[i])) + // true if we didn't encounter a quotes of any type at stream[i] + if (!update_lexing_mode(stream, i, &lexing_mode)) { - if (i == 0) // where we create spe_char token - i++; - break; - } - if (isblank(stream[i])) - { - break; + if (is_special_char(stream[i])) + { + if (i == 0) // where we create spe_char token + i++; + break; + } + if (isblank(stream[i])) + { + break; + } } i++; } diff --git a/src/lexer/lexer.h b/src/lexer/lexer.h index 6c561e0..f71907c 100644 --- a/src/lexer/lexer.h +++ b/src/lexer/lexer.h @@ -3,6 +3,13 @@ #include +enum lexing_mode +{ + LEXER_NORMAL, + LEXER_QUOTE, + LEXER_DOUBLE_QUOTE +}; + enum token_type { // Special characters @@ -10,8 +17,12 @@ enum token_type TOKEN_EOF, TOKEN_WORD, TOKEN_NEWLINE, + + // WARNING: quote and double quote should never be used inside a token. + // Its only use is to know if we are inside a quote, and which type of quote TOKEN_QUOTE, TOKEN_DOUBLE_QUOTE, + TOKEN_GRAVE, TOKEN_SEMICOLON, TOKEN_COMMENT, @@ -43,8 +54,6 @@ struct token /* * @brief: returns the next (newly allocated) token without consuming it. - * if end of input is reached, enters in EOF looping node, - * returning only the same token of type TOKEN_EOF. * if end of input is reached, returns a token of type TOKEN_EOF. */ struct token *peek_token(void); @@ -57,7 +66,6 @@ struct token *peek_token(void); * @warning: if the last returned token was a token EOF, it frees it * and returns NULL. This means that after peeking a token EOF * in the parser, there must be EXACTLY ONE call to pop_token(). - * */ struct token *pop_token(void); From bf7b7f7f68033a1050d11526ff064dea70fd7be0 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Mon, 19 Jan 2026 17:35:37 +0100 Subject: [PATCH 115/282] doc(lexer): update --- src/lexer/lexer.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lexer/lexer.h b/src/lexer/lexer.h index f71907c..0da6f17 100644 --- a/src/lexer/lexer.h +++ b/src/lexer/lexer.h @@ -19,7 +19,6 @@ enum token_type TOKEN_NEWLINE, // WARNING: quote and double quote should never be used inside a token. - // Its only use is to know if we are inside a quote, and which type of quote TOKEN_QUOTE, TOKEN_DOUBLE_QUOTE, From ed4c3d475c35f875c60b066e22ebf113789efe43 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Mon, 19 Jan 2026 17:47:29 +0100 Subject: [PATCH 116/282] fix(lexer): lexing_mode depending on quote --- src/lexer/lexer.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index fd54c98..2505649 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -270,8 +270,13 @@ struct token *peek_token(void) while (i < remaining_chars) { - // true if we didn't encounter a quotes of any type at stream[i] - if (!update_lexing_mode(stream, i, &lexing_mode)) + // true if encountered a quotes of any type at stream[i] + if (update_lexing_mode(stream, i, &lexing_mode)) + { + i++; + continue; + } + else { if (is_special_char(stream[i])) { @@ -309,8 +314,13 @@ struct token *pop_token(void) while (i < remaining_chars) { - // true if we didn't encounter a quotes of any type at stream[i] - if (!update_lexing_mode(stream, i, &lexing_mode)) + // true if encountered a quotes of any type at stream[i] + if (update_lexing_mode(stream, i, &lexing_mode)) + { + i++; + continue; + } + else { if (is_special_char(stream[i])) { From 95da4a56fa3fda80761c7453ce1dd2277c252103 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Mon, 19 Jan 2026 17:56:08 +0100 Subject: [PATCH 117/282] Revert "fix(lexer): lexing_mode depending on quote" This reverts commit ed4c3d475c35f875c60b066e22ebf113789efe43. --- src/lexer/lexer.c | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index 2505649..fd54c98 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -270,13 +270,8 @@ struct token *peek_token(void) while (i < remaining_chars) { - // true if encountered a quotes of any type at stream[i] - if (update_lexing_mode(stream, i, &lexing_mode)) - { - i++; - continue; - } - else + // true if we didn't encounter a quotes of any type at stream[i] + if (!update_lexing_mode(stream, i, &lexing_mode)) { if (is_special_char(stream[i])) { @@ -314,13 +309,8 @@ struct token *pop_token(void) while (i < remaining_chars) { - // true if encountered a quotes of any type at stream[i] - if (update_lexing_mode(stream, i, &lexing_mode)) - { - i++; - continue; - } - else + // true if we didn't encounter a quotes of any type at stream[i] + if (!update_lexing_mode(stream, i, &lexing_mode)) { if (is_special_char(stream[i])) { From 454bd76abc82cc9a6ab974b59e88aeb03bd70184 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Mon, 19 Jan 2026 18:19:35 +0100 Subject: [PATCH 118/282] fix(lexer): now really handling quotes --- src/lexer/lexer.c | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index fd54c98..c135497 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -236,11 +236,6 @@ static bool update_lexing_mode(char *stream, ssize_t i, enum lexing_mode *lexing_mode) { enum lexing_mode mode_before_update = *lexing_mode; - // SECOND quote - if (*lexing_mode == LEXER_QUOTE && stream[i] == '\'') - *lexing_mode = LEXER_NORMAL; - if (*lexing_mode == LEXER_DOUBLE_QUOTE && stream[i] == '"') - *lexing_mode = LEXER_NORMAL; // FIRST quote if (*lexing_mode == LEXER_NORMAL) @@ -251,6 +246,16 @@ static bool update_lexing_mode(char *stream, ssize_t i, if (stream[i] == '\'') *lexing_mode = LEXER_QUOTE; } + + // SECOND quote + else + { + if (*lexing_mode == LEXER_QUOTE && stream[i] == '\'') + *lexing_mode = LEXER_NORMAL; + if (*lexing_mode == LEXER_DOUBLE_QUOTE && stream[i] == '"') + *lexing_mode = LEXER_NORMAL; + } + return *lexing_mode != mode_before_update; } @@ -270,8 +275,10 @@ struct token *peek_token(void) while (i < remaining_chars) { - // true if we didn't encounter a quotes of any type at stream[i] - if (!update_lexing_mode(stream, i, &lexing_mode)) + // true if we didn't encounter a quote of any type at stream[i] + // AND we are not inside quotes + if (!update_lexing_mode(stream, i, &lexing_mode) + && lexing_mode == LEXER_NORMAL) { if (is_special_char(stream[i])) { @@ -309,8 +316,10 @@ struct token *pop_token(void) while (i < remaining_chars) { - // true if we didn't encounter a quotes of any type at stream[i] - if (!update_lexing_mode(stream, i, &lexing_mode)) + // true if we didn't encounter a quote of any type at stream[i] + // AND we are not inside quotes + if (!update_lexing_mode(stream, i, &lexing_mode) + && lexing_mode == LEXER_NORMAL) { if (is_special_char(stream[i])) { From e6bd92af3863d401774bd47b3a1ee210094a6ea8 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Mon, 19 Jan 2026 18:52:23 +0100 Subject: [PATCH 119/282] fix(lexer): now raising error on unmatched quote, rather than crashing --- src/lexer/lexer.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index c135497..1a630e3 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -291,6 +291,13 @@ struct token *peek_token(void) break; } } + else if (stream[i] == EOF) + { + fprintf(stderr, "Lexing error: unmatched quote\n"); + + // error handling + return NULL; + } i++; } @@ -332,6 +339,13 @@ struct token *pop_token(void) break; } } + else if (stream[i] == EOF) + { + fprintf(stderr, "Lexing error: unmatched quote\n"); + + // error handling + return NULL; + } i++; } From b443cd1876b452c9a99ad41008a55d1ff4cbe3d6 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Mon, 19 Jan 2026 17:55:16 +0000 Subject: [PATCH 120/282] feat(expansion) 10291 expansion --- src/expansion/expansion.c | 19 +++++++++++++++---- src/utils/vars/vars.c | 23 +++++++++++++++++++++++ src/utils/vars/vars.h | 13 ++++++++++++- tests/unit/expansion/expand.c | 34 +++++++++++++++++++++++++--------- 4 files changed, 75 insertions(+), 14 deletions(-) diff --git a/src/expansion/expansion.c b/src/expansion/expansion.c index dca13ec..407e2f7 100644 --- a/src/expansion/expansion.c +++ b/src/expansion/expansion.c @@ -86,10 +86,21 @@ static bool expand_var(char **str, size_t pos, const struct hash_map *vars) size_t r = parse_var_name(*str + pos, &var_name); if (r > 0 && var_name != NULL) { - char *value = get_var_or_env(vars, var_name); - if (value == NULL) - // Undefined variable: expand to empty string - value = ""; + char *value; + char rnd_str[10]; // max 5 digits + null terminator + if (strcmp(var_name, "RANDOM") == 0) + { + short rnd = short_random(); + snprintf(rnd_str, 10, "%d", rnd); + value = rnd_str; + } + else + { + value = get_var_or_env(vars, var_name); + if (value == NULL) + // Undefined variable: expand to empty string + value = ""; + } char *p = insert_into(*str, value, pos, r); free(var_name); diff --git a/src/utils/vars/vars.c b/src/utils/vars/vars.c index da3afa2..9a4f13f 100644 --- a/src/utils/vars/vars.c +++ b/src/utils/vars/vars.c @@ -2,8 +2,11 @@ #include "vars.h" #include +#include #include #include +#include +#include #include "../hash_map/hash_map.h" @@ -14,6 +17,26 @@ struct hash_map *vars_init(void) return hash_map_init(VARS_INITIAL_SIZE); } +void vars_default(struct hash_map *vars) +{ + set_var_copy(vars, "?", "0"); + pid_t pid = getpid(); + char pid_str[20]; + snprintf(pid_str, sizeof(pid_str), "%d", pid); + set_var_copy(vars, "$$", pid_str); +} + +short short_random(void) +{ + static bool seeded = false; + if (!seeded) + { + srand((unsigned)time(NULL)); + seeded = true; + } + return (short)(rand() & 0x7FFF); // force 16 bits positive +} + char *get_var(const struct hash_map *vars, const char *key) { return (char *)hash_map_get(vars, key); diff --git a/src/utils/vars/vars.h b/src/utils/vars/vars.h index de1c4c6..85fb5ee 100644 --- a/src/utils/vars/vars.h +++ b/src/utils/vars/vars.h @@ -10,6 +10,16 @@ */ struct hash_map *vars_init(void); +/** + * Set default variables. + */ +void vars_default(struct hash_map *vars); + +/** + * Generate a random short integer (16 bits positive [0-32767]). + */ +short short_random(void); + /** * Get the value of a variable, NULL if not found. */ @@ -26,7 +36,8 @@ char *get_var_or_env(const struct hash_map *vars, const char *key); * the hash_map and need to be on the heap. Returns true on success, false on * failure. */ -bool set_var(struct hash_map *vars, const char *key, const char *value); +bool set_var(struct hash_map *vars, const char *key, const char *value, + bool *updated); /** * Same as set_var, but makes copies of key and value so you don't have to worry diff --git a/tests/unit/expansion/expand.c b/tests/unit/expansion/expand.c index 1e25d7c..cb84c80 100644 --- a/tests/unit/expansion/expand.c +++ b/tests/unit/expansion/expand.c @@ -42,7 +42,7 @@ Test(expand, single_quotes_no_expansion) cr_assert_str_eq((char *)command2->command->data, "echo $VAR", "Variable should not expand inside single quotes"); ast_free(&ast); - hash_map_free(vars); + hash_map_free(&vars); } Test(expand, single_dollar) @@ -61,7 +61,7 @@ Test(expand, single_dollar) cr_assert_str_eq((char *)command2->command->data, "echo $ sign", "Variable should not expand inside single quotes"); ast_free(&ast); - hash_map_free(vars); + hash_map_free(&vars); } Test(expand, empty_braces_no_expansion) @@ -78,7 +78,7 @@ Test(expand, empty_braces_no_expansion) struct ast_command *command2 = expand(ast_command, vars); cr_assert_null(command2, "Expansion should fail on empty braces"); ast_free(&ast); - hash_map_free(vars); + hash_map_free(&vars); } Test(expand, basic_expansion) @@ -97,7 +97,7 @@ Test(expand, basic_expansion) cr_assert_str_eq((char *)command2->command->data, "echo expanded", "Variable should expand correctly"); ast_free(&ast); - hash_map_free(vars); + hash_map_free(&vars); } Test(expand, multiple_expansion) @@ -119,7 +119,7 @@ Test(expand, multiple_expansion) "echo expanded values here", "Multiple variables should expand correctly"); ast_free(&ast); - hash_map_free(vars); + hash_map_free(&vars); } Test(expand, env_variable) @@ -154,7 +154,7 @@ Test(expand, undefined_variable) cr_assert_str_eq((char *)command2->command->data, "echo ", "Undefined variable should expand to empty string"); ast_free(&ast); - hash_map_free(vars); + hash_map_free(&vars); } Test(expand, nested_expansion) @@ -174,7 +174,7 @@ Test(expand, nested_expansion) cr_assert_str_eq((char *)command2->command->data, "echo expanded", "Nested variable should expand correctly"); ast_free(&ast); - hash_map_free(vars); + hash_map_free(&vars); } Test(expand, mixed_quotes_expansion) @@ -196,7 +196,7 @@ Test(expand, mixed_quotes_expansion) "Variable in double quotes should expand, while variable " "in single quotes should not"); ast_free(&ast); - hash_map_free(vars); + hash_map_free(&vars); } Test(expand, adjacent_variables) @@ -216,5 +216,21 @@ Test(expand, adjacent_variables) cr_assert_str_eq((char *)command2->command->data, "echo helloworld", "Adjacent variables should expand correctly"); ast_free(&ast); - hash_map_free(vars); + hash_map_free(&vars); +} + +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_command *ast_command = ast_get_command(ast); + + struct ast_command *command2 = expand(ast_command, NULL); + cr_assert_not_null(command2, "Expansion returned NULL"); + int rnd = atoi((char *)command2->command->data); + cr_assert(rnd >= 0 && rnd <= 32767, + "RANDOM variable should expand to a value between 0 and 32767"); + ast_free(&ast); } From c1f1a2fc372975c3cd8ff1bdc2ee5897ce5b6c30 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Mon, 19 Jan 2026 19:15:28 +0100 Subject: [PATCH 121/282] fix(parser): 3098750984535 compilations errors in parsing_utils --- src/parser/parsing_utils.c | 55 ++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/src/parser/parsing_utils.c b/src/parser/parsing_utils.c index 487602d..544b311 100644 --- a/src/parser/parsing_utils.c +++ b/src/parser/parsing_utils.c @@ -1,9 +1,12 @@ +#define _POSIX_C_SOURCE 200809L + // === Includes #include "parsing_utils.h" #include #include #include +#include #include "lexer/lexer.h" #include "utils/ast/ast.h" @@ -11,7 +14,7 @@ // === Static functions /* Returns true if c is a command terminator, false otherwise - */ +*/ static bool isterminator(struct token *token) { if (token == NULL) @@ -19,17 +22,20 @@ static bool isterminator(struct token *token) switch (token->type) { - case TOKEN_NEWLINE: - case TOKEN_SEMICOLON: - case TOKEN_EOF: - return true; - default: - return false; + case TOKEN_NEWLINE: + case TOKEN_SEMICOLON: + case TOKEN_EOF: + return true; + default: + return false; } } /* @brief: returns true if token is an end of list indicator. + * @warning: not used */ + +/* static bool is_end_of_list(struct token *token) { if (token == NULL) @@ -37,13 +43,14 @@ static bool is_end_of_list(struct token *token) switch (token->type) { - case TOKEN_NEWLINE: - case TOKEN_EOF: - return true; - default: - return false; + case TOKEN_NEWLINE: + case TOKEN_EOF: + return true; + default: + return false; } } +*/ // === Functions @@ -150,7 +157,7 @@ struct ast *parse_if_rule(void) if (token->type != TOKEN_IF) { puts("Internal error: expected a if rule but token has different " - "type"); + "type"); return NULL; } @@ -215,24 +222,28 @@ struct ast *parse_compound_list(void) struct token *token = PEEK_TOKEN(); // Skip newlines - while (token == TOKEN_NEWLINE) + while (token->type == TOKEN_NEWLINE) + { token = POP_TOKEN(); + } // and_or current_cmd = parse_and_or(); if (current_cmd == NULL) return NULL; list_append(result_list, current_cmd); - + // Following commands token = PEEK_TOKEN(); - while (token->type == TOKEN_SEMICOLON || token->type TOKEN_NEWLINE) + while (token->type == TOKEN_SEMICOLON || token->type == TOKEN_NEWLINE) { POP_TOKEN(); // Skip newlines - while (token == TOKEN_NEWLINE) + while (token->type == TOKEN_NEWLINE) + { token = POP_TOKEN(); + } // and_or current_cmd = parse_and_or(); @@ -245,12 +256,16 @@ struct ast *parse_compound_list(void) // Eventual semicolons if (token->type == TOKEN_SEMICOLON) + { token = POP_TOKEN(); + } // Skip newlines - while (token == TOKEN_NEWLINE) + while (token->type == TOKEN_NEWLINE) + { token = POP_TOKEN(); - + } + struct ast *result = ast_create_list(result_list); return result; @@ -281,7 +296,7 @@ struct ast *parse_else_clause(void) // Eventual else clause (recursive) struct ast *else_content = NULL; token = PEEK_TOKEN(); - if (token->type == TOKEN_ELSE || token_type == TOKEN_ELIF) + if (token->type == TOKEN_ELSE || token->type == TOKEN_ELIF) { else_content = parse_else_clause(); } From e569439e78e5a175e10e5f01b5fdb3f0ffefd9c0 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Mon, 19 Jan 2026 18:12:28 +0000 Subject: [PATCH 122/282] fix(string_utils): update include sys/types.h -> stddef.h --- src/utils/string_utils/string_utils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/string_utils/string_utils.h b/src/utils/string_utils/string_utils.h index e411f0e..7f0e622 100644 --- a/src/utils/string_utils/string_utils.h +++ b/src/utils/string_utils/string_utils.h @@ -1,7 +1,7 @@ #ifndef STRING_UTILS_H #define STRING_UTILS_H -#include +#include /* * @brief trims leading blank characters (space and tab) from the input string. From e640e7bdbd6806e176f36f5faf35b52e7a3d11a2 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Mon, 19 Jan 2026 18:15:38 +0000 Subject: [PATCH 123/282] fix(ast): update include paths to relative --- src/utils/ast/ast.h | 16 ++++++++-------- src/utils/ast/ast_and_or.c | 2 +- src/utils/ast/ast_and_or.h | 2 +- src/utils/ast/ast_command.c | 4 ++-- src/utils/ast/ast_command.h | 4 ++-- src/utils/ast/ast_end.c | 2 +- src/utils/ast/ast_end.h | 4 ++-- src/utils/ast/ast_if.c | 2 +- src/utils/ast/ast_if.h | 2 +- src/utils/ast/ast_list.c | 2 +- src/utils/ast/ast_list.h | 4 ++-- src/utils/ast/ast_redir.c | 2 +- src/utils/ast/ast_redir.h | 2 +- src/utils/ast/ast_void.c | 2 +- src/utils/ast/ast_void.h | 4 ++-- 15 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/utils/ast/ast.h b/src/utils/ast/ast.h index 5a06cf8..187f748 100644 --- a/src/utils/ast/ast.h +++ b/src/utils/ast/ast.h @@ -1,13 +1,13 @@ #ifndef AST_H #define AST_H -#include "utils/ast/ast_and_or.h" -#include "utils/ast/ast_base.h" -#include "utils/ast/ast_command.h" -#include "utils/ast/ast_end.h" -#include "utils/ast/ast_if.h" -#include "utils/ast/ast_list.h" -#include "utils/ast/ast_redir.h" -#include "utils/ast/ast_void.h" +#include "ast_and_or.h" +#include "ast_base.h" +#include "ast_command.h" +#include "ast_end.h" +#include "ast_if.h" +#include "ast_list.h" +#include "ast_redir.h" +#include "ast_void.h" #endif /* ! AST_H */ diff --git a/src/utils/ast/ast_and_or.c b/src/utils/ast/ast_and_or.c index 6b6038b..9dea5dd 100644 --- a/src/utils/ast/ast_and_or.c +++ b/src/utils/ast/ast_and_or.c @@ -1,4 +1,4 @@ -#include "utils/ast/ast_and_or.h" +#include "ast_and_or.h" #include diff --git a/src/utils/ast/ast_and_or.h b/src/utils/ast/ast_and_or.h index e370558..7bbe76c 100644 --- a/src/utils/ast/ast_and_or.h +++ b/src/utils/ast/ast_and_or.h @@ -3,7 +3,7 @@ #include -#include "utils/ast/ast_base.h" +#include "ast_base.h" enum ast_and_or_type { diff --git a/src/utils/ast/ast_command.c b/src/utils/ast/ast_command.c index 89ed379..e52787e 100644 --- a/src/utils/ast/ast_command.c +++ b/src/utils/ast/ast_command.c @@ -1,10 +1,10 @@ -#include "utils/ast/ast_command.h" +#include "ast_command.h" #include #include #include -#include "utils/lists/lists.h" +#include "../lists/lists.h" struct ast *ast_create_command(struct list *command) { diff --git a/src/utils/ast/ast_command.h b/src/utils/ast/ast_command.h index b83bade..089225f 100644 --- a/src/utils/ast/ast_command.h +++ b/src/utils/ast/ast_command.h @@ -3,8 +3,8 @@ #include -#include "utils/ast/ast_base.h" -#include "utils/lists/lists.h" +#include "../lists/lists.h" +#include "ast_base.h" struct ast_command { diff --git a/src/utils/ast/ast_end.c b/src/utils/ast/ast_end.c index a74fdb3..fbde72f 100644 --- a/src/utils/ast/ast_end.c +++ b/src/utils/ast/ast_end.c @@ -1,4 +1,4 @@ -#include "utils/ast/ast_end.h" +#include "ast_end.h" #include #include diff --git a/src/utils/ast/ast_end.h b/src/utils/ast/ast_end.h index df24d8b..d846cad 100644 --- a/src/utils/ast/ast_end.h +++ b/src/utils/ast/ast_end.h @@ -3,8 +3,8 @@ #include -#include "utils/ast/ast_base.h" -#include "utils/lists/lists.h" +#include "../lists/lists.h" +#include "ast_base.h" /** * Checks if the given AST node is of type AST_END. diff --git a/src/utils/ast/ast_if.c b/src/utils/ast/ast_if.c index 400a390..cff0320 100644 --- a/src/utils/ast/ast_if.c +++ b/src/utils/ast/ast_if.c @@ -1,4 +1,4 @@ -#include "utils/ast/ast_if.h" +#include "ast_if.h" #include #include diff --git a/src/utils/ast/ast_if.h b/src/utils/ast/ast_if.h index 51c1844..f54a795 100644 --- a/src/utils/ast/ast_if.h +++ b/src/utils/ast/ast_if.h @@ -3,7 +3,7 @@ #include -#include "utils/ast/ast_base.h" +#include "ast_base.h" struct ast_if { diff --git a/src/utils/ast/ast_list.c b/src/utils/ast/ast_list.c index 0455698..2b96a23 100644 --- a/src/utils/ast/ast_list.c +++ b/src/utils/ast/ast_list.c @@ -1,6 +1,6 @@ #include -#include "utils/ast/ast.h" +#include "ast.h" struct ast *ast_create_list(struct list *list) { diff --git a/src/utils/ast/ast_list.h b/src/utils/ast/ast_list.h index 591b045..ee98ea5 100644 --- a/src/utils/ast/ast_list.h +++ b/src/utils/ast/ast_list.h @@ -3,8 +3,8 @@ #include -#include "utils/ast/ast_base.h" -#include "utils/lists/lists.h" +#include "../lists/lists.h" +#include "ast_base.h" struct ast_list { diff --git a/src/utils/ast/ast_redir.c b/src/utils/ast/ast_redir.c index e4c92f3..8f0b1e9 100644 --- a/src/utils/ast/ast_redir.c +++ b/src/utils/ast/ast_redir.c @@ -1,4 +1,4 @@ -#include "utils/ast/ast_redir.h" +#include "ast_redir.h" #include diff --git a/src/utils/ast/ast_redir.h b/src/utils/ast/ast_redir.h index 65be4a2..4b2eb63 100644 --- a/src/utils/ast/ast_redir.h +++ b/src/utils/ast/ast_redir.h @@ -3,7 +3,7 @@ #include -#include "utils/ast/ast_base.h" +#include "ast_base.h" enum ast_redir_type { diff --git a/src/utils/ast/ast_void.c b/src/utils/ast/ast_void.c index 3368c7d..213413b 100644 --- a/src/utils/ast/ast_void.c +++ b/src/utils/ast/ast_void.c @@ -1,4 +1,4 @@ -#include "utils/ast/ast_void.h" +#include "ast_void.h" #include #include diff --git a/src/utils/ast/ast_void.h b/src/utils/ast/ast_void.h index 6abaef8..ecbdcf8 100644 --- a/src/utils/ast/ast_void.h +++ b/src/utils/ast/ast_void.h @@ -3,8 +3,8 @@ #include -#include "utils/ast/ast_base.h" -#include "utils/lists/lists.h" +#include "../lists/lists.h" +#include "ast_base.h" /** * Checks if the given AST node is of type AST_VOID. From 868b1cae175c164367884ef48a97796c275a5473 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Mon, 19 Jan 2026 18:05:00 +0000 Subject: [PATCH 124/282] fix(vars): vars_default var name $$ -> $ --- src/utils/vars/vars.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/vars/vars.c b/src/utils/vars/vars.c index 9a4f13f..8d762da 100644 --- a/src/utils/vars/vars.c +++ b/src/utils/vars/vars.c @@ -23,7 +23,7 @@ void vars_default(struct hash_map *vars) pid_t pid = getpid(); char pid_str[20]; snprintf(pid_str, sizeof(pid_str), "%d", pid); - set_var_copy(vars, "$$", pid_str); + set_var_copy(vars, "$", pid_str); } short short_random(void) From 913e3bb5f507fa9e9f29ccaf38756fc2766ebb0f Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Mon, 19 Jan 2026 18:05:42 +0000 Subject: [PATCH 125/282] feat(expansion): add special var expansion tests $$ and $? --- tests/unit/expansion/expand.c | 41 +++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/unit/expansion/expand.c b/tests/unit/expansion/expand.c index cb84c80..fd4295d 100644 --- a/tests/unit/expansion/expand.c +++ b/tests/unit/expansion/expand.c @@ -3,6 +3,7 @@ #include #include #include +#include #include "../../../src/expansion/expansion.h" #include "../../../src/utils/ast/ast.h" @@ -234,3 +235,43 @@ Test(expand, random) "RANDOM variable should expand to a value between 0 and 32767"); ast_free(&ast); } + +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_command *ast_command = ast_get_command(ast); + + struct hash_map *vars = vars_init(); + vars_default(vars); + + struct ast_command *command2 = expand(ast_command, vars); + cr_assert_not_null(command2, "Expansion returned NULL"); + int pid = atoi((char *)command2->command->data); + cr_assert(pid == getpid(), + "$$ variable should expand to the pid of the process"); + ast_free(&ast); + hash_map_free(&vars); +} + +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_command *ast_command = ast_get_command(ast); + + struct hash_map *vars = vars_init(); + vars_default(vars); + + struct ast_command *command2 = expand(ast_command, vars); + cr_assert_not_null(command2, "Expansion returned NULL"); + int code = atoi((char *)command2->command->data); + cr_assert(code == 0, + "$? variable should expand to the last exit code (default 0)"); + ast_free(&ast); + hash_map_free(&vars); +} From bbb69fddcada9111613c992c1af40d67d1c7b1dc Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Tue, 20 Jan 2026 18:59:40 +0100 Subject: [PATCH 126/282] refactor(lexer): now use struct instead of static var --- src/lexer/lexer.c | 67 ++++++++++++++++++++++------------------------- src/lexer/lexer.h | 15 ++++++++--- 2 files changed, 43 insertions(+), 39 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index 1a630e3..bec5e24 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -10,42 +10,37 @@ #include "io_backend/io_backend.h" #include "utils/string_utils/string_utils.h" -static char *end_last_token; -static ssize_t remaining_chars; -static struct token *last_token; -static struct token *current_token; - -/* @brief: sets the current_token to [tok]. +/* @brief: sets the ctx->current_token to [tok]. * this function is called by token_peek(). */ -static void update_current_token(struct token *tok) +static void update_ctx->current_token(struct token *tok, struct lexer_context *ctx) { - current_token = tok; + ctx->current_token = tok; } /* @brief: frees the last token and sets it to [tok]. - * Also sets current_token to NULL. + * Also sets ctx->current_token to NULL. * this function is called by token_pop(). */ -static void update_last_token(struct token *tok) +static void update_ctx->previous_token(struct token *tok, struct lexer_context *ctx) { - free_token(&last_token); - last_token = tok; + free_token(&ctx->previous_token); + ctx->previous_token = tok; } /* @brief: updates the current position in the stream. * [stream] += [i] - * Also frees the last sent token, and sets it to current_token. + * Also frees the last sent token, and sets it to ctx->current_token. * Current token is then set to NULL. * This function is called by token_pop(). */ -static void save_state(char *stream, ssize_t i) +static void save_state(char *stream, ssize_t i, struct lexer_context *ctx) { - remaining_chars -= i; - end_last_token = stream + i; + ctx->remaining_chars -= i; + ctx->end_ctx->previous_token = stream + i; - update_last_token(current_token); - update_current_token(NULL); + update_ctx->previous_token(ctx->current_token); + update_ctx->current_token(NULL); } /* @return: true if a special character from the grammar was found, @@ -204,21 +199,21 @@ void free_token(struct token **tok) *tok = NULL; } -char *stream_init(void) +char *stream_init(struct lexer_context *ctx) { char *stream; - if (last_token == NULL) // at the begining + if (ctx->previous_token == NULL) // at the begining { - remaining_chars = stream_read(&stream); + ctx->remaining_chars = stream_read(&stream); } else { - stream = end_last_token; + stream = ctx->end_ctx->previous_token; } char *trimed_stream = trim_blank_left(stream); - remaining_chars -= trimed_stream - stream; + ctx->remaining_chars -= trimed_stream - stream; stream = trimed_stream; return stream; @@ -259,12 +254,12 @@ static bool update_lexing_mode(char *stream, ssize_t i, return *lexing_mode != mode_before_update; } -struct token *peek_token(void) +struct token *peek_token(struct lexer_context *ctx) { // we already created the upcoming token during the previous call to peek() - if (current_token != NULL) + if (ctx->current_token != NULL) { - return current_token; + return ctx->current_token; } char *stream = stream_init(); @@ -273,7 +268,7 @@ struct token *peek_token(void) // Usefull to know if we are inside a quote or double quote enum lexing_mode lexing_mode = LEXER_NORMAL; - while (i < remaining_chars) + while (i < ctx->remaining_chars) { // true if we didn't encounter a quote of any type at stream[i] // AND we are not inside quotes @@ -302,17 +297,17 @@ struct token *peek_token(void) } struct token *tok = new_token(stream, i); - update_current_token(tok); + update_ctx->current_token(tok); return tok; } -struct token *pop_token(void) +struct token *pop_token(struct lexer_context *ctx) { - if (current_token != NULL && current_token->type == TOKEN_EOF) + if (ctx->current_token != NULL && ctx->current_token->type == TOKEN_EOF) { // we reached end of input, frees all the token still allocated. - free_token(&last_token); - free_token(¤t_token); + free_token(&ctx->previous_token); + free_token(&ctx->current_token); return NULL; } char *stream = stream_init(); @@ -321,7 +316,7 @@ struct token *pop_token(void) // Usefull to know if we are inside a quote or double quote enum lexing_mode lexing_mode = LEXER_NORMAL; - while (i < remaining_chars) + while (i < ctx->remaining_chars) { // true if we didn't encounter a quote of any type at stream[i] // AND we are not inside quotes @@ -351,11 +346,11 @@ struct token *pop_token(void) // just in case peek() was not called before poping. // (this should never happen) - if (current_token == NULL) + if (ctx->current_token == NULL) { - current_token = new_token(stream, i); + ctx->current_token = new_token(stream, i); } save_state(stream, i); - return last_token; + return ctx->previous_token; } diff --git a/src/lexer/lexer.h b/src/lexer/lexer.h index 0da6f17..95c5504 100644 --- a/src/lexer/lexer.h +++ b/src/lexer/lexer.h @@ -3,6 +3,15 @@ #include +struct lexer_context +{ + char *end_last_token; + ssize_t remaining_chars; + + struct token *last_token; + struct token *current_token; +}; + enum lexing_mode { LEXER_NORMAL, @@ -55,7 +64,7 @@ struct token * @brief: returns the next (newly allocated) token without consuming it. * if end of input is reached, returns a token of type TOKEN_EOF. */ -struct token *peek_token(void); +struct token *peek_token(struct lexer_context *ctx); /* * @brief: returns the next (newly allocated) token and consumes it. @@ -66,7 +75,7 @@ struct token *peek_token(void); * and returns NULL. This means that after peeking a token EOF * in the parser, there must be EXACTLY ONE call to pop_token(). */ -struct token *pop_token(void); +struct token *pop_token(struct lexer_context *ctx); /* @note: maybe usefull for subshells. * @@ -96,6 +105,6 @@ void free_token(struct token **tok); * * @return: char* stream from which we tokenise. */ -char *stream_init(void); +char *stream_init(struct lexer_context *ctx); #endif /* ! LEXER_H */ From 603def159756ed07bfbb2335f32e1e56800b52e5 Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Tue, 20 Jan 2026 19:25:55 +0100 Subject: [PATCH 127/282] fix: includes with relative path and memory leaks --- src/parser/parsing_utils.c | 57 +++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/src/parser/parsing_utils.c b/src/parser/parsing_utils.c index 1841c0c..ea1b113 100644 --- a/src/parser/parsing_utils.c +++ b/src/parser/parsing_utils.c @@ -8,13 +8,13 @@ #include #include -#include "lexer/lexer.h" -#include "utils/ast/ast.h" +#include "../lexer/lexer.h" +#include "../utils/ast/ast.h" // === Static functions /* Returns true if c is a command terminator, false otherwise -*/ + */ static bool isterminator(struct token *token) { if (token == NULL) @@ -22,12 +22,12 @@ static bool isterminator(struct token *token) switch (token->type) { - case TOKEN_NEWLINE: - case TOKEN_SEMICOLON: - case TOKEN_EOF: - return true; - default: - return false; + case TOKEN_NEWLINE: + case TOKEN_SEMICOLON: + case TOKEN_EOF: + return true; + default: + return false; } } @@ -70,7 +70,7 @@ struct ast *parse_list(void) current_node = parse_and_or(); if (current_node == NULL) return NULL; - list_append(result_list, current_node); + result_list = list_append(result_list, current_node); // Following and_or commands token = PEEK_TOKEN(); @@ -82,11 +82,11 @@ struct ast *parse_list(void) current_node = parse_and_or(); if (current_node == NULL) { - //TODO free list - // There must be a function for that + // TODO free list + // There must be a function for that return NULL; } - list_append(result_list, current_node); + result_list = list_append(result_list, current_node); token = PEEK_TOKEN(); } } @@ -135,17 +135,37 @@ struct ast *parse_simple_command(void) while (token->type == TOKEN_WORD) { - token = POP_TOKEN(); - char* word = strdup(token->data); + token = pop_token(); + if (token == NULL) + { + // TODO free + return NULL; + } + char *word = strdup(token->data); + if (word == NULL) + { + // TODO free + puts("Internal error: Couldn't copy token content (is memory full " + "?)"); + return NULL; + } command_elements = list_append(command_elements, word); - token = PEEK_TOKEN(); + token = peek_token(); + if (token == NULL) + { + // TODO free + return NULL; + } } struct ast *result = ast_create_command(command_elements); + if (result == NULL) + { + // TODO free + } return result; } -// TODO check compliance with the grammar struct ast *parse_shell_command(void) { return parse_if_rule(); @@ -158,7 +178,7 @@ struct ast *parse_if_rule(void) if (token->type != TOKEN_IF) { puts("Internal error: expected a if rule but token has different " - "type"); + "type"); return NULL; } @@ -267,7 +287,6 @@ struct ast *parse_compound_list(void) token = POP_TOKEN(); } - struct ast *result = ast_create_list(result_list); return result; } From 927e4ea25aed8f31ea739ce400d0a0443498715f Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Tue, 20 Jan 2026 19:29:24 +0100 Subject: [PATCH 128/282] fix(lexer): typo in func name --- src/lexer/lexer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index bec5e24..c5bd7a5 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -13,7 +13,7 @@ /* @brief: sets the ctx->current_token to [tok]. * this function is called by token_peek(). */ -static void update_ctx->current_token(struct token *tok, struct lexer_context *ctx) +static void update_current_token(struct token *tok, struct lexer_context *ctx) { ctx->current_token = tok; } @@ -22,7 +22,7 @@ static void update_ctx->current_token(struct token *tok, struct lexer_context *c * Also sets ctx->current_token to NULL. * this function is called by token_pop(). */ -static void update_ctx->previous_token(struct token *tok, struct lexer_context *ctx) +static void update_previous_token(struct token *tok, struct lexer_context *ctx) { free_token(&ctx->previous_token); ctx->previous_token = tok; From d59b72dc45d61f475198475d311e28f6386f23a4 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Tue, 20 Jan 2026 19:40:00 +0100 Subject: [PATCH 129/282] feat(lexer): destroy_lexer_context --- src/lexer/lexer.c | 26 +++++++++++++++++++++----- src/lexer/lexer.h | 8 ++++++-- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index c5bd7a5..16c8752 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -10,6 +10,8 @@ #include "io_backend/io_backend.h" #include "utils/string_utils/string_utils.h" +// ######## STATIC FUNCTIONS ############## + /* @brief: sets the ctx->current_token to [tok]. * this function is called by token_peek(). */ @@ -37,10 +39,10 @@ static void update_previous_token(struct token *tok, struct lexer_context *ctx) static void save_state(char *stream, ssize_t i, struct lexer_context *ctx) { ctx->remaining_chars -= i; - ctx->end_ctx->previous_token = stream + i; + ctx->end_previous_token = stream + i; - update_ctx->previous_token(ctx->current_token); - update_ctx->current_token(NULL); + update_previous_token(ctx->current_token); + update_current_token(NULL); } /* @return: true if a special character from the grammar was found, @@ -176,6 +178,20 @@ static void set_token_word(struct token *tok, char *begin, ssize_t size) } } +void destroy_lexer_context(struct lexer_context **ctx) +{ + if (ctx == NULL || *ctx == NULL) + return; + if (ctx->end_previous_token != NULL) + free(ctx->end_previous_token); + if (ctx->previous_token != NULL) + free(ctx->previous_token); + if (ctx->current_token != NULL) + free(ctx->current_token); + free(*ctx); + *ctx = NULL; +} + struct token *new_token(char *begin, ssize_t size) { struct token *tok = calloc(1, sizeof(struct token)); @@ -209,7 +225,7 @@ char *stream_init(struct lexer_context *ctx) } else { - stream = ctx->end_ctx->previous_token; + stream = ctx->end_previous_token; } char *trimed_stream = trim_blank_left(stream); @@ -297,7 +313,7 @@ struct token *peek_token(struct lexer_context *ctx) } struct token *tok = new_token(stream, i); - update_ctx->current_token(tok); + update_current_token(tok); return tok; } diff --git a/src/lexer/lexer.h b/src/lexer/lexer.h index 95c5504..50003b2 100644 --- a/src/lexer/lexer.h +++ b/src/lexer/lexer.h @@ -5,13 +5,17 @@ struct lexer_context { - char *end_last_token; + char *end_previous_token; ssize_t remaining_chars; - struct token *last_token; + struct token *previous_token; struct token *current_token; }; +/* @brief: frees all fields of ctx and sets ctx to NULL. + */ +void destroy_lexer_context(struct lexer_context **ctx); + enum lexing_mode { LEXER_NORMAL, From f1955f0532d425188362b6cc37677f7b209aa750 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Tue, 20 Jan 2026 18:59:40 +0100 Subject: [PATCH 130/282] refactor(lexer): now use struct instead of static var --- src/lexer/lexer.c | 67 ++++++++++++++++++++++------------------------- src/lexer/lexer.h | 15 ++++++++--- 2 files changed, 43 insertions(+), 39 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index 1a630e3..bec5e24 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -10,42 +10,37 @@ #include "io_backend/io_backend.h" #include "utils/string_utils/string_utils.h" -static char *end_last_token; -static ssize_t remaining_chars; -static struct token *last_token; -static struct token *current_token; - -/* @brief: sets the current_token to [tok]. +/* @brief: sets the ctx->current_token to [tok]. * this function is called by token_peek(). */ -static void update_current_token(struct token *tok) +static void update_ctx->current_token(struct token *tok, struct lexer_context *ctx) { - current_token = tok; + ctx->current_token = tok; } /* @brief: frees the last token and sets it to [tok]. - * Also sets current_token to NULL. + * Also sets ctx->current_token to NULL. * this function is called by token_pop(). */ -static void update_last_token(struct token *tok) +static void update_ctx->previous_token(struct token *tok, struct lexer_context *ctx) { - free_token(&last_token); - last_token = tok; + free_token(&ctx->previous_token); + ctx->previous_token = tok; } /* @brief: updates the current position in the stream. * [stream] += [i] - * Also frees the last sent token, and sets it to current_token. + * Also frees the last sent token, and sets it to ctx->current_token. * Current token is then set to NULL. * This function is called by token_pop(). */ -static void save_state(char *stream, ssize_t i) +static void save_state(char *stream, ssize_t i, struct lexer_context *ctx) { - remaining_chars -= i; - end_last_token = stream + i; + ctx->remaining_chars -= i; + ctx->end_ctx->previous_token = stream + i; - update_last_token(current_token); - update_current_token(NULL); + update_ctx->previous_token(ctx->current_token); + update_ctx->current_token(NULL); } /* @return: true if a special character from the grammar was found, @@ -204,21 +199,21 @@ void free_token(struct token **tok) *tok = NULL; } -char *stream_init(void) +char *stream_init(struct lexer_context *ctx) { char *stream; - if (last_token == NULL) // at the begining + if (ctx->previous_token == NULL) // at the begining { - remaining_chars = stream_read(&stream); + ctx->remaining_chars = stream_read(&stream); } else { - stream = end_last_token; + stream = ctx->end_ctx->previous_token; } char *trimed_stream = trim_blank_left(stream); - remaining_chars -= trimed_stream - stream; + ctx->remaining_chars -= trimed_stream - stream; stream = trimed_stream; return stream; @@ -259,12 +254,12 @@ static bool update_lexing_mode(char *stream, ssize_t i, return *lexing_mode != mode_before_update; } -struct token *peek_token(void) +struct token *peek_token(struct lexer_context *ctx) { // we already created the upcoming token during the previous call to peek() - if (current_token != NULL) + if (ctx->current_token != NULL) { - return current_token; + return ctx->current_token; } char *stream = stream_init(); @@ -273,7 +268,7 @@ struct token *peek_token(void) // Usefull to know if we are inside a quote or double quote enum lexing_mode lexing_mode = LEXER_NORMAL; - while (i < remaining_chars) + while (i < ctx->remaining_chars) { // true if we didn't encounter a quote of any type at stream[i] // AND we are not inside quotes @@ -302,17 +297,17 @@ struct token *peek_token(void) } struct token *tok = new_token(stream, i); - update_current_token(tok); + update_ctx->current_token(tok); return tok; } -struct token *pop_token(void) +struct token *pop_token(struct lexer_context *ctx) { - if (current_token != NULL && current_token->type == TOKEN_EOF) + if (ctx->current_token != NULL && ctx->current_token->type == TOKEN_EOF) { // we reached end of input, frees all the token still allocated. - free_token(&last_token); - free_token(¤t_token); + free_token(&ctx->previous_token); + free_token(&ctx->current_token); return NULL; } char *stream = stream_init(); @@ -321,7 +316,7 @@ struct token *pop_token(void) // Usefull to know if we are inside a quote or double quote enum lexing_mode lexing_mode = LEXER_NORMAL; - while (i < remaining_chars) + while (i < ctx->remaining_chars) { // true if we didn't encounter a quote of any type at stream[i] // AND we are not inside quotes @@ -351,11 +346,11 @@ struct token *pop_token(void) // just in case peek() was not called before poping. // (this should never happen) - if (current_token == NULL) + if (ctx->current_token == NULL) { - current_token = new_token(stream, i); + ctx->current_token = new_token(stream, i); } save_state(stream, i); - return last_token; + return ctx->previous_token; } diff --git a/src/lexer/lexer.h b/src/lexer/lexer.h index 0da6f17..95c5504 100644 --- a/src/lexer/lexer.h +++ b/src/lexer/lexer.h @@ -3,6 +3,15 @@ #include +struct lexer_context +{ + char *end_last_token; + ssize_t remaining_chars; + + struct token *last_token; + struct token *current_token; +}; + enum lexing_mode { LEXER_NORMAL, @@ -55,7 +64,7 @@ struct token * @brief: returns the next (newly allocated) token without consuming it. * if end of input is reached, returns a token of type TOKEN_EOF. */ -struct token *peek_token(void); +struct token *peek_token(struct lexer_context *ctx); /* * @brief: returns the next (newly allocated) token and consumes it. @@ -66,7 +75,7 @@ struct token *peek_token(void); * and returns NULL. This means that after peeking a token EOF * in the parser, there must be EXACTLY ONE call to pop_token(). */ -struct token *pop_token(void); +struct token *pop_token(struct lexer_context *ctx); /* @note: maybe usefull for subshells. * @@ -96,6 +105,6 @@ void free_token(struct token **tok); * * @return: char* stream from which we tokenise. */ -char *stream_init(void); +char *stream_init(struct lexer_context *ctx); #endif /* ! LEXER_H */ From c3ab2585e1007e57499d13a09fdc62e166d7d070 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Tue, 20 Jan 2026 19:54:29 +0100 Subject: [PATCH 131/282] feat(parser): adapted to new lexer without static var --- src/parser/parser.c | 8 +++++--- src/parser/parser.h | 2 +- src/parser/parsing_utils.c | 20 ++++++++++---------- src/parser/parsing_utils.h | 30 ++++++++++++++---------------- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/parser/parser.c b/src/parser/parser.c index cf7b052..d1fd70a 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -17,22 +17,23 @@ struct ast *get_ast() { + struct lexer_context *ctx = calloc(1, sizeof(struct lexer_context)); struct token *token = PEEK_TOKEN(); struct ast *res; if (token->type == TOKEN_EOF) { - token = pop_token(); + token = pop_token(ctx); return ast_create_end(); } else if (token->type == TOKEN_NEWLINE) { - token = pop_token(); + token = pop_token(ctx); return ast_create_void(); } else // TOKEN WORD { - res = parse_list(); + res = parse_list(ctx); } /* @@ -43,6 +44,7 @@ struct ast *get_ast() return NULL; } */ + destroy_lexer_context(&ctx); return res; } diff --git a/src/parser/parser.h b/src/parser/parser.h index d10cae8..5089212 100644 --- a/src/parser/parser.h +++ b/src/parser/parser.h @@ -10,7 +10,7 @@ * * @warning NOT IMPLEMENTED */ -struct ast *get_ast(); +struct ast *get_ast(void); /* @brief Builds the AST representation of the given command string. * diff --git a/src/parser/parsing_utils.c b/src/parser/parsing_utils.c index ea1b113..12b0f9b 100644 --- a/src/parser/parsing_utils.c +++ b/src/parser/parsing_utils.c @@ -54,12 +54,12 @@ static bool is_end_of_list(struct token *token) // === Functions -struct ast *parse_input(void) +struct ast *parse_input(struct lexer_context *ctx) { return parse_list(); } -struct ast *parse_list(void) +struct ast *parse_list(struct lexer_context *ctx) { struct list *result_list = NULL; struct ast *current_node = NULL; @@ -95,17 +95,17 @@ struct ast *parse_list(void) return ast_create_list(result_list); } -struct ast *parse_and_or(void) +struct ast *parse_and_or(struct lexer_context *ctx) { return parse_pipeline(); } -struct ast *parse_pipeline(void) +struct ast *parse_pipeline(struct lexer_context *ctx) { return parse_command(); } -struct ast *parse_command(void) +struct ast *parse_command(struct lexer_context *ctx) { struct token *token = PEEK_TOKEN(); @@ -123,7 +123,7 @@ struct ast *parse_command(void) } } -struct ast *parse_simple_command(void) +struct ast *parse_simple_command(struct lexer_context *ctx) { struct list *command_elements = NULL; struct token *token = PEEK_TOKEN(); @@ -166,12 +166,12 @@ struct ast *parse_simple_command(void) return result; } -struct ast *parse_shell_command(void) +struct ast *parse_shell_command(struct lexer_context *ctx) { return parse_if_rule(); } -struct ast *parse_if_rule(void) +struct ast *parse_if_rule(struct lexer_context *ctx) { // If keyword struct token *token = POP_TOKEN(); @@ -236,7 +236,7 @@ struct ast *parse_if_rule(void) return result; } -struct ast *parse_compound_list(void) +struct ast *parse_compound_list(struct lexer_context *ctx) { struct list *result_list = NULL; // ast* list struct ast *current_cmd = NULL; @@ -291,7 +291,7 @@ struct ast *parse_compound_list(void) return result; } -struct ast *parse_else_clause(void) +struct ast *parse_else_clause(struct lexer_context *ctx) { struct token *token = PEEK_TOKEN(); diff --git a/src/parser/parsing_utils.h b/src/parser/parsing_utils.h index 5464981..5b32490 100644 --- a/src/parser/parsing_utils.h +++ b/src/parser/parsing_utils.h @@ -4,7 +4,7 @@ // === Macros #define PEEK_TOKEN() \ - peek_token(); \ + peek_token(ctx); \ if (token == NULL) \ { \ puts("Internal error: cannot get the following token"); \ @@ -12,7 +12,7 @@ } #define POP_TOKEN() \ - pop_token(); \ + pop_token(ctx); \ if (token == NULL) \ { \ puts("Internal error: cannot get the following token"); \ @@ -27,26 +27,26 @@ * | EOF * ; */ -struct ast* parse_input(void); +struct ast *parse_input(struct lexer_context *ctx); /* @brief: parses a list of [and_or] rules separated by semicolons and that * ends by a newline * * @code list = and_or { ';' and_or } [ ';' ] ; */ -struct ast *parse_list(void); +struct ast *parse_list(struct lexer_context *ctx); /* @brief Only parses a pipeline rule for the moment * * @code and_or = pipeline ; */ -struct ast *parse_and_or(void); +struct ast *parse_and_or(struct lexer_context *ctx); /* @brief Only parses a command rule for the moment * * @code pipeline = command ; */ -struct ast* parse_pipeline(void); +struct ast *parse_pipeline(struct lexer_context *ctx); /* @brief Parses a simple command rule or a shell command rule depending on * the first token. @@ -58,36 +58,34 @@ struct ast* parse_pipeline(void); * | shell_command * ; */ -struct ast* parse_command(void); +struct ast *parse_command(struct lexer_context *ctx); /* @brief Parses a simple list of words (command and arguments) * ending by a separator * * @code simple_command = WORD { element } ; */ -struct ast *parse_simple_command(void); - +struct ast *parse_simple_command(struct lexer_context *ctx); /* @brief Only parses if rules for the moment * * @code shell_command = if_rule ; */ -struct ast *parse_shell_command(void); - +struct ast *parse_shell_command(struct lexer_context *ctx); /* @brief Parses a if rule (condition, then-clause, elif-clause, else-clause) * * @code if_rule = 'if' compound_list 'then' compound_list [else_clause] 'fi' ; */ -struct ast *parse_if_rule(void); - +struct ast *parse_if_rule(struct lexer_context *ctx); /* @brief parses commands inside if/else clauses and returns the corresponding * AST list * - * @code compound_list = {'\n'} and_or { ( ';' | '\n' ) {'\n'} and_or } [';'] {'\n'} ; + * @code compound_list = {'\n'} and_or { ( ';' | '\n' ) {'\n'} and_or } [';'] + * {'\n'} ; */ -struct ast* parse_compound_list(void); +struct ast *parse_compound_list(struct lexer_context *ctx); /* @brief * @@ -95,6 +93,6 @@ struct ast* parse_compound_list(void); * | 'elif' compound_list 'then' compound_list [else_clause] * ; */ -struct ast *parse_else_clause(void); +struct ast *parse_else_clause(struct lexer_context *ctx); #endif /* ! PARSING_UTILS_H */ From d5a1ec3ca6f0accd62cc052acd86b8b1beca3820 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Tue, 20 Jan 2026 20:06:25 +0100 Subject: [PATCH 132/282] fix(parser): adapting to new lexer --- src/parser/parsing_utils.c | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/parser/parsing_utils.c b/src/parser/parsing_utils.c index 12b0f9b..08ef540 100644 --- a/src/parser/parsing_utils.c +++ b/src/parser/parsing_utils.c @@ -56,7 +56,7 @@ static bool is_end_of_list(struct token *token) struct ast *parse_input(struct lexer_context *ctx) { - return parse_list(); + return parse_list(ctx); } struct ast *parse_list(struct lexer_context *ctx) @@ -79,7 +79,7 @@ struct ast *parse_list(struct lexer_context *ctx) token = POP_TOKEN(); if (!isterminator(token)) // Follow(list) { - current_node = parse_and_or(); + current_node = parse_and_or(ctx); if (current_node == NULL) { // TODO free list @@ -97,12 +97,12 @@ struct ast *parse_list(struct lexer_context *ctx) struct ast *parse_and_or(struct lexer_context *ctx) { - return parse_pipeline(); + return parse_pipeline(ctx); } struct ast *parse_pipeline(struct lexer_context *ctx) { - return parse_command(); + return parse_command(ctx); } struct ast *parse_command(struct lexer_context *ctx) @@ -111,11 +111,11 @@ struct ast *parse_command(struct lexer_context *ctx) if (token->type == TOKEN_WORD) { - return parse_simple_command(); + return parse_simple_command(ctx); } else if (token->type == TOKEN_IF) { - return parse_shell_command(); + return parse_shell_command(ctx); } else { @@ -135,7 +135,7 @@ struct ast *parse_simple_command(struct lexer_context *ctx) while (token->type == TOKEN_WORD) { - token = pop_token(); + token = pop_token(ctx); if (token == NULL) { // TODO free @@ -150,7 +150,7 @@ struct ast *parse_simple_command(struct lexer_context *ctx) return NULL; } command_elements = list_append(command_elements, word); - token = peek_token(); + token = peek_token(ctx); if (token == NULL) { // TODO free @@ -168,7 +168,7 @@ struct ast *parse_simple_command(struct lexer_context *ctx) struct ast *parse_shell_command(struct lexer_context *ctx) { - return parse_if_rule(); + return parse_if_rule(ctx); } struct ast *parse_if_rule(struct lexer_context *ctx) @@ -183,7 +183,7 @@ struct ast *parse_if_rule(struct lexer_context *ctx) } // Condition content - struct ast *condition_content = parse_compound_list(); + struct ast *condition_content = parse_compound_list(ctx); // Then keyword token = POP_TOKEN(); @@ -195,7 +195,7 @@ struct ast *parse_if_rule(struct lexer_context *ctx) } // Then content - struct ast *then_content = parse_compound_list(); + struct ast *then_content = parse_compound_list(ctx); if (then_content == NULL) { ast_free(&condition_content); @@ -204,7 +204,7 @@ struct ast *parse_if_rule(struct lexer_context *ctx) } // Eventual else/elif clause(s) - struct ast *else_content = parse_else_clause(); + struct ast *else_content = parse_else_clause(ctx); if (else_content == NULL) { ast_free(&condition_content); @@ -249,7 +249,7 @@ struct ast *parse_compound_list(struct lexer_context *ctx) } // and_or - current_cmd = parse_and_or(); + current_cmd = parse_and_or(ctx); if (current_cmd == NULL) return NULL; list_append(result_list, current_cmd); @@ -267,7 +267,7 @@ struct ast *parse_compound_list(struct lexer_context *ctx) } // and_or - current_cmd = parse_and_or(); + current_cmd = parse_and_or(ctx); if (current_cmd == NULL) return NULL; list_append(result_list, current_cmd); @@ -300,7 +300,7 @@ struct ast *parse_else_clause(struct lexer_context *ctx) { // Condition token = POP_TOKEN(); - struct ast *condition = parse_compound_list(); + struct ast *condition = parse_compound_list(ctx); // 'then' token = POP_TOKEN(); @@ -311,14 +311,14 @@ struct ast *parse_else_clause(struct lexer_context *ctx) } // Then clause - struct ast *then_content = parse_compound_list(); + struct ast *then_content = parse_compound_list(ctx); // 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(); + else_content = parse_else_clause(ctx); } struct ast *result = @@ -332,12 +332,12 @@ struct ast *parse_else_clause(struct lexer_context *ctx) if (token->type == TOKEN_ELSE) { - result = parse_compound_list(); + result = parse_compound_list(ctx); token = POP_TOKEN(); // Forward } if (result == NULL) - result = ast_create_void(); + result = ast_create_void(ctx); return result; } From 71e58e38b80233e6c601ab1d7688b903e7f1ae7a Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Tue, 20 Jan 2026 20:32:59 +0100 Subject: [PATCH 133/282] fix: new lexer is linked to everything --- src/lexer/lexer.c | 22 ++++++++++------------ src/main.c | 10 ++++++++-- src/parser/parser.c | 5 +---- src/parser/parser.h | 3 ++- src/parser/parsing_utils.c | 6 +++--- src/parser/parsing_utils.h | 2 ++ 6 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index 16c8752..77e9f92 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -41,8 +41,8 @@ static void save_state(char *stream, ssize_t i, struct lexer_context *ctx) ctx->remaining_chars -= i; ctx->end_previous_token = stream + i; - update_previous_token(ctx->current_token); - update_current_token(NULL); + update_previous_token(ctx->current_token, ctx); + update_current_token(NULL, ctx); } /* @return: true if a special character from the grammar was found, @@ -182,12 +182,10 @@ void destroy_lexer_context(struct lexer_context **ctx) { if (ctx == NULL || *ctx == NULL) return; - if (ctx->end_previous_token != NULL) - free(ctx->end_previous_token); - if (ctx->previous_token != NULL) - free(ctx->previous_token); - if (ctx->current_token != NULL) - free(ctx->current_token); + if ((*ctx)->previous_token != NULL) + free((*ctx)->previous_token); + if ((*ctx)->current_token != NULL) + free((*ctx)->current_token); free(*ctx); *ctx = NULL; } @@ -278,7 +276,7 @@ struct token *peek_token(struct lexer_context *ctx) return ctx->current_token; } - char *stream = stream_init(); + char *stream = stream_init(ctx); ssize_t i = 0; // Usefull to know if we are inside a quote or double quote @@ -313,7 +311,7 @@ struct token *peek_token(struct lexer_context *ctx) } struct token *tok = new_token(stream, i); - update_current_token(tok); + update_current_token(tok, ctx); return tok; } @@ -326,7 +324,7 @@ struct token *pop_token(struct lexer_context *ctx) free_token(&ctx->current_token); return NULL; } - char *stream = stream_init(); + char *stream = stream_init(ctx); ssize_t i = 0; // Usefull to know if we are inside a quote or double quote @@ -366,7 +364,7 @@ struct token *pop_token(struct lexer_context *ctx) { ctx->current_token = new_token(stream, i); } - save_state(stream, i); + save_state(stream, i, ctx); return ctx->previous_token; } diff --git a/src/main.c b/src/main.c index ee2f36a..09b2c45 100644 --- a/src/main.c +++ b/src/main.c @@ -4,6 +4,7 @@ #include "execution/execution.h" #include "io_backend/io_backend.h" +#include "lexer/lexer.h" #include "parser/parser.h" #include "utils/args/args.h" #include "utils/vars/vars.h" @@ -69,8 +70,11 @@ int main(int argc, char **argv) free(io_context); + // init lexer context + struct lexer_context *ctx = calloc(1, sizeof(struct lexer_context)); + // Retrieve and build first AST - struct ast *command_ast = get_ast(); + struct ast *command_ast = get_ast(ctx); // Main parse-execute loop while (command_ast != NULL && command_ast->type != AST_END) @@ -81,9 +85,11 @@ int main(int argc, char **argv) ast_free(&command_ast); // Retrieve and build next AST - command_ast = get_ast(); + command_ast = get_ast(ctx); } + destroy_lexer_context(&ctx); + ast_free(&command_ast); if (command_ast == NULL) diff --git a/src/parser/parser.c b/src/parser/parser.c index d1fd70a..885a274 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -15,9 +15,8 @@ // === Functions -struct ast *get_ast() +struct ast *get_ast(struct lexer_context *ctx) { - struct lexer_context *ctx = calloc(1, sizeof(struct lexer_context)); struct token *token = PEEK_TOKEN(); struct ast *res; @@ -44,8 +43,6 @@ struct ast *get_ast() return NULL; } */ - destroy_lexer_context(&ctx); - return res; } diff --git a/src/parser/parser.h b/src/parser/parser.h index 5089212..4e06511 100644 --- a/src/parser/parser.h +++ b/src/parser/parser.h @@ -1,6 +1,7 @@ #ifndef PARSER_H #define PARSER_H +#include "lexer/lexer.h" #include "utils/ast/ast.h" /* @brief Builds the AST representation of the next command to execute. @@ -10,7 +11,7 @@ * * @warning NOT IMPLEMENTED */ -struct ast *get_ast(void); +struct ast *get_ast(struct lexer_context *ctx); /* @brief Builds the AST representation of the given command string. * diff --git a/src/parser/parsing_utils.c b/src/parser/parsing_utils.c index 08ef540..8511930 100644 --- a/src/parser/parsing_utils.c +++ b/src/parser/parsing_utils.c @@ -67,7 +67,7 @@ struct ast *parse_list(struct lexer_context *ctx) struct token *token = PEEK_TOKEN(); // and_or - current_node = parse_and_or(); + current_node = parse_and_or(ctx); if (current_node == NULL) return NULL; result_list = list_append(result_list, current_node); @@ -90,7 +90,7 @@ struct ast *parse_list(struct lexer_context *ctx) token = PEEK_TOKEN(); } } - result_list = list_append(result_list, current_node); + // result_list = list_append(result_list, current_node); return ast_create_list(result_list); } @@ -337,7 +337,7 @@ struct ast *parse_else_clause(struct lexer_context *ctx) } if (result == NULL) - result = ast_create_void(ctx); + result = ast_create_void(); return result; } diff --git a/src/parser/parsing_utils.h b/src/parser/parsing_utils.h index 5b32490..89eb4bf 100644 --- a/src/parser/parsing_utils.h +++ b/src/parser/parsing_utils.h @@ -1,6 +1,8 @@ #ifndef PARSING_UTILS_H #define PARSING_UTILS_H +#include "../lexer/lexer.h" + // === Macros #define PEEK_TOKEN() \ From b5b40f303cca4d0a109c17494306eaad35584feb Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Tue, 20 Jan 2026 22:16:46 +0100 Subject: [PATCH 134/282] fix: made all includes relatives for the wellbeing of all LSPs seeing this project --- src/io_backend/io_backend.c | 2 +- src/io_backend/io_backend.h | 2 +- src/lexer/lexer.c | 4 ++-- src/parser/parser.c | 6 +++--- src/parser/parser.h | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/io_backend/io_backend.c b/src/io_backend/io_backend.c index 7919e14..5ad63ef 100644 --- a/src/io_backend/io_backend.c +++ b/src/io_backend/io_backend.c @@ -8,7 +8,7 @@ // === Static variables -#include "utils/args/args.h" +#include "../utils/args/args.h" static struct iob_context context; static FILE *input = NULL; diff --git a/src/io_backend/io_backend.h b/src/io_backend/io_backend.h index 413dced..686d604 100644 --- a/src/io_backend/io_backend.h +++ b/src/io_backend/io_backend.h @@ -3,7 +3,7 @@ #include -#include "utils/args/args.h" +#include "../utils/args/args.h" // Error codes #define IOB_ERROR_GENERIC -1 diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index 77e9f92..9097d9e 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -7,8 +7,8 @@ #include #include -#include "io_backend/io_backend.h" -#include "utils/string_utils/string_utils.h" +#include "../io_backend/io_backend.h" +#include "../utils/string_utils/string_utils.h" // ######## STATIC FUNCTIONS ############## diff --git a/src/parser/parser.c b/src/parser/parser.c index 885a274..862c025 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -6,9 +6,9 @@ #include #include -#include "lexer/lexer.h" -#include "parser/parsing_utils.h" -#include "utils/lists/lists.h" +#include "../lexer/lexer.h" +#include "../parser/parsing_utils.h" +#include "../utils/lists/lists.h" // === Static functions // ... diff --git a/src/parser/parser.h b/src/parser/parser.h index 4e06511..1837e75 100644 --- a/src/parser/parser.h +++ b/src/parser/parser.h @@ -1,8 +1,8 @@ #ifndef PARSER_H #define PARSER_H -#include "lexer/lexer.h" -#include "utils/ast/ast.h" +#include "../lexer/lexer.h" +#include "../utils/ast/ast.h" /* @brief Builds the AST representation of the next command to execute. * From 4d27cea46bb559b77aa75502a134830ed6f4c7af Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Tue, 20 Jan 2026 23:00:20 +0100 Subject: [PATCH 135/282] feat: basic foundations for functional testing --- tests/functional/run-tests.sh | 189 ++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 tests/functional/run-tests.sh diff --git a/tests/functional/run-tests.sh b/tests/functional/run-tests.sh new file mode 100644 index 0000000..b549067 --- /dev/null +++ b/tests/functional/run-tests.sh @@ -0,0 +1,189 @@ +#!/bin/bash + +echo WIP: vous pourriez regretter de lancer ce script +exit 2 + + +################### +# Variables # +################### + +executable="../../src/42sh" +errors_count=0 # TODO take into account + + + +################## +# Colors # +################## + +# Reset +Color_Off='\033[0m' # Text Reset + +# Regular Colors +Black='\033[0;30m' # Black +Red='\033[0;31m' # Red +Green='\033[0;32m' # Green +Yellow='\033[0;33m' # Yellow +Blue='\033[0;34m' # Blue +Purple='\033[0;35m' # Purple +Cyan='\033[0;36m' # Cyan +White='\033[0;37m' # White + +# Bold +BBlack='\033[1;30m' # Black +BRed='\033[1;31m' # Red +BGreen='\033[1;32m' # Green +BYellow='\033[1;33m' # Yellow +BBlue='\033[1;34m' # Blue +BPurple='\033[1;35m' # Purple +BCyan='\033[1;36m' # Cyan +BWhite='\033[1;37m' # White + +# Underline +UBlack='\033[4;30m' # Black +URed='\033[4;31m' # Red +UGreen='\033[4;32m' # Green +UYellow='\033[4;33m' # Yellow +UBlue='\033[4;34m' # Blue +UPurple='\033[4;35m' # Purple +UCyan='\033[4;36m' # Cyan +UWhite='\033[4;37m' # White + +# Background +On_Black='\033[40m' # Black +On_Red='\033[41m' # Red +On_Green='\033[42m' # Green +On_Yellow='\033[43m' # Yellow +On_Blue='\033[44m' # Blue +On_Purple='\033[45m' # Purple +On_Cyan='\033[46m' # Cyan +On_White='\033[47m' # White + +# High Intensity +IBlack='\033[0;90m' # Black +IRed='\033[0;91m' # Red +IGreen='\033[0;92m' # Green +IYellow='\033[0;93m' # Yellow +IBlue='\033[0;94m' # Blue +IPurple='\033[0;95m' # Purple +ICyan='\033[0;96m' # Cyan +IWhite='\033[0;97m' # White + +# Bold High Intensity +BIBlack='\033[1;90m' # Black +BIRed='\033[1;91m' # Red +BIGreen='\033[1;92m' # Green +BIYellow='\033[1;93m' # Yellow +BIBlue='\033[1;94m' # Blue +BIPurple='\033[1;95m' # Purple +BICyan='\033[1;96m' # Cyan +BIWhite='\033[1;97m' # White + +# High Intensity backgrounds +On_IBlack='\033[0;100m' # Black +On_IRed='\033[0;101m' # Red +On_IGreen='\033[0;102m' # Green +On_IYellow='\033[0;103m' # Yellow +On_IBlue='\033[0;104m' # Blue +On_IPurple='\033[0;105m' # Purple +On_ICyan='\033[0;106m' # Cyan +On_IWhite='\033[0;107m' # White + + + +################## +# Wrappers # +################## + +# TODO output handling + +# @arg test name +# @arg input string +# @arg expected output +# @arg expected return code +test_cmd() { + + # Check input + if [[ -z "$1" || -z "$2" || -z "$3" || -z "$4"]]; then + echo $BRed "\n\n" "Issue in the testsuite: test_cmd: One or more argument is empty" $Color_off + exit 2 + fi + + echo $BBlue "=== $1 ===" $Color_off + + $executable -c "$2" + actual_code=$? + + if [[ "$actual_code" -ne "$4" ]]; then + echo $BRed "Test failed: expected code $4 but got $actual_code" $Color_off + fi + + echo $BBlue "===================" $Color_off "\n" +} + +# @arg test name +# @arg input script +# @arg expected output +# @arg expected return code +test_script() { + + # Check input + if [[ -z "$1" || -z "$2" || -z "$3" || -z "$4"]]; then + echo $BRed "\n\n" "Issue in the testsuite: test_script: One or more argument is empty" $Color_off + exit 2 + fi + if [[ ! -f "$2" ]]; then + echo $BRed "\n\n" "Issue in the testsuite: test_script: Second argument is not a file" $Color_off + exit 2 + fi + + + echo $BBlue "=== $1 ===" $Color_off + + $executable "$2" + actual_code=$? + + if [[ "$actual_code" -ne "$4" ]]; then + echo $BRed "Test failed: expected code $4 but got $actual_code" $Color_off + fi + + echo $BBlue "===================" $Color_off "\n" +} + +# @arg test name +# @arg input file +# @arg expected output +# @arg expected return code +test_stdin() { + # Check input + if [[ -z "$1" || -z "$2" || -z "$3" || -z "$4"]]; then + echo $BRed "\n\n" "Issue in the testsuite: test_stdin: One or more argument is empty" $Color_off + exit 2 + fi + if [[ ! -f "$2" ]]; then + echo $BRed "\n\n" "Issue in the testsuite: test_stdin: Second argument is not a file" $Color_off + exit 2 + fi + + + echo $BBlue "===== $1 =====" $Color_off + + $executable < "$2" + actual_code=$? + + if [[ "$actual_code" -ne "$4" ]]; then + echo $BRed "Test failed: expected code $4 but got $actual_code" $Color_off + fi + + echo $BBlue "===================" $Color_off "\n" + +} + + +# *********************************************************** +################# +# Tests # +################# + + From 7e413547e11efaaa401460587b4eb28b19cbf564 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Wed, 21 Jan 2026 15:14:22 +0100 Subject: [PATCH 136/282] fix: free hash_map for variables at end of program --- src/main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main.c b/src/main.c index 09b2c45..e9fc492 100644 --- a/src/main.c +++ b/src/main.c @@ -90,6 +90,8 @@ int main(int argc, char **argv) destroy_lexer_context(&ctx); + hash_map_free(&vars); + ast_free(&command_ast); if (command_ast == NULL) From 26ac0ffe05d226e51b346181ee1f2a467bce2ab0 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Wed, 21 Jan 2026 16:19:10 +0100 Subject: [PATCH 137/282] feat(lexer): created parser utils --- src/lexer/Makefile.am | 2 +- src/lexer/lexer.c | 209 ++++------------------------------------ src/lexer/lexer.h | 87 +---------------- src/lexer/lexer_utils.c | 183 +++++++++++++++++++++++++++++++++++ src/lexer/lexer_utils.h | 91 +++++++++++++++++ 5 files changed, 296 insertions(+), 276 deletions(-) create mode 100644 src/lexer/lexer_utils.c create mode 100644 src/lexer/lexer_utils.h diff --git a/src/lexer/Makefile.am b/src/lexer/Makefile.am index dd11411..4dad036 100644 --- a/src/lexer/Makefile.am +++ b/src/lexer/Makefile.am @@ -2,7 +2,7 @@ lib_LIBRARIES = liblexer.a liblexer_a_SOURCES = \ lexer.c \ - lexer.h + lexer_utils.c liblexer_a_CPPFLAGS = -I$(top_srcdir)/src diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index 9097d9e..b6ea0ad 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -7,10 +7,21 @@ #include #include +#include "lexer_utils.h" #include "../io_backend/io_backend.h" #include "../utils/string_utils/string_utils.h" -// ######## STATIC FUNCTIONS ############## +/* @return: true if a special character from the grammar was found, + * false otherwise. + */ +static bool is_special_char(char c) +{ + if (c == EOF) + return true; + + char special_chars[] = "\n'\"`;#|&\\$(){}<>*"; + return strchr(special_chars, c) != NULL; +} /* @brief: sets the ctx->current_token to [tok]. * this function is called by token_peek(). @@ -29,7 +40,6 @@ static void update_previous_token(struct token *tok, struct lexer_context *ctx) free_token(&ctx->previous_token); ctx->previous_token = tok; } - /* @brief: updates the current position in the stream. * [stream] += [i] * Also frees the last sent token, and sets it to ctx->current_token. @@ -45,194 +55,6 @@ static void save_state(char *stream, ssize_t i, struct lexer_context *ctx) update_current_token(NULL, ctx); } -/* @return: true if a special character from the grammar was found, - * false otherwise. - */ -static bool is_special_char(char c) -{ - if (c == EOF) - return true; - - char special_chars[] = "\n'\"`;#|&\\$(){}<>*"; - return strchr(special_chars, c) != NULL; -} - -/* @brief: if a special character is found at [begin], - * [tok->token_type] is set accordingly - */ -static void set_token_spechar(struct token *tok, char *begin, ssize_t size) -{ - if (size != 1) - return; - switch (begin[0]) - { - case EOF: - tok->type = TOKEN_EOF; - break; - case ';': - tok->type = TOKEN_SEMICOLON; - break; - case '\n': - tok->type = TOKEN_NEWLINE; - break; - case '\'': - tok->type = TOKEN_QUOTE; - break; - case '"': - tok->type = TOKEN_DOUBLE_QUOTE; - break; - case '`': - tok->type = TOKEN_GRAVE; - break; - case '#': - tok->type = TOKEN_COMMENT; - break; - case '|': - tok->type = TOKEN_PIPE; - break; - case '&': - tok->type = TOKEN_AMPERSAND; - break; - case '\\': - tok->type = TOKEN_BACKSLASH; - break; - case '$': - tok->type = TOKEN_DOLLAR; - break; - case '(': - tok->type = TOKEN_LEFT_PAREN; - break; - case ')': - tok->type = TOKEN_RIGHT_PAREN; - break; - case '{': - tok->type = TOKEN_LEFT_BRACKET; - break; - case '}': - tok->type = TOKEN_RIGHT_BRACKET; - break; - case '<': - tok->type = TOKEN_LESS; - break; - case '>': - tok->type = TOKEN_GREATER; - break; - case '*': - tok->type = TOKEN_STAR; - break; - default: - break; - } -} - -/* @brief: if a keyword is found at [begin], - * [tok->token_type] is set accordingly - */ -static void set_token_keyword(struct token *tok, char *begin, ssize_t size) -{ - if (tok->type != TOKEN_NULL || size == 0) - return; - if (strncmp(begin, "if", size) == 0) - { - tok->type = TOKEN_IF; - } - else if (strncmp(begin, "fi", size) == 0) - { - tok->type = TOKEN_FI; - } - else if (strncmp(begin, "then", size) == 0) - { - tok->type = TOKEN_THEN; - } - else if (strncmp(begin, "else", size) == 0) - { - tok->type = TOKEN_ELSE; - } - else if (strncmp(begin, "elif", size) == 0) - { - tok->type = TOKEN_ELIF; - } - - // no keywords found. - if (tok->type == TOKEN_NULL) - return; - - tok->data = calloc(size + 1, sizeof(char)); - if (tok->data == NULL) - return; - strncpy(tok->data, begin, size); -} - -/* @brief: if token_type has not yet been set, then it is a TOKEN_WORD - * Also allocates the data and fills it. - */ -static void set_token_word(struct token *tok, char *begin, ssize_t size) -{ - if (tok->type == TOKEN_NULL && size != 0) - { - tok->type = TOKEN_WORD; - tok->data = calloc(size + 1, sizeof(char)); - if (tok->data == NULL) - return; - strncpy(tok->data, begin, size); - } -} - -void destroy_lexer_context(struct lexer_context **ctx) -{ - if (ctx == NULL || *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; -} - -struct token *new_token(char *begin, ssize_t size) -{ - struct token *tok = calloc(1, sizeof(struct token)); - if (tok == NULL) - return NULL; - - set_token_spechar(tok, begin, size); - set_token_keyword(tok, begin, size); - set_token_word(tok, begin, size); - - return tok; -} - -void free_token(struct token **tok) -{ - if (tok == NULL || *tok == NULL) - return; - if ((*tok)->data != NULL) - free((*tok)->data); - free(*tok); - *tok = NULL; -} - -char *stream_init(struct lexer_context *ctx) -{ - char *stream; - - if (ctx->previous_token == NULL) // at the begining - { - ctx->remaining_chars = stream_read(&stream); - } - else - { - stream = ctx->end_previous_token; - } - - char *trimed_stream = trim_blank_left(stream); - ctx->remaining_chars -= trimed_stream - stream; - stream = trimed_stream; - - return stream; -} - /* * @brief: Updates the lexing_mode to LEXER_NORMAL * if the SECOND quote is found at stream[i]. @@ -268,6 +90,7 @@ static bool update_lexing_mode(char *stream, ssize_t i, return *lexing_mode != mode_before_update; } + struct token *peek_token(struct lexer_context *ctx) { // we already created the upcoming token during the previous call to peek() @@ -311,6 +134,12 @@ struct token *peek_token(struct lexer_context *ctx) } struct token *tok = new_token(stream, i); + // if token is comment, we don't want it + + if (tok->type == TOKEN_COMMENT) + { + // tok = peek_token(); + } update_current_token(tok, ctx); return tok; } diff --git a/src/lexer/lexer.h b/src/lexer/lexer.h index 50003b2..0d9ed32 100644 --- a/src/lexer/lexer.h +++ b/src/lexer/lexer.h @@ -1,69 +1,9 @@ #ifndef LEXER_H #define LEXER_H +#include "lexer_utils.h" + #include - -struct lexer_context -{ - char *end_previous_token; - ssize_t remaining_chars; - - struct token *previous_token; - struct token *current_token; -}; - -/* @brief: frees all fields of ctx and sets ctx to NULL. - */ -void destroy_lexer_context(struct lexer_context **ctx); - -enum lexing_mode -{ - LEXER_NORMAL, - LEXER_QUOTE, - LEXER_DOUBLE_QUOTE -}; - -enum token_type -{ - // Special characters - TOKEN_NULL = 0, - TOKEN_EOF, - TOKEN_WORD, - TOKEN_NEWLINE, - - // WARNING: quote and double quote should never be used inside a token. - TOKEN_QUOTE, - TOKEN_DOUBLE_QUOTE, - - TOKEN_GRAVE, - TOKEN_SEMICOLON, - TOKEN_COMMENT, - TOKEN_PIPE, - TOKEN_AMPERSAND, - TOKEN_BACKSLASH, - TOKEN_DOLLAR, - TOKEN_LEFT_PAREN, - TOKEN_RIGHT_PAREN, - TOKEN_LEFT_BRACKET, - TOKEN_RIGHT_BRACKET, - TOKEN_LESS, - TOKEN_GREATER, - TOKEN_STAR, - - // Keywords - TOKEN_IF, - TOKEN_THEN, - TOKEN_ELSE, - TOKEN_FI, - TOKEN_ELIF -}; - -struct token -{ - enum token_type type; - char *data; -}; - /* * @brief: returns the next (newly allocated) token without consuming it. * if end of input is reached, returns a token of type TOKEN_EOF. @@ -88,27 +28,4 @@ struct token *pop_token(struct lexer_context *ctx); struct token *get_token_str(void); -/* - * @brief: return a newly allocated token, with the corresponding type. - * The data contains [size] char, starting from [begin]. - * - * @return: NULL on error, a token otherwise. - */ -struct token *new_token(char *begin, ssize_t size); - -/* @brief: frees the token given in argument - */ -void free_token(struct token **tok); - -/* - * @brief: checks if the stream used for the last token creation is empty. - * If it is, it calls stream_read() from IO_backend, - * and sets [remaing_chars]. - * If not, it starts from the end of the last token. - * Also trims left blanks before returning. - * - * @return: char* stream from which we tokenise. - */ -char *stream_init(struct lexer_context *ctx); - #endif /* ! LEXER_H */ diff --git a/src/lexer/lexer_utils.c b/src/lexer/lexer_utils.c new file mode 100644 index 0000000..a956cb9 --- /dev/null +++ b/src/lexer/lexer_utils.c @@ -0,0 +1,183 @@ +#include "lexer_utils.h" + +#include +#include + +#include "../io_backend/io_backend.h" +#include "../utils/string_utils/string_utils.h" + +/* @brief: if a special character is found at [begin], + * [tok->token_type] is set accordingly + */ +static void set_token_spechar(struct token *tok, char *begin, ssize_t size) +{ + if (size != 1) + return; + switch (begin[0]) + { + case EOF: + tok->type = TOKEN_EOF; + break; + case ';': + tok->type = TOKEN_SEMICOLON; + break; + case '\n': + tok->type = TOKEN_NEWLINE; + break; + case '\'': + tok->type = TOKEN_QUOTE; + break; + case '"': + tok->type = TOKEN_DOUBLE_QUOTE; + break; + case '`': + tok->type = TOKEN_GRAVE; + break; + case '#': + tok->type = TOKEN_COMMENT; + break; + case '|': + tok->type = TOKEN_PIPE; + break; + case '&': + tok->type = TOKEN_AMPERSAND; + break; + case '\\': + tok->type = TOKEN_BACKSLASH; + break; + case '$': + tok->type = TOKEN_DOLLAR; + break; + case '(': + tok->type = TOKEN_LEFT_PAREN; + break; + case ')': + tok->type = TOKEN_RIGHT_PAREN; + break; + case '{': + tok->type = TOKEN_LEFT_BRACKET; + break; + case '}': + tok->type = TOKEN_RIGHT_BRACKET; + break; + case '<': + tok->type = TOKEN_LESS; + break; + case '>': + tok->type = TOKEN_GREATER; + break; + case '*': + tok->type = TOKEN_STAR; + break; + default: + break; + } +} + +/* @brief: if a keyword is found at [begin], + * [tok->token_type] is set accordingly + */ +static void set_token_keyword(struct token *tok, char *begin, ssize_t size) +{ + if (tok->type != TOKEN_NULL || size == 0) + return; + if (strncmp(begin, "if", size) == 0) + { + tok->type = TOKEN_IF; + } + else if (strncmp(begin, "fi", size) == 0) + { + tok->type = TOKEN_FI; + } + else if (strncmp(begin, "then", size) == 0) + { + tok->type = TOKEN_THEN; + } + else if (strncmp(begin, "else", size) == 0) + { + tok->type = TOKEN_ELSE; + } + else if (strncmp(begin, "elif", size) == 0) + { + tok->type = TOKEN_ELIF; + } + + // no keywords found. + if (tok->type == TOKEN_NULL) + return; + + tok->data = calloc(size + 1, sizeof(char)); + if (tok->data == NULL) + return; + strncpy(tok->data, begin, size); +} + +/* @brief: if token_type has not yet been set, then it is a TOKEN_WORD + * Also allocates the data and fills it. + */ +static void set_token_word(struct token *tok, char *begin, ssize_t size) +{ + if (tok->type == TOKEN_NULL && size != 0) + { + tok->type = TOKEN_WORD; + tok->data = calloc(size + 1, sizeof(char)); + if (tok->data == NULL) + return; + strncpy(tok->data, begin, size); + } +} + +struct token *new_token(char *begin, ssize_t size) +{ + struct token *tok = calloc(1, sizeof(struct token)); + if (tok == NULL) + return NULL; + + set_token_spechar(tok, begin, size); + set_token_keyword(tok, begin, size); + set_token_word(tok, begin, size); + + return tok; +} + +void destroy_lexer_context(struct lexer_context **ctx) +{ + if (ctx == NULL || *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; +} + +void free_token(struct token **tok) +{ + if (tok == NULL || *tok == NULL) + return; + if ((*tok)->data != NULL) + free((*tok)->data); + free(*tok); + *tok = NULL; +} + +char *stream_init(struct lexer_context *ctx) +{ + char *stream; + + if (ctx->previous_token == NULL) // at the begining + { + ctx->remaining_chars = stream_read(&stream); + } + else + { + stream = ctx->end_previous_token; + } + + char *trimed_stream = trim_blank_left(stream); + ctx->remaining_chars -= trimed_stream - stream; + stream = trimed_stream; + + return stream; +} diff --git a/src/lexer/lexer_utils.h b/src/lexer/lexer_utils.h new file mode 100644 index 0000000..503c1ec --- /dev/null +++ b/src/lexer/lexer_utils.h @@ -0,0 +1,91 @@ +#ifndef LEXER_UTILS_H +#define LEXER_UTILS_H + +#include +#include + +struct lexer_context +{ + char *end_previous_token; + ssize_t remaining_chars; + + struct token *previous_token; + struct token *current_token; +}; + +/* @brief: frees all fields of ctx and sets ctx to NULL. + */ +void destroy_lexer_context(struct lexer_context **ctx); + +enum lexing_mode +{ + LEXER_NORMAL, + LEXER_QUOTE, + LEXER_DOUBLE_QUOTE +}; + +enum token_type +{ + // Special characters + TOKEN_NULL = 0, + TOKEN_EOF, + TOKEN_WORD, + TOKEN_NEWLINE, + + // WARNING: quote and double quote should never be used inside a token. + TOKEN_QUOTE, + TOKEN_DOUBLE_QUOTE, + + TOKEN_GRAVE, + TOKEN_SEMICOLON, + TOKEN_COMMENT, + TOKEN_PIPE, + TOKEN_AMPERSAND, + TOKEN_BACKSLASH, + TOKEN_DOLLAR, + TOKEN_LEFT_PAREN, + TOKEN_RIGHT_PAREN, + TOKEN_LEFT_BRACKET, + TOKEN_RIGHT_BRACKET, + TOKEN_LESS, + TOKEN_GREATER, + TOKEN_STAR, + + // Keywords + TOKEN_IF, + TOKEN_THEN, + TOKEN_ELSE, + TOKEN_FI, + TOKEN_ELIF +}; + +struct token +{ + enum token_type type; + char *data; +}; + +/* + * @brief: return a newly allocated token, with the corresponding type. + * The data contains [size] char, starting from [begin]. + * + * @return: NULL on error, a token otherwise. + */ +struct token *new_token(char *begin, ssize_t size); + +/* @brief: frees the token given in argument + */ +void free_token(struct token **tok); + +/* + * @brief: checks if the stream used for the last token creation is empty. + * If it is, it calls stream_read() from IO_backend, + * and sets [remaing_chars]. + * If not, it starts from the end of the last token. + * Also trims left blanks before returning. + * + * @return: char* stream from which we tokenise. + */ +char *stream_init(struct lexer_context *ctx); + +#endif /* LEXER_UTILS_H */ From 03e5305b56555ad76bc19c6703905358a344b82b Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Wed, 21 Jan 2026 18:59:53 +0100 Subject: [PATCH 138/282] fix(IOB): stdin and script should work --- src/io_backend/io_backend.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/io_backend/io_backend.c b/src/io_backend/io_backend.c index 5ad63ef..41236e9 100644 --- a/src/io_backend/io_backend.c +++ b/src/io_backend/io_backend.c @@ -90,6 +90,7 @@ ssize_t stream_read(char **stream) else if (nread < 0) state = IOB_STATE_ERROR; + *stream = stream_buf; return nread; } // Use args From 306b13308b7215abed6190334ba39ee149293053 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Wed, 21 Jan 2026 18:43:17 +0100 Subject: [PATCH 139/282] feat(lexer): comment handling + healthy changes --- src/lexer/lexer.c | 14 +++++++++----- src/lexer/lexer.h | 4 ++-- src/lexer/lexer_utils.c | 13 +++++++++---- src/lexer/lexer_utils.h | 11 ++++++++--- 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index b6ea0ad..89b5f71 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -7,9 +7,9 @@ #include #include -#include "lexer_utils.h" #include "../io_backend/io_backend.h" #include "../utils/string_utils/string_utils.h" +#include "lexer_utils.h" /* @return: true if a special character from the grammar was found, * false otherwise. @@ -90,7 +90,6 @@ static bool update_lexing_mode(char *stream, ssize_t i, return *lexing_mode != mode_before_update; } - struct token *peek_token(struct lexer_context *ctx) { // we already created the upcoming token during the previous call to peek() @@ -99,7 +98,8 @@ struct token *peek_token(struct lexer_context *ctx) return ctx->current_token; } - char *stream = stream_init(ctx); + 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 @@ -138,7 +138,10 @@ struct token *peek_token(struct lexer_context *ctx) if (tok->type == TOKEN_COMMENT) { - // tok = peek_token(); + // drop current stream + get_next_stream(ctx); + free_token(&tok); + tok = peek_token(ctx); } update_current_token(tok, ctx); return tok; @@ -153,7 +156,8 @@ struct token *pop_token(struct lexer_context *ctx) free_token(&ctx->current_token); return NULL; } - char *stream = stream_init(ctx); + 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 diff --git a/src/lexer/lexer.h b/src/lexer/lexer.h index 0d9ed32..e06bec0 100644 --- a/src/lexer/lexer.h +++ b/src/lexer/lexer.h @@ -1,9 +1,9 @@ #ifndef LEXER_H #define LEXER_H -#include "lexer_utils.h" - #include + +#include "lexer_utils.h" /* * @brief: returns the next (newly allocated) token without consuming it. * if end of input is reached, returns a token of type TOKEN_EOF. diff --git a/src/lexer/lexer_utils.c b/src/lexer/lexer_utils.c index a956cb9..f954676 100644 --- a/src/lexer/lexer_utils.c +++ b/src/lexer/lexer_utils.c @@ -162,11 +162,11 @@ void free_token(struct token **tok) *tok = NULL; } -char *stream_init(struct lexer_context *ctx) +void stream_init(struct lexer_context *ctx) { char *stream; - if (ctx->previous_token == NULL) // at the begining + if (ctx->remaining_chars == 0) // at the begining { ctx->remaining_chars = stream_read(&stream); } @@ -177,7 +177,12 @@ char *stream_init(struct lexer_context *ctx) char *trimed_stream = trim_blank_left(stream); ctx->remaining_chars -= trimed_stream - stream; - stream = trimed_stream; - return stream; + ctx->end_previous_token = trimed_stream; +} + +void get_next_stream(struct lexer_context *ctx) +{ + ctx->remaining_chars = 0; + stream_init(ctx); } diff --git a/src/lexer/lexer_utils.h b/src/lexer/lexer_utils.h index 503c1ec..729e912 100644 --- a/src/lexer/lexer_utils.h +++ b/src/lexer/lexer_utils.h @@ -1,8 +1,8 @@ #ifndef LEXER_UTILS_H #define LEXER_UTILS_H -#include #include +#include struct lexer_context { @@ -80,12 +80,17 @@ void free_token(struct token **tok); /* * @brief: checks if the stream used for the last token creation is empty. * If it is, it calls stream_read() from IO_backend, - * and sets [remaing_chars]. + * and sets [remaining_chars]. * If not, it starts from the end of the last token. * Also trims left blanks before returning. * * @return: char* stream from which we tokenise. */ -char *stream_init(struct lexer_context *ctx); +void stream_init(struct lexer_context *ctx); + +/* + * @brief: drops the current stream and asks IOB for a new one + */ +void get_next_stream(struct lexer_context *ctx); #endif /* LEXER_UTILS_H */ From 627c3057e976b9d49bf2f5926fcab8f74429bfb6 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Wed, 21 Jan 2026 19:11:38 +0100 Subject: [PATCH 140/282] fix(main): return code because of ast_free --- src/main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.c b/src/main.c index e9fc492..f9c4407 100644 --- a/src/main.c +++ b/src/main.c @@ -92,10 +92,10 @@ int main(int argc, char **argv) hash_map_free(&vars); - ast_free(&command_ast); - if (command_ast == NULL) return ERR_INPUT_PROCESSING; + ast_free(&command_ast); + return return_code; } From 402b1614b5b8173f45050a858ecaf889aeae3bbd Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Wed, 21 Jan 2026 19:17:29 +0100 Subject: [PATCH 141/282] feat: Extended base tools for testing and made a basic test --- tests/functional/run-tests.sh | 137 +++++++++++++++++++++------------- 1 file changed, 86 insertions(+), 51 deletions(-) diff --git a/tests/functional/run-tests.sh b/tests/functional/run-tests.sh index b549067..14a68b4 100644 --- a/tests/functional/run-tests.sh +++ b/tests/functional/run-tests.sh @@ -9,7 +9,11 @@ exit 2 ################### executable="../../src/42sh" -errors_count=0 # TODO take into account +errors_count=0 +total_tests=0 +tmp_script="/tmp/test_script.sh" +output="/tmp/42sh_tests.output" +ref_output="/tmp/42sh_tests_ref.output" @@ -96,40 +100,91 @@ On_IWhite='\033[0;107m' # White # Wrappers # ################## -# TODO output handling + +# @arg test command +# @arg actual code +# @arg ref code +check_result() { + + command="$1" + actual_code="$2" + ref_code="$3" + + test_failed=0 + + # Check return code + if [[ "$actual_code" -ne "$ref_code" ]]; then + echo $BRed "FAILED" $Color_off + echo $Blue ' ' "on '$2'" $Color_off + echo ' ' "Expected code $ref_code but got $actual_code" + test_failed=1 + # Check output + elif [ diff $output $ref_output > /dev/null ]; then + echo $BRed "FAILED" $Color_off + echo $Blue ' ' "on '$2'" $Color_off + echo ' ' "Output is not the one expected" + test_failed=1 + else + echo $Blue OK $Colors_off + fi + + if [[ "$test_failed" -eq 1 ]]; then + ((errors_count++)) + fi + +} + # @arg test name # @arg input string -# @arg expected output -# @arg expected return code -test_cmd() { +test_str() { # Check input - if [[ -z "$1" || -z "$2" || -z "$3" || -z "$4"]]; then - echo $BRed "\n\n" "Issue in the testsuite: test_cmd: One or more argument is empty" $Color_off + if [[ -z "$1" || -z "$2" ]]; then + echo $BRed "\n\n" "Issue in the testsuite: test_str: One or more argument is empty" $Color_off exit 2 fi echo $BBlue "=== $1 ===" $Color_off - $executable -c "$2" - actual_code=$? + echo "$2" > $tmp_script - if [[ "$actual_code" -ne "$4" ]]; then - echo $BRed "Test failed: expected code $4 but got $actual_code" $Color_off - fi + # Arg + echo -n "\n= [ARG] " + $executable -c "$2" &> $output + actual_code=$? + $ref_executable -c "$2" &> $ref_output + ref_code=$? + ((total_tests++)) + check_result "$2" "$actual_code" "$ref_code" + + # Script + echo -n "\n= [SCRIPT] " + $executable "$tmp_script" &> $output + actual_code=$? + $ref_executable "$tmp_script" &> $ref_output + ref_code=$? + ((total_tests++)) + check_result "$2" "$actual_code" "$ref_code" + + # Stdin + echo -n "\n= [STDIN] " + $executable < "$tmp_script" &> $output + actual_code=$? + $ref_executable < "$2" &> $ref_output + ref_code=$? + ((total_tests++)) + check_result "$tmp_script" "$actual_code" "$ref_code" echo $BBlue "===================" $Color_off "\n" } # @arg test name # @arg input script -# @arg expected output -# @arg expected return code test_script() { # Check input - if [[ -z "$1" || -z "$2" || -z "$3" || -z "$4"]]; then + if [[ -z "$1" || -z "$2" ]]; then echo $BRed "\n\n" "Issue in the testsuite: test_script: One or more argument is empty" $Color_off exit 2 fi @@ -138,52 +193,32 @@ test_script() { exit 2 fi - echo $BBlue "=== $1 ===" $Color_off - $executable "$2" + # Script + echo -n "\n= [SCRIPT] " + $executable "$tmp_script" &> $output actual_code=$? + $ref_executable "$tmp_script" &> $ref_output + ref_code=$? + ((total_tests++)) + check_result "$2" "$actual_code" "$ref_code" - if [[ "$actual_code" -ne "$4" ]]; then - echo $BRed "Test failed: expected code $4 but got $actual_code" $Color_off - fi + # Stdin + echo -n "\n= [STDIN] " + $executable < "$tmp_script" &> $output + actual_code=$? + $ref_executable < "$2" &> $ref_output + ref_code=$? + ((total_tests++)) + check_result "$tmp_script" "$actual_code" "$ref_code" echo $BBlue "===================" $Color_off "\n" } -# @arg test name -# @arg input file -# @arg expected output -# @arg expected return code -test_stdin() { - # Check input - if [[ -z "$1" || -z "$2" || -z "$3" || -z "$4"]]; then - echo $BRed "\n\n" "Issue in the testsuite: test_stdin: One or more argument is empty" $Color_off - exit 2 - fi - if [[ ! -f "$2" ]]; then - echo $BRed "\n\n" "Issue in the testsuite: test_stdin: Second argument is not a file" $Color_off - exit 2 - fi - - - echo $BBlue "===== $1 =====" $Color_off - - $executable < "$2" - actual_code=$? - - if [[ "$actual_code" -ne "$4" ]]; then - echo $BRed "Test failed: expected code $4 but got $actual_code" $Color_off - fi - - echo $BBlue "===================" $Color_off "\n" - -} - - # *********************************************************** ################# # Tests # ################# - +test_str "Hello" "echo Hello there" From 4a1fa12941cc04a380e47efa5e1d15fb6b283428 Mon Sep 17 00:00:00 2001 From: Jean Herail Date: Wed, 21 Jan 2026 19:39:11 +0100 Subject: [PATCH 142/282] Added documentation for try_builtin --- src/execution/execution.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/execution/execution.c b/src/execution/execution.c index a882397..ec0fe21 100644 --- a/src/execution/execution.c +++ b/src/execution/execution.c @@ -116,6 +116,12 @@ static int builtin_cd(char **argv) return 0; } +/** + * @brief Tries to execute a builtin command if the command matches a builtin + * + * @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) { if (!argv || !argv[0]) From 0ede8d3eef003ee31d8718fb14ce4260b21a3ce4 Mon Sep 17 00:00:00 2001 From: Jean Herail Date: Wed, 21 Jan 2026 19:57:44 +0100 Subject: [PATCH 143/282] Re-functionnal pretty-printing --- src/main.c | 5 +++++ src/parser/parser.c | 2 +- src/utils/ast/ast.c | 27 ++++++++++----------------- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/main.c b/src/main.c index b664786..189f9db 100644 --- a/src/main.c +++ b/src/main.c @@ -60,6 +60,11 @@ int main(int argc, char **argv) // Call the parser to get the AST struct ast *command_ast = get_ast(); // We'll pass the options later + if (options.pretty_print) + { + ast_print_dot(command_ast); + } + // Call the executor with the AST r = execution(command_ast); diff --git a/src/parser/parser.c b/src/parser/parser.c index 30cecdd..8468b20 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -11,4 +11,4 @@ struct ast *get_ast_str(char *command) { (void)command; return NULL; -} +} \ No newline at end of file diff --git a/src/utils/ast/ast.c b/src/utils/ast/ast.c index 4019e44..cd875e7 100644 --- a/src/utils/ast/ast.c +++ b/src/utils/ast/ast.c @@ -1,10 +1,10 @@ +#include "ast.h" + #include #include #include #include -#include "ast.h" - static void ast_print_dot_recursive(struct ast *node, FILE *out) { if (!node) @@ -12,8 +12,7 @@ static void ast_print_dot_recursive(struct ast *node, FILE *out) switch (node->type) { - case AST_IF: - { + case AST_IF: { struct ast_if *if_data = ast_get_if(node); fprintf(out, " node%p [label=\"IF\"];\n", (void *)node); @@ -21,28 +20,30 @@ static void ast_print_dot_recursive(struct ast *node, FILE *out) { fprintf(out, " node%p -> node%p;\n", (void *)node, (void *)if_data->condition); - fprintf(out, " node%p [fillcolor=\"lightyellow\", style=\"filled\"];\n", + fprintf(out, + " node%p [fillcolor=\"lightyellow\", style=\"filled\"];\n", (void *)if_data->condition); ast_print_dot_recursive(if_data->condition, out); if (if_data->then_clause) { fprintf(out, " node%p -> node%p [label=\"true\"];\n", - (void *)if_data->condition, (void *)if_data->then_clause); + (void *)if_data->condition, + (void *)if_data->then_clause); ast_print_dot_recursive(if_data->then_clause, out); } if (if_data->else_clause) { fprintf(out, " node%p -> node%p [label=\"false\"];\n", - (void *)if_data->condition, (void *)if_data->else_clause); + (void *)if_data->condition, + (void *)if_data->else_clause); ast_print_dot_recursive(if_data->else_clause, out); } } break; } - case AST_CMD: - { + case AST_CMD: { struct ast_cmd *cmd_data = ast_get_cmd(node); fprintf(out, " node%p [label=\"", (void *)node); struct list *l = cmd_data->cmd; @@ -66,14 +67,6 @@ static void ast_print_dot_recursive(struct ast *node, FILE *out) void ast_print_dot(struct ast *ast) { - // Always print to stdout so the user can see it in the terminal - printf("digraph AST {\n"); - if (ast) - { - ast_print_dot_recursive(ast, stdout); - } - printf("}\n"); - if (!ast) return; From 39ca927c0646b2b5e5c992efb7312f2c1cb2767d Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Wed, 21 Jan 2026 20:00:48 +0100 Subject: [PATCH 144/282] fix(IOB): EOF does not overwrite last char anymore --- src/io_backend/io_backend.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/io_backend/io_backend.c b/src/io_backend/io_backend.c index 41236e9..401daed 100644 --- a/src/io_backend/io_backend.c +++ b/src/io_backend/io_backend.c @@ -99,7 +99,7 @@ ssize_t stream_read(char **stream) *stream = context.args; size_t len = strlen(context.args); context.args[len] = EOF; - return len; + return len + 1; } else { From ed6e761f9cb40a7dddc54f7b49ed98190da2079c Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Wed, 21 Jan 2026 22:59:33 +0100 Subject: [PATCH 145/282] feat: testsuite summary and enhanced tests look --- tests/functional/run-tests.sh | 112 +++++++++++++++++++++++----------- 1 file changed, 78 insertions(+), 34 deletions(-) mode change 100644 => 100755 tests/functional/run-tests.sh diff --git a/tests/functional/run-tests.sh b/tests/functional/run-tests.sh old mode 100644 new mode 100755 index 14a68b4..ef5030f --- a/tests/functional/run-tests.sh +++ b/tests/functional/run-tests.sh @@ -1,20 +1,20 @@ #!/bin/bash -echo WIP: vous pourriez regretter de lancer ce script -exit 2 - ################### # Variables # ################### executable="../../src/42sh" -errors_count=0 -total_tests=0 +ref_executable="dash" + tmp_script="/tmp/test_script.sh" output="/tmp/42sh_tests.output" ref_output="/tmp/42sh_tests_ref.output" +total_tests=0 +errors_count=0 +timeouts_count=0 ################## @@ -113,19 +113,25 @@ check_result() { test_failed=0 # Check return code - if [[ "$actual_code" -ne "$ref_code" ]]; then - echo $BRed "FAILED" $Color_off - echo $Blue ' ' "on '$2'" $Color_off - echo ' ' "Expected code $ref_code but got $actual_code" + if [[ "$actual_code" -eq 124 ]]; then + echo -e $BRed "TIMEOUT" $Color_Off + echo -e ' ' "on '$command'" + echo -e ' ' "Expected code $ref_code but got $actual_code" + ((timeouts_count++)) + test_failed=1 + elif [[ "$actual_code" -ne "$ref_code" ]]; then + echo -e $BRed "FAILED" $Color_Off + echo -e ' ' "on '$command'" + echo -e ' ' "Expected code $ref_code but got $actual_code" test_failed=1 # Check output - elif [ diff $output $ref_output > /dev/null ]; then - echo $BRed "FAILED" $Color_off - echo $Blue ' ' "on '$2'" $Color_off - echo ' ' "Output is not the one expected" + elif diff $output $ref_output > /dev/null; then + echo -e $BRed "FAILED" $Color_Off + echo -e ' ' "on '$command'" + echo -e ' ' "Output is not the one expected" test_failed=1 else - echo $Blue OK $Colors_off + echo -e $BGreen OK $Color_Off fi if [[ "$test_failed" -eq 1 ]]; then @@ -141,17 +147,17 @@ test_str() { # Check input if [[ -z "$1" || -z "$2" ]]; then - echo $BRed "\n\n" "Issue in the testsuite: test_str: One or more argument is empty" $Color_off + echo -e $BRed "\n\n" "Issue in the testsuite: test_str: One or more argument is empty" $Color_Off exit 2 fi - echo $BBlue "=== $1 ===" $Color_off + echo -e $BBlue "================== $1 ==================" $Color_Off - echo "$2" > $tmp_script + echo -e "$2" > $tmp_script # Arg - echo -n "\n= [ARG] " - $executable -c "$2" &> $output + echo -e -n $Blue "= [ARG] " $Color_Off + timeout 2 $executable -c "$2" &> $output actual_code=$? $ref_executable -c "$2" &> $ref_output ref_code=$? @@ -159,8 +165,8 @@ test_str() { check_result "$2" "$actual_code" "$ref_code" # Script - echo -n "\n= [SCRIPT] " - $executable "$tmp_script" &> $output + echo -e -n $Blue "= [SCRIPT]" $Color_Off + timeout 2 $executable "$tmp_script" &> $output actual_code=$? $ref_executable "$tmp_script" &> $ref_output ref_code=$? @@ -168,15 +174,16 @@ test_str() { check_result "$2" "$actual_code" "$ref_code" # Stdin - echo -n "\n= [STDIN] " - $executable < "$tmp_script" &> $output + echo -e -n $Blue "= [STDIN] " $Color_Off + timeout 2 $executable < "$tmp_script" &> $output actual_code=$? - $ref_executable < "$2" &> $ref_output + $ref_executable < "$tmp_script" &> $ref_output ref_code=$? ((total_tests++)) - check_result "$tmp_script" "$actual_code" "$ref_code" + check_result "$2" "$actual_code" "$ref_code" - echo $BBlue "===================" $Color_off "\n" + echo -e "\n" + # echo -e $BBlue "===========================================" $Color_Off "\n" } # @arg test name @@ -185,19 +192,19 @@ test_script() { # Check input if [[ -z "$1" || -z "$2" ]]; then - echo $BRed "\n\n" "Issue in the testsuite: test_script: One or more argument is empty" $Color_off + echo -e $BRed "\n\n" "Issue in the testsuite: test_script: One or more argument is empty" $Color_Off exit 2 fi if [[ ! -f "$2" ]]; then - echo $BRed "\n\n" "Issue in the testsuite: test_script: Second argument is not a file" $Color_off + echo -e $BRed "\n\n" "Issue in the testsuite: test_script: Second argument is not a file" $Color_Off exit 2 fi - echo $BBlue "=== $1 ===" $Color_off + echo -e $BBlue "================== $1 ==================" $Color_Off # Script - echo -n "\n= [SCRIPT] " - $executable "$tmp_script" &> $output + echo -e -n "= [SCRIPT] " + timeout 2 $executable "$tmp_script" &> $output actual_code=$? $ref_executable "$tmp_script" &> $ref_output ref_code=$? @@ -205,15 +212,38 @@ test_script() { check_result "$2" "$actual_code" "$ref_code" # Stdin - echo -n "\n= [STDIN] " - $executable < "$tmp_script" &> $output + echo -e -n "= [STDIN] " + timeout 2 $executable < "$tmp_script" &> $output actual_code=$? $ref_executable < "$2" &> $ref_output ref_code=$? ((total_tests++)) check_result "$tmp_script" "$actual_code" "$ref_code" - echo $BBlue "===================" $Color_off "\n" + echo -e "\n" + # echo -e $BBlue "===========================================" $Color_Off "\n" +} + +summarize() { + + (( passed_tests = $total_tests - $errors_count )) + (( tests_percentage = 100 * $passed_tests / $total_tests )) + + if [[ tests_percentage -gt 80 ]]; then + coverage_color=$BGreen + elif [[ tests_percentage -gt 50 ]]; then + coverage_color=$BYellow + elif [[ tests_percentage -gt 30 ]]; then + coverage_color=$BOrange + else + coverage_color=$BRed + fi + + 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 " Got $BRed$timeouts_count timeout(s)$Color_Off" + echo + } # *********************************************************** @@ -221,4 +251,18 @@ test_script() { # Tests # ################# +echo -e "\n\n""===$BGreen TestsuitatorX Ultra Pro Max+ 365 Premium Gris Sidéral" "\n\n"$Color_Off + + +# echo test_str "Hello" "echo Hello there" +test_str "Hello;" "echo Hello there;" +test_str "'Hello'" "echo 'Hello there'" +test_str "Hello;Hello" "echo Hello; echo Wesh attends quoi; echo pouquoi je suis une ligne en dessous" + +# programs +test_str "LS" "ls" +test_str "Le SS" "lss" + + +summarize From 687f38523b7671fbd338892a5b49559cd7ba9841 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Thu, 22 Jan 2026 12:08:51 +0100 Subject: [PATCH 146/282] fix(main_loop): no double echo --- src/main.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main.c b/src/main.c index a6b18a7..b86a9a7 100644 --- a/src/main.c +++ b/src/main.c @@ -81,9 +81,6 @@ int main(int argc, char **argv) ast_print_dot(command_ast); } - // Call the executor with the AST - return_code = execution(command_ast); - // Main parse-execute loop while (command_ast != NULL && command_ast->type != AST_END) { From 609c1667af13343b03a83038bde67405457e10fd Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Thu, 22 Jan 2026 12:59:04 +0100 Subject: [PATCH 147/282] fix(lexer): POKEDALLARS --- src/lexer/lexer.c | 2 +- src/lexer/lexer_utils.c | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index 89b5f71..f16088b 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -19,7 +19,7 @@ static bool is_special_char(char c) if (c == EOF) return true; - char special_chars[] = "\n'\"`;#|&\\$(){}<>*"; + char special_chars[] = "\n'\"`;#|&\\(){}<>*"; return strchr(special_chars, c) != NULL; } diff --git a/src/lexer/lexer_utils.c b/src/lexer/lexer_utils.c index f954676..0a0a23d 100644 --- a/src/lexer/lexer_utils.c +++ b/src/lexer/lexer_utils.c @@ -45,9 +45,6 @@ static void set_token_spechar(struct token *tok, char *begin, ssize_t size) case '\\': tok->type = TOKEN_BACKSLASH; break; - case '$': - tok->type = TOKEN_DOLLAR; - break; case '(': tok->type = TOKEN_LEFT_PAREN; break; From 4fc43e4678656088b66bafeb9d62a80eb29c2168 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Thu, 22 Jan 2026 12:09:18 +0000 Subject: [PATCH 148/282] feat(dev): expansion --- src/Makefile.am | 2 +- src/execution/execution.c | 22 +++++++++++++--------- src/execution/execution.h | 3 ++- src/expansion/expansion.c | 13 ++++++------- src/expansion/expansion.h | 4 ++-- src/main.c | 4 +++- src/utils/vars/vars.c | 15 +++++++++------ src/utils/vars/vars.h | 5 ----- 8 files changed, 36 insertions(+), 32 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index bd5d691..56c48b6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -18,8 +18,8 @@ bin_PROGRAMS = 42sh parser/libparser.a \ lexer/liblexer.a \ io_backend/libio_backend.a \ - expansion/libexpansion.a \ execution/libexecution.a \ + expansion/libexpansion.a \ utils/libutils.a # ================ TESTS ================ diff --git a/src/execution/execution.c b/src/execution/execution.c index ec0fe21..a98ff2f 100644 --- a/src/execution/execution.c +++ b/src/execution/execution.c @@ -9,7 +9,9 @@ #include #include +#include "../expansion/expansion.h" #include "../utils/ast/ast.h" +#include "../utils/hash_map/hash_map.h" // --- Helpers --- @@ -205,7 +207,7 @@ static int exec_command(struct ast_command *command) * @param ast The AST to execute * @return int The exit status of the last executed command. */ -int execution(struct ast *ast) +int execution(struct ast *ast, struct hash_map *vars) { if (!ast) { @@ -219,18 +221,20 @@ int execution(struct ast *ast) } case AST_CMD: { struct ast_command *command = ast_get_command(ast); + if (!expand(command, vars)) + fprintf(stderr, "Error: Variable expansion failed\n"); return exec_command(command); } case AST_IF: { struct ast_if *if_node = ast_get_if(ast); - int cond = execution(if_node->condition); + int cond = execution(if_node->condition, vars); if (cond == 0) // True { - return execution(if_node->then_clause); + return execution(if_node->then_clause, vars); } else // False { - return execution(if_node->else_clause); + return execution(if_node->else_clause, vars); } } case AST_LIST: { @@ -240,25 +244,25 @@ int execution(struct ast *ast) while (cur) { struct ast *child = (struct ast *)cur->data; - ret = execution(child); + ret = execution(child, vars); cur = cur->next; } return ret; } case AST_AND_OR: { struct ast_and_or *ao_node = ast_get_and_or(ast); - int left_ret = execution(ao_node->left); + 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); + return execution(ao_node->right, vars); return left_ret; } else // OR { if (left_ret != 0) - return execution(ao_node->right); + return execution(ao_node->right, vars); return left_ret; } } @@ -335,7 +339,7 @@ int execution(struct ast *ast) close(new_fd); } - int ret = execution(redir->child); + int ret = execution(redir->child, vars); if (saved_fd != -1) { diff --git a/src/execution/execution.h b/src/execution/execution.h index aacacc3..872f0ad 100644 --- a/src/execution/execution.h +++ b/src/execution/execution.h @@ -3,6 +3,7 @@ #include "../utils/ast/ast.h" #include "../utils/lists/lists.h" +#include "../utils/hash_map/hash_map.h" /** * @brief Execute the AST @@ -10,6 +11,6 @@ * @param ast Pointer to the AST structure * @return int Execution status code of the last command */ -int execution(struct ast *ast); +int execution(struct ast *ast, struct hash_map *vars); #endif /* ! EXECUTION_H */ diff --git a/src/expansion/expansion.c b/src/expansion/expansion.c index 407e2f7..ab6241e 100644 --- a/src/expansion/expansion.c +++ b/src/expansion/expansion.c @@ -115,11 +115,10 @@ static bool expand_var(char **str, size_t pos, const struct hash_map *vars) return false; } -struct ast_command *expand(struct ast_command *command, - const struct hash_map *vars) +bool expand(struct ast_command *command, const struct hash_map *vars) { if (command == NULL) - return NULL; + return false; char *str; size_t len; @@ -150,7 +149,7 @@ struct ast_command *expand(struct ast_command *command, // variable expansion bool r = expand_var(&str, i, vars); if (r == false || str == NULL) - return NULL; + return false; i--; // -1 because loop will increment i } @@ -160,7 +159,7 @@ struct ast_command *expand(struct ast_command *command, { // error: quote not closed fprintf(stderr, "Error: quote not closed in string: %s\n", str); - return NULL; + return false; } if (len != strlen(str)) @@ -169,12 +168,12 @@ struct ast_command *expand(struct ast_command *command, if (new_str == NULL) { // error: realloc fail - return NULL; + return false; } l->data = new_str; } l = l->next; } - return command; + return true; } diff --git a/src/expansion/expansion.h b/src/expansion/expansion.h index 7211ae3..64b016f 100644 --- a/src/expansion/expansion.h +++ b/src/expansion/expansion.h @@ -1,6 +1,7 @@ #ifndef EXPANSION_H #define EXPANSION_H +#include #include #include "../utils/hash_map/hash_map.h" @@ -20,7 +21,6 @@ size_t parse_var_name(char *str, char **res); * @param vars The hash map containing variables. * @return A new AST command with variables expanded, or NULL on error. */ -struct ast_command *expand(struct ast_command *command, - const struct hash_map *vars); +bool expand(struct ast_command *command, const struct hash_map *vars); #endif /* ! EXPANSION_H */ diff --git a/src/main.c b/src/main.c index b86a9a7..10644ac 100644 --- a/src/main.c +++ b/src/main.c @@ -85,7 +85,9 @@ int main(int argc, char **argv) while (command_ast != NULL && command_ast->type != AST_END) { // Execute AST - return_code = execution(command_ast); + return_code = execution(command_ast, vars); + + // set $? variable ast_free(&command_ast); diff --git a/src/utils/vars/vars.c b/src/utils/vars/vars.c index 8d762da..290566d 100644 --- a/src/utils/vars/vars.c +++ b/src/utils/vars/vars.c @@ -12,12 +12,7 @@ #define VARS_INITIAL_SIZE 16 -struct hash_map *vars_init(void) -{ - return hash_map_init(VARS_INITIAL_SIZE); -} - -void vars_default(struct hash_map *vars) +static void vars_default(struct hash_map *vars) { set_var_copy(vars, "?", "0"); pid_t pid = getpid(); @@ -26,6 +21,14 @@ void vars_default(struct hash_map *vars) set_var_copy(vars, "$", pid_str); } +struct hash_map *vars_init(void) +{ + struct hash_map *vars = hash_map_init(VARS_INITIAL_SIZE); + if (vars != NULL) + vars_default(vars); + return vars; +} + short short_random(void) { static bool seeded = false; diff --git a/src/utils/vars/vars.h b/src/utils/vars/vars.h index 85fb5ee..9e5fa4c 100644 --- a/src/utils/vars/vars.h +++ b/src/utils/vars/vars.h @@ -10,11 +10,6 @@ */ struct hash_map *vars_init(void); -/** - * Set default variables. - */ -void vars_default(struct hash_map *vars); - /** * Generate a random short integer (16 bits positive [0-32767]). */ From fbc36f35b2c5193261abce512a13e8f8cdad9549 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Thu, 22 Jan 2026 14:05:49 +0100 Subject: [PATCH 149/282] fix(IOB): EOF in stdin works --- src/io_backend/io_backend.c | 6 +++++- src/parser/parsing_utils.c | 11 ++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/io_backend/io_backend.c b/src/io_backend/io_backend.c index 401daed..1ce256e 100644 --- a/src/io_backend/io_backend.c +++ b/src/io_backend/io_backend.c @@ -85,7 +85,11 @@ ssize_t stream_read(char **stream) if (nread == -1) { state = IOB_STATE_FINISHED; - return 0; + // MAGNIFICO + // malloc(1); + *stream_buf = EOF; + *stream = stream_buf; + return 1; } else if (nread < 0) state = IOB_STATE_ERROR; diff --git a/src/parser/parsing_utils.c b/src/parser/parsing_utils.c index 8511930..3be4f05 100644 --- a/src/parser/parsing_utils.c +++ b/src/parser/parsing_utils.c @@ -14,7 +14,6 @@ // === Static functions /* Returns true if c is a command terminator, false otherwise - */ static bool isterminator(struct token *token) { if (token == NULL) @@ -31,6 +30,8 @@ static bool isterminator(struct token *token) } } + */ + /* @brief: returns true if token is an end of list indicator. * @warning: not used */ @@ -77,8 +78,8 @@ struct ast *parse_list(struct lexer_context *ctx) while (token->type == TOKEN_SEMICOLON) { token = POP_TOKEN(); - if (!isterminator(token)) // Follow(list) - { + // if (!isterminator(token)) // Follow(list) + // { current_node = parse_and_or(ctx); if (current_node == NULL) { @@ -87,8 +88,8 @@ struct ast *parse_list(struct lexer_context *ctx) return NULL; } result_list = list_append(result_list, current_node); - token = PEEK_TOKEN(); - } + // } + token = PEEK_TOKEN(); } // result_list = list_append(result_list, current_node); From 6dd19a75addd85632f6fb1735624a2af05b2585e Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Thu, 22 Jan 2026 13:10:23 +0000 Subject: [PATCH 150/282] feat(execution): set $? var --- src/execution/execution.c | 1 + src/main.c | 10 +++++++--- src/utils/string_utils/string_utils.c | 9 +++++++++ src/utils/string_utils/string_utils.h | 9 +++++++++ src/utils/vars/vars.c | 12 ++++++++++-- src/utils/vars/vars.h | 6 ++++++ 6 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/execution/execution.c b/src/execution/execution.c index a98ff2f..d1d0e97 100644 --- a/src/execution/execution.c +++ b/src/execution/execution.c @@ -216,6 +216,7 @@ int execution(struct ast *ast, struct hash_map *vars) switch (ast->type) { + case AST_VOID: case AST_END: { return 0; } diff --git a/src/main.c b/src/main.c index 10644ac..a03cfa9 100644 --- a/src/main.c +++ b/src/main.c @@ -84,10 +84,14 @@ int main(int argc, char **argv) // Main parse-execute loop while (command_ast != NULL && command_ast->type != AST_END) { - // Execute AST - return_code = execution(command_ast, vars); + if (command_ast->type != AST_VOID) + { + // Execute AST + return_code = execution(command_ast, vars); - // set $? variable + // set $? variable + set_var_int(vars, "?", return_code); + } ast_free(&command_ast); diff --git a/src/utils/string_utils/string_utils.c b/src/utils/string_utils/string_utils.c index e5b7040..15ad87e 100644 --- a/src/utils/string_utils/string_utils.c +++ b/src/utils/string_utils/string_utils.c @@ -1,6 +1,7 @@ #include "string_utils.h" #include +#include #include #include @@ -42,3 +43,11 @@ char *insert_into(char *dest, const char *src, size_t pos, size_t len) return realloc(dest, new_len + 1); return dest; } + +void int_to_str(int value, char *buffer) +{ + if (buffer == NULL) + return; + + snprintf(buffer, 11, "%d", value); +} diff --git a/src/utils/string_utils/string_utils.h b/src/utils/string_utils/string_utils.h index 7f0e622..36f23ac 100644 --- a/src/utils/string_utils/string_utils.h +++ b/src/utils/string_utils/string_utils.h @@ -18,4 +18,13 @@ char *trim_blank_left(char *str); */ char *insert_into(char *dest, const char *src, size_t pos, size_t len); +/** + * Converts an integer to its string representation. + * @param value The integer value to convert. + * @param buffer A character array where the resulting string will be stored. + * The buffer must be at least 11 bytes long to accommodate the largest + * 32-bit integer and the null terminator. + */ +void int_to_str(int value, char *buffer); + #endif /* STRING_UTILS_H */ diff --git a/src/utils/vars/vars.c b/src/utils/vars/vars.c index 290566d..a69d349 100644 --- a/src/utils/vars/vars.c +++ b/src/utils/vars/vars.c @@ -9,6 +9,7 @@ #include #include "../hash_map/hash_map.h" +#include "../string_utils/string_utils.h" #define VARS_INITIAL_SIZE 16 @@ -16,8 +17,8 @@ static void vars_default(struct hash_map *vars) { set_var_copy(vars, "?", "0"); pid_t pid = getpid(); - char pid_str[20]; - snprintf(pid_str, sizeof(pid_str), "%d", pid); + char pid_str[11]; + int_to_str(pid, pid_str); set_var_copy(vars, "$", pid_str); } @@ -71,3 +72,10 @@ bool set_var_copy(struct hash_map *vars, const char *key, const char *value) free(value_copy); return res; } + +bool set_var_int(struct hash_map *vars, const char *key, int value) +{ + char value_str[11]; + int_to_str(value, value_str); + return set_var_copy(vars, key, value_str); +} diff --git a/src/utils/vars/vars.h b/src/utils/vars/vars.h index 9e5fa4c..19ffae1 100644 --- a/src/utils/vars/vars.h +++ b/src/utils/vars/vars.h @@ -40,4 +40,10 @@ bool set_var(struct hash_map *vars, const char *key, const char *value, */ bool set_var_copy(struct hash_map *vars, const char *key, const char *value); +/** + * Set the value of a variable to an integer. Behavior is similar to + * set_var_copy. + */ +bool set_var_int(struct hash_map *vars, const char *key, int value); + #endif /* ! VARS_H */ From 3445a36a7c89ee561e304497bc2eb74f31221c70 Mon Sep 17 00:00:00 2001 From: Jean Herail Date: Thu, 22 Jan 2026 16:46:33 +0100 Subject: [PATCH 151/282] Pretty_print --- src/main.c | 5 ++ src/utils/ast/ast.c | 165 ++++++++++++++++++++++++++------------------ 2 files changed, 103 insertions(+), 67 deletions(-) diff --git a/src/main.c b/src/main.c index f9c4407..b86a9a7 100644 --- a/src/main.c +++ b/src/main.c @@ -76,6 +76,11 @@ int main(int argc, char **argv) // 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) { diff --git a/src/utils/ast/ast.c b/src/utils/ast/ast.c index cd875e7..910fd10 100644 --- a/src/utils/ast/ast.c +++ b/src/utils/ast/ast.c @@ -2,9 +2,91 @@ #include #include +#include #include #include +void ast_free(struct ast **node) +{ + if (!node) + if (*node == NULL) + return; + // ast void does not need to be freed. + if (ast_is_if(*node)) + ast_free_if(ast_get_if(*node)); + else if (ast_is_command(*node)) + ast_free_command(ast_get_command(*node)); + else if (ast_is_list(*node)) + ast_free_list(ast_get_list(*node)); + else if (ast_is_and_or(*node)) + ast_free_and_or(ast_get_and_or(*node)); + else if (ast_is_redir(*node)) + ast_free_redir(ast_get_redir(*node)); +} + +struct ast *ast_create(enum ast_type type, void *data) +{ + struct ast *node = malloc(sizeof(struct ast)); + if (!node) + return NULL; + + node->type = type; + node->data = data; + + return node; +} + +/* struct ast *ast_create_if(struct ast *condition, struct ast *then_clause, + struct ast *else_clause) +{ + struct ast_if *if_data = malloc(sizeof(struct ast_if)); + if (!if_data) + return NULL; + + if_data->condition = condition; + if_data->then_clause = then_clause; + if_data->else_clause = else_clause; + + return ast_create(AST_IF, if_data); +} + +struct ast *ast_create_cmd(struct list *cmd) +{ + struct ast_cmd *cmd_data = malloc(sizeof(struct ast_cmd)); + if (!cmd_data) + return NULL; + + cmd_data->cmd = cmd; + + return ast_create(AST_CMD, cmd_data); +} */ +/* +bool ast_is_if(struct ast *node) +{ + assert(node != NULL); + return node->type == AST_IF; +} + +bool ast_is_cmd(struct ast *node) +{ + assert(node != NULL); + return node->type == AST_CMD; +} + +struct ast_if *ast_get_if(struct ast *node) +{ + assert(node != NULL); + assert(node->type == AST_IF); + return (struct ast_if *)node->data; +} + +struct ast_cmd *ast_get_cmd(struct ast *node) +{ + assert(node != NULL); + assert(node->type == AST_CMD); + return (struct ast_cmd *)node->data; +} + */ static void ast_print_dot_recursive(struct ast *node, FILE *out) { if (!node) @@ -12,6 +94,20 @@ static void ast_print_dot_recursive(struct ast *node, FILE *out) switch (node->type) { + case AST_LIST: { + struct ast_list *ast_list = ast_get_list(node); + fprintf(out, " node%p [label=\"LIST\"];\n", (void *)node); + + struct list *elt = ast_list->children; + while (elt != NULL) + { + struct ast *child = (struct ast *)elt->data; + fprintf(out, " node%p -> node%p;\n", (void *)node, (void *)child); + ast_print_dot_recursive(child, out); + elt = elt->next; + } + break; + } case AST_IF: { struct ast_if *if_data = ast_get_if(node); fprintf(out, " node%p [label=\"IF\"];\n", (void *)node); @@ -44,9 +140,9 @@ static void ast_print_dot_recursive(struct ast *node, FILE *out) break; } case AST_CMD: { - struct ast_cmd *cmd_data = ast_get_cmd(node); + struct ast_command *command_data = ast_get_command(node); fprintf(out, " node%p [label=\"", (void *)node); - struct list *l = cmd_data->cmd; + struct list *l = command_data->command; while (l) { fprintf(out, "%s", (char *)l->data); @@ -80,69 +176,4 @@ void ast_print_dot(struct ast *ast) ast_print_dot_recursive(ast, dot_pipe); fprintf(dot_pipe, "}\n"); pclose(dot_pipe); - - system("open ast.svg"); -} - -bool ast_is_if(struct ast *node) -{ - assert(node != NULL); - return node->type == AST_IF; -} - -bool ast_is_cmd(struct ast *node) -{ - assert(node != NULL); - return node->type == AST_CMD; -} - -struct ast_if *ast_get_if(struct ast *node) -{ - assert(node != NULL); - assert(node->type == AST_IF); - return (struct ast_if *)node->data; -} - -struct ast_cmd *ast_get_cmd(struct ast *node) -{ - assert(node != NULL); - assert(node->type == AST_CMD); - return (struct ast_cmd *)node->data; -} - -static struct ast *ast_create(enum ast_type type, void *data) -{ - struct ast *node = malloc(sizeof(struct ast)); - if (!node) - return NULL; - - node->type = type; - node->data = data; - - return node; -} - -struct ast *ast_create_if(struct ast *condition, struct ast *then_clause, - struct ast *else_clause) -{ - struct ast_if *if_data = malloc(sizeof(struct ast_if)); - if (!if_data) - return NULL; - - if_data->condition = condition; - if_data->then_clause = then_clause; - if_data->else_clause = else_clause; - - return ast_create(AST_IF, if_data); -} - -struct ast *ast_create_cmd(struct list *cmd) -{ - struct ast_cmd *cmd_data = malloc(sizeof(struct ast_cmd)); - if (!cmd_data) - return NULL; - - cmd_data->cmd = cmd; - - return ast_create(AST_CMD, cmd_data); } From 2f2509f1c91b1e560ee8e6888feeab5b1414e8f0 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Thu, 22 Jan 2026 17:19:17 +0100 Subject: [PATCH 152/282] fix: clean merge of pretty-print with no memory leaks --- src/utils/ast/ast.c | 61 ++++++--------------------------------------- 1 file changed, 7 insertions(+), 54 deletions(-) diff --git a/src/utils/ast/ast.c b/src/utils/ast/ast.c index 910fd10..e336a73 100644 --- a/src/utils/ast/ast.c +++ b/src/utils/ast/ast.c @@ -1,3 +1,5 @@ +#define _POSIX_C_SOURCE 12344 + #include "ast.h" #include @@ -8,9 +10,8 @@ void ast_free(struct ast **node) { - if (!node) - if (*node == NULL) - return; + if (*node == NULL) + return; // ast void does not need to be freed. if (ast_is_if(*node)) ast_free_if(ast_get_if(*node)); @@ -22,6 +23,9 @@ void ast_free(struct ast **node) ast_free_and_or(ast_get_and_or(*node)); else if (ast_is_redir(*node)) ast_free_redir(ast_get_redir(*node)); + + free(*node); + *node = NULL; } struct ast *ast_create(enum ast_type type, void *data) @@ -36,57 +40,6 @@ struct ast *ast_create(enum ast_type type, void *data) return node; } -/* struct ast *ast_create_if(struct ast *condition, struct ast *then_clause, - struct ast *else_clause) -{ - struct ast_if *if_data = malloc(sizeof(struct ast_if)); - if (!if_data) - return NULL; - - if_data->condition = condition; - if_data->then_clause = then_clause; - if_data->else_clause = else_clause; - - return ast_create(AST_IF, if_data); -} - -struct ast *ast_create_cmd(struct list *cmd) -{ - struct ast_cmd *cmd_data = malloc(sizeof(struct ast_cmd)); - if (!cmd_data) - return NULL; - - cmd_data->cmd = cmd; - - return ast_create(AST_CMD, cmd_data); -} */ -/* -bool ast_is_if(struct ast *node) -{ - assert(node != NULL); - return node->type == AST_IF; -} - -bool ast_is_cmd(struct ast *node) -{ - assert(node != NULL); - return node->type == AST_CMD; -} - -struct ast_if *ast_get_if(struct ast *node) -{ - assert(node != NULL); - assert(node->type == AST_IF); - return (struct ast_if *)node->data; -} - -struct ast_cmd *ast_get_cmd(struct ast *node) -{ - assert(node != NULL); - assert(node->type == AST_CMD); - return (struct ast_cmd *)node->data; -} - */ static void ast_print_dot_recursive(struct ast *node, FILE *out) { if (!node) From 5faa179b63e49b6f0668f794094a4c77c5249a98 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Thu, 22 Jan 2026 19:06:58 +0100 Subject: [PATCH 153/282] fix(parser): rule if now works --- src/execution/execution.h | 2 +- src/parser/parsing_utils.c | 29 +++++++++++++++++------------ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/execution/execution.h b/src/execution/execution.h index 872f0ad..c3e0080 100644 --- a/src/execution/execution.h +++ b/src/execution/execution.h @@ -2,8 +2,8 @@ #define EXECUTION_H #include "../utils/ast/ast.h" -#include "../utils/lists/lists.h" #include "../utils/hash_map/hash_map.h" +#include "../utils/lists/lists.h" /** * @brief Execute the AST diff --git a/src/parser/parsing_utils.c b/src/parser/parsing_utils.c index 3be4f05..2273654 100644 --- a/src/parser/parsing_utils.c +++ b/src/parser/parsing_utils.c @@ -78,16 +78,16 @@ struct ast *parse_list(struct lexer_context *ctx) while (token->type == TOKEN_SEMICOLON) { token = POP_TOKEN(); - // if (!isterminator(token)) // Follow(list) + // if (!isterminator(token)) // Follow(list) // { - current_node = parse_and_or(ctx); - if (current_node == NULL) - { - // TODO free list - // There must be a function for that - return NULL; - } - result_list = list_append(result_list, current_node); + current_node = parse_and_or(ctx); + if (current_node == NULL) + { + // TODO free list + // There must be a function for that + return NULL; + } + result_list = list_append(result_list, current_node); // } token = PEEK_TOKEN(); } @@ -247,31 +247,34 @@ struct ast *parse_compound_list(struct lexer_context *ctx) while (token->type == TOKEN_NEWLINE) { token = POP_TOKEN(); + token = PEEK_TOKEN(); } // and_or current_cmd = parse_and_or(ctx); if (current_cmd == NULL) return NULL; - list_append(result_list, current_cmd); + result_list = list_append(result_list, current_cmd); // Following commands token = PEEK_TOKEN(); while (token->type == TOKEN_SEMICOLON || token->type == TOKEN_NEWLINE) { POP_TOKEN(); + token = PEEK_TOKEN(); // Skip newlines while (token->type == TOKEN_NEWLINE) { token = POP_TOKEN(); + token = PEEK_TOKEN(); } // and_or current_cmd = parse_and_or(ctx); if (current_cmd == NULL) return NULL; - list_append(result_list, current_cmd); + result_list = list_append(result_list, current_cmd); token = PEEK_TOKEN(); } @@ -280,12 +283,14 @@ struct ast *parse_compound_list(struct lexer_context *ctx) if (token->type == TOKEN_SEMICOLON) { token = POP_TOKEN(); + token = PEEK_TOKEN(); } // Skip newlines while (token->type == TOKEN_NEWLINE) { token = POP_TOKEN(); + token = PEEK_TOKEN(); } struct ast *result = ast_create_list(result_list); @@ -333,8 +338,8 @@ struct ast *parse_else_clause(struct lexer_context *ctx) if (token->type == TOKEN_ELSE) { + token = POP_TOKEN(); // eat else result = parse_compound_list(ctx); - token = POP_TOKEN(); // Forward } if (result == NULL) From 82f35cfa082f27939946f935711cb65ee6e05f7e Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Thu, 22 Jan 2026 19:57:49 +0100 Subject: [PATCH 154/282] feat(parser): and_or_rule in progress --- src/lexer/lexer_utils.c | 8 +++++++ src/lexer/lexer_utils.h | 4 +++- src/parser/parsing_utils.c | 46 +++++++++++++++++++++++++++++++++++++- src/utils/ast/ast_and_or.h | 1 + 4 files changed, 57 insertions(+), 2 deletions(-) diff --git a/src/lexer/lexer_utils.c b/src/lexer/lexer_utils.c index 0a0a23d..4206c4c 100644 --- a/src/lexer/lexer_utils.c +++ b/src/lexer/lexer_utils.c @@ -98,6 +98,14 @@ static void set_token_keyword(struct token *tok, char *begin, ssize_t size) { tok->type = TOKEN_ELIF; } + else if (strncmp(begin, "&&", size) == 0) + { + tok->type = TOKEN_AND; + } + else if (strncmp(begin, "||", size) == 0) + { + tok->type = TOKEN_OR; + } // no keywords found. if (tok->type == TOKEN_NULL) diff --git a/src/lexer/lexer_utils.h b/src/lexer/lexer_utils.h index 729e912..83bc772 100644 --- a/src/lexer/lexer_utils.h +++ b/src/lexer/lexer_utils.h @@ -56,7 +56,9 @@ enum token_type TOKEN_THEN, TOKEN_ELSE, TOKEN_FI, - TOKEN_ELIF + TOKEN_ELIF, + TOKEN_AND, + TOKEN_OR }; struct token diff --git a/src/parser/parsing_utils.c b/src/parser/parsing_utils.c index 2273654..2daeb3d 100644 --- a/src/parser/parsing_utils.c +++ b/src/parser/parsing_utils.c @@ -13,6 +13,20 @@ // === Static functions +enum ast_and_or_type and_or_tok_to_ast(enum token_type tok_type) +{ + switch (tok_type) + { + case TOKEN_AND: + return AST_AND_OR_TYPE_AND; + case TOKEN_OR: + return AST_AND_OR_TYPE_OR; + default: + fprintf(stderr, "and_or impossible to init, wrong token type"); + return AST_AND_OR_NULL; + } +} + /* Returns true if c is a command terminator, false otherwise static bool isterminator(struct token *token) { @@ -98,7 +112,37 @@ struct ast *parse_list(struct lexer_context *ctx) struct ast *parse_and_or(struct lexer_context *ctx) { - return parse_pipeline(ctx); + struct ast *result = parse_pipeline(ctx); + struct token *token = PEEK_TOKEN(); + + if (token->type == TOKEN_AND || token->type == TOKEN_OR) + { + // set left part + + 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); + + token = PEEK_TOKEN(); + + // skip newlines + while (token->type == TOKEN_NEWLINE) + { + token = POP_TOKEN(); + token = PEEK_TOKEN(); + } + + // right part + struct ast *right = parse_pipeline(ctx); + + result = ast_create_and_or(left, right, type); + } + + return result; } struct ast *parse_pipeline(struct lexer_context *ctx) diff --git a/src/utils/ast/ast_and_or.h b/src/utils/ast/ast_and_or.h index 7bbe76c..344cfbf 100644 --- a/src/utils/ast/ast_and_or.h +++ b/src/utils/ast/ast_and_or.h @@ -7,6 +7,7 @@ enum ast_and_or_type { + AST_AND_OR_NULL, AST_AND_OR_TYPE_AND, AST_AND_OR_TYPE_OR }; From 3cdba0ad9c37dd4fc9a4e819dece3554456a0b26 Mon Sep 17 00:00:00 2001 From: matteo Date: Fri, 23 Jan 2026 15:56:21 +0100 Subject: [PATCH 155/282] feat(lexer): comments done + operator WIP --- src/lexer/lexer.c | 12 ++++++++---- src/lexer/lexer_utils.c | 35 ++++++++++++++++++++++++++++++++++- src/lexer/lexer_utils.h | 22 ++++++++++++++++++---- 3 files changed, 60 insertions(+), 9 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index f16088b..e6dcfc7 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -133,13 +133,14 @@ struct token *peek_token(struct lexer_context *ctx) i++; } - struct token *tok = new_token(stream, i); + struct token *tok = new_token(stream, i, ctx->only_digits); // if token is comment, we don't want it if (tok->type == TOKEN_COMMENT) { - // drop current stream - get_next_stream(ctx); + // Find next newline or EOF. + go_end_of_line(ctx); + free_token(&tok); tok = peek_token(ctx); } @@ -170,6 +171,9 @@ struct token *pop_token(struct lexer_context *ctx) if (!update_lexing_mode(stream, i, &lexing_mode) && lexing_mode == LEXER_NORMAL) { + // TODO call here a function + // it must check if is a spe char or an operator + // and sets i accordingly. if (is_special_char(stream[i])) { if (i == 0) // where we create spe_char token @@ -195,7 +199,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->current_token = new_token(stream, i, ctx->only_digits); } save_state(stream, i, ctx); diff --git a/src/lexer/lexer_utils.c b/src/lexer/lexer_utils.c index 4206c4c..4906fcb 100644 --- a/src/lexer/lexer_utils.c +++ b/src/lexer/lexer_utils.c @@ -132,12 +132,30 @@ static void set_token_word(struct token *tok, char *begin, ssize_t size) } } -struct token *new_token(char *begin, ssize_t size) +/* @brief: Sets the token to an IO number + * Also allocates the data and fills it. + */ +static void set_token_ION(struct token *tok, char *begin, ssize_t size) +{ + if (tok->type == TOKEN_NULL && size != 0) + { + tok->type = TOKEN_IONUMBER; + tok->data = calloc(size + 1, sizeof(char)); + if (tok->data == NULL) + return; + strncpy(tok->data, begin, size); + } +} + +struct token *new_token(char *begin, ssize_t size, bool only_digits) { struct token *tok = calloc(1, sizeof(struct token)); if (tok == NULL) return NULL; + if (only_digits) + set_token_ION(tok, begin, size); + set_token_spechar(tok, begin, size); set_token_keyword(tok, begin, size); set_token_word(tok, begin, size); @@ -186,6 +204,21 @@ void stream_init(struct lexer_context *ctx) ctx->end_previous_token = trimed_stream; } +void go_end_of_line(struct lexer_context *ctx) +{ + if (ctx == NULL || ctx->end_previous_token == NULL) + return; + + ssize_t i = 0; + while (ctx->end_previous_token[i] != '\n' + && ctx->end_previous_token[i] != EOF) + { + i++; + } + ctx->end_previous_token += i; + ctx->remaining_chars -= i; +} + void get_next_stream(struct lexer_context *ctx) { ctx->remaining_chars = 0; diff --git a/src/lexer/lexer_utils.h b/src/lexer/lexer_utils.h index 83bc772..fde696b 100644 --- a/src/lexer/lexer_utils.h +++ b/src/lexer/lexer_utils.h @@ -1,6 +1,7 @@ #ifndef LEXER_UTILS_H #define LEXER_UTILS_H +#include #include #include @@ -9,6 +10,10 @@ 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; + struct token *previous_token; struct token *current_token; }; @@ -39,17 +44,20 @@ enum token_type TOKEN_GRAVE, TOKEN_SEMICOLON, TOKEN_COMMENT, - TOKEN_PIPE, - TOKEN_AMPERSAND, + TOKEN_STAR, TOKEN_BACKSLASH, TOKEN_DOLLAR, TOKEN_LEFT_PAREN, TOKEN_RIGHT_PAREN, TOKEN_LEFT_BRACKET, TOKEN_RIGHT_BRACKET, + + // redirections TOKEN_LESS, TOKEN_GREATER, - TOKEN_STAR, + TOKEN_PIPE, + TOKEN_AMPERSAND, + TOKEN_IONUMBER, // Keywords TOKEN_IF, @@ -73,7 +81,7 @@ struct token * * @return: NULL on error, a token otherwise. */ -struct token *new_token(char *begin, ssize_t size); +struct token *new_token(char *begin, ssize_t size, bool only_digits); /* @brief: frees the token given in argument */ @@ -90,6 +98,12 @@ void free_token(struct token **tok); */ void stream_init(struct lexer_context *ctx); +/* @brief: finds the next '\n' or EOF character, + * starting at [ctx->end_previous_token], + * and updates the stream and remaining_chars accordingly. + */ +void go_end_of_line(struct lexer_context *ctx); + /* * @brief: drops the current stream and asks IOB for a new one */ From 96ac2fea77cbf010e849716c60f0ea5407dfa068 Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Fri, 23 Jan 2026 17:01:26 +0100 Subject: [PATCH 156/282] feat: yet another new parser architecture --- src/main.c | 4 + src/parser/grammar.c | 41 ++++++++++ src/parser/grammar.h | 65 +++++++++++++++ src/parser/grammar_advanced.c | 0 src/parser/grammar_advanced.h | 4 + .../{parsing_utils.c => grammar_basic.c} | 79 +++---------------- .../{parsing_utils.h => grammar_basic.h} | 35 ++------ src/parser/parser.c | 63 ++++++++------- src/parser/parser.h | 19 +++++ 9 files changed, 182 insertions(+), 128 deletions(-) create mode 100644 src/parser/grammar.c create mode 100644 src/parser/grammar.h create mode 100644 src/parser/grammar_advanced.c create mode 100644 src/parser/grammar_advanced.h rename src/parser/{parsing_utils.c => grammar_basic.c} (82%) rename src/parser/{parsing_utils.h => grammar_basic.h} (59%) diff --git a/src/main.c b/src/main.c index a03cfa9..7ef833c 100644 --- a/src/main.c +++ b/src/main.c @@ -73,6 +73,9 @@ int main(int argc, char **argv) // init lexer context struct lexer_context *ctx = calloc(1, sizeof(struct lexer_context)); + // init parser + int parser_init(); + // Retrieve and build first AST struct ast *command_ast = get_ast(ctx); @@ -107,6 +110,7 @@ int main(int argc, char **argv) return ERR_INPUT_PROCESSING; ast_free(&command_ast); + parser_close(); return return_code; } diff --git a/src/parser/grammar.c b/src/parser/grammar.c new file mode 100644 index 0000000..15ee342 --- /dev/null +++ b/src/parser/grammar.c @@ -0,0 +1,41 @@ +#define _POSIX_C_SOURCE 200809L + +// === Includes +#include "grammar.h" + +#include "../utils/hash_map/hash_map.h" +#include "grammar_basic.h" + +// === Static variables + +static struct hash_map *firsts_map = NULL; + +// === Static functions +static enum token_type first(enum rule r) +{ + // TODO + return TOKEN_NULL; +} + +// === Functions + +bool grammar_init(void) +{ + // Create firsts hashmap + // TODO + + // Populate the hashmap + // TODO + + return true; +} + +void grammar_close(void) +{ + // TODO free hashmap +} + +struct ast *parse_input(struct lexer_context *ctx) +{ + return parse_list(ctx); +} diff --git a/src/parser/grammar.h b/src/parser/grammar.h new file mode 100644 index 0000000..fe31849 --- /dev/null +++ b/src/parser/grammar.h @@ -0,0 +1,65 @@ +#ifndef GRAMMAR_H +#define GRAMMAR_H + +#include + +#include "../lexer/lexer.h" + +// === Macros + +#define PEEK_TOKEN() \ + peek_token(ctx); \ + if (token == NULL) \ + { \ + puts("Internal error: cannot get the following token"); \ + return NULL; \ + } + +#define POP_TOKEN() \ + pop_token(ctx); \ + if (token == NULL) \ + { \ + puts("Internal error: cannot get the following token"); \ + return NULL; \ + } + +// === Structures + +enum rule { + RULE_NULL, + RULE_INPUT, + RULE_LIST, + RULE_AND_OR, + RULE_PIPELINE, + RULE_COMMAND, + RULE_SIMPLE_COMMAND, + RULE_SHELL_COMMAND, + RULE_IF, + RULE_COMPOUND_LIST, + RULE_ELSE_CLAUSE +}; + +// === Functions + +/* @brief Initializes the grammar submodule + * @return PARSER_INIT_SUCCESS on success PARSER_INIT_ERROR on error + * @warning Do not use outside the parser + */ + bool grammar_init(void); + +/* @brief Closes the grammar submodule + * @warning Do not use outside the parser + */ + void grammar_close(void); + +/* @brief Acts as the entry point of the parser, calls parse_list + * + * @code input = list '\n' + * | list EOF + * | '\n' + * | EOF + * ; + */ +struct ast *parse_input(struct lexer_context *ctx); + +#endif /* ! GRAMMAR_H */ diff --git a/src/parser/grammar_advanced.c b/src/parser/grammar_advanced.c new file mode 100644 index 0000000..e69de29 diff --git a/src/parser/grammar_advanced.h b/src/parser/grammar_advanced.h new file mode 100644 index 0000000..8773655 --- /dev/null +++ b/src/parser/grammar_advanced.h @@ -0,0 +1,4 @@ +#ifndef GRAMMAR_ADVANCED_H +#define GRAMMAR_ADVANCED_H + +#endif /* ! GRAMMAR_ADVANCED_H */ diff --git a/src/parser/parsing_utils.c b/src/parser/grammar_basic.c similarity index 82% rename from src/parser/parsing_utils.c rename to src/parser/grammar_basic.c index 3be4f05..dcb1f1b 100644 --- a/src/parser/parsing_utils.c +++ b/src/parser/grammar_basic.c @@ -1,64 +1,11 @@ -#define _POSIX_C_SOURCE 200809L +#include "grammar_basic.h" -// === Includes -#include "parsing_utils.h" - -#include -#include #include #include #include "../lexer/lexer.h" -#include "../utils/ast/ast.h" - -// === Static functions - -/* Returns true if c is a command terminator, false otherwise -static bool isterminator(struct token *token) -{ - if (token == NULL) - return false; - - switch (token->type) - { - case TOKEN_NEWLINE: - case TOKEN_SEMICOLON: - case TOKEN_EOF: - return true; - default: - return false; - } -} - - */ - -/* @brief: returns true if token is an end of list indicator. - * @warning: not used - */ - -/* -static bool is_end_of_list(struct token *token) -{ - if (token == NULL) - return false; - - switch (token->type) - { - case TOKEN_NEWLINE: - case TOKEN_EOF: - return true; - default: - return false; - } -} -*/ - -// === Functions - -struct ast *parse_input(struct lexer_context *ctx) -{ - return parse_list(ctx); -} +#include "../utils/lists/lists.h" +#include "grammar.h" struct ast *parse_list(struct lexer_context *ctx) { @@ -78,20 +25,16 @@ struct ast *parse_list(struct lexer_context *ctx) while (token->type == TOKEN_SEMICOLON) { token = POP_TOKEN(); - // if (!isterminator(token)) // Follow(list) - // { - current_node = parse_and_or(ctx); - if (current_node == NULL) - { - // TODO free list - // There must be a function for that - return NULL; - } - result_list = list_append(result_list, current_node); - // } + current_node = parse_and_or(ctx); + if (current_node == NULL) + { + // TODO free list + // There must be a function for that + return NULL; + } + result_list = list_append(result_list, current_node); token = PEEK_TOKEN(); } - // result_list = list_append(result_list, current_node); return ast_create_list(result_list); } diff --git a/src/parser/parsing_utils.h b/src/parser/grammar_basic.h similarity index 59% rename from src/parser/parsing_utils.h rename to src/parser/grammar_basic.h index 89eb4bf..0990e1b 100644 --- a/src/parser/parsing_utils.h +++ b/src/parser/grammar_basic.h @@ -1,35 +1,10 @@ -#ifndef PARSING_UTILS_H -#define PARSING_UTILS_H +#ifndef GRAMMAR_BASIC_H +#define GRAMMAR_BASIC_H +#include "../utils/ast/ast.h" #include "../lexer/lexer.h" -// === Macros - -#define PEEK_TOKEN() \ - peek_token(ctx); \ - if (token == NULL) \ - { \ - puts("Internal error: cannot get the following token"); \ - return NULL; \ - } - -#define POP_TOKEN() \ - pop_token(ctx); \ - if (token == NULL) \ - { \ - puts("Internal error: cannot get the following token"); \ - return NULL; \ - } - -/* @brief Acts as the entry point of the parser, calls parse_list - * - * @code input = list '\n' - * | list EOF - * | '\n' - * | EOF - * ; - */ -struct ast *parse_input(struct lexer_context *ctx); +// === Functions /* @brief: parses a list of [and_or] rules separated by semicolons and that * ends by a newline @@ -97,4 +72,4 @@ struct ast *parse_compound_list(struct lexer_context *ctx); */ struct ast *parse_else_clause(struct lexer_context *ctx); -#endif /* ! PARSING_UTILS_H */ +#endif /* ! GRAMMAR_BASIC_H */ diff --git a/src/parser/parser.c b/src/parser/parser.c index dbe3f72..b86c696 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -1,49 +1,52 @@ #include "parser.h" -#include -#include #include -#include -#include -#include "../lexer/lexer.h" -#include "../parser/parsing_utils.h" -#include "../utils/lists/lists.h" +#include "grammar.h" -// === Static functions -// ... +// === Static variables + +static enum parser_state state = PARSER_STATE_NOT_INITIALIZED; // === Functions +bool parser_init(void) +{ + if (state == PARSER_STATE_READY) + { + puts("Internal error: tried to initialize the parser module twice."); + return NULL; + } + int success = grammar_init(); + if (success == false) + return false; + + state = PARSER_STATE_READY; + return true; +} + struct ast *get_ast(struct lexer_context *ctx) { - struct token *token = PEEK_TOKEN(); - struct ast *res; - - if (token->type == TOKEN_EOF) + if (ctx == NULL) { - token = pop_token(ctx); - return ast_create_end(); + puts("Internal error: called parser with no lexer context (NULL " + "pointer). Aborting."); + return NULL; } - else if (token->type == TOKEN_NEWLINE) + if (state == PARSER_STATE_NOT_INITIALIZED) { - token = pop_token(ctx); - return ast_create_void(); + puts("Internal error: attempted to call parser without initializing " + "it. Aborting."); + return NULL; } - else // TOKEN WORD + if (state == PARSER_STATE_CLOSED) { - res = parse_list(ctx); + puts("Internal error: attempted to call parser after closing it. " + "Aborting."); + return NULL; } - /* - if (token == NULL) - { - puts("Internal error: cannot get the following token"); - puts("Hint: EOF might be missing"); - return NULL; - } - */ - return res; + return parse_input(ctx); } // TODO @@ -51,4 +54,4 @@ struct ast *get_ast_str(char *command) { (void)command; return NULL; -} \ No newline at end of file +} diff --git a/src/parser/parser.h b/src/parser/parser.h index 1837e75..b51d2f5 100644 --- a/src/parser/parser.h +++ b/src/parser/parser.h @@ -1,9 +1,28 @@ #ifndef PARSER_H #define PARSER_H +#include + #include "../lexer/lexer.h" #include "../utils/ast/ast.h" +enum parser_state { + PARSER_STATE_NOT_INITIALIZED = 0, + PARSER_STATE_READY, + PARSER_STATE_CLOSED +}; + +/* @brief Initializes the parser module + * @warning parser needs to be closed after use with parser_close() + * + * @return Returns false on error and true on success + */ +bool parser_init(void); + +/* @brief Closes the parser module after use + */ +void parser_close(void); + /* @brief Builds the AST representation of the next command to execute. * * @return Returns the AST representation of the next command to execute. From 32e182bd50ccbb9e9ed4170964684fac46d81adf Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Fri, 23 Jan 2026 17:33:15 +0100 Subject: [PATCH 157/282] fix: random fixes --- src/parser/grammar_basic.c | 26 +++++++++++++++++--------- src/parser/grammar_basic.h | 2 +- src/utils/ast/ast.c | 2 +- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index b90cf68..b7f74c0 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -9,7 +9,7 @@ // === Static functions -enum ast_and_or_type and_or_tok_to_ast(enum token_type tok_type) +static enum ast_and_or_type and_or_tok_to_ast(enum token_type tok_type) { switch (tok_type) { @@ -47,7 +47,7 @@ struct ast *parse_list(struct lexer_context *ctx) if (current_node == NULL) { // TODO free list - // There must be a function for that + // There must be a function for that return NULL; } result_list = list_append(result_list, current_node); @@ -64,29 +64,35 @@ struct ast *parse_and_or(struct lexer_context *ctx) if (token->type == TOKEN_AND || token->type == TOKEN_OR) { - // set left part + // Set left part struct ast *left = result; // eat and_or token token = POP_TOKEN(); - // set type + // Set type enum ast_and_or_type type = and_or_tok_to_ast(token->type); token = PEEK_TOKEN(); - // skip newlines + // Skip newlines while (token->type == TOKEN_NEWLINE) { token = POP_TOKEN(); token = PEEK_TOKEN(); } - // right part + // Right part struct ast *right = parse_pipeline(ctx); result = ast_create_and_or(left, right, type); + if (result == NULL) + { + ast_free(&left); + ast_free(&right); + return NULL; + } } return result; @@ -204,6 +210,7 @@ struct ast *parse_if_rule(struct lexer_context *ctx) return NULL; } + // Fi keyword token = POP_TOKEN(); if (token->type != TOKEN_FI) { @@ -214,6 +221,7 @@ struct ast *parse_if_rule(struct lexer_context *ctx) return NULL; } + // Result struct ast *result = ast_create_if(condition_content, then_content, else_content); if (result == NULL) @@ -241,7 +249,7 @@ struct ast *parse_compound_list(struct lexer_context *ctx) token = PEEK_TOKEN(); } - // and_or + // And/or current_cmd = parse_and_or(ctx); if (current_cmd == NULL) return NULL; @@ -261,7 +269,7 @@ struct ast *parse_compound_list(struct lexer_context *ctx) token = PEEK_TOKEN(); } - // and_or + // And/or current_cmd = parse_and_or(ctx); if (current_cmd == NULL) return NULL; @@ -299,7 +307,7 @@ struct ast *parse_else_clause(struct lexer_context *ctx) token = POP_TOKEN(); struct ast *condition = parse_compound_list(ctx); - // 'then' + // Then keyword token = POP_TOKEN(); if (token->type != TOKEN_THEN) { diff --git a/src/parser/grammar_basic.h b/src/parser/grammar_basic.h index 0990e1b..840f05d 100644 --- a/src/parser/grammar_basic.h +++ b/src/parser/grammar_basic.h @@ -15,7 +15,7 @@ struct ast *parse_list(struct lexer_context *ctx); /* @brief Only parses a pipeline rule for the moment * - * @code and_or = pipeline ; + * @code and_or = pipeline { ( '&&' | '||' ) {'\n'} pipeline } ; */ struct ast *parse_and_or(struct lexer_context *ctx); diff --git a/src/utils/ast/ast.c b/src/utils/ast/ast.c index e336a73..75e1e7f 100644 --- a/src/utils/ast/ast.c +++ b/src/utils/ast/ast.c @@ -10,7 +10,7 @@ void ast_free(struct ast **node) { - if (*node == NULL) + if (node == NULL || *node == NULL) return; // ast void does not need to be freed. if (ast_is_if(*node)) From 7bcf48f63eb2c4887c9a3fdb819429c7814cfd5c Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Fri, 23 Jan 2026 17:05:56 +0000 Subject: [PATCH 158/282] feat(args): $0, $1, ... in vars --- src/main.c | 15 +++++----- src/utils/args/args.c | 27 ++++++++++++------ src/utils/args/args.h | 6 +++- tests/unit/utils/args.c | 63 +++++++++++++++++++++++++++++++++-------- 4 files changed, 82 insertions(+), 29 deletions(-) diff --git a/src/main.c b/src/main.c index a03cfa9..1c802d7 100644 --- a/src/main.c +++ b/src/main.c @@ -20,9 +20,16 @@ int main(int argc, char **argv) { + struct hash_map *vars = vars_init(); + if (vars == NULL) + { + fprintf(stderr, "Error: Failed to initialize variables hash map\n"); + return ERR_MALLOC; + } + // Create the options struct (with argument handler) struct args_options options; - int return_code = args_handler(argc, argv, &options); + int return_code = args_handler(argc, argv, &options, vars); if (return_code != 0) { print_usage(stderr, argv[0]); @@ -31,12 +38,6 @@ int main(int argc, char **argv) // args_print(&options); // Initialize variables hash map - struct hash_map *vars = vars_init(); - if (vars == NULL) - { - fprintf(stderr, "Error: Failed to initialize variables hash map\n"); - return ERR_MALLOC; - } // Create the IO-Backend context struct struct iob_context *io_context = malloc(sizeof(struct iob_context)); diff --git a/src/utils/args/args.c b/src/utils/args/args.c index a2dabd8..e6995fa 100644 --- a/src/utils/args/args.c +++ b/src/utils/args/args.c @@ -4,13 +4,22 @@ #include #include -int args_handler(int argc, char **argv, struct args_options *options) +#include "../string_utils/string_utils.h" +#include "../vars/vars.h" + +int args_handler(int argc, char **argv, struct args_options *options, + struct hash_map *vars) { options->type = INPUT_UNDEFINED; options->input_source = NULL; options->pretty_print = false; options->verbose = false; + int arg_index = 1; + char index_str[11]; + + set_var_copy(vars, "0", argv[0]); + for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "--pretty-print") == 0) @@ -44,18 +53,18 @@ int args_handler(int argc, char **argv, struct args_options *options) fprintf(stderr, "Unknown option: %s\n", argv[i]); return 1; } - else + else if (options->type == INPUT_UNDEFINED) { - if (options->type != INPUT_UNDEFINED) - { - fprintf(stderr, "Multiple input sources specified: %s\n", - argv[i]); - return 1; - } - options->type = INPUT_FILE; options->input_source = argv[i]; } + else + { + // All remaining arguments are treated as additional arguments + int_to_str(arg_index++, index_str); + set_var_copy(vars, index_str, argv[i]); + continue; + } } if (options->type == INPUT_UNDEFINED) diff --git a/src/utils/args/args.h b/src/utils/args/args.h index d19859b..6eac20c 100644 --- a/src/utils/args/args.h +++ b/src/utils/args/args.h @@ -4,6 +4,8 @@ #include #include +#include "../hash_map/hash_map.h" + enum input_type { INPUT_UNDEFINED, @@ -30,9 +32,11 @@ struct args_options * @param argc The argument count. * @param argv The argument vector. * @param options Pointer to args_options structure to be populated. + * @param vars Pointer to the variables hash map. * @return 0 on success, non-zero on failure. */ -int args_handler(int argc, char **argv, struct args_options *options); +int args_handler(int argc, char **argv, struct args_options *options, + struct hash_map *vars); /** Prints the parsed arguments for debugging purposes. * @param options Pointer to args_options structure containing parsed options. diff --git a/tests/unit/utils/args.c b/tests/unit/utils/args.c index 04dfe26..672f6e9 100644 --- a/tests/unit/utils/args.c +++ b/tests/unit/utils/args.c @@ -4,6 +4,9 @@ #include #include +#include "../../../src/utils/hash_map/hash_map.h" +#include "../../../src/utils/vars/vars.h" + TestSuite(utils_args); Test(utils_args, basic_command) @@ -12,13 +15,15 @@ Test(utils_args, basic_command) struct args_options options; char *input[] = { "program", "-c", "echo Hello, World!" }; - int r = args_handler(argc, input, &options); + struct hash_map *vars = vars_init(); + int r = args_handler(argc, input, &options, vars); cr_expect(r == 0); 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!")); + hash_map_free(&vars); } Test(utils_args, basic_command_with_flags) @@ -28,13 +33,15 @@ Test(utils_args, basic_command_with_flags) char *input[] = { "program", "--pretty-print", "-c", "echo Hello, World!", "--verbose" }; - int r = args_handler(argc, input, &options); + 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.verbose == true); cr_expect(options.type == INPUT_CMD); cr_expect(eq(options.input_source, "echo Hello, World!")); + hash_map_free(&vars); } Test(utils_args, basic_file_input) @@ -43,13 +50,15 @@ Test(utils_args, basic_file_input) struct args_options options; char *input[] = { "program", "input.txt" }; - int r = args_handler(argc, input, &options); + struct hash_map *vars = vars_init(); + int r = args_handler(argc, input, &options, vars); cr_expect(r == 0); 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")); + hash_map_free(&vars); } Test(utils_args, basic_file_input_with_flags) @@ -58,13 +67,15 @@ Test(utils_args, basic_file_input_with_flags) struct args_options options; char *input[] = { "program", "--verbose", "input.txt", "--pretty-print" }; - int r = args_handler(argc, input, &options); + 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.verbose == true); cr_expect(options.type == INPUT_FILE); cr_expect(eq(options.input_source, "input.txt")); + hash_map_free(&vars); } Test(utils_args, basic_stdin_input) @@ -73,13 +84,15 @@ Test(utils_args, basic_stdin_input) struct args_options options; char *input[] = { "program" }; - int r = args_handler(argc, input, &options); + struct hash_map *vars = vars_init(); + int r = args_handler(argc, input, &options, vars); cr_expect(r == 0); cr_expect(options.pretty_print == false); cr_expect(options.verbose == false); cr_expect(options.type == INPUT_STDIN); cr_expect(options.input_source == NULL); + hash_map_free(&vars); } Test(utils_args, pretty_print_and_verbose_flags) @@ -88,13 +101,15 @@ Test(utils_args, pretty_print_and_verbose_flags) struct args_options options; char *input[] = { "program", "--pretty-print", "--verbose" }; - int r = args_handler(argc, input, &options); + 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.verbose == true); cr_expect(options.type == INPUT_STDIN); cr_expect(options.input_source == NULL); + hash_map_free(&vars); } Test(utils_args, missing_command_after_c, .init = cr_redirect_stderr) @@ -103,10 +118,12 @@ Test(utils_args, missing_command_after_c, .init = cr_redirect_stderr) struct args_options options; char *input[] = { "program", "-c" }; - int r = args_handler(argc, input, &options); + struct hash_map *vars = vars_init(); + int r = args_handler(argc, input, &options, vars); cr_expect(r != 0); cr_assert_stderr_eq_str("No command provided after -c\n"); + hash_map_free(&vars); } Test(utils_args, unknown_option, .init = cr_redirect_stderr) @@ -115,20 +132,42 @@ Test(utils_args, unknown_option, .init = cr_redirect_stderr) struct args_options options; char *input[] = { "program", "--unknown" }; - int r = args_handler(argc, input, &options); + struct hash_map *vars = vars_init(); + int r = args_handler(argc, input, &options, vars); cr_expect(r != 0); cr_assert_stderr_eq_str("Unknown option: --unknown\n"); + hash_map_free(&vars); } -Test(utils_args, conflicting_input_types, .init = cr_redirect_stderr) +Test(utils_args, multiple_command, .init = cr_redirect_stderr) { int argc = 4; struct args_options options; - char *input[] = { "program", "-c", "echo Hello", "input.txt" }; + char *input[] = { "program", "exec.sh", "-c", "echo World" }; - int r = args_handler(argc, input, &options); + struct hash_map *vars = vars_init(); + int r = args_handler(argc, input, &options, vars); cr_expect(r != 0); - cr_assert_stderr_eq_str("Multiple input sources specified: input.txt\n"); + cr_assert_stderr_eq_str("Multiple input sources specified: echo World\n"); + hash_map_free(&vars); +} + +Test(utils_args, command_with_additional_arguments) +{ + int argc = 5; + struct args_options options; + char *input[] = { "program", "-c", "echo Hello", "arg1", "arg2" }; + + struct hash_map *vars = vars_init(); + int r = args_handler(argc, input, &options, vars); + + cr_expect(r == 0); + cr_expect(options.type == INPUT_CMD); + cr_expect(eq(options.input_source, "echo Hello")); + cr_expect_str_eq(get_var(vars, "0"), "program"); + cr_expect_str_eq(get_var(vars, "1"), "arg1"); + cr_expect_str_eq(get_var(vars, "2"), "arg2"); + hash_map_free(&vars); } From 67ffea83d3b09ebddee6d3fa3822fab5e82df08a Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Fri, 23 Jan 2026 17:13:41 +0000 Subject: [PATCH 159/282] feat(vars): $UID --- src/utils/vars/vars.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/utils/vars/vars.c b/src/utils/vars/vars.c index a69d349..1d94355 100644 --- a/src/utils/vars/vars.c +++ b/src/utils/vars/vars.c @@ -26,7 +26,12 @@ struct hash_map *vars_init(void) { struct hash_map *vars = hash_map_init(VARS_INITIAL_SIZE); if (vars != NULL) + { vars_default(vars); + char uid_str[11]; + int_to_str((int)getuid(), uid_str); + set_var_copy(vars, "UID", uid_str); + } return vars; } From 1e5593fc8ea8de76df49ce1c7ce4b2f3932b622d Mon Sep 17 00:00:00 2001 From: matteo Date: Fri, 23 Jan 2026 19:34:47 +0100 Subject: [PATCH 160/282] feat(lexer): operators done --- src/lexer/lexer.c | 14 +----- src/lexer/lexer_utils.c | 102 ++++++++++++++++++++++++++++++++-------- src/lexer/lexer_utils.h | 38 +++++++++++---- 3 files changed, 111 insertions(+), 43 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index e6dcfc7..6c46f51 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -11,18 +11,6 @@ #include "../utils/string_utils/string_utils.h" #include "lexer_utils.h" -/* @return: true if a special character from the grammar was found, - * false otherwise. - */ -static bool is_special_char(char c) -{ - if (c == EOF) - return true; - - char special_chars[] = "\n'\"`;#|&\\(){}<>*"; - return strchr(special_chars, c) != NULL; -} - /* @brief: sets the ctx->current_token to [tok]. * this function is called by token_peek(). */ @@ -177,7 +165,7 @@ struct token *pop_token(struct lexer_context *ctx) if (is_special_char(stream[i])) { if (i == 0) // where we create spe_char token - i++; + i += len_op_sepchar(stream, i); break; } if (isblank(stream[i])) diff --git a/src/lexer/lexer_utils.c b/src/lexer/lexer_utils.c index 4906fcb..d80134a 100644 --- a/src/lexer/lexer_utils.c +++ b/src/lexer/lexer_utils.c @@ -24,24 +24,12 @@ static void set_token_spechar(struct token *tok, char *begin, ssize_t size) case '\n': tok->type = TOKEN_NEWLINE; break; - case '\'': - tok->type = TOKEN_QUOTE; - break; - case '"': - tok->type = TOKEN_DOUBLE_QUOTE; - break; case '`': tok->type = TOKEN_GRAVE; break; case '#': tok->type = TOKEN_COMMENT; break; - case '|': - tok->type = TOKEN_PIPE; - break; - case '&': - tok->type = TOKEN_AMPERSAND; - break; case '\\': tok->type = TOKEN_BACKSLASH; break; @@ -57,12 +45,6 @@ static void set_token_spechar(struct token *tok, char *begin, ssize_t size) case '}': tok->type = TOKEN_RIGHT_BRACKET; break; - case '<': - tok->type = TOKEN_LESS; - break; - case '>': - tok->type = TOKEN_GREATER; - break; case '*': tok->type = TOKEN_STAR; break; @@ -117,6 +99,47 @@ static void set_token_keyword(struct token *tok, char *begin, ssize_t size) strncpy(tok->data, begin, size); } +/* @brief: if an operator is found at [begin], + * [tok->token_type] is set accordingly + */ +static void set_token_operator(struct token *tok, char *begin, ssize_t size) +{ + if (tok->type != TOKEN_NULL) + return; + if (strncmp(begin, ">", size) == 0) + { + tok->type = TOKEN_REDIR_RIGHT; + } + else if (strncmp(begin, "<", size) == 0) + { + tok->type = TOKEN_REDIR_LEFT; + } + else if (strncmp(begin, ">>", size) == 0) + { + tok->type = TOKEN_REDIR_DOUBLE_RIGHT; + } + else if (strncmp(begin, ">&", size) == 0) + { + tok->type = TOKEN_REDIR_RIGHT_AMP; + } + else if (strncmp(begin, ">|", size) == 0) + { + tok->type = TOKEN_REDIR_RIGHT_PIPE; + } + else if (strncmp(begin, "<&", size) == 0) + { + tok->type = TOKEN_REDIR_LEFT_AMP; + } + else if (strncmp(begin, "<>", size) == 0) + { + tok->type = TOKEN_REDIR_LEFT_RIGHT; + } + else if (strncmp(begin, "|", size) == 0) + { + tok->type = TOKEN_PIPE; + } +} + /* @brief: if token_type has not yet been set, then it is a TOKEN_WORD * Also allocates the data and fills it. */ @@ -147,6 +170,23 @@ static void set_token_ION(struct token *tok, char *begin, ssize_t size) } } +/* @brief: check if [c] is a delimiter for end of line. + * @return: true if [c] == '\n' or EOF. false otherwise. + */ +static bool is_end_of_line(char c) +{ + return c == EOF || c == '\n'; +} + +bool is_special_char(char c) +{ + if (c == EOF) + return true; + + char special_chars[] = "\n'\"`;#|&\\(){}<>*"; + return strchr(special_chars, c) != NULL; +} + struct token *new_token(char *begin, ssize_t size, bool only_digits) { struct token *tok = calloc(1, sizeof(struct token)); @@ -156,6 +196,7 @@ struct token *new_token(char *begin, ssize_t size, bool only_digits) if (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); @@ -204,14 +245,35 @@ void stream_init(struct lexer_context *ctx) ctx->end_previous_token = trimed_stream; } +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) + + // operator + + if (stream[i] == '<') + { + if (stream[i + 1] == '&' || stream[i + 1] == '>') + return 2; // <&, <> + } + else if (stream[i + 1] == '>' || stream[i + 1] == '|' + || stream[i + 1] == '&') + return 2; // >>, >&, >| + + return 1; // >, < +} + void go_end_of_line(struct lexer_context *ctx) { if (ctx == NULL || ctx->end_previous_token == NULL) return; ssize_t i = 0; - while (ctx->end_previous_token[i] != '\n' - && ctx->end_previous_token[i] != EOF) + while (!is_end_of_line(ctx->end_previous_token[i])) { i++; } diff --git a/src/lexer/lexer_utils.h b/src/lexer/lexer_utils.h index fde696b..aa8941a 100644 --- a/src/lexer/lexer_utils.h +++ b/src/lexer/lexer_utils.h @@ -53,11 +53,16 @@ enum token_type TOKEN_RIGHT_BRACKET, // redirections - TOKEN_LESS, - TOKEN_GREATER, - TOKEN_PIPE, - TOKEN_AMPERSAND, + TOKEN_REDIR_LEFT, + TOKEN_REDIR_RIGHT, + TOKEN_REDIR_LEFT_RIGHT, + TOKEN_REDIR_DOUBLE_RIGHT, + TOKEN_REDIR_LEFT_AMP, + TOKEN_REDIR_RIGHT_AMP, + TOKEN_REDIR_RIGHT_PIPE, + TOKEN_IONUMBER, + TOKEN_PIPE, // Keywords TOKEN_IF, @@ -75,8 +80,12 @@ struct token char *data; }; -/* - * @brief: return a newly allocated token, with the corresponding type. +/* @return: true if a special character from the grammar was found, + * false otherwise. + */ +bool is_special_char(char c); + +/* @brief: return a newly allocated token, with the corresponding type. * The data contains [size] char, starting from [begin]. * * @return: NULL on error, a token otherwise. @@ -99,13 +108,22 @@ void free_token(struct token **tok); void stream_init(struct lexer_context *ctx); /* @brief: finds the next '\n' or EOF character, - * starting at [ctx->end_previous_token], - * and updates the stream and remaining_chars accordingly. + * starting at [ctx->end_previous_token], + * and updates the stream and remaining_chars accordingly. + * + * @note: Daft Punk. bang. */ void go_end_of_line(struct lexer_context *ctx); -/* - * @brief: drops the current stream and asks IOB for a new one +/* @brief: this function is called when we found a special character + * in the stream. This can either be an operator (ig '>>' or '<&' etc), + * or a special char (ig '\' or '#' etc). + * @return: the length of the operator/special char found (can be 1, 2 or 3). + * -1 on error. + */ +ssize_t len_op_sepchar(char *stream, ssize_t i); + +/* @brief: drops the current stream and asks IOB for a new one */ void get_next_stream(struct lexer_context *ctx); From 9f22aa59b67e6346b38480e4c7c7c569f37eb4ec Mon Sep 17 00:00:00 2001 From: matteo Date: Fri, 23 Jan 2026 19:43:54 +0100 Subject: [PATCH 161/282] fix(parser): building + clang format -- UNSTABLE --- src/parser/Makefile.am | 4 +++- src/parser/grammar.h | 29 +++++++++++++++-------------- src/parser/grammar_basic.h | 2 +- src/parser/parser.h | 3 ++- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/parser/Makefile.am b/src/parser/Makefile.am index 3b7b6f5..642414b 100644 --- a/src/parser/Makefile.am +++ b/src/parser/Makefile.am @@ -2,7 +2,9 @@ lib_LIBRARIES = libparser.a libparser_a_SOURCES = \ parser.c \ - parsing_utils.c + grammar.c \ + grammar_basic.c \ + grammar_advanced.c libparser_a_CPPFLAGS = -I$(top_srcdir)/src diff --git a/src/parser/grammar.h b/src/parser/grammar.h index fe31849..cd9e888 100644 --- a/src/parser/grammar.h +++ b/src/parser/grammar.h @@ -25,18 +25,19 @@ // === Structures -enum rule { - RULE_NULL, - RULE_INPUT, - RULE_LIST, - RULE_AND_OR, - RULE_PIPELINE, - RULE_COMMAND, - RULE_SIMPLE_COMMAND, - RULE_SHELL_COMMAND, - RULE_IF, - RULE_COMPOUND_LIST, - RULE_ELSE_CLAUSE +enum rule +{ + RULE_NULL, + RULE_INPUT, + RULE_LIST, + RULE_AND_OR, + RULE_PIPELINE, + RULE_COMMAND, + RULE_SIMPLE_COMMAND, + RULE_SHELL_COMMAND, + RULE_IF, + RULE_COMPOUND_LIST, + RULE_ELSE_CLAUSE }; // === Functions @@ -45,12 +46,12 @@ enum rule { * @return PARSER_INIT_SUCCESS on success PARSER_INIT_ERROR on error * @warning Do not use outside the parser */ - bool grammar_init(void); +bool grammar_init(void); /* @brief Closes the grammar submodule * @warning Do not use outside the parser */ - void grammar_close(void); +void grammar_close(void); /* @brief Acts as the entry point of the parser, calls parse_list * diff --git a/src/parser/grammar_basic.h b/src/parser/grammar_basic.h index 840f05d..7d9803a 100644 --- a/src/parser/grammar_basic.h +++ b/src/parser/grammar_basic.h @@ -1,8 +1,8 @@ #ifndef GRAMMAR_BASIC_H #define GRAMMAR_BASIC_H -#include "../utils/ast/ast.h" #include "../lexer/lexer.h" +#include "../utils/ast/ast.h" // === Functions diff --git a/src/parser/parser.h b/src/parser/parser.h index b51d2f5..a79497b 100644 --- a/src/parser/parser.h +++ b/src/parser/parser.h @@ -6,7 +6,8 @@ #include "../lexer/lexer.h" #include "../utils/ast/ast.h" -enum parser_state { +enum parser_state +{ PARSER_STATE_NOT_INITIALIZED = 0, PARSER_STATE_READY, PARSER_STATE_CLOSED From e853249fbf08a8cf0f19255c8ff681f3762dd586 Mon Sep 17 00:00:00 2001 From: matteo Date: Fri, 23 Jan 2026 20:11:21 +0100 Subject: [PATCH 162/282] fix(lexer): $# and backslash implemented (partial version) --- src/lexer/lexer.c | 9 +++------ src/lexer/lexer_utils.c | 13 ++++++++++--- src/lexer/lexer_utils.h | 4 ++-- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c index 6c46f51..ff7f351 100644 --- a/src/lexer/lexer.c +++ b/src/lexer/lexer.c @@ -100,10 +100,10 @@ struct token *peek_token(struct lexer_context *ctx) if (!update_lexing_mode(stream, i, &lexing_mode) && lexing_mode == LEXER_NORMAL) { - if (is_special_char(stream[i])) + if (is_special_char(stream, i)) { if (i == 0) // where we create spe_char token - i++; + i += len_op_sepchar(stream, i); break; } if (isblank(stream[i])) @@ -159,10 +159,7 @@ struct token *pop_token(struct lexer_context *ctx) if (!update_lexing_mode(stream, i, &lexing_mode) && lexing_mode == LEXER_NORMAL) { - // TODO call here a function - // it must check if is a spe char or an operator - // and sets i accordingly. - if (is_special_char(stream[i])) + if (is_special_char(stream, i)) { if (i == 0) // where we create spe_char token i += len_op_sepchar(stream, i); diff --git a/src/lexer/lexer_utils.c b/src/lexer/lexer_utils.c index d80134a..e4a9bab 100644 --- a/src/lexer/lexer_utils.c +++ b/src/lexer/lexer_utils.c @@ -178,12 +178,19 @@ static bool is_end_of_line(char c) return c == EOF || c == '\n'; } -bool is_special_char(char c) +bool is_special_char(char *stream, ssize_t i) { + char c = stream[i]; if (c == EOF) return true; + if (i > 0 && c == '#' && stream[i - 1] == '$') + return false; // the edge case of $# + if (i > 0 && stream[i - 1] == '\\') + return false; // TODO handle backslash better + // this doesnt work, ex : echo \\#comment + // (need to count the previous consequtive backslashes) - char special_chars[] = "\n'\"`;#|&\\(){}<>*"; + char special_chars[] = "\n'\"`;#|&(){}<>*"; return strchr(special_chars, c) != NULL; } @@ -247,7 +254,7 @@ void stream_init(struct lexer_context *ctx) ssize_t len_op_sepchar(char *stream, ssize_t i) { - if (!is_special_char(stream[i])) + if (!is_special_char(stream, i)) return -1; // should never happen if (stream[i] != '>' && stream[i] != '<') diff --git a/src/lexer/lexer_utils.h b/src/lexer/lexer_utils.h index aa8941a..6173485 100644 --- a/src/lexer/lexer_utils.h +++ b/src/lexer/lexer_utils.h @@ -80,10 +80,10 @@ struct token char *data; }; -/* @return: true if a special character from the grammar was found, +/* @return: true if a special character from the grammar was found at stream[i], * false otherwise. */ -bool is_special_char(char c); +bool is_special_char(char *stream, ssize_t i); /* @brief: return a newly allocated token, with the corresponding type. * The data contains [size] char, starting from [begin]. From b9d7dbce1f00abd5e144d36adb1b3fdf9e7ea7f0 Mon Sep 17 00:00:00 2001 From: matteo Date: Fri, 23 Jan 2026 20:31:49 +0100 Subject: [PATCH 163/282] feat: build system ready for testsuite using `make check` --- src/Makefile.am | 8 ++++- tests/unit/lexer/lexer_tests.c | 58 ++++++++++------------------------ 2 files changed, 23 insertions(+), 43 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 56c48b6..994f1ad 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -24,6 +24,12 @@ bin_PROGRAMS = 42sh # ================ TESTS ================ +check-local: $(bin_PROGRAMS) # $(check_PROGRAMS) + ./../testsuite.sh +#./../tests/functional/testsuite.sh + +#./testsuite + # ------------- Unit tests -------------- check_PROGRAMS = testsuite @@ -36,7 +42,7 @@ testsuite_SOURCES = ../tests/unit/lexer/lexer_tests.c \ testsuite_CPPFLAGS = $(42sh_CPPFLAGS) -testsuite_LDADD = $(42sh_LDADD) $(CRITERION_LIBS) +testsuite_LDADD = $(42sh_LDADD) -lcriterion # ------------- asan debug -------------- diff --git a/tests/unit/lexer/lexer_tests.c b/tests/unit/lexer/lexer_tests.c index 7cb84a5..ecf662f 100644 --- a/tests/unit/lexer/lexer_tests.c +++ b/tests/unit/lexer/lexer_tests.c @@ -17,14 +17,20 @@ Test(peek_token, basic_empty) struct iob_context context = { IOB_MODE_CMD, command }; iob_init(&context); + struct lexer_context ctx = { .end_previous_token = NULL, + .remaining_chars = 0, + .only_digits = false, + .previous_token = NULL, + .current_token = NULL }; + // test - struct token *tok = peek_token(); + struct token *tok = peek_token(&ctx); // expected enum token_type type_expected = TOKEN_EOF; char *string_expected = NULL; - cr_assert(eq(str, string_expected, tok->data)); + cr_assert(tok->data == string_expected); cr_assert(type_expected == tok->type); free_token(&tok); @@ -37,8 +43,14 @@ Test(peek_token, basic_word) struct iob_context context = { IOB_MODE_CMD, command }; iob_init(&context); + struct lexer_context ctx = { .end_previous_token = NULL, + .remaining_chars = 0, + .only_digits = false, + .previous_token = NULL, + .current_token = NULL }; + // test - struct token *tok = peek_token(); + struct token *tok = peek_token(&ctx); // expected enum token_type type_expected = TOKEN_WORD; @@ -47,42 +59,4 @@ Test(peek_token, basic_word) cr_assert(eq(str, string_expected, tok->data)); cr_assert(type_expected == tok->type); free_token(&tok); -} -Test(peek_token, basic_words) -{ - // simulates input - char command[] = "echo hello there"; - struct iob_context context = { IOB_MODE_CMD, command }; - iob_init(&context); - - // ======= echo - - struct token *tok = peek_token(); - - enum token_type type_expected = TOKEN_WORD; - char string_expected[] = "echo"; - - cr_assert(eq(str, string_expected, tok->data)); - cr_assert(type_expected == tok->type); - free_token(&tok); - - // ======= hello - - tok = peek_token(); - - char string_expected2[] = "hello"; - - cr_assert(eq(str, string_expected2, tok->data)); - cr_assert(type_expected == tok->type); - free_token(&tok); - - // ======= there - - tok = peek_token(); - - char string_expected3[] = "there"; - - cr_assert(eq(str, string_expected3, tok->data)); - cr_assert(type_expected == tok->type); - free_token(&tok); -} +} \ No newline at end of file From a3998358eab30e4799234a359d476d8bf4e011a8 Mon Sep 17 00:00:00 2001 From: matteo Date: Fri, 23 Jan 2026 20:38:52 +0100 Subject: [PATCH 164/282] fix: make check calls the functional testsuite --- src/Makefile.am | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 994f1ad..8cee132 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -25,8 +25,7 @@ bin_PROGRAMS = 42sh # ================ TESTS ================ check-local: $(bin_PROGRAMS) # $(check_PROGRAMS) - ./../testsuite.sh -#./../tests/functional/testsuite.sh + ./../tests/functional/run-tests.sh #./testsuite From de9173a71f8c36a74ee4667c6f3f64b6f0087a65 Mon Sep 17 00:00:00 2001 From: matteo Date: Fri, 23 Jan 2026 23:20:32 +0100 Subject: [PATCH 165/282] fix: testsuite --- src/Makefile.am | 18 ++++++++---------- tests/functional/run-tests.sh | 5 ++++- tests/wrap.sh | 6 ++++++ 3 files changed, 18 insertions(+), 11 deletions(-) create mode 100755 tests/wrap.sh diff --git a/src/Makefile.am b/src/Makefile.am index 8cee132..2971e09 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -24,10 +24,8 @@ bin_PROGRAMS = 42sh # ================ TESTS ================ -check-local: $(bin_PROGRAMS) # $(check_PROGRAMS) - ./../tests/functional/run-tests.sh - -#./testsuite +check: + ../tests/wrap.sh # "$(COVERAGE)" "$(OUTPUT_FILE)" # ------------- Unit tests -------------- @@ -45,13 +43,13 @@ testsuite_LDADD = $(42sh_LDADD) -lcriterion # ------------- asan debug -------------- -check_PROGRAMS += debug +# check_PROGRAMS += debug #debug_CFLAGS = $(42sh_CFLAGS) #debug_CFLAGS += -fsanitize=address -g -debug_SOURCES = $(42sh_SOURCES) - -debug_CPPFLAGS = $(42sh_CPPFLAGS) - -debug_LDADD = $(42sh_LDADD) +# debug_SOURCES = $(42sh_SOURCES) +# +# debug_CPPFLAGS = $(42sh_CPPFLAGS) +# +# debug_LDADD = $(42sh_LDADD) diff --git a/tests/functional/run-tests.sh b/tests/functional/run-tests.sh index ef5030f..0da9f15 100755 --- a/tests/functional/run-tests.sh +++ b/tests/functional/run-tests.sh @@ -5,7 +5,7 @@ # Variables # ################### -executable="../../src/42sh" +executable="$BIN_PATH" ref_executable="dash" tmp_script="/tmp/test_script.sh" @@ -242,6 +242,9 @@ summarize() { 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 " Got $BRed$timeouts_count timeout(s)$Color_Off" + if [ "$OUTPUT_FILE" != "" ]; + then echo $tests_percentage > "$OUTPUT_FILE"; + fi echo } diff --git a/tests/wrap.sh b/tests/wrap.sh new file mode 100755 index 0000000..5cd6f53 --- /dev/null +++ b/tests/wrap.sh @@ -0,0 +1,6 @@ +if [ "$COVERAGE" = "yes" ]; #coverage +then ./testsuite && ../tests/functional/run-tests.sh +else ../tests/functional/run-tests.sh +fi +echo bin path: "$BIN_PATH" +echo output file: "$OUTPUT_FILE" From 71fadb1b4a1695e4caec4d8b9bf54dca95aa61df Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 24 Jan 2026 12:55:11 +0100 Subject: [PATCH 166/282] fix: clang tidy + made static what could be instead of malloc --- src/main.c | 88 ++++++++++++++--------------- src/utils/lists/lists.c | 121 ---------------------------------------- 2 files changed, 44 insertions(+), 165 deletions(-) diff --git a/src/main.c b/src/main.c index 1c802d7..1d9f7b2 100644 --- a/src/main.c +++ b/src/main.c @@ -18,6 +18,44 @@ // === Functions +static int main_loop(struct lexer_context *ctx, struct args_options *options, + struct hash_map *vars) +{ + int return_code = SUCCESS; + // Retrieve and build first AST + struct ast *command_ast = get_ast(ctx); + + if (options->pretty_print) + { + ast_print_dot(command_ast); + } + + // Main parse-execute loop + while (command_ast != NULL && command_ast->type != AST_END) + { + if (command_ast->type != AST_VOID) + { + // Execute AST + return_code = execution(command_ast, vars); + + // set $? variable + set_var_int(vars, "?", return_code); + } + + ast_free(&command_ast); + + // Retrieve and build next AST + command_ast = get_ast(ctx); + } + + if (command_ast == NULL) + return ERR_INPUT_PROCESSING; + + ast_free(&command_ast); + + return return_code; +} + int main(int argc, char **argv) { struct hash_map *vars = vars_init(); @@ -40,74 +78,36 @@ int main(int argc, char **argv) // Initialize variables hash map // Create the IO-Backend context struct - struct iob_context *io_context = malloc(sizeof(struct iob_context)); - if (io_context == NULL) - { - fprintf(stderr, - "Error: Memory allocation failed for IO Backend context\n"); - return ERR_MALLOC; - } + struct iob_context io_context = { 0 }; // Convert args_options to iob_context - return_code = iob_config_from_args(&options, io_context); + return_code = iob_config_from_args(&options, &io_context); if (return_code != 0) { fprintf(stderr, "Error: Failed to configure IO Backend from arguments\n"); - free(io_context); return ERR_INPUT_PROCESSING; } // Init IO Backend (with the context struct) - return_code = iob_init(io_context); + return_code = iob_init(&io_context); if (return_code != 0) { fprintf(stderr, "Error: IO Backend initialization failed with code %d\n", return_code); - free(io_context); return ERR_INPUT_PROCESSING; } - free(io_context); // init lexer context - struct lexer_context *ctx = calloc(1, sizeof(struct lexer_context)); + struct lexer_context ctx = { 0 }; - // Retrieve and build first AST - struct ast *command_ast = get_ast(ctx); + return_code = main_loop(&ctx, &options,vars); - if (options.pretty_print) - { - ast_print_dot(command_ast); - } - - // Main parse-execute loop - while (command_ast != NULL && command_ast->type != AST_END) - { - if (command_ast->type != AST_VOID) - { - // Execute AST - return_code = execution(command_ast, vars); - - // set $? variable - set_var_int(vars, "?", return_code); - } - - ast_free(&command_ast); - - // Retrieve and build next AST - command_ast = get_ast(ctx); - } - - destroy_lexer_context(&ctx); + // === free hash_map_free(&vars); - if (command_ast == NULL) - return ERR_INPUT_PROCESSING; - - ast_free(&command_ast); - return return_code; } diff --git a/src/utils/lists/lists.c b/src/utils/lists/lists.c index be97835..ba0ec50 100644 --- a/src/utils/lists/lists.c +++ b/src/utils/lists/lists.c @@ -4,63 +4,6 @@ #include #include -struct list *list_prepend(struct list *list, void *value) -{ - struct list *new_elt = malloc(sizeof(struct list)); - if (new_elt == NULL) - { - return NULL; - } - new_elt->next = list; - new_elt->data = value; - - return new_elt; -} - -size_t list_length(struct list *list) -{ - size_t len = 0; - while (list != NULL) - { - len++; - list = list->next; - } - return len; -} - -void list_print(struct list *list) -{ - if (list == NULL) - { - return; - } - - while (list != NULL) - { - if (list->next != NULL) - { - printf("%p ", list->data); - } - else - { - printf("%p\n", list->data); - } - list = list->next; - } -} - -void list_destroy(struct list *list) -{ - struct list *elt = list; - struct list *next_elt; - while (elt != NULL) - { - next_elt = elt->next; - free(elt); - elt = next_elt; - } -} - struct list *list_append(struct list *list, void *value) { if (list == NULL) @@ -93,36 +36,6 @@ struct list *list_append(struct list *list, void *value) ******************* * */ - -struct list *list_insert(struct list *list, void *value, size_t index) -{ - if (list == NULL || index == 0) - { - struct list *new_elt = malloc(sizeof(struct list)); - new_elt->data = value; - new_elt->next = list; - return new_elt; - } - - struct list *elt = list; - - for (size_t i = 0; i < index - 1; i++) - { - if (elt->next == NULL) - { - break; - } - elt = elt->next; - } - - struct list *new_elt = malloc(sizeof(struct list)); - new_elt->data = value; - new_elt->next = elt->next; - elt->next = new_elt; - - return list; -} - struct list *list_remove(struct list *list, size_t index) { struct list *elt = list; @@ -204,40 +117,6 @@ static void swap_next(struct list *elt) elt->next->data = elt->data; elt->data = c; } -struct list *list_sort(struct list *list) -{ - // Bubble sort go ! - if (list == NULL) - { - return list; - } - struct list *elt = list; - int len = 0; - while (elt->next != NULL) - { - if (elt->data > elt->next->data) - { - swap_next(elt); - } - elt = elt->next; - len++; - } - - for (int i = 1; i < len; i++) - { - elt = list; - while (elt->next != NULL) - { - if (elt->data > elt->next->data) - { - swap_next(elt); - } - elt = elt->next; - } - } - - return list; -} // Old proto // WARNING no malloc/free allowed (moulinette issue) From da1a73c264b682df98e01c13d04912c0cc06e4d2 Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Sat, 24 Jan 2026 13:06:39 +0100 Subject: [PATCH 167/282] feat: made the firsts system for parser (not yet populated) --- src/parser/grammar.c | 106 +++++++++++++++++++++++++++++++++---- src/parser/grammar.h | 28 ++++++---- src/parser/grammar_basic.h | 17 ++++++ 3 files changed, 129 insertions(+), 22 deletions(-) diff --git a/src/parser/grammar.c b/src/parser/grammar.c index 15ee342..3fbb5fd 100644 --- a/src/parser/grammar.c +++ b/src/parser/grammar.c @@ -1,30 +1,105 @@ -#define _POSIX_C_SOURCE 200809L - // === Includes #include "grammar.h" -#include "../utils/hash_map/hash_map.h" +#include +#include + #include "grammar_basic.h" // === Static variables -static struct hash_map *firsts_map = NULL; +// rule-indexed array containing firsts +static struct firsts_list *firsts_map = NULL; // === Static functions -static enum token_type first(enum rule r) + +/* @brief get the first accepted tokens of a rule + * + * @arg r the rule + * @return the accepted tokens as a firsts_list struct + */ +static struct firsts_list *first(enum rule r) { - // TODO - return TOKEN_NULL; + if (firsts_map == NULL || firsts_map[r].tokens == NULL) + { + puts("Internal error: attempted to get the firsts of a rule without " + "properly initializing the firsts map"); + return NULL; + } + + return &firsts_map[r]; +} + +/* @brief Add a token to a rule's firsts (in firsts_map) + * + * @arg rule the rule to which add a first + * @arg token the token to add to the rule's firsts + * @return true on success, false on error + */ +static bool add_first(enum rule rule, struct token token) +{ + struct firsts_list *item = &firsts_map[rule]; + if (item->tokens != NULL) + { + // Check for duplicates + for (size_t i = 0; i < item->list_length; i++) + { + if (item->tokens[i].type == token.type) + return true; + } + + // Append + item->list_length++; + item->tokens = realloc( + item->tokens, (item->list_length) * sizeof(struct firsts_list)); + } + else + { + // Create entry + item->tokens = + calloc(item->list_length + 1, sizeof(struct firsts_list)); + } + + // Check for alloc error + if (item->tokens == NULL) + { + item->list_length = 0; + return false; + } + + // Fill + item->list_length++; + item->tokens[item->list_length - 1] = token; + + return true; +} + +/* @brief initializes the firsts_map static variable (does not populate it) + * @return true on success, false on error + */ +static bool init_firsts_map(void) +{ + firsts_map = calloc(NUMBER_OF_RULES, sizeof(struct firsts_list)); + if (firsts_map == NULL) + { + puts("Internal error: couldn't create the firsts_map (is your memory " + "full ?)"); + return false; + } + + return true; } // === Functions bool grammar_init(void) { - // Create firsts hashmap - // TODO + // Initialize the firsts map + bool success = init_firsts_map(); + if (success != true) + return false; - // Populate the hashmap + // Populate the firsts map // TODO return true; @@ -32,7 +107,16 @@ bool grammar_init(void) void grammar_close(void) { - // TODO free hashmap + // Deep free firsts map + for (int i = 0; i < NUMBER_OF_RULES; i++) + { + if (firsts_map[i].tokens != NULL) + { + free(firsts_map[i].tokens); + } + } + free(firsts_map); + firsts_map = NULL; } struct ast *parse_input(struct lexer_context *ctx) diff --git a/src/parser/grammar.h b/src/parser/grammar.h index fe31849..d2c1551 100644 --- a/src/parser/grammar.h +++ b/src/parser/grammar.h @@ -26,17 +26,23 @@ // === Structures enum rule { - RULE_NULL, - RULE_INPUT, - RULE_LIST, - RULE_AND_OR, - RULE_PIPELINE, - RULE_COMMAND, - RULE_SIMPLE_COMMAND, - RULE_SHELL_COMMAND, - RULE_IF, - RULE_COMPOUND_LIST, - RULE_ELSE_CLAUSE + RULE_NULL = 0, + RULE_INPUT, + RULE_LIST, + RULE_AND_OR, + RULE_PIPELINE, + RULE_COMMAND, + RULE_SIMPLE_COMMAND, + RULE_SHELL_COMMAND, + RULE_IF, + RULE_COMPOUND_LIST, + RULE_ELSE_CLAUSE, + NUMBER_OF_RULES +}; + +struct firsts_list { + struct token* tokens; // Heap allocated array + size_t list_length; }; // === Functions diff --git a/src/parser/grammar_basic.h b/src/parser/grammar_basic.h index 840f05d..ba3c817 100644 --- a/src/parser/grammar_basic.h +++ b/src/parser/grammar_basic.h @@ -10,18 +10,24 @@ * ends by a newline * * @code list = and_or { ';' and_or } [ ';' ] ; + * + * @first first(and_or) */ struct ast *parse_list(struct lexer_context *ctx); /* @brief Only parses a pipeline rule for the moment * * @code and_or = pipeline { ( '&&' | '||' ) {'\n'} pipeline } ; + * + * @first first(pipeline) */ struct ast *parse_and_or(struct lexer_context *ctx); /* @brief Only parses a command rule for the moment * * @code pipeline = command ; + * + * @first first(command) */ struct ast *parse_pipeline(struct lexer_context *ctx); @@ -34,6 +40,7 @@ struct ast *parse_pipeline(struct lexer_context *ctx); * @code command = simple_command * | shell_command * ; + * @first first(simple_command), first(shell_command) */ struct ast *parse_command(struct lexer_context *ctx); @@ -41,18 +48,24 @@ struct ast *parse_command(struct lexer_context *ctx); * ending by a separator * * @code simple_command = WORD { element } ; + * + * @first WORD */ struct ast *parse_simple_command(struct lexer_context *ctx); /* @brief Only parses if rules for the moment * * @code shell_command = if_rule ; + * + * @first first(if_rule) */ struct ast *parse_shell_command(struct lexer_context *ctx); /* @brief Parses a if rule (condition, then-clause, elif-clause, else-clause) * * @code if_rule = 'if' compound_list 'then' compound_list [else_clause] 'fi' ; + * + * @first TOKEN_IF */ struct ast *parse_if_rule(struct lexer_context *ctx); @@ -61,6 +74,8 @@ struct ast *parse_if_rule(struct lexer_context *ctx); * * @code compound_list = {'\n'} and_or { ( ';' | '\n' ) {'\n'} and_or } [';'] * {'\n'} ; + * + * @first TOKEN_NEWLINE, first(and_or) */ struct ast *parse_compound_list(struct lexer_context *ctx); @@ -69,6 +84,8 @@ struct ast *parse_compound_list(struct lexer_context *ctx); * @code else_clause = 'else' compound_list * | 'elif' compound_list 'then' compound_list [else_clause] * ; + * + * @first TOKEN_ELSE, TOKEN_ELIF */ struct ast *parse_else_clause(struct lexer_context *ctx); From 6be3dde607d2eaf3aff73f3de2ad4c32ec38e85a Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 24 Jan 2026 13:20:29 +0100 Subject: [PATCH 168/282] fix(main): memeory leaks from vars on error_input_processing --- src/main.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main.c b/src/main.c index 1d9f7b2..81200c2 100644 --- a/src/main.c +++ b/src/main.c @@ -18,6 +18,15 @@ // === Functions +/* @brief: frees the hash map. + * @return: always ERR_INPUT_PROCESSING. + */ +static int err_input(struct hash_map **vars) +{ + hash_map_free(vars); + return ERR_INPUT_PROCESSING; +} + static int main_loop(struct lexer_context *ctx, struct args_options *options, struct hash_map *vars) { @@ -49,7 +58,7 @@ static int main_loop(struct lexer_context *ctx, struct args_options *options, } if (command_ast == NULL) - return ERR_INPUT_PROCESSING; + return err_input(&vars); ast_free(&command_ast); @@ -71,7 +80,7 @@ int main(int argc, char **argv) if (return_code != 0) { print_usage(stderr, argv[0]); - return ERR_INPUT_PROCESSING; + return err_input(&vars); } // args_print(&options); @@ -86,7 +95,7 @@ int main(int argc, char **argv) { fprintf(stderr, "Error: Failed to configure IO Backend from arguments\n"); - return ERR_INPUT_PROCESSING; + return err_input(&vars); } // Init IO Backend (with the context struct) @@ -96,10 +105,9 @@ int main(int argc, char **argv) fprintf(stderr, "Error: IO Backend initialization failed with code %d\n", return_code); - return ERR_INPUT_PROCESSING; + return err_input(&vars); } - // init lexer context struct lexer_context ctx = { 0 }; From da948f58fe7c382bef7d17010517002a31eb31ab Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 24 Jan 2026 14:52:37 +0100 Subject: [PATCH 169/282] fix(execution): ast_void does not affect return value => if statements now work --- src/execution/execution.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/execution/execution.c b/src/execution/execution.c index d1d0e97..4522bcc 100644 --- a/src/execution/execution.c +++ b/src/execution/execution.c @@ -245,7 +245,8 @@ int execution(struct ast *ast, struct hash_map *vars) while (cur) { struct ast *child = (struct ast *)cur->data; - ret = execution(child, vars); + if (!ast_is_void(child)) + ret = execution(child, vars); cur = cur->next; } return ret; @@ -358,4 +359,4 @@ int execution(struct ast *ast, struct hash_map *vars) return 127; } } -} \ No newline at end of file +} From 32f56beb6bff65750700c14410901d3dd003d9ef Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Sat, 24 Jan 2026 15:34:10 +0100 Subject: [PATCH 170/282] feat: redirection rules --- src/parser/grammar_advanced.c | 28 ++++++++++++++ src/parser/grammar_advanced.h | 23 ++++++++++++ src/parser/grammar_basic.c | 70 +++++++++++++++++++++++++++-------- src/parser/grammar_basic.h | 38 ++++++++++++++----- src/utils/ast/ast.h | 1 + src/utils/ast/ast_base.h | 3 +- src/utils/ast/ast_word.c | 45 ++++++++++++++++++++++ src/utils/ast/ast_word.h | 35 ++++++++++++++++++ 8 files changed, 218 insertions(+), 25 deletions(-) create mode 100644 src/utils/ast/ast_word.c create mode 100644 src/utils/ast/ast_word.h diff --git a/src/parser/grammar_advanced.c b/src/parser/grammar_advanced.c index e69de29..0f29b9b 100644 --- a/src/parser/grammar_advanced.c +++ b/src/parser/grammar_advanced.c @@ -0,0 +1,28 @@ +#include "grammar_advanced.h" + +#include + +#include "grammar_basic.h" + +struct ast *parse_redirection(struct lexer_context *ctx) +{ + struct token *token = PEEK_TOKEN(); + if (token->type == TOKEN_IONUMBER) + { + // TODO + POP_TOKEN(); + token = PEEK_TOKEN(); + } + + if (token->type != TOKEN_REDIRECTION) + { + puts("Syntax error: expected a redirection token but got something " + "else"); + return NULL; + } +} + +struct ast *parse_prefix(struct lexer_context *ctx) +{ + return parse_redirection(ctx); +} diff --git a/src/parser/grammar_advanced.h b/src/parser/grammar_advanced.h index 8773655..72b5a43 100644 --- a/src/parser/grammar_advanced.h +++ b/src/parser/grammar_advanced.h @@ -1,4 +1,27 @@ #ifndef GRAMMAR_ADVANCED_H #define GRAMMAR_ADVANCED_H +#include "grammar.h" + +// === Functions + +/* + * @brief parses a redirection rule + * + * @code redirection = [IONUMBER] ( '>' | '<' | '>>' | '>&' | '<&' | '>|' | '<>' ) WORD ; + * + * @first TOKEN_IONUMBER, TOKEN_REDIRECTION + */ +struct ast *parse_redirection(struct lexer_context *ctx); + +/* + * @brief parses a prefix rule + * + * @code prefix = redirection ; + * + * @first first(redirection) + */ +struct ast *parse_prefix(struct lexer_context *ctx); + + #endif /* ! GRAMMAR_ADVANCED_H */ diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index b7f74c0..efa105d 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -3,9 +3,9 @@ #include #include -#include "../lexer/lexer.h" #include "../utils/lists/lists.h" #include "grammar.h" +#include "grammar_advanced.h" // === Static functions @@ -124,46 +124,86 @@ struct ast *parse_command(struct lexer_context *ctx) struct ast *parse_simple_command(struct lexer_context *ctx) { struct list *command_elements = NULL; - struct token *token = PEEK_TOKEN(); + + // WORD + struct token *token = POP_TOKEN(); if (token->type != TOKEN_WORD) { puts("Expected a command but got a different token type"); return NULL; } + char *command = strdup(token->data); + command_elements = list_append(command_elements, command); + token = PEEK_TOKEN(); + + // Eventual elements while (token->type == TOKEN_WORD) { - token = pop_token(ctx); - if (token == NULL) + // Get element + struct ast *element = parse_element(ctx); + if (element == NULL) { - // TODO free + list_deep_destroy(command_elements); return NULL; } - char *word = strdup(token->data); - if (word == NULL) + + // Get element type + if (ast_is_word(element)) { - // TODO free - puts("Internal error: Couldn't copy token content (is memory full " - "?)"); + struct ast_word *element_word = ast_get_word(element); + command_elements = + list_append(command_elements, element_word->word); + } + else if (ast_is_redir(element)) + { + // TODO + puts("NOT IMPLEMENTED"); return NULL; } - command_elements = list_append(command_elements, word); - token = peek_token(ctx); - if (token == NULL) + else { - // TODO free + puts("Internal error: unexpected return value from parse_element " + "in parse_simple_command"); + list_deep_destroy(command_elements); return NULL; } + + // Forward + POP_TOKEN(); + token = PEEK_TOKEN(); } + // Result struct ast *result = ast_create_command(command_elements); if (result == NULL) { - // TODO free + list_deep_destroy(command_elements); + return NULL; } return result; } +struct ast *parse_element(struct lexer_context *ctx) +{ + struct token *token = PEEK_TOKEN(); + if (token->type == TOKEN_WORD) + { + // TODO + puts("NOT IMPLEMENTED"); + return NULL; + } + else if (token->type == TOKEN_IONUMBER || token->type == TOKEN_REDIRECTION) + { + return parse_redirection(ctx); + } + else + { + puts("Syntax error: unexpected token at parse_element"); + return NULL; + } +} + struct ast *parse_shell_command(struct lexer_context *ctx) { return parse_if_rule(ctx); diff --git a/src/parser/grammar_basic.h b/src/parser/grammar_basic.h index cb49172..f1c2f77 100644 --- a/src/parser/grammar_basic.h +++ b/src/parser/grammar_basic.h @@ -6,7 +6,8 @@ // === Functions -/* @brief: parses a list of [and_or] rules separated by semicolons and that +/* + * @brief parses a list of [and_or] rules separated by semicolons and that * ends by a newline * * @code list = and_or { ';' and_or } [ ';' ] ; @@ -15,7 +16,8 @@ */ struct ast *parse_list(struct lexer_context *ctx); -/* @brief Only parses a pipeline rule for the moment +/* + * @brief Only parses a pipeline rule for the moment * * @code and_or = pipeline { ( '&&' | '||' ) {'\n'} pipeline } ; * @@ -23,7 +25,8 @@ struct ast *parse_list(struct lexer_context *ctx); */ struct ast *parse_and_or(struct lexer_context *ctx); -/* @brief Only parses a command rule for the moment +/* + * @brief Only parses a command rule for the moment * * @code pipeline = command ; * @@ -31,7 +34,8 @@ struct ast *parse_and_or(struct lexer_context *ctx); */ struct ast *parse_pipeline(struct lexer_context *ctx); -/* @brief Parses a simple command rule or a shell command rule depending on +/* + * @brief Parses a simple command rule or a shell command rule depending on * the first token. * @note * TOKEN_WORD => simple_command @@ -44,7 +48,8 @@ struct ast *parse_pipeline(struct lexer_context *ctx); */ struct ast *parse_command(struct lexer_context *ctx); -/* @brief Parses a simple list of words (command and arguments) +/* + * @brief Parses a simple list of words (command and arguments) * ending by a separator * * @code simple_command = WORD { element } ; @@ -53,7 +58,19 @@ struct ast *parse_command(struct lexer_context *ctx); */ struct ast *parse_simple_command(struct lexer_context *ctx); -/* @brief Only parses if rules for the moment +/* + * @brief Parses an element rule + * + * @code element = WORD + * | redirection + * ; + * + * @first WORD, first(redirection) + */ +struct ast *parse_element(struct lexer_context *ctx); + +/* + * @brief Only parses if rules for the moment * * @code shell_command = if_rule ; * @@ -61,7 +78,8 @@ struct ast *parse_simple_command(struct lexer_context *ctx); */ struct ast *parse_shell_command(struct lexer_context *ctx); -/* @brief Parses a if rule (condition, then-clause, elif-clause, else-clause) +/* + * @brief Parses a if rule (condition, then-clause, elif-clause, else-clause) * * @code if_rule = 'if' compound_list 'then' compound_list [else_clause] 'fi' ; * @@ -69,7 +87,8 @@ struct ast *parse_shell_command(struct lexer_context *ctx); */ struct ast *parse_if_rule(struct lexer_context *ctx); -/* @brief parses commands inside if/else clauses and returns the corresponding +/* + * @brief parses commands inside if/else clauses and returns the corresponding * AST list * * @code compound_list = {'\n'} and_or { ( ';' | '\n' ) {'\n'} and_or } [';'] @@ -79,7 +98,8 @@ struct ast *parse_if_rule(struct lexer_context *ctx); */ struct ast *parse_compound_list(struct lexer_context *ctx); -/* @brief +/* + * @brief * * @code else_clause = 'else' compound_list * | 'elif' compound_list 'then' compound_list [else_clause] diff --git a/src/utils/ast/ast.h b/src/utils/ast/ast.h index a98d88b..fc35666 100644 --- a/src/utils/ast/ast.h +++ b/src/utils/ast/ast.h @@ -9,6 +9,7 @@ #include "ast_list.h" #include "ast_redir.h" #include "ast_void.h" +#include "ast_word.h" /** * Prints the Graphviz DOT representation of the given AST to stdout. diff --git a/src/utils/ast/ast_base.h b/src/utils/ast/ast_base.h index e09b25d..4ae00ec 100644 --- a/src/utils/ast/ast_base.h +++ b/src/utils/ast/ast_base.h @@ -11,7 +11,8 @@ enum ast_type AST_AND_OR, AST_REDIR, AST_VOID, - AST_CMD + AST_CMD, + AST_WORD }; struct ast diff --git a/src/utils/ast/ast_word.c b/src/utils/ast/ast_word.c new file mode 100644 index 0000000..d798c77 --- /dev/null +++ b/src/utils/ast/ast_word.c @@ -0,0 +1,45 @@ +#include "ast_word.h" + +#include +#include +#include + +struct ast *ast_create_word(char *word) +{ + struct ast_word *ast_node = malloc(sizeof(struct ast_word)); + if (ast_node == NULL) + return NULL; + + ast_node->type = AST_WORD; + ast_node->word = strdup(word); + struct ast *res = ast_create(AST_WORD, ast_node); + if (res == NULL) + { + free(ast_node); + return NULL; + } + + return res; +} + +struct ast_word *ast_get_word(struct ast *node) +{ + if (node == NULL || node->type != AST_WORD) + return NULL; + + return node->data; +} + +bool ast_is_word(struct ast *node) +{ + return node && node->type == AST_WORD; +} + +void ast_free_word(struct ast_word *ast_node) +{ + if (ast_node == NULL) + return; + + free(ast_node->word); + free(ast_node); +} diff --git a/src/utils/ast/ast_word.h b/src/utils/ast/ast_word.h new file mode 100644 index 0000000..d5e0e1d --- /dev/null +++ b/src/utils/ast/ast_word.h @@ -0,0 +1,35 @@ +#ifndef AST_WORD_H +#define AST_WORD_H + +#include + +#include "ast_base.h" + +struct ast_word +{ + enum ast_type type; + char *word; +}; + +/** + * Checks if the given AST node is a command. + */ +bool ast_is_word(struct ast *node); + +/** + * Retrieves the command data from the given AST node. + * Assumes that the node is of type AST_CMD. + */ +struct ast_word *ast_get_word(struct ast *node); + +/** + * Creates a new AST node representing a command. + */ +struct ast *ast_create_word(char* word); + +/* + * @brief: frees the given ast_command and sets the pointer to NULL. + */ +void ast_free_word(struct ast_word *ast_node); + +#endif /* ! AST_WORD_H */ From f3be15abb505c46cda593901051cab51f2e114bb Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 24 Jan 2026 15:46:19 +0100 Subject: [PATCH 171/282] fix: testsuite sees the BIN_PATH, default value is to src/42sh --- tests/wrap.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/wrap.sh b/tests/wrap.sh index 5cd6f53..a3bd89e 100755 --- a/tests/wrap.sh +++ b/tests/wrap.sh @@ -1,3 +1,7 @@ +if [ "$BIN_PATH" = "" ]; +then export BIN_PATH="$(pwd)/42sh" +fi + if [ "$COVERAGE" = "yes" ]; #coverage then ./testsuite && ../tests/functional/run-tests.sh else ../tests/functional/run-tests.sh From 07d345f75412904f49a2c89018a727adddd3790a Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Sat, 24 Jan 2026 14:51:27 +0000 Subject: [PATCH 172/282] feat(vars): $# init --- src/utils/args/args.c | 3 +++ tests/unit/expansion/expand.c | 4 ---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/utils/args/args.c b/src/utils/args/args.c index e6995fa..89a00a3 100644 --- a/src/utils/args/args.c +++ b/src/utils/args/args.c @@ -67,6 +67,9 @@ int args_handler(int argc, char **argv, struct args_options *options, } } + int_to_str(arg_index - 1, index_str); + set_var_copy(vars, "#", index_str); + if (options->type == INPUT_UNDEFINED) options->type = INPUT_STDIN; diff --git a/tests/unit/expansion/expand.c b/tests/unit/expansion/expand.c index fd4295d..04714bf 100644 --- a/tests/unit/expansion/expand.c +++ b/tests/unit/expansion/expand.c @@ -245,8 +245,6 @@ Test(expand, pid) struct ast_command *ast_command = ast_get_command(ast); struct hash_map *vars = vars_init(); - vars_default(vars); - struct ast_command *command2 = expand(ast_command, vars); cr_assert_not_null(command2, "Expansion returned NULL"); int pid = atoi((char *)command2->command->data); @@ -265,8 +263,6 @@ Test(expand, default_last_exit_code) struct ast_command *ast_command = ast_get_command(ast); struct hash_map *vars = vars_init(); - vars_default(vars); - struct ast_command *command2 = expand(ast_command, vars); cr_assert_not_null(command2, "Expansion returned NULL"); int code = atoi((char *)command2->command->data); From 91806d926e1c713340f188e7c48453134b74264c Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 24 Jan 2026 15:46:19 +0100 Subject: [PATCH 173/282] fix: testsuite sees the BIN_PATH, default value is to src/42sh --- tests/wrap.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/wrap.sh b/tests/wrap.sh index 5cd6f53..a3bd89e 100755 --- a/tests/wrap.sh +++ b/tests/wrap.sh @@ -1,3 +1,7 @@ +if [ "$BIN_PATH" = "" ]; +then export BIN_PATH="$(pwd)/42sh" +fi + if [ "$COVERAGE" = "yes" ]; #coverage then ./testsuite && ../tests/functional/run-tests.sh else ../tests/functional/run-tests.sh From 3ee4a0b9ca90117a4613ab79e38527b77351a4cb Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Sat, 24 Jan 2026 16:13:16 +0100 Subject: [PATCH 174/282] feat: finished the new firsts system and began supporting redirections --- src/parser/grammar.c | 91 ++++++++++++++++++++++++++--------- src/parser/grammar.h | 28 +++++++++-- src/parser/grammar_advanced.c | 22 +++++++++ src/parser/grammar_basic.c | 37 +++++++------- 4 files changed, 134 insertions(+), 44 deletions(-) diff --git a/src/parser/grammar.c b/src/parser/grammar.c index 3fbb5fd..982a744 100644 --- a/src/parser/grammar.c +++ b/src/parser/grammar.c @@ -13,30 +13,13 @@ static struct firsts_list *firsts_map = NULL; // === Static functions -/* @brief get the first accepted tokens of a rule - * - * @arg r the rule - * @return the accepted tokens as a firsts_list struct - */ -static struct firsts_list *first(enum rule r) -{ - if (firsts_map == NULL || firsts_map[r].tokens == NULL) - { - puts("Internal error: attempted to get the firsts of a rule without " - "properly initializing the firsts map"); - return NULL; - } - - return &firsts_map[r]; -} - /* @brief Add a token to a rule's firsts (in firsts_map) * * @arg rule the rule to which add a first * @arg token the token to add to the rule's firsts * @return true on success, false on error */ -static bool add_first(enum rule rule, struct token token) +static bool add_first(enum rule rule, enum token_type token) { struct firsts_list *item = &firsts_map[rule]; if (item->tokens != NULL) @@ -44,20 +27,20 @@ static bool add_first(enum rule rule, struct token token) // Check for duplicates for (size_t i = 0; i < item->list_length; i++) { - if (item->tokens[i].type == token.type) + if (item->tokens[i] == token) return true; } // Append item->list_length++; item->tokens = realloc( - item->tokens, (item->list_length) * sizeof(struct firsts_list)); + item->tokens, (item->list_length) * sizeof(enum token_type)); } else { // Create entry - item->tokens = - calloc(item->list_length + 1, sizeof(struct firsts_list)); + item->list_length = 1; + item->tokens = calloc(1, sizeof(enum token_type)); } // Check for alloc error @@ -100,7 +83,45 @@ bool grammar_init(void) return false; // Populate the firsts map - // TODO + add_first(RULE_INPUT, TOKEN_WORD); + add_first(RULE_INPUT, TOKEN_IF); + 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_AND_OR, TOKEN_WORD); + add_first(RULE_AND_OR, TOKEN_IF); + + add_first(RULE_PIPELINE, TOKEN_WORD); + add_first(RULE_PIPELINE, TOKEN_IF); + + 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); + + 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); + + add_first(RULE_ELSE_CLAUSE, TOKEN_ELSE); + add_first(RULE_ELSE_CLAUSE, TOKEN_ELIF); + + add_first(RULE_ELEMENT, TOKEN_WORD); + add_first(RULE_ELEMENT, TOKEN_IONUMBER); + add_first(RULE_ELEMENT, TOKEN_REDIRECTION); + + add_first(RULE_REDIRECTION, TOKEN_IONUMBER); + add_first(RULE_REDIRECTION, TOKEN_REDIRECTION); + + add_first(RULE_PREFIX, TOKEN_IONUMBER); + add_first(RULE_PREFIX, TOKEN_REDIRECTION); return true; } @@ -119,6 +140,30 @@ void grammar_close(void) firsts_map = NULL; } +struct firsts_list *first(enum rule rule) +{ + if (firsts_map == NULL || firsts_map[rule].tokens == NULL) + { + puts("Internal error: attempted to get the firsts of a rule without " + "properly initializing the firsts map"); + return NULL; + } + + return &firsts_map[rule]; +} + +bool is_first(struct token token, enum rule rule) +{ + struct firsts_list *firsts = &firsts_map[rule]; + for (size_t i = 0; i < firsts->list_length; i++) + { + if (firsts->tokens[i] == token.type) + return true; + } + + return false; +} + struct ast *parse_input(struct lexer_context *ctx) { return parse_list(ctx); diff --git a/src/parser/grammar.h b/src/parser/grammar.h index a5ec6e2..5721ee7 100644 --- a/src/parser/grammar.h +++ b/src/parser/grammar.h @@ -37,27 +37,49 @@ enum rule { RULE_IF, RULE_COMPOUND_LIST, RULE_ELSE_CLAUSE, + RULE_ELEMENT, + RULE_REDIRECTION, + RULE_PREFIX, NUMBER_OF_RULES }; struct firsts_list { - struct token* tokens; // Heap allocated array + enum token_type* tokens; // Heap allocated array size_t list_length; }; // === Functions -/* @brief Initializes the grammar submodule +/* + * @brief Initializes the grammar submodule * @return PARSER_INIT_SUCCESS on success PARSER_INIT_ERROR on error * @warning Do not use outside the parser */ bool grammar_init(void); -/* @brief Closes the grammar submodule +/* + * @brief Closes the grammar submodule * @warning Do not use outside the parser */ void grammar_close(void); +/* + * @brief get the first accepted tokens of a rule + * + * @arg r the rule + * @return the accepted tokens as a firsts_list struct + */ +struct firsts_list *first(enum rule r); + +/* + * @brief tells is token belong to the firsts of a specific rule + * + * @arg token + * @arg rule + * @return true if token belongs to rule's firsts, false otherwise + */ +bool is_first(struct token token, enum rule rule); + /* @brief Acts as the entry point of the parser, calls parse_list * * @code input = list '\n' diff --git a/src/parser/grammar_advanced.c b/src/parser/grammar_advanced.c index 0f29b9b..1608d5c 100644 --- a/src/parser/grammar_advanced.c +++ b/src/parser/grammar_advanced.c @@ -1,6 +1,8 @@ #include "grammar_advanced.h" #include +#include +#include #include "grammar_basic.h" @@ -10,6 +12,12 @@ struct ast *parse_redirection(struct lexer_context *ctx) if (token->type == TOKEN_IONUMBER) { // TODO + } + + int io_number = -1; + if (token->type == TOKEN_IONUMBER) + { + io_number = atoi(token->data); POP_TOKEN(); token = PEEK_TOKEN(); } @@ -20,6 +28,20 @@ struct ast *parse_redirection(struct lexer_context *ctx) "else"); return NULL; } + char *redir_op = strdup(token->data); + POP_TOKEN(); + + token = PEEK_TOKEN(); + if (token->type != TOKEN_WORD) + { + puts("Syntax error: expected a word after redirection"); + free(redir_op); + return NULL; + } + char *target = strdup(token->data); + POP_TOKEN(); + + return ast_create_redir(io_number, redir_op, target); } struct ast *parse_prefix(struct lexer_context *ctx) diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index efa105d..78b3b3d 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -43,15 +43,15 @@ struct ast *parse_list(struct lexer_context *ctx) while (token->type == TOKEN_SEMICOLON) { token = POP_TOKEN(); - current_node = parse_and_or(ctx); - if (current_node == NULL) - { - // TODO free list - // There must be a function for that - return NULL; - } - result_list = list_append(result_list, current_node); token = PEEK_TOKEN(); + if (is_first(*token, RULE_AND_OR)) + { + current_node = parse_and_or(ctx); + if (current_node == NULL) + return NULL; + result_list = list_append(result_list, current_node); + token = PEEK_TOKEN(); + } } return ast_create_list(result_list); @@ -138,7 +138,7 @@ struct ast *parse_simple_command(struct lexer_context *ctx) token = PEEK_TOKEN(); // Eventual elements - while (token->type == TOKEN_WORD) + while (is_first(*token, RULE_ELEMENT)) { // Get element struct ast *element = parse_element(ctx); @@ -170,7 +170,6 @@ struct ast *parse_simple_command(struct lexer_context *ctx) } // Forward - POP_TOKEN(); token = PEEK_TOKEN(); } @@ -189,9 +188,8 @@ struct ast *parse_element(struct lexer_context *ctx) struct token *token = PEEK_TOKEN(); if (token->type == TOKEN_WORD) { - // TODO - puts("NOT IMPLEMENTED"); - return NULL; + token = POP_TOKEN(); + return ast_create_word(token->data); } else if (token->type == TOKEN_IONUMBER || token->type == TOKEN_REDIRECTION) { @@ -310,11 +308,14 @@ struct ast *parse_compound_list(struct lexer_context *ctx) } // And/or - current_cmd = parse_and_or(ctx); - if (current_cmd == NULL) - return NULL; - result_list = list_append(result_list, current_cmd); - + if (is_first(*token, RULE_AND_OR)) + { + current_cmd = parse_and_or(ctx); + if (current_cmd == NULL) + return NULL; + result_list = list_append(result_list, current_cmd); + } + token = PEEK_TOKEN(); } From 00c2b9c97996adcdf5101cf89c6f449791be2211 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Sat, 24 Jan 2026 15:23:31 +0000 Subject: [PATCH 175/282] fix(lists): restored lists in splitted files --- src/utils/Makefile.am | 10 +- src/utils/lists/lists1.c | 87 ++++++++++++++++ src/utils/lists/lists2.c | 107 ++++++++++++++++++++ src/utils/lists/{lists.c => lists3.c} | 138 +++++++------------------- 4 files changed, 235 insertions(+), 107 deletions(-) create mode 100644 src/utils/lists/lists1.c create mode 100644 src/utils/lists/lists2.c rename src/utils/lists/{lists.c => lists3.c} (52%) diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am index 57e6e4d..65cc594 100644 --- a/src/utils/Makefile.am +++ b/src/utils/Makefile.am @@ -1,7 +1,11 @@ lib_LIBRARIES = libutils.a libutils_a_SOURCES = \ - args/args.c \ + lists/lists1.c \ + lists/lists2.c \ + lists/lists3.c \ + hash_map/hash_map.c \ + string_utils/string_utils.c \ ast/ast.c \ ast/ast_if.c \ ast/ast_command.c \ @@ -10,9 +14,7 @@ libutils_a_SOURCES = \ ast/ast_redir.c \ ast/ast_void.c \ ast/ast_end.c \ - hash_map/hash_map.c \ - lists/lists.c \ - string_utils/string_utils.c \ + args/args.c \ vars/vars.c libutils_a_CPPFLAGS = -I$(top_srcdir)/src diff --git a/src/utils/lists/lists1.c b/src/utils/lists/lists1.c new file mode 100644 index 0000000..d191bf7 --- /dev/null +++ b/src/utils/lists/lists1.c @@ -0,0 +1,87 @@ +#include +#include +#include + +#include "lists.h" + +struct list *list_prepend(struct list *list, void *value) +{ + struct list *new_elt = malloc(sizeof(struct list)); + if (new_elt == NULL) + { + return NULL; + } + new_elt->next = list; + new_elt->data = value; + + return new_elt; +} + +size_t list_length(struct list *list) +{ + size_t len = 0; + while (list != NULL) + { + len++; + list = list->next; + } + return len; +} + +void list_print(struct list *list) +{ + if (list == NULL) + { + return; + } + + while (list != NULL) + { + if (list->next != NULL) + { + printf("%p ", list->data); + } + else + { + printf("%p\n", list->data); + } + list = list->next; + } +} + +void list_destroy(struct list *list) +{ + struct list *elt = list; + struct list *next_elt; + while (elt != NULL) + { + next_elt = elt->next; + free(elt); + elt = next_elt; + } +} + +struct list *list_append(struct list *list, void *value) +{ + if (list == NULL) + { + struct list *new_elt = malloc(sizeof(struct list)); + new_elt->data = value; + new_elt->next = NULL; + return new_elt; + } + + struct list *elt = list; + + while (elt->next != NULL) + { + elt = elt->next; + } + + struct list *new_elt = malloc(sizeof(struct list)); + new_elt->data = value; + new_elt->next = NULL; + elt->next = new_elt; + + return list; +} diff --git a/src/utils/lists/lists2.c b/src/utils/lists/lists2.c new file mode 100644 index 0000000..745098a --- /dev/null +++ b/src/utils/lists/lists2.c @@ -0,0 +1,107 @@ +#include + +#include "lists.h" + +/* + * + ******************* + * Advanced * + ******************* + * + */ + +struct list *list_insert(struct list *list, void *value, size_t index) +{ + if (list == NULL || index == 0) + { + struct list *new_elt = malloc(sizeof(struct list)); + new_elt->data = value; + new_elt->next = list; + return new_elt; + } + + struct list *elt = list; + + for (size_t i = 0; i < index - 1; i++) + { + if (elt->next == NULL) + { + break; + } + elt = elt->next; + } + + struct list *new_elt = malloc(sizeof(struct list)); + new_elt->data = value; + new_elt->next = elt->next; + elt->next = new_elt; + + return list; +} + +struct list *list_remove(struct list *list, size_t index) +{ + struct list *elt = list; + struct list *prev_elt; + + if (index == 0) + { + struct list *res = elt->next; + free(elt); + return res; + } + + for (size_t i = 0; i < index; i++) + { + if (elt == NULL) + { + return list; + } + prev_elt = elt; + elt = elt->next; + } + if (elt == NULL) + { + return list; + } + + prev_elt->next = elt->next; + free(elt); + + return list; +} + +int list_find(struct list *list, void *value) +{ + if (list == NULL) + { + return -1; + } + + int res = 0; + while (list->data != value) + { + list = list->next; + res++; + if (list == NULL) + { + return -1; + } + } + return res; +} + +struct list *list_concat(struct list *list, struct list *list2) +{ + if (list == NULL) + { + return list2; + } + struct list *elt = list; + while (elt->next != NULL) + { + elt = elt->next; + } + elt->next = list2; + return list; +} diff --git a/src/utils/lists/lists.c b/src/utils/lists/lists3.c similarity index 52% rename from src/utils/lists/lists.c rename to src/utils/lists/lists3.c index ba0ec50..e9cc545 100644 --- a/src/utils/lists/lists.c +++ b/src/utils/lists/lists3.c @@ -1,108 +1,6 @@ -#include "lists.h" - -#include #include -#include - -struct list *list_append(struct list *list, void *value) -{ - if (list == NULL) - { - struct list *new_elt = malloc(sizeof(struct list)); - new_elt->data = value; - new_elt->next = NULL; - return new_elt; - } - - struct list *elt = list; - - while (elt->next != NULL) - { - elt = elt->next; - } - - struct list *new_elt = malloc(sizeof(struct list)); - new_elt->data = value; - new_elt->next = NULL; - elt->next = new_elt; - - return list; -} - -/* - * - ******************* - * Advanced * - ******************* - * - */ -struct list *list_remove(struct list *list, size_t index) -{ - struct list *elt = list; - struct list *prev_elt; - - if (index == 0) - { - struct list *res = elt->next; - free(elt); - return res; - } - - for (size_t i = 0; i < index; i++) - { - if (elt == NULL) - { - return list; - } - prev_elt = elt; - elt = elt->next; - } - if (elt == NULL) - { - return list; - } - - prev_elt->next = elt->next; - free(elt); - - return list; -} - -int list_find(struct list *list, void *value) -{ - if (list == NULL) - { - return -1; - } - - int res = 0; - while (list->data != value) - { - list = list->next; - res++; - if (list == NULL) - { - return -1; - } - } - return res; -} - -struct list *list_concat(struct list *list, struct list *list2) -{ - if (list == NULL) - { - return list2; - } - struct list *elt = list; - while (elt->next != NULL) - { - elt = elt->next; - } - elt->next = list2; - return list; -} +#include "lists.h" /* * ****************** @@ -117,6 +15,40 @@ static void swap_next(struct list *elt) elt->next->data = elt->data; elt->data = c; } +struct list *list_sort(struct list *list) +{ + // Bubble sort go ! + if (list == NULL) + { + return list; + } + struct list *elt = list; + int len = 0; + while (elt->next != NULL) + { + if (elt->data > elt->next->data) + { + swap_next(elt); + } + elt = elt->next; + len++; + } + + for (int i = 1; i < len; i++) + { + elt = list; + while (elt->next != NULL) + { + if (elt->data > elt->next->data) + { + swap_next(elt); + } + elt = elt->next; + } + } + + return list; +} // Old proto // WARNING no malloc/free allowed (moulinette issue) From 79a73a23a031ad264cc98fbdf6fe3d8857cf8f8c Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 24 Jan 2026 16:36:23 +0100 Subject: [PATCH 176/282] fix(testsuite): unit testing running --- src/Makefile.am | 8 +++- src/expansion/expansion.h | 1 + src/main.c | 2 +- tests/unit/expansion/expand.c | 82 +++++++++++++++++------------------ tests/wrap.sh | 2 +- 5 files changed, 51 insertions(+), 44 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 2971e09..24c27a7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -35,7 +35,13 @@ check_PROGRAMS = testsuite #testsuite_CFLAGS += $(CRITERION_CFLAGS) testsuite_SOURCES = ../tests/unit/lexer/lexer_tests.c \ - ../tests/unit/utils/utils_tests.c + ../tests/unit/utils/utils_tests.c \ + ../tests/unit/expansion/expand.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 testsuite_CPPFLAGS = $(42sh_CPPFLAGS) diff --git a/src/expansion/expansion.h b/src/expansion/expansion.h index 64b016f..420ed02 100644 --- a/src/expansion/expansion.h +++ b/src/expansion/expansion.h @@ -4,6 +4,7 @@ #include #include +#include "../utils/ast/ast.h" #include "../utils/hash_map/hash_map.h" /** diff --git a/src/main.c b/src/main.c index 81200c2..f1d0c00 100644 --- a/src/main.c +++ b/src/main.c @@ -111,7 +111,7 @@ int main(int argc, char **argv) // init lexer context struct lexer_context ctx = { 0 }; - return_code = main_loop(&ctx, &options,vars); + return_code = main_loop(&ctx, &options, vars); // === free diff --git a/tests/unit/expansion/expand.c b/tests/unit/expansion/expand.c index 04714bf..859256e 100644 --- a/tests/unit/expansion/expand.c +++ b/tests/unit/expansion/expand.c @@ -20,9 +20,9 @@ Test(expand, no_expansion) struct ast *ast = ast_create_command(list); struct ast_command *ast_command = ast_get_command(ast); - struct ast_command *command2 = expand(ast_command, NULL); - cr_assert_not_null(command2, "Expansion returned NULL"); - cr_assert_str_eq((char *)command2->command->data, "echo something", + bool ret = expand(ast_command, NULL); + cr_expect(ret, "expansion failed with %s", str); + cr_expect_str_eq((char *)ast_command->command->data, "echo something", "String without variables should remain unchanged"); ast_free(&ast); } @@ -38,9 +38,9 @@ Test(expand, single_quotes_no_expansion) struct hash_map *vars = vars_init(); set_var_copy(vars, "VAR", "expanded"); - struct ast_command *command2 = expand(ast_command, vars); - cr_assert_not_null(command2, "Expansion returned NULL"); - cr_assert_str_eq((char *)command2->command->data, "echo $VAR", + bool ret = expand(ast_command, vars); + cr_expect(ret, "expansion failed with %s", str); + cr_expect_str_eq((char *)ast_command->command->data, "echo $VAR", "Variable should not expand inside single quotes"); ast_free(&ast); hash_map_free(&vars); @@ -57,9 +57,9 @@ Test(expand, single_dollar) struct hash_map *vars = vars_init(); set_var_copy(vars, "VAR", "expanded"); - struct ast_command *command2 = expand(ast_command, vars); - cr_assert_not_null(command2, "Expansion returned NULL"); - cr_assert_str_eq((char *)command2->command->data, "echo $ sign", + bool ret = expand(ast_command, vars); + cr_expect(ret, "expansion failed with %s", str); + cr_expect_str_eq((char *)ast_command->command->data, "echo $ sign", "Variable should not expand inside single quotes"); ast_free(&ast); hash_map_free(&vars); @@ -76,8 +76,8 @@ Test(expand, empty_braces_no_expansion) struct hash_map *vars = vars_init(); set_var_copy(vars, "VAR", "expanded"); - struct ast_command *command2 = expand(ast_command, vars); - cr_assert_null(command2, "Expansion should fail on empty braces"); + bool ret = expand(ast_command, vars); + cr_expect(ret == false, "expansion should fail with %s", str); ast_free(&ast); hash_map_free(&vars); } @@ -93,9 +93,9 @@ Test(expand, basic_expansion) struct hash_map *vars = vars_init(); set_var_copy(vars, "VAR", "expanded"); - struct ast_command *command2 = expand(ast_command, vars); - cr_assert_not_null(command2, "Expansion returned NULL"); - cr_assert_str_eq((char *)command2->command->data, "echo 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", "Variable should expand correctly"); ast_free(&ast); hash_map_free(&vars); @@ -114,9 +114,9 @@ Test(expand, multiple_expansion) set_var_copy(vars, "VAR2", "values"); set_var_copy(vars, "VAR3", "here"); - struct ast_command *command2 = expand(ast_command, vars); - cr_assert_not_null(command2, "Expansion returned NULL"); - cr_assert_str_eq((char *)command2->command->data, + 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 values here", "Multiple variables should expand correctly"); ast_free(&ast); @@ -133,9 +133,9 @@ Test(expand, env_variable) setenv("MY_ENV_VAR", "environment", 0); - struct ast_command *command2 = expand(ast_command, NULL); - cr_assert_not_null(command2, "Expansion returned NULL"); - cr_assert_str_eq((char *)command2->command->data, "echo environment", + bool ret = expand(ast_command, NULL); + cr_expect(ret, "expansion failed with %s", str); + cr_expect_str_eq((char *)ast_command->command->data, "echo environment", "Environment variable should expand correctly"); ast_free(&ast); } @@ -150,9 +150,9 @@ Test(expand, undefined_variable) struct hash_map *vars = vars_init(); - struct ast_command *command2 = expand(ast_command, vars); - cr_assert_not_null(command2, "Expansion returned NULL"); - cr_assert_str_eq((char *)command2->command->data, "echo ", + bool ret = expand(ast_command, vars); + cr_expect(ret, "expansion failed with %s", str); + cr_expect_str_eq((char *)ast_command->command->data, "echo ", "Undefined variable should expand to empty string"); ast_free(&ast); hash_map_free(&vars); @@ -170,9 +170,9 @@ Test(expand, nested_expansion) set_var_copy(vars, "A", "expanded"); set_var_copy(vars, "B", "$A"); - struct ast_command *command2 = expand(ast_command, vars); - cr_assert_not_null(command2, "Expansion returned NULL"); - cr_assert_str_eq((char *)command2->command->data, "echo 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", "Nested variable should expand correctly"); ast_free(&ast); hash_map_free(&vars); @@ -190,9 +190,9 @@ Test(expand, mixed_quotes_expansion) set_var_copy(vars, "VAR1", "expanded"); set_var_copy(vars, "VAR2", "not_expanded"); - struct ast_command *command2 = expand(ast_command, vars); - cr_assert_not_null(command2, "Expansion returned NULL"); - cr_assert_str_eq((char *)command2->command->data, + 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"); @@ -212,9 +212,9 @@ Test(expand, adjacent_variables) set_var_copy(vars, "VAR1", "hello"); set_var_copy(vars, "VAR2", "world"); - struct ast_command *command2 = expand(ast_command, vars); - cr_assert_not_null(command2, "Expansion returned NULL"); - cr_assert_str_eq((char *)command2->command->data, "echo helloworld", + bool ret = expand(ast_command, vars); + cr_expect(ret, "expansion failed with %s", str); + cr_expect_str_eq((char *)ast_command->command->data, "echo helloworld", "Adjacent variables should expand correctly"); ast_free(&ast); hash_map_free(&vars); @@ -228,9 +228,9 @@ Test(expand, random) struct ast *ast = ast_create_command(list); struct ast_command *ast_command = ast_get_command(ast); - struct ast_command *command2 = expand(ast_command, NULL); - cr_assert_not_null(command2, "Expansion returned NULL"); - int rnd = atoi((char *)command2->command->data); + bool ret = expand(ast_command, NULL); + cr_expect(ret, "expansion failed with %s", str); + int rnd = atoi((char *)ast_command->command->data); cr_assert(rnd >= 0 && rnd <= 32767, "RANDOM variable should expand to a value between 0 and 32767"); ast_free(&ast); @@ -245,9 +245,9 @@ Test(expand, pid) struct ast_command *ast_command = ast_get_command(ast); struct hash_map *vars = vars_init(); - struct ast_command *command2 = expand(ast_command, vars); - cr_assert_not_null(command2, "Expansion returned NULL"); - int pid = atoi((char *)command2->command->data); + bool ret = expand(ast_command, vars); + cr_expect(ret, "expansion failed with %s", str); + int pid = atoi((char *)ast_command->command->data); cr_assert(pid == getpid(), "$$ variable should expand to the pid of the process"); ast_free(&ast); @@ -263,9 +263,9 @@ Test(expand, default_last_exit_code) struct ast_command *ast_command = ast_get_command(ast); struct hash_map *vars = vars_init(); - struct ast_command *command2 = expand(ast_command, vars); - cr_assert_not_null(command2, "Expansion returned NULL"); - int code = atoi((char *)command2->command->data); + bool ret = expand(ast_command, vars); + cr_expect(ret, "expansion failed with %s", str); + int code = atoi((char *)ast_command->command->data); cr_assert(code == 0, "$? variable should expand to the last exit code (default 0)"); ast_free(&ast); diff --git a/tests/wrap.sh b/tests/wrap.sh index a3bd89e..97ebd72 100755 --- a/tests/wrap.sh +++ b/tests/wrap.sh @@ -3,7 +3,7 @@ then export BIN_PATH="$(pwd)/42sh" fi if [ "$COVERAGE" = "yes" ]; #coverage -then ./testsuite && ../tests/functional/run-tests.sh +then (./testsuite || true) && ../tests/functional/run-tests.sh else ../tests/functional/run-tests.sh fi echo bin path: "$BIN_PATH" From 787b1aed35ef083ab2da5d260c001f89d92de190 Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Sat, 24 Jan 2026 16:48:21 +0100 Subject: [PATCH 177/282] fix: random fixes --- src/parser/grammar.c | 27 ++++++++++++++++++++++++++- src/parser/grammar_advanced.c | 5 ----- src/parser/grammar_basic.c | 11 +++++++++-- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/parser/grammar.c b/src/parser/grammar.c index 982a744..be26dfc 100644 --- a/src/parser/grammar.c +++ b/src/parser/grammar.c @@ -166,5 +166,30 @@ bool is_first(struct token token, enum rule rule) struct ast *parse_input(struct lexer_context *ctx) { - return parse_list(ctx); + struct token *token = PEEK_TOKEN(); + + if (token->type == TOKEN_EOF) + return ast_create_list(NULL); + + if (token->type == TOKEN_NEWLINE) + { + POP_TOKEN(); + return ast_create_list(NULL); + } + + struct ast *ast = parse_list(ctx); + if (ast == NULL) + return NULL; + + token = PEEK_TOKEN(); + if (token->type == TOKEN_NEWLINE || token->type == TOKEN_EOF) + { + if (token->type == TOKEN_NEWLINE) + POP_TOKEN(); + return ast; + } + + puts("Syntax error: expected newline or EOF after list"); + ast_free(&ast); + return NULL; } diff --git a/src/parser/grammar_advanced.c b/src/parser/grammar_advanced.c index 1608d5c..aac5742 100644 --- a/src/parser/grammar_advanced.c +++ b/src/parser/grammar_advanced.c @@ -9,11 +9,6 @@ struct ast *parse_redirection(struct lexer_context *ctx) { struct token *token = PEEK_TOKEN(); - if (token->type == TOKEN_IONUMBER) - { - // TODO - } - int io_number = -1; if (token->type == TOKEN_IONUMBER) { diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index 78b3b3d..a0e0868 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -48,7 +48,11 @@ struct ast *parse_list(struct lexer_context *ctx) { current_node = parse_and_or(ctx); if (current_node == NULL) + { + struct ast *tmp = ast_create_list(result_list); + ast_free(&tmp); return NULL; + } result_list = list_append(result_list, current_node); token = PEEK_TOKEN(); } @@ -60,9 +64,11 @@ struct ast *parse_list(struct lexer_context *ctx) struct ast *parse_and_or(struct lexer_context *ctx) { struct ast *result = parse_pipeline(ctx); + if (result == NULL) + return NULL; struct token *token = PEEK_TOKEN(); - if (token->type == TOKEN_AND || token->type == TOKEN_OR) + while (token->type == TOKEN_AND || token->type == TOKEN_OR) { // Set left part @@ -117,7 +123,8 @@ struct ast *parse_command(struct lexer_context *ctx) } else { - return ast_create_void(); // TODO not sure what to do + puts("Syntax error: expected command"); + return NULL; } } From e11eca72dcfe8b06a6391382ec6ca79de6228d15 Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 24 Jan 2026 16:54:26 +0100 Subject: [PATCH 178/282] fix(testsuite): iobackend tests are now useable --- src/Makefile.am | 2 +- tests/unit/io_backend/io_backend.c | 86 ++++++++++++++++-------------- 2 files changed, 46 insertions(+), 42 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 24c27a7..0dad66f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -38,7 +38,7 @@ testsuite_SOURCES = ../tests/unit/lexer/lexer_tests.c \ ../tests/unit/utils/utils_tests.c \ ../tests/unit/expansion/expand.c \ ../tests/unit/expansion/parse_var.c \ - #../tests/unit/io_backend/io_backend.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 diff --git a/tests/unit/io_backend/io_backend.c b/tests/unit/io_backend/io_backend.c index 47e02db..1fa9dfa 100644 --- a/tests/unit/io_backend/io_backend.c +++ b/tests/unit/io_backend/io_backend.c @@ -11,10 +11,10 @@ Test(IO_Backend, init_null) { struct iob_context ctx = { - .iob_mode = IOB_MODE_NULL; - .args = NULL; -}; -int actual = iob_init(ctx); + .mode = IOB_MODE_NULL, + .args = NULL + }; + int actual = iob_init(&ctx); int expected = IOB_ERROR_BAD_ARG; cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); } @@ -23,10 +23,10 @@ Test(IO_Backend, init_stdin) { struct iob_context ctx = { - .iob_mode = IOB_MODE_STDIN; - .args = NULL; -}; -int actual = iob_init(ctx); + .mode = IOB_MODE_STDIN, + .args = NULL + }; + int actual = iob_init(&ctx); int expected = 0; cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); iob_close(); @@ -36,15 +36,16 @@ iob_close(); // Same applies for other tests Test(IO_Backend, init_script) { - char *script_name = "script.tmp" struct iob_context ctx = { - .iob_mode = IOB_MODE_SCRIPT; - .args = script_name; -}; -// Create file -FILE *f = fopen(script_name, "w"); -fclose(f); + char *script_name = "script.tmp"; + struct iob_context ctx = { + .mode = IOB_MODE_SCRIPT, + .args = script_name + }; + // Create file + FILE *f = fopen(script_name, "w"); + fclose(f); -int actual = iob_init(ctx); + int actual = iob_init(&ctx); int expected = 0; cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); iob_close(); @@ -53,11 +54,12 @@ remove(script_name); Test(IO_Backend, init_script_not_a_file) { - char *script_name = "not_a_file.tmp" struct iob_context ctx = { - .iob_mode = IOB_MODE_SCRIPT; - .args = script_name; -}; -int actual = iob_init(ctx); + char *script_name = "not_a_file.tmp"; + struct iob_context ctx = { + .mode = IOB_MODE_SCRIPT, + .args = script_name + }; + int actual = iob_init(&ctx); int expected = IOB_ERROR_CANNOT_OPEN_FILE; cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); } @@ -66,21 +68,22 @@ Test(IO_Backend, init_script_null) { struct iob_context ctx = { - .iob_mode = IOB_MODE_SCRIPT; - .args = NULL; -}; -int actual = iob_init(ctx); + .mode = IOB_MODE_SCRIPT, + .args = NULL + }; + int actual = iob_init(&ctx); int expected = IOB_ERROR_CANNOT_OPEN_FILE; cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); } Test(IO_Backend, init_cmd) { - char *cmd = "iamacommand --yesido" struct iob_context ctx = { - .iob_mode = IOB_MODE_CMD; - .args = cmd; -}; -int actual = iob_init(ctx); + char *cmd = "iamacommand --yesido"; + struct iob_context ctx = { + .mode = IOB_MODE_CMD, + .args = cmd + }; + int actual = iob_init(&ctx); int expected = 0; cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); iob_close(); @@ -90,23 +93,24 @@ Test(IO_Backend, init_cmd_null) { struct iob_context ctx = { - .iob_mode = IOB_MODE_CMD; - .args = NULL; -}; -int actual = iob_init(ctx); + .mode = IOB_MODE_CMD, + .args = NULL + }; + int actual = iob_init(&ctx); int expected = IOB_ERROR_BAD_ARG; cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); } Test(IO_Backend, init_already_init) { - char *cmd = "iamacommand --yesido" struct iob_context ctx = { - .iob_mode = IOB_MODE_CMD; - .args = cmd; -}; -iob_init(ctx); -int actual = iob_init(ctx); -int expected = IOB_ERROR_ALREADY_INITIALIZED; + char *cmd = "iamacommand --yesido"; + struct iob_context ctx = { + .mode = IOB_MODE_CMD, + .args = cmd + }; + iob_init(&ctx); + int actual = iob_init(&ctx); + int expected = IOB_ERROR_MODULE_ALREADY_INITIALIZED; cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); iob_close(); } From 32d38c0d13b9f5d6dc9791f3027a684c710840ee Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Sat, 24 Jan 2026 16:02:37 +0000 Subject: [PATCH 179/282] feat(vars): $@ and $* init --- src/utils/args/args.c | 65 ++++++++++++++++++++++++++++++++++++---- src/utils/lists/lists.h | 5 ++++ src/utils/lists/lists3.c | 10 +++++++ 3 files changed, 74 insertions(+), 6 deletions(-) diff --git a/src/utils/args/args.c b/src/utils/args/args.c index 89a00a3..a957382 100644 --- a/src/utils/args/args.c +++ b/src/utils/args/args.c @@ -1,12 +1,67 @@ +#define _POSIX_C_SOURCE 200809L #include "./args.h" #include #include +#include #include +#include "../lists/lists.h" #include "../string_utils/string_utils.h" #include "../vars/vars.h" +static void strlen_acc(void *acc, void *data) +{ + size_t *len = (size_t *)acc; + char *str = (char *)data; + *len += strlen(str); +} + +static char *concat_list_str(struct list *list) +{ + char *res = NULL; + size_t total_len = list_length(list) + 1; // + spaces + null terminator + + list_fold(list, &total_len, strlen_acc); + + res = malloc(total_len); + if (res != NULL) + { + res[0] = 0; + for (struct list *it = list; it != NULL; it = it->next) + { + strcat(res, (char *)it->data); + if (it->next != NULL) + strcat(res, " "); + } + res[total_len - 1] = 0; + } + + return res; +} + +static void args_in_var(struct hash_map *vars, struct list *args_list) +{ + int arg_index = 1; + char index_str[11]; + + for (struct list *it = args_list; it != NULL; it = it->next) + { + int_to_str(arg_index++, index_str); + set_var_copy(vars, index_str, (char *)it->data); + } + + char *concated_args = concat_list_str(args_list); + set_var_copy(vars, "*", concated_args); + + char *key = strdup("@"); + set_var(vars, key, concated_args, NULL); + // key and concated_args consumed by hash_map + + int_to_str(arg_index - 1, index_str); + set_var_copy(vars, "#", index_str); +} + int args_handler(int argc, char **argv, struct args_options *options, struct hash_map *vars) { @@ -15,8 +70,7 @@ int args_handler(int argc, char **argv, struct args_options *options, options->pretty_print = false; options->verbose = false; - int arg_index = 1; - char index_str[11]; + struct list *args_list = NULL; set_var_copy(vars, "0", argv[0]); @@ -61,14 +115,13 @@ int args_handler(int argc, char **argv, struct args_options *options, else { // All remaining arguments are treated as additional arguments - int_to_str(arg_index++, index_str); - set_var_copy(vars, index_str, argv[i]); + args_list = list_append(args_list, argv[i]); continue; } } - int_to_str(arg_index - 1, index_str); - set_var_copy(vars, "#", index_str); + args_in_var(vars, 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 a0fedf3..9b5d38e 100644 --- a/src/utils/lists/lists.h +++ b/src/utils/lists/lists.h @@ -107,4 +107,9 @@ int list_find(struct list *list, void *value); // struct list *list_split(struct list *list, size_t index); // END PROTO list_split +/** + * @brief: Folds the list from left to right using func and an accumulator. + */ +void list_fold(struct list *list, void *acc, void (*func)(void *, void *)); + #endif /* ! LISTS_H */ diff --git a/src/utils/lists/lists3.c b/src/utils/lists/lists3.c index e9cc545..c18c7be 100644 --- a/src/utils/lists/lists3.c +++ b/src/utils/lists/lists3.c @@ -115,3 +115,13 @@ void list_deep_destroy(struct list *l) elt = next_elt; } } + +void list_fold(struct list *list, void *acc, void (*func)(void *, void *)) +{ + struct list *elt = list; + while (elt != NULL) + { + func(acc, elt->data); + elt = elt->next; + } +} From 83935e9180418d32701a45f5e35cd095b5737663 Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Sat, 24 Jan 2026 17:19:03 +0100 Subject: [PATCH 180/282] feat: vla les tests --- tests/functional/run-tests.sh | 185 +++++++++++++++++++++++++++++++++- 1 file changed, 181 insertions(+), 4 deletions(-) diff --git a/tests/functional/run-tests.sh b/tests/functional/run-tests.sh index 0da9f15..17ff162 100755 --- a/tests/functional/run-tests.sh +++ b/tests/functional/run-tests.sh @@ -257,15 +257,192 @@ summarize() { echo -e "\n\n""===$BGreen TestsuitatorX Ultra Pro Max+ 365 Premium Gris Sidéral" "\n\n"$Color_Off + + +# + +echo -e "\n$BBlue=== Builtins ===$Color_Off" # echo -test_str "Hello" "echo Hello there" -test_str "Hello;" "echo Hello there;" +test_str "Hello" "echo Hello" +test_str "Hello there" "echo Hello there;" +test_str "Hello there;" "echo Hello there;" +test_str "Hello; there;" "echo Hello; echo there;" test_str "'Hello'" "echo 'Hello there'" test_str "Hello;Hello" "echo Hello; echo Wesh attends quoi; echo pouquoi je suis une ligne en dessous" +test_str "Echo -n" "echo -n Hello" +test_str "Echo -e" "echo -e 'Hello\nThere'" +test_str "Empty echo" "echo" +test_str "Spaced echo" " echo spaced " -# programs +test_str "Exit 0" "exit 0" +test_str "Exit 1" "exit 1" + +test_str "True" "true" +test_str "False" "false" + +test_str "cd basic" "cd /tmp; pwd" +test_str "cd maison" "cd; pwd" + +test_str "Alias basic" "alias foo=echo; foo bar" + +test_str "cat $0" "alias foo=echo; foo bar" + + +echo -e "\n$BBlue=== Programs ===$Color_Off" test_str "LS" "ls" -test_str "Le SS" "lss" +test_str "Wrong ls" "sl" +test_str "IPA ma gueule" "ip a" +test_str "Wrong ls" "sl --bachibouzouk" +test_str "ls a b c" "ls a b c" +test_str "ls -a --best" "ls -a --best" +test_str "ls -a --help" "ls -a --help" + + +echo -e "\n$BBlue=== Quotes ===$Color_Off" +test_str "Single quotes" "echo 'Single Quote'" +test_str "Double quotes" "echo \"Double Quote\"" +test_str "Mixed quotes 1" "echo \"Mixed 'Quotes'\"" +test_str "Mixed quotes 2" "echo 'Mixed \"Quotes\"'" +test_str "Escaped double quote" "echo \"Escaped \\\"Quote\\\"\"" +test_str "Variable in double quotes" "VAR=val; echo \"Variable \$VAR\"" +test_str "Variable in single quotes" "VAR=val; echo 'Variable \$VAR'" +test_str "Backslash in double quotes" "echo \"Backslash \\\\\"" +test_str "Newline in string" "echo \"New\nline\"" +test_str "Empty quotes" "echo '' \"\"" +test_str "Concatenated quotes" "echo 'a'\"b\"'c'" + + +echo -e "\n$BBlue=== Comments ===$Color_Off" +test_str "Hello commentaire" "echo Hello # Commentaire" +test_str "Comment only" "# Comment only" +test_str "Comment after space" "echo foo #bar" +test_str "Hash inside word" "echo foo#bar" +test_str "Comment with special chars" "# echo 'hidden' $PATH" + + +echo -e "\n$BBlue=== Pipelines ===$Color_Off" +test_str "Simple pipe" "echo Hello | cat" +test_str "Double pipe" "echo Hello | rev | rev" # Pas mal non ? c'est frenssé +test_str "Pipe with grep" "echo 'a\nb\nc' | grep b" +test_str "Pipe exit code (là c'est dur)" "true | false | true" +test_str "Pipe sequence" "echo a | echo b" +test_str "Pipe with chiottes" "ls | wc -l" + + +echo -e "\n$BBlue=== Redirections ===$Color_Off" +test_str "Redirect output" "echo hello > /tmp/test_redir; cat /tmp/test_redir; rm /tmp/test_redir" +test_str "Append output" "echo Hello > /tmp/test_redir; echo World >> /tmp/test_redir; cat /tmp/test_redir; rm /tmp/test_redir" +test_str "Redirect input" "echo Hello > /tmp/test_in; cat < /tmp/test_in; rm /tmp/test_in" +test_str "Redirect stderr" "echo Error >&2" +test_str "Redirect to null" "echo Hello > /dev/null" +test_str "Redirect both" "ls > /dev/null 2> /dev/null" +test_str "Redirect fd" "echo foo 2>&1" +test_str "Clobbering" "echo foo > file; echo bar > file; cat file; rm file" +test_str "Pipe and redirect" "echo foo | cat > file; cat file; rm file" +test_str "Heredoc basic" "cat << EOF\nhello\nEOF" + + +echo -e "\n$BBlue=== And/Or ===$Color_Off" +test_str "AND true" "true && echo Oui" +test_str "AND false" "false && echo Non" +test_str "OR true" "true || echo Non" +test_str "OR false" "false || echo Oui" +test_str "AND OR mixed 1" "true && false || echo Oui" +test_str "AND OR mixed 2" "false || true && echo Oui" +test_str "Sequence AND" "echo a && echo b" +test_str "Sequence OR" "echo a || echo b" +test_str "Negation true" "! true" +test_str "Negation false" "! false" + + +echo -e "\n$BBlue=== If ===$Color_Off" +test_str "If true" "if true; then echo Yes; fi" +test_str "If false else" "if false; then echo No; else echo Yes; fi" +test_str "If elif" "if false; then echo No; elif true; then echo Yes; fi" +test_str "Nested if" "if true; then if true; then echo Nested; fi; fi" +test_str "If multiple commands" "if true; then echo a; echo b; fi" +test_str "If complex condition" "if true && true; then echo Yes; fi" +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" +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" +test_str "For loop glob" "for i in *; do echo \$i; done" +test_str "For loop break" "for i in 1 2 3; do break; done" +test_str "Azy continue la con de ta mère" "for i in 1 2 3; do continue; done" +test_str "For loop empty" "for i in; do echo \$i; done" +test_str "For loop variable" "VAR='a b'; for i in \$VAR; do echo \$i; done" + + +echo -e "\n$BBlue=== Case ===$Color_Off" +test_str "Case simple" "case a in a) echo Yes;; esac" +test_str "Case basique" "case z in a) echo No;; *) echo Default;; esac" +test_str "Case multiple patterns" "case a in a|b) echo Yes;; esac" +test_str "Case no match (c pcq t'es moche)" "case z in a) echo No;; esac" +test_str "Case with variable" "v=foo; case \$v in foo) echo Yes;; esac" +test_str "Case nested" "case a in a) case b in b) echo Nested;; esac;; esac" + + +echo -e "\n$BBlue=== Variables ===$Color_Off" +test_str "Set and get" "var=value; echo \$var" +test_str "Braces" "var=value; echo \${var}" +test_str "Multi-word value" "var='a b'; echo \$var" +test_str "Unset" "var=value; unset var; echo \$var" +test_str "Export" "export VAR=val; env | grep VAR" +test_str "Assignment return" "a=1" +test_str "Multiple assignment (ouais askip c possible)" "a=1 b=2; echo \$a \$b" +test_str "Default value" "unset v; echo \${v:-default}" +test_str "Assign default" "unset v; echo \${v:=default}; echo \$v" +test_str "Alternative value" "v=val; echo \${v:+alt}" +test_str "Use default if unset" "echo \${unset_var-default}" + +test_str "\$@" "echo \$@" +test_str "\$*" "echo \$*" +test_str "\$?" "echo \$?" +test_str "\$$" "echo \$$" +test_str "\$1" "echo \$1" +test_str "\$2" "echo \$2" +test_str "\${10}" "echo \${10}" +test_str "\$#" "echo \$#" +test_str "\$RANDOM" "echo \$RANDOM" +test_str "\$UID" "echo \$UID" +test_str "\$OLDPWD" "echo \$OLDPWD" +test_str "\$PWD" "echo \$PWD" +test_str "\$IFS" "echo \$IFS" + +test_str "Default exit status" "echo \$?" +test_str "Exit status" "true; echo \$?" +test_str "PID" "echo \$\$" +test_str "Arg count" "echo \$#" + + +echo -e "\n$BBlue=== Arithmetic expansions de fou furieux ===$Color_Off" +test_str "Arithmetic add" "echo \$((1 + 1))" +test_str "Arithmetic mul" "echo \$((2 * 3))" +test_str "Arithmetic div" "echo \$((10 / 2))" +test_str "Arithmetic nested" "echo \$(( (1+2) * 3 ))" +test_str "Arithmetic mod" "echo \$(( 5 % 2 ))" +test_str "Arithmetic var" "var=1; echo \$(( var + 1 ))" +test_str "Command subst" "echo \$(echo command)" +test_str "Backticks" "echo \`echo backticks\`" +test_str "Tilde" "echo ~" +test_str "Length" "v=abc; echo \${#v}" + + +echo -e "\n$BBlue=== Subshells du démon ===$Color_Off" +test_str "Subshell basic" "(echo a; echo b)" +test_str "Subshell isolation" "var=1; (var=2; echo \$var); echo \$var" +test_str "Subshell exit" "(exit 1); echo \$?" +test_str "Grouping basic" "{ echo a; echo b; }" +test_str "Grouping side effect" "var=1; { var=2; }; echo \$var" +test_str "Nested subshells" "( ( echo nested ) )" +test_str "Subshell redirect" "(echo a) > /tmp/sub; cat /tmp/sub; rm /tmp/sub" +test_str "Group redirect" "{ echo a; } > /tmp/grp; cat /tmp/grp; rm /tmp/grp" summarize From 3b538cbb79caa77d9380949b97e4765ce52b09ee Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 24 Jan 2026 17:21:33 +0100 Subject: [PATCH 181/282] fix(lexer): edge cases where 'f' can be lexed as 'fi' for example --- src/lexer/lexer_utils.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/lexer/lexer_utils.c b/src/lexer/lexer_utils.c index e4a9bab..833b274 100644 --- a/src/lexer/lexer_utils.c +++ b/src/lexer/lexer_utils.c @@ -60,31 +60,31 @@ static void set_token_keyword(struct token *tok, char *begin, ssize_t size) { if (tok->type != TOKEN_NULL || size == 0) return; - if (strncmp(begin, "if", size) == 0) + if (strncmp(begin, "if", size) == 0 && size == 2) { tok->type = TOKEN_IF; } - else if (strncmp(begin, "fi", size) == 0) + else if (strncmp(begin, "fi", size) == 0 && size == 2) { tok->type = TOKEN_FI; } - else if (strncmp(begin, "then", size) == 0) + else if (strncmp(begin, "then", size) == 0 && size == 4) { tok->type = TOKEN_THEN; } - else if (strncmp(begin, "else", size) == 0) + else if (strncmp(begin, "else", size) == 0 && size == 4) { tok->type = TOKEN_ELSE; } - else if (strncmp(begin, "elif", size) == 0) + else if (strncmp(begin, "elif", size) == 0 && size == 4) { tok->type = TOKEN_ELIF; } - else if (strncmp(begin, "&&", size) == 0) + else if (strncmp(begin, "&&", size) == 0 && size == 2) { tok->type = TOKEN_AND; } - else if (strncmp(begin, "||", size) == 0) + else if (strncmp(begin, "||", size) == 0 && size == 2) { tok->type = TOKEN_OR; } From 9276e1a8a5dc2e870418f886a52d09cad13d23d1 Mon Sep 17 00:00:00 2001 From: Jean <47366872+jean-voila@users.noreply.github.com> Date: Sat, 24 Jan 2026 17:35:06 +0100 Subject: [PATCH 182/282] Fixed the length of the execution function --- .gitignore | 4 + src/execution/Makefile.am | 4 +- src/execution/execution.c | 316 ++---------------------------- src/execution/execution_helpers.c | 216 ++++++++++++++++++++ src/execution/execution_helpers.h | 13 ++ src/utils/ast/ast.c | 2 +- 6 files changed, 251 insertions(+), 304 deletions(-) create mode 100644 src/execution/execution_helpers.c create mode 100644 src/execution/execution_helpers.h diff --git a/.gitignore b/.gitignore index 09e5b39..f9b5175 100644 --- a/.gitignore +++ b/.gitignore @@ -178,3 +178,7 @@ m4/lt~obsolete.m4 Makefile *.svg + + +.venv/ +*.py \ No newline at end of file diff --git a/src/execution/Makefile.am b/src/execution/Makefile.am index 5a71cbb..f061dfc 100644 --- a/src/execution/Makefile.am +++ b/src/execution/Makefile.am @@ -2,7 +2,9 @@ lib_LIBRARIES = libexecution.a libexecution_a_SOURCES = \ execution.c \ - execution.h + execution.h \ + execution_helpers.c \ + execution_helpers.h libexecution_a_CPPFLAGS = -I$(top_srcdir)/src diff --git a/src/execution/execution.c b/src/execution/execution.c index 4522bcc..82fcacf 100644 --- a/src/execution/execution.c +++ b/src/execution/execution.c @@ -1,3 +1,4 @@ +#define _POSIX_C_SOURCE 200809L #include "execution.h" #include @@ -23,100 +24,6 @@ * @return char** Array of command arguments suitable for execvp. Terminated by * NULL */ -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; -} - -// --- Builtins --- - -static int builtin_echo(char **argv) -{ - bool newline = true; - int i = 1; - - if (argv[1] && strcmp(argv[1], "-n") == 0) - { - newline = false; - i++; - } - - for (; argv[i]; i++) - { - printf("%s", argv[i]); - if (argv[i + 1]) - printf(" "); - } - if (newline) - printf("\n"); - - fflush(stdout); - return 0; -} - -static int builtin_true(char **argv) -{ - (void)argv; - return 0; -} - -static int builtin_false(char **argv) -{ - (void)argv; - return 1; -} - -static int builtin_exit(char **argv) -{ - int exit_val = 0; - if (argv[1]) - exit_val = atoi(argv[1]); - exit(exit_val); - return exit_val; -} - -static int builtin_cd(char **argv) -{ - const char *path = argv[1]; - if (!path) - { - path = getenv("HOME"); - if (!path) - { - fprintf(stderr, "cd: HOME not set\n"); - return 1; - } - } - if (chdir(path) != 0) - { - perror("cd"); - return 1; - } - return 0; -} /** * @brief Tries to execute a builtin command if the command matches a builtin @@ -124,24 +31,6 @@ 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) -{ - if (!argv || !argv[0]) - return 0; - - if (strcmp(argv[0], "echo") == 0) - return builtin_echo(argv); - if (strcmp(argv[0], "true") == 0) - return builtin_true(argv); - if (strcmp(argv[0], "false") == 0) - return builtin_false(argv); - if (strcmp(argv[0], "exit") == 0) - return builtin_exit(argv); - if (strcmp(argv[0], "cd") == 0) - return builtin_cd(argv); - - return -1; -} // --- Execution Core --- @@ -151,212 +40,35 @@ static int try_builtin(char **argv) * @param command The command to execute * @return int The exit status of the command */ -static int exec_command(struct ast_command *command) -{ - if (!command || !(command->command)) - { - return 1; - } - char **argv = list_to_argv(command->command); +// Refactored: delegates to helpers in execution_helpers.c +#include "execution_helpers.h" - if (!argv || !(argv[0])) - { - free(argv); - return 0; - } - - int builtin_ret = try_builtin(argv); - if (builtin_ret != -1) - { - free(argv); - return builtin_ret; - } - - pid_t pid = fork(); - - if (pid < 0) - { - perror("fork"); - free(argv); - return 1; - } - - if (pid == 0) - { - execvp(argv[0], argv); - perror("execvp"); - _exit(127); - } - - int status = 0; - waitpid(pid, &status, 0); - free(argv); - - if (WIFEXITED(status)) - { - return WEXITSTATUS(status); - } - - return 1; -} - -/** - * @brief Executes the AST returned by the parser - * - * @param ast The AST to execute - * @return int The exit status of the last executed command. - */ int execution(struct ast *ast, struct hash_map *vars) { if (!ast) - { return 0; - } switch (ast->type) { case AST_VOID: - case AST_END: { + case AST_END: return 0; - } case AST_CMD: { struct ast_command *command = ast_get_command(ast); if (!expand(command, vars)) fprintf(stderr, "Error: Variable expansion failed\n"); - return exec_command(command); + return exec_ast_command(command, vars); } - case AST_IF: { - struct ast_if *if_node = ast_get_if(ast); - int cond = execution(if_node->condition, vars); - if (cond == 0) // True - { - return execution(if_node->then_clause, vars); - } - else // False - { - return execution(if_node->else_clause, vars); - } - } - case AST_LIST: { - struct ast_list *list_node = ast_get_list(ast); - 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; - } - case AST_AND_OR: { - struct ast_and_or *ao_node = ast_get_and_or(ast); - 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 // OR - { - if (left_ret != 0) - return execution(ao_node->right, vars); - return left_ret; - } - } - case AST_REDIR: { - struct ast_redir *redir = ast_get_redir(ast); - - int fd_target = redir->io_number; - if (fd_target == -1) - { - if (redir->type == AST_REDIR_TYPE_LESS - || redir->type == AST_REDIR_TYPE_DLESS - || redir->type == AST_REDIR_TYPE_LESSAND) - fd_target = 0; - else - fd_target = 1; - } - - int saved_fd = dup(fd_target); - - int new_fd = -1; - int flags = 0; - int mode = 0644; - - if (redir->type == AST_REDIR_TYPE_GREAT - || redir->type == AST_REDIR_TYPE_CLOBBER) - { - flags = O_WRONLY | O_CREAT | O_TRUNC; - new_fd = open(redir->filename, flags, mode); - } - else if (redir->type == AST_REDIR_TYPE_DGREAT) - { - flags = O_WRONLY | O_CREAT | O_APPEND; - new_fd = open(redir->filename, flags, mode); - } - else if (redir->type == AST_REDIR_TYPE_LESS) - { - flags = O_RDONLY; - new_fd = open(redir->filename, flags); - } - else if (redir->type == AST_REDIR_TYPE_GREATAND - || redir->type == AST_REDIR_TYPE_LESSAND) - { - // Simple fd duplication - new_fd = atoi(redir->filename); - // Verify new_fd is valid? dup2 will check. - if (dup2(new_fd, fd_target) == -1) - { - perror("dup2"); - if (saved_fd != -1) - close(saved_fd); - return 1; - } - new_fd = -2; // Mark as "already duped" - } - - if (new_fd == -1) - { - perror("open"); - if (saved_fd != -1) - close(saved_fd); - return 1; - } - - if (new_fd != -2) - { - if (dup2(new_fd, fd_target) == -1) - { - perror("dup2"); - close(new_fd); - if (saved_fd != -1) - close(saved_fd); - return 1; - } - close(new_fd); - } - - int ret = execution(redir->child, vars); - - if (saved_fd != -1) - { - dup2(saved_fd, fd_target); - close(saved_fd); - } - else - { - close(fd_target); - } - - return ret; - } - default: { + case AST_IF: + return exec_ast_if(ast_get_if(ast), vars); + case AST_LIST: + 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 new file mode 100644 index 0000000..8041a3c --- /dev/null +++ b/src/execution/execution_helpers.c @@ -0,0 +1,216 @@ +#define _POSIX_C_SOURCE 200809L +#include "execution_helpers.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "../expansion/expansion.h" +#include "../utils/ast/ast.h" +#include "../utils/hash_map/hash_map.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); + +int exec_ast_command(struct ast_command *command, struct hash_map *vars) +{ + (void)vars; + if (!command || !(command->command)) + return 1; + char **argv = list_to_argv(command->command); + if (!argv || !(argv[0])) + { + free(argv); + return 0; + } + int builtin_ret = try_builtin(argv); + if (builtin_ret != -1) + { + free(argv); + return builtin_ret; + } + pid_t pid = fork(); + if (pid < 0) + { + perror("fork"); + free(argv); + return 1; + } + if (pid == 0) + { + execvp(argv[0], argv); + perror("execvp"); + _exit(127); + } + int status = 0; + waitpid(pid, &status, 0); + free(argv); + 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_DLESS + || 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; + if (redir->type == AST_REDIR_TYPE_GREAT + || redir->type == AST_REDIR_TYPE_CLOBBER) + { + *flags = O_WRONLY | O_CREAT | O_TRUNC; + return open(redir->filename, *flags, *mode); + } + else if (redir->type == AST_REDIR_TYPE_DGREAT) + { + *flags = O_WRONLY | O_CREAT | O_APPEND; + return open(redir->filename, *flags, *mode); + } + else if (redir->type == AST_REDIR_TYPE_LESS) + { + *flags = O_RDONLY; + return open(redir->filename, *flags); + } + 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; +} + +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; +} + +// Dummy try_builtin for linking +static int try_builtin(char **argv) +{ + (void)argv; + return -1; +} diff --git a/src/execution/execution_helpers.h b/src/execution/execution_helpers.h new file mode 100644 index 0000000..ebac0cb --- /dev/null +++ b/src/execution/execution_helpers.h @@ -0,0 +1,13 @@ +#ifndef EXECUTION_HELPERS_H +#define EXECUTION_HELPERS_H + +#include "../utils/ast/ast.h" +#include "../utils/hash_map/hash_map.h" + +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.c b/src/utils/ast/ast.c index e336a73..ae042ee 100644 --- a/src/utils/ast/ast.c +++ b/src/utils/ast/ast.c @@ -1,5 +1,5 @@ -#define _POSIX_C_SOURCE 12344 +#define _POSIX_C_SOURCE 200809L #include "ast.h" #include From 0c238635800305b9480643357b116df9e9bad574 Mon Sep 17 00:00:00 2001 From: matteo Date: Sat, 24 Jan 2026 17:45:17 +0100 Subject: [PATCH 183/282] fix(functional-testsuite): timeout shorter, 2s => 0.2s --- tests/functional/run-tests.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/functional/run-tests.sh b/tests/functional/run-tests.sh index 17ff162..f2cbda1 100755 --- a/tests/functional/run-tests.sh +++ b/tests/functional/run-tests.sh @@ -157,7 +157,7 @@ test_str() { # Arg echo -e -n $Blue "= [ARG] " $Color_Off - timeout 2 $executable -c "$2" &> $output + timeout 0.2 $executable -c "$2" &> $output actual_code=$? $ref_executable -c "$2" &> $ref_output ref_code=$? @@ -166,7 +166,7 @@ test_str() { # Script echo -e -n $Blue "= [SCRIPT]" $Color_Off - timeout 2 $executable "$tmp_script" &> $output + timeout 0.2 $executable "$tmp_script" &> $output actual_code=$? $ref_executable "$tmp_script" &> $ref_output ref_code=$? @@ -175,7 +175,7 @@ test_str() { # Stdin echo -e -n $Blue "= [STDIN] " $Color_Off - timeout 2 $executable < "$tmp_script" &> $output + timeout 0.2 $executable < "$tmp_script" &> $output actual_code=$? $ref_executable < "$tmp_script" &> $ref_output ref_code=$? @@ -204,7 +204,7 @@ test_script() { # Script echo -e -n "= [SCRIPT] " - timeout 2 $executable "$tmp_script" &> $output + timeout 0.2 $executable "$tmp_script" &> $output actual_code=$? $ref_executable "$tmp_script" &> $ref_output ref_code=$? @@ -213,7 +213,7 @@ test_script() { # Stdin echo -e -n "= [STDIN] " - timeout 2 $executable < "$tmp_script" &> $output + timeout 0.2 $executable < "$tmp_script" &> $output actual_code=$? $ref_executable < "$2" &> $ref_output ref_code=$? From 69bdecbb6227d7590468fbcfe1d6a3e2fb7edeef Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Sun, 25 Jan 2026 16:50:22 +0100 Subject: [PATCH 184/282] fix: fixed testsuite reporting false failures --- tests/functional/run-tests.sh | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/tests/functional/run-tests.sh b/tests/functional/run-tests.sh index f2cbda1..93b1bf3 100755 --- a/tests/functional/run-tests.sh +++ b/tests/functional/run-tests.sh @@ -125,7 +125,7 @@ check_result() { echo -e ' ' "Expected code $ref_code but got $actual_code" test_failed=1 # Check output - elif diff $output $ref_output > /dev/null; then + elif [[ $(diff $output $ref_output > /dev/null) -ne 0 ]]; then echo -e $BRed "FAILED" $Color_Off echo -e ' ' "on '$command'" echo -e ' ' "Output is not the one expected" @@ -226,9 +226,13 @@ test_script() { summarize() { + # Compute statistics (( passed_tests = $total_tests - $errors_count )) (( tests_percentage = 100 * $passed_tests / $total_tests )) + # Adapt color depending on results + + # Percentage if [[ tests_percentage -gt 80 ]]; then coverage_color=$BGreen elif [[ tests_percentage -gt 50 ]]; then @@ -238,12 +242,19 @@ summarize() { else coverage_color=$BRed fi + # Timeouts + if [[ $timeouts_count -eq 0 ]]; then + timeouts_color=$BGreen + else + timeouts_color=$BRed + fi + # 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 " Got $BRed$timeouts_count timeout(s)$Color_Off" - if [ "$OUTPUT_FILE" != "" ]; - then echo $tests_percentage > "$OUTPUT_FILE"; + echo -e " Got $timeouts_color$timeouts_count timeout(s)$Color_Off" + if [ "$OUTPUT_FILE" != "" ]; then + echo $tests_percentage > "$OUTPUT_FILE"; fi echo From c8c72085cbea87abea48894bbc671f49a98a56de Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Mon, 26 Jan 2026 17:21:34 +0100 Subject: [PATCH 185/282] fix(testsuite): files .sh now have the correct shebang --- tests/functional/run-tests.sh | 2 +- tests/wrap.sh | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/functional/run-tests.sh b/tests/functional/run-tests.sh index f2cbda1..6bb6ee1 100755 --- a/tests/functional/run-tests.sh +++ b/tests/functional/run-tests.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh ################### diff --git a/tests/wrap.sh b/tests/wrap.sh index 97ebd72..58519d3 100755 --- a/tests/wrap.sh +++ b/tests/wrap.sh @@ -1,3 +1,5 @@ +#!/bin/sh + if [ "$BIN_PATH" = "" ]; then export BIN_PATH="$(pwd)/42sh" fi From 351562bb8928c6100d3db7f918672f0ce36fac8d Mon Sep 17 00:00:00 2001 From: Jean <47366872+jean-voila@users.noreply.github.com> Date: Mon, 26 Jan 2026 18:35:08 +0100 Subject: [PATCH 186/282] feat: flemme scripts for building and testing --- README.md | 4 +++- build_flemme.sh | 47 +++++++++++++++++++++++++++++++++++++++++++++++ check_flemme.sh | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 1 deletion(-) create mode 100755 build_flemme.sh create mode 100755 check_flemme.sh diff --git a/README.md b/README.md index be08206..d3686bf 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ then: #### 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` @@ -26,7 +28,7 @@ then: ## Authors - Matteo Flebus -- Jean Hérail +- Jean Herail - William Valenduc - Guillem George diff --git a/build_flemme.sh b/build_flemme.sh new file mode 100755 index 0000000..e16b9f0 --- /dev/null +++ b/build_flemme.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash + +# build_only.sh - Automates build for 42sh with pretty output (no tests) + +set -e + +# Colors +GREEN="\033[1;32m" +RED="\033[1;31m" +YELLOW="\033[1;33m" +RESET="\033[0m" + +VERBOSE=0 +if [[ "$1" == "--verbose" ]]; then + VERBOSE=1 +fi + +run_cmd() { + local desc="$1" + shift + if [[ $VERBOSE -eq 1 ]]; then + echo -e "${YELLOW}${desc}...${RESET}" + "$@" + else + echo -ne "${YELLOW}${desc}...${RESET}" + "$@" &> /dev/null && echo -e " ${GREEN}done${RESET}" || { echo -e " ${RED}failed${RESET}"; exit 1; } + fi +} + +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 -fsanitize=address' +fi + +run_cmd "Cleaning build" make clean + +if [[ $VERBOSE -eq 1 ]]; then + echo -e "${YELLOW}Building...${RESET}" + make +else + echo -ne "${YELLOW}Building...${RESET}" + echo + make +fi diff --git a/check_flemme.sh b/check_flemme.sh new file mode 100755 index 0000000..0543001 --- /dev/null +++ b/check_flemme.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +# build_and_test.sh - Automates build and test for 42sh with pretty output + +set -e + +# Colors +GREEN="\033[1;32m" +RED="\033[1;31m" +YELLOW="\033[1;33m" +RESET="\033[0m" + +VERBOSE=0 +if [[ "$1" == "--verbose" ]]; then + VERBOSE=1 +fi + +run_cmd() { + local desc="$1" + shift + if [[ $VERBOSE -eq 1 ]]; then + echo -e "${YELLOW}${desc}...${RESET}" + "$@" + else + echo -ne "${YELLOW}${desc}...${RESET}" + "$@" &> /dev/null && echo -e " ${GREEN}done${RESET}" || { echo -e " ${RED}failed${RESET}"; exit 1; } + fi +} + +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 -fsanitize=address' +fi + +run_cmd "Cleaning build" make clean +if [[ $VERBOSE -eq 1 ]]; then + echo -e "${YELLOW}Running tests...${RESET}" + make check +else + echo -ne "${YELLOW}Running tests...${RESET}" + echo + make check +fi From 23f681516254350a281ace91fe478d71d23704a3 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Mon, 26 Jan 2026 18:35:48 +0100 Subject: [PATCH 187/282] feat(parser): redirections types handled in grammar init --- src/parser/grammar.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/parser/grammar.c b/src/parser/grammar.c index be26dfc..02098e0 100644 --- a/src/parser/grammar.c +++ b/src/parser/grammar.c @@ -73,6 +73,20 @@ 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_PIPE); +} + // === Functions bool grammar_init(void) @@ -114,14 +128,11 @@ bool grammar_init(void) add_first(RULE_ELSE_CLAUSE, TOKEN_ELIF); add_first(RULE_ELEMENT, TOKEN_WORD); - add_first(RULE_ELEMENT, TOKEN_IONUMBER); - add_first(RULE_ELEMENT, TOKEN_REDIRECTION); + add_first_redir(RULE_ELEMENT); - add_first(RULE_REDIRECTION, TOKEN_IONUMBER); - add_first(RULE_REDIRECTION, TOKEN_REDIRECTION); + add_first_redir(RULE_REDIRECTION); - add_first(RULE_PREFIX, TOKEN_IONUMBER); - add_first(RULE_PREFIX, TOKEN_REDIRECTION); + add_first_redir(RULE_PREFIX); return true; } From 666517e3c12697de85a2caa97b0eda630055c6a0 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Mon, 26 Jan 2026 19:00:20 +0100 Subject: [PATCH 188/282] feat(parser): implementing all redirection types -- WIP --- src/parser/grammar.c | 22 ++++++++++++++++++++++ src/parser/grammar.h | 4 ++++ src/parser/grammar_basic.c | 4 +++- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/parser/grammar.c b/src/parser/grammar.c index 02098e0..c1457eb 100644 --- a/src/parser/grammar.c +++ b/src/parser/grammar.c @@ -5,6 +5,7 @@ #include #include "grammar_basic.h" +#include "../lexer/lexer.h" // === Static variables @@ -84,11 +85,30 @@ static void add_first_redir(enum rule rule) 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 is_token_redir(struct token *token) +{ + switch (token->type) + { + case TOKEN_REDIR_LEFT: + case TOKEN_REDIR_RIGHT: + case TOKEN_REDIR_LEFT_RIGHT: + case TOKEN_REDIR_DOUBLE_RIGHT: + case TOKEN_REDIR_LEFT_AMP: + case TOKEN_REDIR_RIGHT_AMP: + case TOKEN_REDIR_RIGHT_PIPE: + return true; + default: + return false; + } +} + + bool grammar_init(void) { // Initialize the firsts map @@ -196,7 +216,9 @@ struct ast *parse_input(struct lexer_context *ctx) if (token->type == TOKEN_NEWLINE || token->type == TOKEN_EOF) { if (token->type == TOKEN_NEWLINE) + { POP_TOKEN(); + } return ast; } diff --git a/src/parser/grammar.h b/src/parser/grammar.h index 5721ee7..80961f1 100644 --- a/src/parser/grammar.h +++ b/src/parser/grammar.h @@ -50,6 +50,10 @@ struct firsts_list { // === Functions +/* @brief: returns true if token has a type redirection (not pipe or IONUMBER). + */ +bool is_token_redir(struct token *token); + /* * @brief Initializes the grammar submodule * @return PARSER_INIT_SUCCESS on success PARSER_INIT_ERROR on error diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index a0e0868..d99ba6d 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -1,3 +1,5 @@ +#define _POSIX_C_SOURCE 200809L + #include "grammar_basic.h" #include @@ -198,7 +200,7 @@ struct ast *parse_element(struct lexer_context *ctx) token = POP_TOKEN(); return ast_create_word(token->data); } - else if (token->type == TOKEN_IONUMBER || token->type == TOKEN_REDIRECTION) + else if (token->type == TOKEN_IONUMBER || is_token_redir(token)) { return parse_redirection(ctx); } From 07e7d83c6058b08ec6c81c704a19bee6cf4c96aa Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Mon, 26 Jan 2026 19:54:43 +0100 Subject: [PATCH 189/282] fix(parser): error message now displayed on stderr --- src/parser/grammar.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/parser/grammar.h b/src/parser/grammar.h index 80961f1..3faf39d 100644 --- a/src/parser/grammar.h +++ b/src/parser/grammar.h @@ -11,7 +11,7 @@ peek_token(ctx); \ if (token == NULL) \ { \ - puts("Internal error: cannot get the following token"); \ + perror("Internal error: cannot get the following token"); \ return NULL; \ } @@ -19,7 +19,7 @@ pop_token(ctx); \ if (token == NULL) \ { \ - puts("Internal error: cannot get the following token"); \ + perror("Internal error: cannot get the following token"); \ return NULL; \ } From 96626d9850a5e3adf2dec02b1fad438894d2530e Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Tue, 27 Jan 2026 00:30:19 +0100 Subject: [PATCH 190/282] =?UTF-8?q?feat:=20toujours=20les=20m=C3=AAmes=20q?= =?UTF-8?q?ui=20font=20les=20pipes.=20Plus=20de=20assert=20dans=20ASTs=20(?= =?UTF-8?q?pour=20des=20raisons=20=C3=A9videntes=20de=20stabilit=C3=A9=20d?= =?UTF-8?q?u=20code)=20et=20nouveaux=20types=20(AST=5FPIPE=20et=20AST=5FNE?= =?UTF-8?q?G),=20+=20modifs=20random=20dans=20le=20parser?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lexer/lexer.h | 6 +++++ src/lexer/lexer_utils.c | 19 ++++++++++++++++ src/lexer/lexer_utils.h | 16 +++++++------ src/parser/grammar.c | 28 ++++++----------------- src/parser/grammar.h | 4 ---- src/parser/grammar_basic.c | 45 ++++++++++++++++++++++++++++++++----- src/parser/grammar_basic.h | 4 ++-- src/utils/ast/ast.c | 41 +++++++++++++++++++++++++++------ src/utils/ast/ast.h | 1 + src/utils/ast/ast_and_or.h | 2 -- src/utils/ast/ast_base.h | 5 ++++- src/utils/ast/ast_command.c | 8 +++---- src/utils/ast/ast_command.h | 2 -- src/utils/ast/ast_end.c | 4 +--- src/utils/ast/ast_end.h | 2 -- src/utils/ast/ast_if.c | 10 ++++----- src/utils/ast/ast_if.h | 2 -- src/utils/ast/ast_list.c | 11 +++++---- src/utils/ast/ast_list.h | 2 -- src/utils/ast/ast_neg.c | 32 ++++++++++++++++++++++++++ src/utils/ast/ast_neg.h | 17 ++++++++++++++ src/utils/ast/ast_pipe.c | 35 +++++++++++++++++++++++++++++ src/utils/ast/ast_pipe.h | 18 +++++++++++++++ src/utils/ast/ast_redir.h | 2 -- src/utils/ast/ast_void.c | 4 +--- src/utils/ast/ast_void.h | 2 -- src/utils/ast/ast_word.h | 2 -- 27 files changed, 238 insertions(+), 86 deletions(-) create mode 100644 src/utils/ast/ast_neg.c create mode 100644 src/utils/ast/ast_neg.h create mode 100644 src/utils/ast/ast_pipe.c create mode 100644 src/utils/ast/ast_pipe.h diff --git a/src/lexer/lexer.h b/src/lexer/lexer.h index e06bec0..8c5b93e 100644 --- a/src/lexer/lexer.h +++ b/src/lexer/lexer.h @@ -4,6 +4,12 @@ #include #include "lexer_utils.h" + +/* + * @brief returns true if token is a redir type, false otherwise + */ +bool is_token_redir(struct token *token); + /* * @brief: returns the next (newly allocated) token without consuming it. * if end of input is reached, returns a token of type TOKEN_EOF. diff --git a/src/lexer/lexer_utils.c b/src/lexer/lexer_utils.c index 833b274..9883bf7 100644 --- a/src/lexer/lexer_utils.c +++ b/src/lexer/lexer_utils.c @@ -178,6 +178,25 @@ static bool is_end_of_line(char c) return c == EOF || c == '\n'; } +// === Functions + +bool is_token_redir(struct token *token) +{ + switch (token->type) + { + case TOKEN_REDIR_LEFT: + case TOKEN_REDIR_RIGHT: + case TOKEN_REDIR_LEFT_RIGHT: + case TOKEN_REDIR_DOUBLE_RIGHT: + case TOKEN_REDIR_LEFT_AMP: + case TOKEN_REDIR_RIGHT_AMP: + case TOKEN_REDIR_RIGHT_PIPE: + return true; + default: + return false; + } +} + bool is_special_char(char *stream, ssize_t i) { char c = stream[i]; diff --git a/src/lexer/lexer_utils.h b/src/lexer/lexer_utils.h index 6173485..29d444d 100644 --- a/src/lexer/lexer_utils.h +++ b/src/lexer/lexer_utils.h @@ -31,16 +31,13 @@ enum lexing_mode enum token_type { - // Special characters + // Blanks TOKEN_NULL = 0, TOKEN_EOF, TOKEN_WORD, TOKEN_NEWLINE, - // WARNING: quote and double quote should never be used inside a token. - TOKEN_QUOTE, - TOKEN_DOUBLE_QUOTE, - + // SPecial characters TOKEN_GRAVE, TOKEN_SEMICOLON, TOKEN_COMMENT, @@ -51,8 +48,14 @@ enum token_type TOKEN_RIGHT_PAREN, TOKEN_LEFT_BRACKET, TOKEN_RIGHT_BRACKET, + TOKEN_PIPE, + TOKEN_NEGATION, // TODO handle - // redirections + // 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, TOKEN_REDIR_LEFT_RIGHT, @@ -62,7 +65,6 @@ enum token_type TOKEN_REDIR_RIGHT_PIPE, TOKEN_IONUMBER, - TOKEN_PIPE, // Keywords TOKEN_IF, diff --git a/src/parser/grammar.c b/src/parser/grammar.c index c1457eb..6a192db 100644 --- a/src/parser/grammar.c +++ b/src/parser/grammar.c @@ -4,8 +4,8 @@ #include #include -#include "grammar_basic.h" #include "../lexer/lexer.h" +#include "grammar_basic.h" // === Static variables @@ -34,8 +34,8 @@ static bool add_first(enum rule rule, enum token_type token) // Append item->list_length++; - item->tokens = realloc( - item->tokens, (item->list_length) * sizeof(enum token_type)); + item->tokens = realloc(item->tokens, + (item->list_length) * sizeof(enum token_type)); } else { @@ -91,24 +91,6 @@ static void add_first_redir(enum rule rule) // === Functions -bool is_token_redir(struct token *token) -{ - switch (token->type) - { - case TOKEN_REDIR_LEFT: - case TOKEN_REDIR_RIGHT: - case TOKEN_REDIR_LEFT_RIGHT: - case TOKEN_REDIR_DOUBLE_RIGHT: - case TOKEN_REDIR_LEFT_AMP: - case TOKEN_REDIR_RIGHT_AMP: - case TOKEN_REDIR_RIGHT_PIPE: - return true; - default: - return false; - } -} - - bool grammar_init(void) { // Initialize the firsts map @@ -119,17 +101,21 @@ bool grammar_init(void) // 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_COMMAND, 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_PIPELINE, TOKEN_WORD); add_first(RULE_PIPELINE, TOKEN_IF); + add_first(RULE_COMMAND, TOKEN_NEGATION); add_first(RULE_COMMAND, TOKEN_WORD); add_first(RULE_COMMAND, TOKEN_IF); diff --git a/src/parser/grammar.h b/src/parser/grammar.h index 3faf39d..9538848 100644 --- a/src/parser/grammar.h +++ b/src/parser/grammar.h @@ -50,10 +50,6 @@ struct firsts_list { // === Functions -/* @brief: returns true if token has a type redirection (not pipe or IONUMBER). - */ -bool is_token_redir(struct token *token); - /* * @brief Initializes the grammar submodule * @return PARSER_INIT_SUCCESS on success PARSER_INIT_ERROR on error diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index d99ba6d..0b3b7c6 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -1,13 +1,13 @@ +#include #define _POSIX_C_SOURCE 200809L -#include "grammar_basic.h" - #include #include #include "../utils/lists/lists.h" #include "grammar.h" #include "grammar_advanced.h" +#include "grammar_basic.h" // === Static functions @@ -108,7 +108,42 @@ struct ast *parse_and_or(struct lexer_context *ctx) struct ast *parse_pipeline(struct lexer_context *ctx) { - return parse_command(ctx); + bool negation = false; + struct token *token = PEEK_TOKEN(); + + // Eventual '!' + if (token->type == TOKEN_NEGATION) + { + negation = true; + POP_TOKEN(); + token = PEEK_TOKEN(); + } + + // TODO handle negation (new AST type) + + struct ast *left = parse_command(ctx); + + token = PEEK_TOKEN(); + while (token->type == TOKEN_PIPE) + { + POP_TOKEN(); + + // skip newlines + token = PEEK_TOKEN(); + while (token->type == TOKEN_NEWLINE) + { + POP_TOKEN(); + token = PEEK_TOKEN(); + } + + struct ast *right = parse_command(ctx); + + // Create AST + left = ast_create_pipe(left, right); + token = PEEK_TOKEN(); + } + + return left; } struct ast *parse_command(struct lexer_context *ctx) @@ -125,7 +160,7 @@ struct ast *parse_command(struct lexer_context *ctx) } else { - puts("Syntax error: expected command"); + puts("Syntax error: unexpected token"); return NULL; } } @@ -324,7 +359,7 @@ struct ast *parse_compound_list(struct lexer_context *ctx) return NULL; result_list = list_append(result_list, current_cmd); } - + token = PEEK_TOKEN(); } diff --git a/src/parser/grammar_basic.h b/src/parser/grammar_basic.h index f1c2f77..5ef6a56 100644 --- a/src/parser/grammar_basic.h +++ b/src/parser/grammar_basic.h @@ -28,9 +28,9 @@ struct ast *parse_and_or(struct lexer_context *ctx); /* * @brief Only parses a command rule for the moment * - * @code pipeline = command ; + * @code pipeline = ['!'] command { '|' {'\n'} command } ; * - * @first first(command) + * @first '!', first(command) */ struct ast *parse_pipeline(struct lexer_context *ctx); diff --git a/src/utils/ast/ast.c b/src/utils/ast/ast.c index b4afb9c..a507a33 100644 --- a/src/utils/ast/ast.c +++ b/src/utils/ast/ast.c @@ -2,7 +2,6 @@ #define _POSIX_C_SOURCE 200809L #include "ast.h" -#include #include #include #include @@ -11,18 +10,45 @@ void ast_free(struct ast **node) { if (node == NULL || *node == NULL) + { + puts( + "WARNING: Internal error: failed to free AST node (NULL argument)"); return; - // ast void does not need to be freed. - if (ast_is_if(*node)) + } + + switch ((*node)->type) + { + case AST_IF: ast_free_if(ast_get_if(*node)); - else if (ast_is_command(*node)) + break; + case AST_CMD: ast_free_command(ast_get_command(*node)); - else if (ast_is_list(*node)) + break; + case AST_LIST: ast_free_list(ast_get_list(*node)); - else if (ast_is_and_or(*node)) + break; + case AST_AND_OR: ast_free_and_or(ast_get_and_or(*node)); - else if (ast_is_redir(*node)) + break; + case AST_REDIR: ast_free_redir(ast_get_redir(*node)); + break; + case AST_PIPE: + ast_free_pipe(ast_get_pipe(*node)); + break; + case AST_WORD: + ast_free_word(ast_get_word(*node)); + break; + + case AST_VOID: + case AST_END: + break; + + default: + puts("WARNING: Internal error: failed to free an AST node (Unknown " + "type)"); + return; + } free(*node); *node = NULL; @@ -40,6 +66,7 @@ struct ast *ast_create(enum ast_type type, void *data) return node; } +// TODO handle new types (AST_WORD, AST_PIPE, etc.) static void ast_print_dot_recursive(struct ast *node, FILE *out) { if (!node) diff --git a/src/utils/ast/ast.h b/src/utils/ast/ast.h index fc35666..cbb5efc 100644 --- a/src/utils/ast/ast.h +++ b/src/utils/ast/ast.h @@ -10,6 +10,7 @@ #include "ast_redir.h" #include "ast_void.h" #include "ast_word.h" +#include "ast_pipe.h" /** * Prints the Graphviz DOT representation of the given AST to stdout. diff --git a/src/utils/ast/ast_and_or.h b/src/utils/ast/ast_and_or.h index 344cfbf..4813592 100644 --- a/src/utils/ast/ast_and_or.h +++ b/src/utils/ast/ast_and_or.h @@ -1,8 +1,6 @@ #ifndef AST_AND_OR_H #define AST_AND_OR_H -#include - #include "ast_base.h" enum ast_and_or_type diff --git a/src/utils/ast/ast_base.h b/src/utils/ast/ast_base.h index 4ae00ec..341b380 100644 --- a/src/utils/ast/ast_base.h +++ b/src/utils/ast/ast_base.h @@ -2,6 +2,7 @@ #define AST_BASE_H #include +#include enum ast_type { @@ -12,7 +13,9 @@ enum ast_type AST_REDIR, AST_VOID, AST_CMD, - AST_WORD + AST_WORD, + AST_PIPE, + AST_NEG }; struct ast diff --git a/src/utils/ast/ast_command.c b/src/utils/ast/ast_command.c index e52787e..2b6bbf6 100644 --- a/src/utils/ast/ast_command.c +++ b/src/utils/ast/ast_command.c @@ -1,6 +1,5 @@ #include "ast_command.h" -#include #include #include @@ -19,15 +18,14 @@ struct ast *ast_create_command(struct list *command) struct ast_command *ast_get_command(struct ast *node) { - assert(node != NULL); - assert(node->type == AST_CMD); + if (node == NULL || node->type == AST_CMD) + return NULL; return (struct ast_command *)node->data; } bool ast_is_command(struct ast *node) { - assert(node != NULL); - return node->type == AST_CMD; + return node != NULL && node->type == AST_CMD; } void ast_free_command(struct ast_command *command_data) diff --git a/src/utils/ast/ast_command.h b/src/utils/ast/ast_command.h index 089225f..cf6b913 100644 --- a/src/utils/ast/ast_command.h +++ b/src/utils/ast/ast_command.h @@ -1,8 +1,6 @@ #ifndef AST_COMMAND_H #define AST_COMMAND_H -#include - #include "../lists/lists.h" #include "ast_base.h" diff --git a/src/utils/ast/ast_end.c b/src/utils/ast/ast_end.c index fbde72f..0d314bc 100644 --- a/src/utils/ast/ast_end.c +++ b/src/utils/ast/ast_end.c @@ -1,13 +1,11 @@ #include "ast_end.h" -#include #include #include bool ast_is_end(struct ast *node) { - assert(node != NULL); - return node->type == AST_END; + return node != NULL && node->type == AST_END; } struct ast *ast_create_end(void) diff --git a/src/utils/ast/ast_end.h b/src/utils/ast/ast_end.h index d846cad..55b5322 100644 --- a/src/utils/ast/ast_end.h +++ b/src/utils/ast/ast_end.h @@ -1,8 +1,6 @@ #ifndef AST_END_H #define AST_END_H -#include - #include "../lists/lists.h" #include "ast_base.h" diff --git a/src/utils/ast/ast_if.c b/src/utils/ast/ast_if.c index cff0320..1402ff6 100644 --- a/src/utils/ast/ast_if.c +++ b/src/utils/ast/ast_if.c @@ -1,6 +1,5 @@ #include "ast_if.h" -#include #include #include @@ -20,15 +19,14 @@ struct ast *ast_create_if(struct ast *condition, struct ast *then_clause, struct ast_if *ast_get_if(struct ast *node) { - assert(node != NULL); - assert(node->type == AST_IF); - return (struct ast_if *)node->data; + if (node == NULL || node->type == AST_IF) + return NULL; + return node->data; } bool ast_is_if(struct ast *node) { - assert(node != NULL); - return node->type == AST_IF; + return node != NULL && node->type == AST_IF; } void ast_free_if(struct ast_if *if_data) diff --git a/src/utils/ast/ast_if.h b/src/utils/ast/ast_if.h index f54a795..f1842bd 100644 --- a/src/utils/ast/ast_if.h +++ b/src/utils/ast/ast_if.h @@ -1,8 +1,6 @@ #ifndef AST_IF_H #define AST_IF_H -#include - #include "ast_base.h" struct ast_if diff --git a/src/utils/ast/ast_list.c b/src/utils/ast/ast_list.c index 2b96a23..cb4aaa6 100644 --- a/src/utils/ast/ast_list.c +++ b/src/utils/ast/ast_list.c @@ -1,6 +1,4 @@ -#include - -#include "ast.h" +#include "ast_list.h" struct ast *ast_create_list(struct list *list) { @@ -15,13 +13,14 @@ struct ast *ast_create_list(struct list *list) struct ast_list *ast_get_list(struct ast *node) { - assert(node != NULL); - return (struct ast_list *)node->data; + if (node == NULL) + return NULL; + return node->data; } bool ast_is_list(struct ast *node) { - return node->type == AST_LIST; + return node != NULL && node->type == AST_LIST; } void ast_free_list(struct ast_list *ast_list) diff --git a/src/utils/ast/ast_list.h b/src/utils/ast/ast_list.h index ee98ea5..21b24fb 100644 --- a/src/utils/ast/ast_list.h +++ b/src/utils/ast/ast_list.h @@ -1,8 +1,6 @@ #ifndef AST_LIST_H #define AST_LIST_H -#include - #include "../lists/lists.h" #include "ast_base.h" diff --git a/src/utils/ast/ast_neg.c b/src/utils/ast/ast_neg.c new file mode 100644 index 0000000..f5817fc --- /dev/null +++ b/src/utils/ast/ast_neg.c @@ -0,0 +1,32 @@ +#include "ast_neg.h" + +#include + +bool ast_is_neg(struct ast *node) +{ + return node != NULL && node->type == AST_REDIR; +} + +struct ast_neg *ast_get_neg(struct ast *node) +{ + if (ast_is_neg(node)) + return node->data; + return NULL; +} + +struct ast *ast_create_neg(bool negation, struct ast *child) +{ + struct ast_neg *node = malloc(sizeof(struct ast_neg)); + if (!node) + return NULL; + + return ast_create(AST_NEG, node); +} + +void ast_free_neg(struct ast_neg *node) +{ + if (!node) + return; + ast_free(&node->child); + free(node); +} diff --git a/src/utils/ast/ast_neg.h b/src/utils/ast/ast_neg.h new file mode 100644 index 0000000..8c2d4e8 --- /dev/null +++ b/src/utils/ast/ast_neg.h @@ -0,0 +1,17 @@ +#ifndef AST_NEG_H +#define AST_NEG_H + +#include "ast_base.h" + +struct ast_neg +{ + bool negation; // True negates the child's output + struct ast* child; +}; + +bool ast_is_neg(struct ast *node); +struct ast_neg *ast_get_neg(struct ast *node); +struct ast *ast_create_neg(bool negation, struct ast* child); +void ast_free_neg(struct ast_neg* node); + +#endif /* ! AST_NEG_H */ diff --git a/src/utils/ast/ast_pipe.c b/src/utils/ast/ast_pipe.c new file mode 100644 index 0000000..92754cf --- /dev/null +++ b/src/utils/ast/ast_pipe.c @@ -0,0 +1,35 @@ +#include "ast_pipe.h" + +#include + +bool ast_is_pipe(struct ast *node) +{ + return node != NULL && node->type == AST_REDIR; +} + +struct ast_pipe *ast_get_pipe(struct ast *node) +{ + if (ast_is_pipe(node)) + return node->data; + return NULL; +} + +struct ast *ast_create_pipe(struct ast *left, struct ast *right) +{ + struct ast_pipe *node = malloc(sizeof(struct ast_pipe)); + if (!node) + return NULL; + node->left = left; + node->right = right; + + return ast_create(AST_PIPE, node); +} + +void ast_free_pipe(struct ast_pipe *node) +{ + if (!node) + return; + ast_free(&node->left); + ast_free(&node->right); + free(node); +} diff --git a/src/utils/ast/ast_pipe.h b/src/utils/ast/ast_pipe.h new file mode 100644 index 0000000..c5e1e39 --- /dev/null +++ b/src/utils/ast/ast_pipe.h @@ -0,0 +1,18 @@ +#ifndef AST_PIPE_H +#define AST_PIPE_H + +#include "ast_base.h" + +struct ast_pipe +{ + struct ast* left; + struct ast* right; + // Output of left will be redirected to right stdin +}; + +bool ast_is_pipe(struct ast *node); +struct ast_pipe *ast_get_pipe(struct ast *node); +struct ast *ast_create_pipe(struct ast* left, struct ast* right); +void ast_free_pipe(struct ast_pipe* node); + +#endif /* ! AST_PIPE_H */ diff --git a/src/utils/ast/ast_redir.h b/src/utils/ast/ast_redir.h index 4b2eb63..4c1008a 100644 --- a/src/utils/ast/ast_redir.h +++ b/src/utils/ast/ast_redir.h @@ -1,8 +1,6 @@ #ifndef AST_REDIR_H #define AST_REDIR_H -#include - #include "ast_base.h" enum ast_redir_type diff --git a/src/utils/ast/ast_void.c b/src/utils/ast/ast_void.c index 213413b..e7d2dee 100644 --- a/src/utils/ast/ast_void.c +++ b/src/utils/ast/ast_void.c @@ -1,13 +1,11 @@ #include "ast_void.h" -#include #include #include bool ast_is_void(struct ast *node) { - assert(node != NULL); - return node->type == AST_VOID; + return node != NULL && node->type == AST_VOID; } struct ast *ast_create_void(void) diff --git a/src/utils/ast/ast_void.h b/src/utils/ast/ast_void.h index ecbdcf8..05a5933 100644 --- a/src/utils/ast/ast_void.h +++ b/src/utils/ast/ast_void.h @@ -1,8 +1,6 @@ #ifndef AST_VOID_H #define AST_VOID_H -#include - #include "../lists/lists.h" #include "ast_base.h" diff --git a/src/utils/ast/ast_word.h b/src/utils/ast/ast_word.h index d5e0e1d..571f869 100644 --- a/src/utils/ast/ast_word.h +++ b/src/utils/ast/ast_word.h @@ -1,8 +1,6 @@ #ifndef AST_WORD_H #define AST_WORD_H -#include - #include "ast_base.h" struct ast_word From d52f603eec142c3dc782497275f3e69d749da6dc Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Tue, 27 Jan 2026 16:05:11 +0100 Subject: [PATCH 191/282] fix: changed puts to perror + redirections in parser --- src/parser/grammar.c | 6 +++--- src/parser/grammar_advanced.c | 12 +++++++----- src/parser/grammar_basic.c | 20 ++++++++++---------- src/parser/parser.c | 8 ++++---- src/utils/ast/ast.c | 4 ++-- 5 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/parser/grammar.c b/src/parser/grammar.c index 6a192db..33551f5 100644 --- a/src/parser/grammar.c +++ b/src/parser/grammar.c @@ -66,7 +66,7 @@ static bool init_firsts_map(void) firsts_map = calloc(NUMBER_OF_RULES, sizeof(struct firsts_list)); if (firsts_map == NULL) { - puts("Internal error: couldn't create the firsts_map (is your memory " + perror("Internal error: couldn't create the firsts_map (is your memory " "full ?)"); return false; } @@ -161,7 +161,7 @@ struct firsts_list *first(enum rule rule) { if (firsts_map == NULL || firsts_map[rule].tokens == NULL) { - puts("Internal error: attempted to get the firsts of a rule without " + perror("Internal error: attempted to get the firsts of a rule without " "properly initializing the firsts map"); return NULL; } @@ -208,7 +208,7 @@ struct ast *parse_input(struct lexer_context *ctx) return ast; } - puts("Syntax error: expected newline or EOF after list"); + perror("Syntax error: expected newline or EOF after list"); ast_free(&ast); return NULL; } diff --git a/src/parser/grammar_advanced.c b/src/parser/grammar_advanced.c index aac5742..9416eba 100644 --- a/src/parser/grammar_advanced.c +++ b/src/parser/grammar_advanced.c @@ -6,6 +6,8 @@ #include "grammar_basic.h" +bool + struct ast *parse_redirection(struct lexer_context *ctx) { struct token *token = PEEK_TOKEN(); @@ -17,20 +19,20 @@ struct ast *parse_redirection(struct lexer_context *ctx) token = PEEK_TOKEN(); } - if (token->type != TOKEN_REDIRECTION) + if (!is_token_redir(token)) { - puts("Syntax error: expected a redirection token but got something " + perror("Syntax error: expected a redirection token but got something " "else"); return NULL; } - char *redir_op = strdup(token->data); + // char *redir_op = strdup(token->data); POP_TOKEN(); token = PEEK_TOKEN(); if (token->type != TOKEN_WORD) { - puts("Syntax error: expected a word after redirection"); - free(redir_op); + perror("Syntax error: expected a word after redirection"); + // free(redir_op); return NULL; } char *target = strdup(token->data); diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index 0b3b7c6..d229549 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -160,7 +160,7 @@ struct ast *parse_command(struct lexer_context *ctx) } else { - puts("Syntax error: unexpected token"); + perror("Syntax error: unexpected token"); return NULL; } } @@ -173,7 +173,7 @@ struct ast *parse_simple_command(struct lexer_context *ctx) struct token *token = POP_TOKEN(); if (token->type != TOKEN_WORD) { - puts("Expected a command but got a different token type"); + perror("Expected a command but got a different token type"); return NULL; } char *command = strdup(token->data); @@ -202,12 +202,12 @@ struct ast *parse_simple_command(struct lexer_context *ctx) else if (ast_is_redir(element)) { // TODO - puts("NOT IMPLEMENTED"); + perror("NOT IMPLEMENTED"); return NULL; } else { - puts("Internal error: unexpected return value from parse_element " + perror("Internal error: unexpected return value from parse_element " "in parse_simple_command"); list_deep_destroy(command_elements); return NULL; @@ -241,7 +241,7 @@ struct ast *parse_element(struct lexer_context *ctx) } else { - puts("Syntax error: unexpected token at parse_element"); + perror("Syntax error: unexpected token at parse_element"); return NULL; } } @@ -257,7 +257,7 @@ struct ast *parse_if_rule(struct lexer_context *ctx) struct token *token = POP_TOKEN(); if (token->type != TOKEN_IF) { - puts("Internal error: expected a if rule but token has different " + perror("Internal error: expected a if rule but token has different " "type"); return NULL; } @@ -270,7 +270,7 @@ struct ast *parse_if_rule(struct lexer_context *ctx) if (token->type != TOKEN_THEN) { ast_free(&condition_content); - puts("Expected the 'then' keyword but token has different type"); + perror("Expected the 'then' keyword but token has different type"); return NULL; } @@ -299,7 +299,7 @@ struct ast *parse_if_rule(struct lexer_context *ctx) ast_free(&condition_content); ast_free(&then_content); ast_free(&else_content); - puts("Expected the 'fi' keyword but token has different type"); + perror("Expected the 'fi' keyword but token has different type"); return NULL; } @@ -311,7 +311,7 @@ struct ast *parse_if_rule(struct lexer_context *ctx) ast_free(&condition_content); ast_free(&then_content); ast_free(&else_content); - puts("Internal error: could not create a new AST (AST_IF)"); + perror("Internal error: could not create a new AST (AST_IF)"); return NULL; } @@ -396,7 +396,7 @@ struct ast *parse_else_clause(struct lexer_context *ctx) token = POP_TOKEN(); if (token->type != TOKEN_THEN) { - puts("Expected the 'then' keyword but got a different token type"); + perror("Expected the 'then' keyword but got a different token type"); return NULL; } diff --git a/src/parser/parser.c b/src/parser/parser.c index b86c696..b71867a 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -14,7 +14,7 @@ bool parser_init(void) { if (state == PARSER_STATE_READY) { - puts("Internal error: tried to initialize the parser module twice."); + perror("Internal error: tried to initialize the parser module twice."); return NULL; } int success = grammar_init(); @@ -29,19 +29,19 @@ struct ast *get_ast(struct lexer_context *ctx) { if (ctx == NULL) { - puts("Internal error: called parser with no lexer context (NULL " + perror("Internal error: called parser with no lexer context (NULL " "pointer). Aborting."); return NULL; } if (state == PARSER_STATE_NOT_INITIALIZED) { - puts("Internal error: attempted to call parser without initializing " + perror("Internal error: attempted to call parser without initializing " "it. Aborting."); return NULL; } if (state == PARSER_STATE_CLOSED) { - puts("Internal error: attempted to call parser after closing it. " + perror("Internal error: attempted to call parser after closing it. " "Aborting."); return NULL; } diff --git a/src/utils/ast/ast.c b/src/utils/ast/ast.c index a507a33..a099473 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) { - puts( + perror( "WARNING: Internal error: failed to free AST node (NULL argument)"); return; } @@ -45,7 +45,7 @@ void ast_free(struct ast **node) break; default: - puts("WARNING: Internal error: failed to free an AST node (Unknown " + perror("WARNING: Internal error: failed to free an AST node (Unknown " "type)"); return; } From 7614370d00d2991d12b2594b21e9d288bd425129 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Tue, 27 Jan 2026 16:17:40 +0100 Subject: [PATCH 192/282] feat(parser): implementing redirections... --- src/parser/grammar_advanced.c | 18 ++++++++++++++++-- src/utils/ast/ast_redir.h | 1 + 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/parser/grammar_advanced.c b/src/parser/grammar_advanced.c index 9416eba..aef3c27 100644 --- a/src/parser/grammar_advanced.c +++ b/src/parser/grammar_advanced.c @@ -6,7 +6,19 @@ #include "grammar_basic.h" -bool +static enum ast_redir_type redir_tok_to_ast_type(enum token_type tok_type) +{ + switch(tok_type) + { + case TOKEN_REDIR_LEFT: + return AST_REDIR_TYPE_LESS; + case TOKEN_REDIR_RIGHT: + return AST_REDIR_TYPE_GREAT; + // TODO finish this + default: + return AST_REDIR_TYPE_NULL; + } +} struct ast *parse_redirection(struct lexer_context *ctx) { @@ -26,6 +38,8 @@ struct ast *parse_redirection(struct lexer_context *ctx) return NULL; } // char *redir_op = strdup(token->data); + + enum ast_redir_type redir_type = redir_tok_to_ast_type(token->type); POP_TOKEN(); token = PEEK_TOKEN(); @@ -38,7 +52,7 @@ struct ast *parse_redirection(struct lexer_context *ctx) char *target = strdup(token->data); POP_TOKEN(); - return ast_create_redir(io_number, redir_op, target); + return ast_create_redir(io_number, redir_type, target); } struct ast *parse_prefix(struct lexer_context *ctx) diff --git a/src/utils/ast/ast_redir.h b/src/utils/ast/ast_redir.h index 4c1008a..4dae912 100644 --- a/src/utils/ast/ast_redir.h +++ b/src/utils/ast/ast_redir.h @@ -5,6 +5,7 @@ enum ast_redir_type { + AST_REDIR_TYPE_NULL, AST_REDIR_TYPE_LESS, // < AST_REDIR_TYPE_GREAT, // > AST_REDIR_TYPE_DLESS, // << From c48d86c8de2bcd88629fcf675b279831221740e0 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Tue, 27 Jan 2026 16:35:30 +0100 Subject: [PATCH 193/282] feat(parser): negation handled --- src/parser/grammar_basic.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index d229549..25d29d1 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -119,9 +119,11 @@ struct ast *parse_pipeline(struct lexer_context *ctx) token = PEEK_TOKEN(); } - // TODO handle negation (new AST type) - struct ast *left = parse_command(ctx); + if (negation) + { + left = ast_create_neg(negation, left); + } token = PEEK_TOKEN(); while (token->type == TOKEN_PIPE) From 9003675c40c892e699f649642aec9d51feabeb90 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Tue, 27 Jan 2026 16:44:53 +0100 Subject: [PATCH 194/282] feat(lexer): negation --- src/lexer/lexer_utils.c | 21 ++++++--------------- src/lexer/lexer_utils.h | 2 +- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/lexer/lexer_utils.c b/src/lexer/lexer_utils.c index 9883bf7..07455a7 100644 --- a/src/lexer/lexer_utils.c +++ b/src/lexer/lexer_utils.c @@ -60,34 +60,22 @@ static void set_token_keyword(struct token *tok, char *begin, ssize_t size) { if (tok->type != TOKEN_NULL || size == 0) return; - if (strncmp(begin, "if", size) == 0 && size == 2) - { + if (strncmp(begin, "!", size) == 0 && size == 1) + tok->type = TOKEN_NEGATION; + else if (strncmp(begin, "if", size) == 0 && size == 2) tok->type = TOKEN_IF; - } else if (strncmp(begin, "fi", size) == 0 && size == 2) - { tok->type = TOKEN_FI; - } else if (strncmp(begin, "then", size) == 0 && size == 4) - { tok->type = TOKEN_THEN; - } else if (strncmp(begin, "else", size) == 0 && size == 4) - { 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) @@ -95,7 +83,10 @@ static void set_token_keyword(struct token *tok, char *begin, ssize_t size) tok->data = calloc(size + 1, sizeof(char)); if (tok->data == NULL) + { + perror("could not allocate memory in lexer"); return; + } strncpy(tok->data, begin, size); } diff --git a/src/lexer/lexer_utils.h b/src/lexer/lexer_utils.h index 29d444d..6b26c74 100644 --- a/src/lexer/lexer_utils.h +++ b/src/lexer/lexer_utils.h @@ -49,7 +49,7 @@ enum token_type TOKEN_LEFT_BRACKET, TOKEN_RIGHT_BRACKET, TOKEN_PIPE, - TOKEN_NEGATION, // TODO handle + TOKEN_NEGATION, // TODO merge into one and use the data field // (Too difficult to handle in the parser because of firsts) From 13018e0a03865394fae478d0b366515b173853ba Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Tue, 27 Jan 2026 18:00:59 +0100 Subject: [PATCH 195/282] fix: small bugs to make it compile --- src/main.c | 5 ++++- src/parser/grammar.c | 5 ++++- src/parser/grammar_advanced.c | 4 ++++ src/parser/parser.c | 10 ++++++++++ src/utils/Makefile.am | 3 +++ src/utils/ast/ast.h | 1 + src/utils/ast/ast_base.h | 1 + src/utils/ast/ast_command.c | 2 +- src/utils/ast/ast_neg.c | 4 +++- src/utils/ast/ast_word.c | 2 ++ 10 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/main.c b/src/main.c index e548360..c8988ca 100644 --- a/src/main.c +++ b/src/main.c @@ -32,7 +32,10 @@ static int main_loop(struct lexer_context *ctx, struct args_options *options, { int return_code = SUCCESS; // init parser - int parser_init(); + if (!parser_init()) + { + perror("parser initialization failed."); + } // Retrieve and build first AST struct ast *command_ast = get_ast(ctx); diff --git a/src/parser/grammar.c b/src/parser/grammar.c index 33551f5..b734c7b 100644 --- a/src/parser/grammar.c +++ b/src/parser/grammar.c @@ -186,7 +186,10 @@ struct ast *parse_input(struct lexer_context *ctx) struct token *token = PEEK_TOKEN(); if (token->type == TOKEN_EOF) - return ast_create_list(NULL); + { + POP_TOKEN(); + return ast_create_end(); + } if (token->type == TOKEN_NEWLINE) { diff --git a/src/parser/grammar_advanced.c b/src/parser/grammar_advanced.c index aef3c27..faa2f5e 100644 --- a/src/parser/grammar_advanced.c +++ b/src/parser/grammar_advanced.c @@ -22,6 +22,9 @@ static enum ast_redir_type redir_tok_to_ast_type(enum token_type tok_type) struct ast *parse_redirection(struct lexer_context *ctx) { + (void)ctx; + return NULL; + /* struct token *token = PEEK_TOKEN(); int io_number = -1; if (token->type == TOKEN_IONUMBER) @@ -53,6 +56,7 @@ struct ast *parse_redirection(struct lexer_context *ctx) POP_TOKEN(); return ast_create_redir(io_number, redir_type, target); + */ } struct ast *parse_prefix(struct lexer_context *ctx) diff --git a/src/parser/parser.c b/src/parser/parser.c index b71867a..e5c5f94 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -25,6 +25,16 @@ bool parser_init(void) return true; } +void parser_close(void) +{ + if (state != PARSER_STATE_READY) + { + perror("trying to close parser which was not opened"); + } + state = PARSER_STATE_CLOSED; + // TODO close grammar +} + struct ast *get_ast(struct lexer_context *ctx) { if (ctx == NULL) diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am index 65cc594..c2cf250 100644 --- a/src/utils/Makefile.am +++ b/src/utils/Makefile.am @@ -14,6 +14,9 @@ libutils_a_SOURCES = \ ast/ast_redir.c \ ast/ast_void.c \ ast/ast_end.c \ + ast/ast_word.c \ + ast/ast_neg.c \ + ast/ast_pipe.c \ args/args.c \ vars/vars.c diff --git a/src/utils/ast/ast.h b/src/utils/ast/ast.h index cbb5efc..334820b 100644 --- a/src/utils/ast/ast.h +++ b/src/utils/ast/ast.h @@ -11,6 +11,7 @@ #include "ast_void.h" #include "ast_word.h" #include "ast_pipe.h" +#include "ast_neg.h" /** * Prints the Graphviz DOT representation of the given AST to stdout. diff --git a/src/utils/ast/ast_base.h b/src/utils/ast/ast_base.h index 341b380..127dad7 100644 --- a/src/utils/ast/ast_base.h +++ b/src/utils/ast/ast_base.h @@ -29,6 +29,7 @@ struct ast * - struct ast_command* (AST_CMD) * - struct ast_and_or* (AST_AND_OR) * - struct ast_redir* (AST_REDIR) + * - and a lot more now... */ void *data; }; diff --git a/src/utils/ast/ast_command.c b/src/utils/ast/ast_command.c index 2b6bbf6..14904ae 100644 --- a/src/utils/ast/ast_command.c +++ b/src/utils/ast/ast_command.c @@ -18,7 +18,7 @@ struct ast *ast_create_command(struct list *command) struct ast_command *ast_get_command(struct ast *node) { - if (node == NULL || node->type == AST_CMD) + if (node == NULL || node->type != AST_CMD) return NULL; return (struct ast_command *)node->data; } diff --git a/src/utils/ast/ast_neg.c b/src/utils/ast/ast_neg.c index f5817fc..80dc4f3 100644 --- a/src/utils/ast/ast_neg.c +++ b/src/utils/ast/ast_neg.c @@ -4,7 +4,7 @@ bool ast_is_neg(struct ast *node) { - return node != NULL && node->type == AST_REDIR; + return node != NULL && node->type == AST_NEG; } struct ast_neg *ast_get_neg(struct ast *node) @@ -20,6 +20,8 @@ struct ast *ast_create_neg(bool negation, struct ast *child) if (!node) return NULL; + node->negation = negation; + node->child = child; return ast_create(AST_NEG, node); } diff --git a/src/utils/ast/ast_word.c b/src/utils/ast/ast_word.c index d798c77..957952c 100644 --- a/src/utils/ast/ast_word.c +++ b/src/utils/ast/ast_word.c @@ -1,5 +1,7 @@ +#define _POSIX_C_SOURCE 200809L #include "ast_word.h" +#include #include #include #include From 9ea979b303d20ac5b4309a17521b20a2f39c8954 Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Tue, 27 Jan 2026 19:10:32 +0100 Subject: [PATCH 196/282] fix(parser): initialization is not heap buffer overflowing anymore --- src/parser/grammar.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/parser/grammar.c b/src/parser/grammar.c index b734c7b..5b3d78a 100644 --- a/src/parser/grammar.c +++ b/src/parser/grammar.c @@ -52,7 +52,6 @@ static bool add_first(enum rule rule, enum token_type token) } // Fill - item->list_length++; item->tokens[item->list_length - 1] = token; return true; From 8a5c58974200553c2830b594b34d9c47aa8eee2e Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Tue, 27 Jan 2026 19:56:33 +0100 Subject: [PATCH 197/282] feat(parser): redirections --- src/lexer/lexer_utils.h | 2 +- src/parser/grammar.c | 4 +- src/parser/grammar.h | 12 +++-- src/parser/grammar_advanced.c | 28 +++++----- src/parser/grammar_advanced.h | 4 +- src/parser/grammar_basic.c | 12 +++-- src/parser/parser.c | 6 +-- src/utils/ast/ast.c | 2 +- src/utils/ast/ast.h | 4 +- src/utils/ast/ast_base.h | 2 +- src/utils/ast/ast_neg.h | 6 +-- src/utils/ast/ast_pipe.h | 8 +-- src/utils/ast/ast_redir.c | 4 +- src/utils/ast/ast_redir.h | 3 +- src/utils/ast/ast_word.c | 3 +- src/utils/ast/ast_word.h | 2 +- tests/unit/io_backend/io_backend.c | 84 ++++++++++-------------------- 17 files changed, 78 insertions(+), 108 deletions(-) diff --git a/src/lexer/lexer_utils.h b/src/lexer/lexer_utils.h index 6b26c74..c6c26c9 100644 --- a/src/lexer/lexer_utils.h +++ b/src/lexer/lexer_utils.h @@ -54,7 +54,7 @@ enum token_type // 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, diff --git a/src/parser/grammar.c b/src/parser/grammar.c index 5b3d78a..83f2266 100644 --- a/src/parser/grammar.c +++ b/src/parser/grammar.c @@ -66,7 +66,7 @@ static bool init_firsts_map(void) if (firsts_map == NULL) { perror("Internal error: couldn't create the firsts_map (is your memory " - "full ?)"); + "full ?)"); return false; } @@ -161,7 +161,7 @@ struct firsts_list *first(enum rule rule) if (firsts_map == NULL || firsts_map[rule].tokens == NULL) { perror("Internal error: attempted to get the firsts of a rule without " - "properly initializing the firsts map"); + "properly initializing the firsts map"); return NULL; } diff --git a/src/parser/grammar.h b/src/parser/grammar.h index 9538848..3c77747 100644 --- a/src/parser/grammar.h +++ b/src/parser/grammar.h @@ -11,7 +11,7 @@ peek_token(ctx); \ if (token == NULL) \ { \ - perror("Internal error: cannot get the following token"); \ + perror("Internal error: cannot get the following token"); \ return NULL; \ } @@ -19,13 +19,14 @@ pop_token(ctx); \ if (token == NULL) \ { \ - perror("Internal error: cannot get the following token"); \ + perror("Internal error: cannot get the following token"); \ return NULL; \ } // === Structures -enum rule { +enum rule +{ RULE_NULL = 0, RULE_INPUT, RULE_LIST, @@ -43,8 +44,9 @@ enum rule { NUMBER_OF_RULES }; -struct firsts_list { - enum token_type* tokens; // Heap allocated array +struct firsts_list +{ + enum token_type *tokens; // Heap allocated array size_t list_length; }; diff --git a/src/parser/grammar_advanced.c b/src/parser/grammar_advanced.c index faa2f5e..3850564 100644 --- a/src/parser/grammar_advanced.c +++ b/src/parser/grammar_advanced.c @@ -1,3 +1,5 @@ +#define _POSIX_C_SOURCE 200809L + #include "grammar_advanced.h" #include @@ -8,23 +10,20 @@ static enum ast_redir_type redir_tok_to_ast_type(enum token_type tok_type) { - switch(tok_type) + switch (tok_type) { - case TOKEN_REDIR_LEFT: - return AST_REDIR_TYPE_LESS; - case TOKEN_REDIR_RIGHT: - return AST_REDIR_TYPE_GREAT; - // TODO finish this - default: - return AST_REDIR_TYPE_NULL; + case TOKEN_REDIR_LEFT: + return AST_REDIR_TYPE_LESS; + case TOKEN_REDIR_RIGHT: + return AST_REDIR_TYPE_GREAT; + // TODO finish this + default: + return AST_REDIR_TYPE_NULL; } } struct ast *parse_redirection(struct lexer_context *ctx) { - (void)ctx; - return NULL; - /* struct token *token = PEEK_TOKEN(); int io_number = -1; if (token->type == TOKEN_IONUMBER) @@ -37,10 +36,9 @@ struct ast *parse_redirection(struct lexer_context *ctx) if (!is_token_redir(token)) { perror("Syntax error: expected a redirection token but got something " - "else"); + "else"); return NULL; } - // char *redir_op = strdup(token->data); enum ast_redir_type redir_type = redir_tok_to_ast_type(token->type); POP_TOKEN(); @@ -49,14 +47,12 @@ struct ast *parse_redirection(struct lexer_context *ctx) if (token->type != TOKEN_WORD) { perror("Syntax error: expected a word after redirection"); - // free(redir_op); return NULL; } char *target = strdup(token->data); POP_TOKEN(); - return ast_create_redir(io_number, redir_type, target); - */ + return ast_create_redir(target, io_number, redir_type); } struct ast *parse_prefix(struct lexer_context *ctx) diff --git a/src/parser/grammar_advanced.h b/src/parser/grammar_advanced.h index 72b5a43..f3c27ae 100644 --- a/src/parser/grammar_advanced.h +++ b/src/parser/grammar_advanced.h @@ -8,7 +8,8 @@ /* * @brief parses a redirection rule * - * @code redirection = [IONUMBER] ( '>' | '<' | '>>' | '>&' | '<&' | '>|' | '<>' ) WORD ; + * @code redirection = [IONUMBER] ( '>' | '<' | '>>' | '>&' | '<&' | '>|' | '<>' + * ) WORD ; * * @first TOKEN_IONUMBER, TOKEN_REDIRECTION */ @@ -23,5 +24,4 @@ struct ast *parse_redirection(struct lexer_context *ctx); */ struct ast *parse_prefix(struct lexer_context *ctx); - #endif /* ! GRAMMAR_ADVANCED_H */ diff --git a/src/parser/grammar_basic.c b/src/parser/grammar_basic.c index 25d29d1..d4a740f 100644 --- a/src/parser/grammar_basic.c +++ b/src/parser/grammar_basic.c @@ -1,13 +1,14 @@ -#include #define _POSIX_C_SOURCE 200809L +#include "grammar_basic.h" + +#include #include #include #include "../utils/lists/lists.h" #include "grammar.h" #include "grammar_advanced.h" -#include "grammar_basic.h" // === Static functions @@ -210,7 +211,7 @@ struct ast *parse_simple_command(struct lexer_context *ctx) else { perror("Internal error: unexpected return value from parse_element " - "in parse_simple_command"); + "in parse_simple_command"); list_deep_destroy(command_elements); return NULL; } @@ -260,7 +261,7 @@ struct ast *parse_if_rule(struct lexer_context *ctx) if (token->type != TOKEN_IF) { perror("Internal error: expected a if rule but token has different " - "type"); + "type"); return NULL; } @@ -398,7 +399,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; } diff --git a/src/parser/parser.c b/src/parser/parser.c index e5c5f94..c2b8aaf 100644 --- a/src/parser/parser.c +++ b/src/parser/parser.c @@ -40,19 +40,19 @@ struct ast *get_ast(struct lexer_context *ctx) if (ctx == NULL) { perror("Internal error: called parser with no lexer context (NULL " - "pointer). Aborting."); + "pointer). Aborting."); return NULL; } if (state == PARSER_STATE_NOT_INITIALIZED) { perror("Internal error: attempted to call parser without initializing " - "it. Aborting."); + "it. Aborting."); return NULL; } if (state == PARSER_STATE_CLOSED) { perror("Internal error: attempted to call parser after closing it. " - "Aborting."); + "Aborting."); return NULL; } diff --git a/src/utils/ast/ast.c b/src/utils/ast/ast.c index a099473..16db64e 100644 --- a/src/utils/ast/ast.c +++ b/src/utils/ast/ast.c @@ -46,7 +46,7 @@ void ast_free(struct ast **node) default: perror("WARNING: Internal error: failed to free an AST node (Unknown " - "type)"); + "type)"); return; } diff --git a/src/utils/ast/ast.h b/src/utils/ast/ast.h index 334820b..3964968 100644 --- a/src/utils/ast/ast.h +++ b/src/utils/ast/ast.h @@ -7,11 +7,11 @@ #include "ast_end.h" #include "ast_if.h" #include "ast_list.h" +#include "ast_neg.h" +#include "ast_pipe.h" #include "ast_redir.h" #include "ast_void.h" #include "ast_word.h" -#include "ast_pipe.h" -#include "ast_neg.h" /** * Prints the Graphviz DOT representation of the given AST to stdout. diff --git a/src/utils/ast/ast_base.h b/src/utils/ast/ast_base.h index 127dad7..f81f2f0 100644 --- a/src/utils/ast/ast_base.h +++ b/src/utils/ast/ast_base.h @@ -1,8 +1,8 @@ #ifndef AST_BASE_H #define AST_BASE_H -#include #include +#include enum ast_type { diff --git a/src/utils/ast/ast_neg.h b/src/utils/ast/ast_neg.h index 8c2d4e8..738c246 100644 --- a/src/utils/ast/ast_neg.h +++ b/src/utils/ast/ast_neg.h @@ -6,12 +6,12 @@ struct ast_neg { bool negation; // True negates the child's output - struct ast* child; + struct ast *child; }; bool ast_is_neg(struct ast *node); struct ast_neg *ast_get_neg(struct ast *node); -struct ast *ast_create_neg(bool negation, struct ast* child); -void ast_free_neg(struct ast_neg* node); +struct ast *ast_create_neg(bool negation, struct ast *child); +void ast_free_neg(struct ast_neg *node); #endif /* ! AST_NEG_H */ diff --git a/src/utils/ast/ast_pipe.h b/src/utils/ast/ast_pipe.h index c5e1e39..930cb2c 100644 --- a/src/utils/ast/ast_pipe.h +++ b/src/utils/ast/ast_pipe.h @@ -5,14 +5,14 @@ struct ast_pipe { - struct ast* left; - struct ast* right; + struct ast *left; + struct ast *right; // Output of left will be redirected to right stdin }; bool ast_is_pipe(struct ast *node); struct ast_pipe *ast_get_pipe(struct ast *node); -struct ast *ast_create_pipe(struct ast* left, struct ast* right); -void ast_free_pipe(struct ast_pipe* node); +struct ast *ast_create_pipe(struct ast *left, struct ast *right); +void ast_free_pipe(struct ast_pipe *node); #endif /* ! AST_PIPE_H */ diff --git a/src/utils/ast/ast_redir.c b/src/utils/ast/ast_redir.c index 8f0b1e9..d1dcedb 100644 --- a/src/utils/ast/ast_redir.c +++ b/src/utils/ast/ast_redir.c @@ -14,13 +14,12 @@ struct ast_redir *ast_get_redir(struct ast *node) return NULL; } -struct ast *ast_create_redir(struct ast *child, char *filename, int io_number, +struct ast *ast_create_redir(char *filename, int io_number, enum ast_redir_type type) { struct ast_redir *redir = malloc(sizeof(struct ast_redir)); if (!redir) return NULL; - redir->child = child; redir->filename = filename; // Takes ownership? Usually yes in simple ASTs, or dup. Let's // assume pointer copy for now, but user must manage memory. @@ -34,7 +33,6 @@ void ast_free_redir(struct ast_redir *redir) { if (!redir) return; - ast_free(&redir->child); free(redir->filename); free(redir); } diff --git a/src/utils/ast/ast_redir.h b/src/utils/ast/ast_redir.h index 4dae912..eb95942 100644 --- a/src/utils/ast/ast_redir.h +++ b/src/utils/ast/ast_redir.h @@ -17,7 +17,6 @@ enum ast_redir_type struct ast_redir { - struct ast *child; char *filename; int io_number; // The FD being redirected (default -1 if not specified, // implies 0 or 1 based on type) @@ -26,7 +25,7 @@ struct ast_redir bool ast_is_redir(struct ast *node); struct ast_redir *ast_get_redir(struct ast *node); -struct ast *ast_create_redir(struct ast *child, char *filename, int io_number, +struct ast *ast_create_redir(char *filename, int io_number, enum ast_redir_type type); void ast_free_redir(struct ast_redir *redir); diff --git a/src/utils/ast/ast_word.c b/src/utils/ast/ast_word.c index 957952c..6870c50 100644 --- a/src/utils/ast/ast_word.c +++ b/src/utils/ast/ast_word.c @@ -1,8 +1,8 @@ #define _POSIX_C_SOURCE 200809L #include "ast_word.h" -#include #include +#include #include #include @@ -17,6 +17,7 @@ struct ast *ast_create_word(char *word) struct ast *res = ast_create(AST_WORD, ast_node); if (res == NULL) { + free(ast_node->word); free(ast_node); return NULL; } diff --git a/src/utils/ast/ast_word.h b/src/utils/ast/ast_word.h index 571f869..a049003 100644 --- a/src/utils/ast/ast_word.h +++ b/src/utils/ast/ast_word.h @@ -23,7 +23,7 @@ struct ast_word *ast_get_word(struct ast *node); /** * Creates a new AST node representing a command. */ -struct ast *ast_create_word(char* word); +struct ast *ast_create_word(char *word); /* * @brief: frees the given ast_command and sets the pointer to NULL. diff --git a/tests/unit/io_backend/io_backend.c b/tests/unit/io_backend/io_backend.c index 1fa9dfa..4449a57 100644 --- a/tests/unit/io_backend/io_backend.c +++ b/tests/unit/io_backend/io_backend.c @@ -9,27 +9,19 @@ TestSuite(IO_Backend); Test(IO_Backend, init_null) { - struct iob_context ctx = - { - .mode = IOB_MODE_NULL, - .args = NULL - }; + struct iob_context ctx = { .mode = IOB_MODE_NULL, .args = NULL }; int actual = iob_init(&ctx); -int expected = IOB_ERROR_BAD_ARG; -cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); + int expected = IOB_ERROR_BAD_ARG; + cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); } Test(IO_Backend, init_stdin) { - struct iob_context ctx = - { - .mode = IOB_MODE_STDIN, - .args = NULL - }; + struct iob_context ctx = { .mode = IOB_MODE_STDIN, .args = NULL }; int actual = iob_init(&ctx); -int expected = 0; -cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); -iob_close(); + int expected = 0; + cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); + iob_close(); } // WARNING: this one could fail because of iob_close in the previous test @@ -37,82 +29,62 @@ iob_close(); Test(IO_Backend, init_script) { char *script_name = "script.tmp"; - struct iob_context ctx = { - .mode = IOB_MODE_SCRIPT, - .args = script_name - }; + struct iob_context ctx = { .mode = IOB_MODE_SCRIPT, .args = script_name }; // Create file FILE *f = fopen(script_name, "w"); fclose(f); int actual = iob_init(&ctx); -int expected = 0; -cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); -iob_close(); -remove(script_name); + int expected = 0; + cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); + iob_close(); + remove(script_name); } Test(IO_Backend, init_script_not_a_file) { char *script_name = "not_a_file.tmp"; - struct iob_context ctx = { - .mode = IOB_MODE_SCRIPT, - .args = script_name - }; + struct iob_context ctx = { .mode = IOB_MODE_SCRIPT, .args = script_name }; int actual = iob_init(&ctx); -int expected = IOB_ERROR_CANNOT_OPEN_FILE; -cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); + int expected = IOB_ERROR_CANNOT_OPEN_FILE; + cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); } Test(IO_Backend, init_script_null) { - struct iob_context ctx = - { - .mode = IOB_MODE_SCRIPT, - .args = NULL - }; + struct iob_context ctx = { .mode = IOB_MODE_SCRIPT, .args = NULL }; int actual = iob_init(&ctx); -int expected = IOB_ERROR_CANNOT_OPEN_FILE; -cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); + int expected = IOB_ERROR_CANNOT_OPEN_FILE; + cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); } Test(IO_Backend, init_cmd) { char *cmd = "iamacommand --yesido"; - struct iob_context ctx = { - .mode = IOB_MODE_CMD, - .args = cmd - }; + struct iob_context ctx = { .mode = IOB_MODE_CMD, .args = cmd }; int actual = iob_init(&ctx); -int expected = 0; -cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); -iob_close(); + int expected = 0; + cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); + iob_close(); } Test(IO_Backend, init_cmd_null) { - struct iob_context ctx = - { - .mode = IOB_MODE_CMD, - .args = NULL - }; + struct iob_context ctx = { .mode = IOB_MODE_CMD, .args = NULL }; int actual = iob_init(&ctx); -int expected = IOB_ERROR_BAD_ARG; -cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); + int expected = IOB_ERROR_BAD_ARG; + cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); } Test(IO_Backend, init_already_init) { char *cmd = "iamacommand --yesido"; - struct iob_context ctx = { - .mode = IOB_MODE_CMD, - .args = cmd - }; + struct iob_context ctx = { .mode = IOB_MODE_CMD, .args = cmd }; iob_init(&ctx); int actual = iob_init(&ctx); int expected = IOB_ERROR_MODULE_ALREADY_INITIALIZED; -cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); -iob_close(); + cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual); + iob_close(); } Test(IO_Backend, close_not_init) From 399d1ed3e15077e277164880caff15d9649143ed Mon Sep 17 00:00:00 2001 From: Matteo Flebus Date: Tue, 27 Jan 2026 21:06:36 +0100 Subject: [PATCH 198/282] 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 a8b4e5d6df529c79faab9bdfb47196494df8e801 Mon Sep 17 00:00:00 2001 From: Jean HERAIL Date: Tue, 27 Jan 2026 21:10:24 +0100 Subject: [PATCH 199/282] fix(execution): reimplemented the builtins --- check_flemme.sh | 2 +- src/execution/execution.c | 25 --------- src/execution/execution_helpers.c | 92 ++++++++++++++++++++++++++++++- 3 files changed, 91 insertions(+), 28 deletions(-) diff --git a/check_flemme.sh b/check_flemme.sh index 0543001..12398ae 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 -fsanitize=address' + run_cmd "Configuring for Linux" ./configure CFLAGS='-std=c99 -Werror -Wall -Wextra -Wvla -g' fi run_cmd "Cleaning build" make clean diff --git a/src/execution/execution.c b/src/execution/execution.c index 82fcacf..90bb8eb 100644 --- a/src/execution/execution.c +++ b/src/execution/execution.c @@ -14,32 +14,7 @@ #include "../utils/ast/ast.h" #include "../utils/hash_map/hash_map.h" -// --- Helpers --- -/** - * @brief converts a linked list of command arguments to an argv array. Don't - * forget to free the result - * - * @param command_list Linked list of command arguments - * @return char** Array of command arguments suitable for execvp. Terminated by - * NULL - */ - -/** - * @brief Tries to execute a builtin command if the command matches a builtin - * - * @param argv Array of command arguments - * @return int Exit status of the builtin command, or -1 if not a builtin - */ - -// --- Execution Core --- - -/** - * @brief Executes a command represented by an ast_command structure - * - * @param command The command to execute - * @return int The exit status of the command - */ // Refactored: delegates to helpers in execution_helpers.c #include "execution_helpers.h" diff --git a/src/execution/execution_helpers.c b/src/execution/execution_helpers.c index 8041a3c..e82cbfe 100644 --- a/src/execution/execution_helpers.c +++ b/src/execution/execution_helpers.c @@ -208,9 +208,97 @@ int exec_ast_redir(struct ast_redir *redir, struct hash_map *vars) return ret; } -// Dummy try_builtin for linking -static int try_builtin(char **argv) +// --- Builtins --- + +static int builtin_echo(char **argv) +{ + bool newline = true; + int i = 1; + + if (argv[1] && strcmp(argv[1], "-n") == 0) + { + newline = false; + i++; + } + + for (; argv[i]; i++) + { + printf("%s", argv[i]); + if (argv[i + 1]) + printf(" "); + } + if (newline) + printf("\n"); + + fflush(stdout); + return 0; +} + + +static int builtin_true(char **argv) { (void)argv; + return 0; +} + +static int builtin_false(char **argv) +{ + (void)argv; + return 1; +} + +static int builtin_exit(char **argv) +{ + int exit_val = 0; + if (argv[1]) + exit_val = atoi(argv[1]); + exit(exit_val); + return exit_val; +} + + +static int builtin_cd(char **argv) +{ + const char *path = argv[1]; + if (!path) + { + path = getenv("HOME"); + if (!path) + { + fprintf(stderr, "cd: HOME not set\n"); + return 1; + } + } + if (chdir(path) != 0) + { + perror("cd"); + return 1; + } + return 0; +} + + +/** + * @brief Tries to execute a builtin command if the command matches a builtin + * + * @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) +{ + if (!argv || !argv[0]) + return 0; + + if (strcmp(argv[0], "echo") == 0) + return builtin_echo(argv); + if (strcmp(argv[0], "true") == 0) + return builtin_true(argv); + if (strcmp(argv[0], "false") == 0) + return builtin_false(argv); + if (strcmp(argv[0], "exit") == 0) + return builtin_exit(argv); + if (strcmp(argv[0], "cd") == 0) + return builtin_cd(argv); + return -1; } From 9a0f9bc6f1965ac683dd9b8208643517b8b4a0a9 Mon Sep 17 00:00:00 2001 From: matteo Date: Wed, 28 Jan 2026 11:34:29 +0100 Subject: [PATCH 200/282] 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 201/282] 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 202/282] 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 203/282] 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 c40e5c2d0f7ee9e8ebaa76e504453b1d3b7ebba6 Mon Sep 17 00:00:00 2001 From: matteo Date: Wed, 28 Jan 2026 19:16:48 +0100 Subject: [PATCH 204/282] 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 205/282] 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 206/282] 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 207/282] 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 208/282] 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 209/282] 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 210/282] 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 211/282] 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 212/282] 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 988d8ef298918656ad00c946cf85212e5e697307 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Thu, 29 Jan 2026 11:29:03 +0000 Subject: [PATCH 213/282] 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 214/282] 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 215/282] 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 216/282] 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 217/282] 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 218/282] 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 219/282] 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 220/282] 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 221/282] 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 222/282] 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 223/282] 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 224/282] 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 225/282] 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 226/282] 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 227/282] 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 228/282] 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 229/282] 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 230/282] 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 231/282] 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 232/282] 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 233/282] 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 234/282] 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 235/282] 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 236/282] 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 237/282] 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 238/282] 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 239/282] 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 240/282] 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 241/282] 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 242/282] 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 243/282] 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 244/282] 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 245/282] 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 246/282] 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 247/282] 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 248/282] 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 249/282] 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 250/282] 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 251/282] 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 252/282] 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 253/282] 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 254/282] 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 255/282] 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 256/282] 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 257/282] 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 258/282] 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 259/282] 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 260/282] 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 261/282] 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 262/282] 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 263/282] 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 264/282] 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 265/282] 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 266/282] 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 267/282] 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 268/282] 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 269/282] 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 270/282] 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 271/282] 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 272/282] 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 273/282] 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 274/282] 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 275/282] 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 276/282] 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 277/282] 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 278/282] 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 279/282] 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 280/282] 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 281/282] 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 282/282] 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.