feat(expansion): parse_subshell_str and tests
This commit is contained in:
parent
2e7bcd7fa1
commit
bb7d4b772e
4 changed files with 151 additions and 21 deletions
|
|
@ -1,4 +1,6 @@
|
|||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "expansion.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
72
tests/unit/expansion/parse_subshell.c
Normal file
72
tests/unit/expansion/parse_subshell.c
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
#include <criterion/criterion.h>
|
||||
#include <criterion/new/assert.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
#include <criterion/criterion.h>
|
||||
#include <criterion/new/assert.h>
|
||||
#include <criterion/redirect.h>
|
||||
|
||||
#include "../../../src/expansion/expansion.h"
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue