From dadbebfceba8c00f7a9f5710ffe1339977bcd0a6 Mon Sep 17 00:00:00 2001 From: "william.valenduc" Date: Fri, 16 Jan 2026 21:48:27 +0000 Subject: [PATCH] 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); +}