summaryrefslogtreecommitdiff
path: root/42sh/src/lexer/expansion.c
diff options
context:
space:
mode:
Diffstat (limited to '42sh/src/lexer/expansion.c')
-rw-r--r--42sh/src/lexer/expansion.c386
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;
+}