feat(expansion): expand_var and expand
This commit is contained in:
parent
a16712e802
commit
34c741d86e
4 changed files with 276 additions and 14 deletions
|
|
@ -6,6 +6,9 @@
|
|||
#include <string.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
#include <stddef.h>
|
||||
|
||||
#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 */
|
||||
|
|
|
|||
220
tests/unit/expansion/expand.c
Normal file
220
tests/unit/expansion/expand.c
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
#define _POSIX_C_SOURCE 200809L
|
||||
#include <criterion/criterion.h>
|
||||
#include <criterion/new/assert.h>
|
||||
#include <criterion/redirect.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
|
@ -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";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue