diff options
| author | Martial Simon <msimon_fr@hotmail.com> | 2025-09-15 01:07:58 +0200 |
|---|---|---|
| committer | Martial Simon <msimon_fr@hotmail.com> | 2025-09-15 01:07:58 +0200 |
| commit | 967be9e750221ab2ab783f95df79bb26d290a45e (patch) | |
| tree | 6802900a5e975f9f68b169f0f503f040056d6952 /42sh/src/lexer/expansion.c | |
Diffstat (limited to '42sh/src/lexer/expansion.c')
| -rw-r--r-- | 42sh/src/lexer/expansion.c | 386 |
1 files changed, 386 insertions, 0 deletions
diff --git a/42sh/src/lexer/expansion.c b/42sh/src/lexer/expansion.c new file mode 100644 index 0000000..d648009 --- /dev/null +++ b/42sh/src/lexer/expansion.c @@ -0,0 +1,386 @@ +#include <ctype.h> +#include <lexer/expansion.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "utils/env.h" + +#define BUFFER_SIZE 51 + +#define ERROR_CHECK(MSG) \ + if (str[*i] == '\0') \ + { \ + string_free(input); \ + return clean_exit(MSG, error); \ + } + +#define DQUOTEESCAPED "$\\\n`\"" +// Identifies double-quote escaped characters +#define ISDQUOTEESCAPED(C) strchr(DQUOTEESCAPED, (int)C) + +#define SPECIAL "@*?$#" +// Identifies special variable names +#define ISSPECIAL(C) strchr(SPECIAL, (int)C) + +// error = 1 indicates a missing } +// error = 0 and NULL returned indicates an unrecognized var name +// error = 0 and anything else than NULL returned is the var name +static struct string *get_var_name(char *str, int *error) +{ + struct string *res = string_create(NULL); + int i = 0; + if (str[i] == '{') + { + while (str[i] && str[i] != '}') + { + if (str[i] == '\\' && str[i + 1] == '}') + i++; + string_pushc(res, str[i]); + i++; + } + if (!str[i]) + { + string_free(res); + *error = 1; + return NULL; + } + *error = 0; + return res; + } + else if (ISSPECIAL(str[i]) || isdigit(str[i])) + { + string_pushc(res, str[i]); + *error = 0; + return res; + } + else if (!isalpha(str[i])) + { + *error = 0; + string_free(res); + return NULL; + } + else + { + while (isalnum(str[i]) || str[i] == '_') + string_pushc(res, str[i++]); + return res; + } +} + +// Useful to automate the same exit process accross the few functions +// that often do this +static struct string *clean_exit(char *txt, int *error) +{ + fprintf(stderr, "%s", txt); + *error = 1; + return NULL; +} + +// Creates the fork() in order to make a subshell for the command expansion +// (cf section 2.6.3 of the SCL) +// Only called in expand_substitution() +static struct string *fork_subshell(struct string *input, int j, char *str, + int *error) +{ + int fds[2]; + if (pipe(fds) == -1) + { + return clean_exit("pipe() faild to create 2 fds\n", error); + } + + struct string *output; + pid_t child = fork(); + + if (child == -1) + { + // Fork not working + return clean_exit("fork() faild to produce a children\n", error); + } + else if (child) + { + // Parent process + close(fds[1]); + + output = string_create(NULL); + + char buff[BUFFER_SIZE]; + + buff[BUFFER_SIZE - 1] = 0; + + int r; + + int status; + waitpid(child, &status, 0); + + // Check if the child terminated normally + if (!WIFEXITED(status)) + { + close(fds[0]); + string_free(output); + return clean_exit("Child process failed miserably\n", error); + } + + while ((r = read(fds[0], buff, 50))) + { + buff[r] = 0; + if (!string_pushstr(output, buff)) + { + string_free(output); + return clean_exit("Failed to transfer from pipe\n", error); + } + } + + close(fds[0]); + return output; + } + else + { + // Child process + str += j; + close(fds[0]); + + if (dup2(fds[1], STDOUT_FILENO) == -1) + { + // We are forced to return NULL + // how are we going to know if something wnet wrong ? + exit(-1); + } + + _process_input(input); + + close(fds[1]); + exit(1); + } +} + +static int look_for_next(char *in, int i, char c) +{ + int escaped = 0; + while (in[i] && (in[i] != c || (escaped && in[i] == c))) + { + if (in[i] == '\\') + escaped ^= 1; + else + escaped = 0; + i++; + } + return i; +} + +// Removes all the <newline> characters at the end of the string obtained by +// the command substitution (Also section 2.6.3 of the SCL) +static void trimming_newline(struct string *txt) +{ + if (!txt->length) + { + return; + } + char *str = txt->data; + size_t len = txt->length; + + size_t i = len - 1; + while (str[i] == '\n') + { + str[i] = 0; + len--; + } + + // I am scared and I know this isn't useful but just in case + txt->data = str; + txt->length = len; +} + +// Performs the substitution (forks and get back the stdout) +struct string *expand_substitution(char *str, int *i, int *error, char delim) +{ + int j = *i; + struct string *input = string_create(NULL); + if (input == NULL) + { + return clean_exit("Could not create string for input\n", error); + } + + if (delim == '`') + { + *i = look_for_next(str, j, delim); + + ERROR_CHECK("Could not match `\n") + + str[*i] = '\0'; + } + // Sadly, there is no other way around this + else + { + int escaped = 0; + int par_count = 1; + + while (str[*i] != 0) + { + if (str[*i] == '\\') + { + escaped ^= 1; + } + else if (str[*i] == '\'') + { + (*i) += 1; + while (str[*i] != '\0' && str[*i] != '\'') + { + (*i) += 1; + } + ERROR_CHECK("Missing matching '\n") + } + else if ((str[*i] == '\"' || str[*i] == '`') && !escaped) + { + (*i) += 1; + *i = look_for_next(str, *i, str[(*i) - 1]); + + ERROR_CHECK("Missing matching `\n") + } + else if (str[*i] == '(' && !escaped) + { + par_count++; + } + else if (str[*i] == delim && !escaped) + { + par_count--; + if (!par_count) + { + str[*i] = 0; + break; + } + } + else + { + escaped = 0; + } + + (*i)++; + } + } + + string_pushstr(input, str + j); + struct string *output = fork_subshell(input, j, str, error); + string_free(input); + + trimming_newline(output); + str[*i] = delim; + return output; +} + +static int expand_var(struct string *res, char *input, int i) +{ + // Will only be called after a '$' was read + + int e = 0; + struct string *name = get_var_name(input + i + 1, &e); + + if (e) + { + string_free(name); + fprintf(stderr, "Missing } in variable expansion\n"); + return -1; + } + else if (name == NULL) + { + string_pushc(res, input[i]); + i++; + } + else + { + // Get the value associated to the name + char *value = env_get(name->data); + // Concatenate the strings if the variable has a value + if (value) + string_pushstr(res, value); + if (input[++i] == '{') + i += 2; + i += name->length; + string_free(name); + } + return i; +} + +static int expand_dquotes(char *input, int i, struct string *res) +{ + while (input[i] != '"') + { + if (input[i] == '$') + { + if ((i = i + expand_var(res, input, i)) == -1) + { + string_free(res); + return -1; + } + continue; + } + if ((input[i] == '`' || (input[i] == '$' && input[i + 1] == '('))) + { + int e = 0; + i += (input[i] == '$' ? 2 : 1); + struct string *output = + expand_substitution(input, &i, &e, input[i]); + if (!e) + { + string_free(res); + return -1; + } + + // +1 for the last parenthesis/backquote + i++; + string_catenate(res, output); + continue; + } + if (input[i] == '\\' && ISDQUOTEESCAPED(input[i + 1])) + i++; + string_pushc(res, input[i]); + i++; + } + return i; +} + +struct string *expand_word(struct string *word) +{ + char *input = word->data; + int escape = 0; + struct string *res = string_create(NULL); + for (int i = 0; input[i]; i++) + { + if (!escape && input[i] == '\'') + { + while (input[++i] != '\'') + string_pushc(res, input[i]); + } + else if (!escape && input[i] == '"') + { + i++; + + if ((i = expand_dquotes(input, i, res)) == -1) + return NULL; + } + else if (!escape && input[i] == '\\') + escape ^= 1; + else + { + // We don't care if we are after a backslash, we just include this + // char + if (input[i] == '$' && !escape) + { + if ((i = i + expand_var(res, input, i)) == -1) + { + string_free(res); + return NULL; + } + continue; + } + string_pushc(res, input[i]); + escape = 0; + } + } + + // string_free(word); + return res; +} |
