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";