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"