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
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
#include "expansion.h"
|
||||||
|
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
@ -115,6 +117,40 @@ static bool expand_var(char **str, size_t pos, const struct hash_map *vars)
|
||||||
return false;
|
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)
|
bool expand(struct ast_command *command, const struct hash_map *vars)
|
||||||
{
|
{
|
||||||
if (command == NULL)
|
if (command == NULL)
|
||||||
|
|
@ -122,34 +158,42 @@ bool expand(struct ast_command *command, const struct hash_map *vars)
|
||||||
|
|
||||||
char *str;
|
char *str;
|
||||||
size_t len;
|
size_t len;
|
||||||
bool in_quotes;
|
enum quote_state quotes;
|
||||||
struct list *l = command->command;
|
struct list *l = command->command;
|
||||||
|
|
||||||
while (l != NULL)
|
while (l != NULL)
|
||||||
{
|
{
|
||||||
in_quotes = false;
|
quotes = NO_QUOTE;
|
||||||
str = (char *)l->data;
|
str = (char *)l->data;
|
||||||
len = strlen(str);
|
len = strlen(str);
|
||||||
|
|
||||||
for (size_t i = 0; str[i] != 0; i++)
|
for (size_t i = 0; str[i] != 0; i++)
|
||||||
{
|
{
|
||||||
if (str[i] == '\'')
|
if (str[i] == '\'' || str[i] == '\"')
|
||||||
{
|
{
|
||||||
// remove single quote
|
if (quotes == NO_QUOTE)
|
||||||
in_quotes = !in_quotes;
|
{
|
||||||
|
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);
|
memmove(str + i, str + i + 1, strlen(str + i + 1) + 1);
|
||||||
i--;
|
i--;
|
||||||
}
|
}
|
||||||
else if (in_quotes)
|
else if (quotes == SINGLE_QUOTE)
|
||||||
{
|
{
|
||||||
continue; // do nothing
|
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]))
|
else if (str[i] == '$' && str[i + 1] != 0 && !isspace(str[i + 1]))
|
||||||
{
|
{
|
||||||
// variable expansion
|
// variable expansion
|
||||||
|
|
@ -161,21 +205,20 @@ bool expand(struct ast_command *command, const struct hash_map *vars)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (in_quotes)
|
// if (quotes != NO_QUOTE)
|
||||||
{
|
// {
|
||||||
// error: quote not closed
|
// // error: quote not closed
|
||||||
fprintf(stderr, "Error: quote not closed in string: %s\n", str);
|
// fprintf(stderr, "Error: quote not closed in string: %s\n", str);
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (len != strlen(str))
|
if (len != strlen(str))
|
||||||
{
|
{
|
||||||
char *new_str = realloc(str, strlen(str) + 1);
|
char *new_str = realloc(str, strlen(str) + 1);
|
||||||
if (new_str == NULL)
|
if (new_str == NULL)
|
||||||
{
|
|
||||||
// error: realloc fail
|
// error: realloc fail
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
l->data = new_str;
|
l->data = new_str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,13 @@
|
||||||
#include "../utils/ast/ast.h"
|
#include "../utils/ast/ast.h"
|
||||||
#include "../utils/hash_map/hash_map.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 '$'.
|
* Parse a variable from a string starting with '$'.
|
||||||
* @param str The input string starting with '$'. It must start 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);
|
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.
|
* Expand variables in an AST command using the provided variable map.
|
||||||
* @param command The AST command to expand.
|
* @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/criterion.h>
|
||||||
#include <criterion/new/assert.h>
|
#include <criterion/new/assert.h>
|
||||||
#include <criterion/redirect.h>
|
|
||||||
|
|
||||||
#include "../../../src/expansion/expansion.h"
|
#include "../../../src/expansion/expansion.h"
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue