42sh/src/expansion/expansion.c
2026-01-19 17:55:16 +00:00

180 lines
4.1 KiB
C

#define _POSIX_C_SOURCE 200809L
#include <ctype.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#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)
{
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;
}
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;
char rnd_str[10]; // max 5 digits + null terminator
if (strcmp(var_name, "RANDOM") == 0)
{
short rnd = short_random();
snprintf(rnd_str, 10, "%d", rnd);
value = rnd_str;
}
else
{
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;
char *str;
size_t len;
bool in_quotes;
struct list *l = command->command;
while (l != NULL)
{
in_quotes = false;
str = (char *)l->data;
len = strlen(str);
for (size_t i = 0; str[i] != 0; i++)
{
if (str[i] == '\'')
{
// remove quote
in_quotes = !in_quotes;
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))
{
char *new_str = realloc(str, strlen(str) + 1);
if (new_str == NULL)
{
// error: realloc fail
return NULL;
}
l->data = new_str;
}
l = l->next;
}
return command;
}