/* ************************************************************************** */ /* */ /* ::: :::::::: */ /* parse_command.c :+: :+: :+: */ /* +:+ +:+ +:+ */ /* By: mcolonna +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2024/04/24 13:47:40 by mcolonna #+# #+# */ /* Updated: 2024/06/25 15:39:55 by mcolonna ### ########.fr */ /* */ /* ************************************************************************** */ #include "include.h" #define SYMBOL_CHARS "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_" // Return a pointer to the variable 'name'. // If the variable doesn't exist, it's created with an empty value. // (An undefined variable is considered of an empty value) static t_variable *variables_find(t_list *variables, const char *name) { t_list_element *el; t_variable *r; el = variables->first; while (el) { r = (t_variable *)el->value; if (str_eq(r->name, name)) return (r); el = el->next; } r = mem_alloc(fatal_error, variables->mc, sizeof(t_variable)); r->name = str_dup(fatal_error, variables->mc, name); r->value = ""; list_add(fatal_error, variables, r); return (r); } // Set a variable to a new value. void variables_set(t_list *variables, const t_variable var) { variables_find(variables, var.name)->value = str_dup(fatal_error, variables->mc, var.value); } // Get the value of a variable from its name. const char *variables_get(t_list *variables, const char *name) { return (variables_find(variables, name)->value); } // Ask the user for data and set readfd to the fd which will receive this // data. // Returns 'errno' on error. static int heredoc(t_memclass mc, int *readfd, const char *eof) { const t_memclass mc_in = mem_subclass(fatal_error, mc); int outpipe[2]; char *line; char *to_free; const t_const_string eof_line = str_join(fatal_error, mc_in, eof, "\n"); if (pipe(outpipe) == -1) return (minishell_error("errno"), errno); *readfd = outpipe[0]; while (true) { line = cool_readline("\e[38;5;33m( 'o')> \e[0m"); to_free = line; if (!line) line = ""; if (str_eq(line, eof) || str_eq(line, eof_line)) break ; print_str(err_remember, outpipe[1], line); print_str(err_remember, outpipe[1], "\n"); free(to_free); if (err_get()) return (minishell_error("errno"), errno); } mem_freeall(mc_in); if (close(outpipe[1]) == -1) return (minishell_error("errno"), errno); return (0); } // To call when a parse error occurs. // Always returns 1 (parse error's status). static int parse_error(const char *msg) { print_str(fatal_error, 2, "parse error: "); print_line(fatal_error, 2, msg); return (1); } // Global variables for all the parsing functions typedef struct s_parsing_args { t_memclass mc; // mc freed given to parse_command t_command r; // t_command that parse_command will return t_stream stream; // stream reading the command string t_list calls; // list of calls bool got_first_call; // already got at least the first program call? const char *heredoc; // EOF line for heredoc. NULL if no heredoc t_list *variables; // list of current variables t_env *env; } t_parsing_args; // Skip blank characters static void skip_blank(t_stream *stream) { stream_skip(stream, " \r\n\t"); } // Add 'c' at the end of 'str' and return the result. // Also mem_free() 'str'. static char *str_addchar(t_memclass mc, const char *str, char c) { char *s; char *r; s = str_dup(fatal_error, mc, "-"); s[0] = c; r = str_join(fatal_error, mc, str, s); mem_free(s); mem_free((void *)str); return (r); } // Read until a character is in the charset or is '\0' // and append the string to dest. static void read_until(t_parsing_args *args, const char **dest, const char *stop_charset) { while (stream_read(&args->stream) && !char_isin(stream_read(&args->stream), stop_charset) ) *dest = str_addchar(args->mc, *dest, stream_pop(&args->stream)); } // Read until a character isn't in the charset or is '\0' // and append the string to dest. void read_only(t_parsing_args *args, const char **dest, const char *charset) { while (char_isin(stream_read(&args->stream), charset)) *dest = str_addchar(args->mc, *dest, stream_pop(&args->stream)); } // Read the value of the variable and append it to dest. // The stream must point to the first char of the variable name. // Write a parse error and change args->r.error if no variable name. static void read_variable_value(t_parsing_args *args, const char **dest) { const char *name; const char *value; const char *tmp; name = NULL; if (stream_read(&args->stream) == '?') { stream_pop(&args->stream); value = str_inttostr(fatal_error, args->mc, args->env->errorstatus); } else { name = str_dup(fatal_error, args->mc, ""); read_only(args, &name, SYMBOL_CHARS); if (str_eq(name, "")) { args->r.error = parse_error("variable name expected"); return ; } value = variables_get(args->variables, name); } tmp = *dest; *dest = str_join(fatal_error, args->mc, *dest, value); mem_free((char *)name); mem_free((char *)tmp); } // Read a string without quotes. // Append it to dest. // Change args->r.error if error. static void read_string_noquote(t_parsing_args *args, const char **dest, const char *stop_charset) { const char *real_stop_charset = str_join(fatal_error, args->mc, stop_charset, "$"); while (stream_read(&args->stream) && !char_isin(stream_read(&args->stream), stop_charset)) { read_until(args, dest, real_stop_charset); if (stream_read(&args->stream) == '$') { stream_pop(&args->stream); read_variable_value(args, dest); if (args->r.error) return ; } } } // Read a string with ' quotes. // Append it to dest. // Return false if it is not a ' quoted string. // If parse error, return true and change args.r.error accordingly. static bool read_string_quote(t_parsing_args *args, const char **dest) { if (stream_read(&args->stream) != '\'') return (false); stream_pop(&args->stream); read_until(args, dest, "'"); if (!stream_read(&args->stream)) { args->r.error = parse_error("EOF unexpected"); return (true); } stream_pop(&args->stream); return (true); } // Read a string with " quotes. // Append it to dest. // Return false if it is not a " quoted string. // If parse error, return true and change args.r.error accordingly. static bool read_string_doublequote(t_parsing_args *args, const char **dest) { if (stream_read(&args->stream) != '"') return (false); stream_pop(&args->stream); while (stream_read(&args->stream) && stream_read(&args->stream) != '"') { read_until(args, dest, "\"$"); if (stream_read(&args->stream) == '$') { stream_pop(&args->stream); read_variable_value(args, dest); if (args->r.error) return (true); } } if (!stream_read(&args->stream)) { args->r.error = parse_error("EOF unexpected"); return (true); } stream_pop(&args->stream); return (true); } // Read the string, stop if the char is in stop_charset. // Possible syntaxes: // - /[^(stop_charset)]+/ // - /'.*'/ // - /".*"/ // Change args->r.error and return NULL if error. static const char *read_string(t_parsing_args *args, const char *stop_charset) { const char *str; char *const real_stop_charset = str_join(fatal_error, args->mc, stop_charset, " \r\n\t"); if (!stream_read(&args->stream)) return (NULL); str = str_dup(fatal_error, args->mc, ""); while (!args->r.error && stream_read(&args->stream) && !char_isin(stream_read(&args->stream), real_stop_charset)) { if (!read_string_quote(args, &str) && !read_string_doublequote(args, &str)) read_string_noquote(args, &str, real_stop_charset); } mem_free(real_stop_charset); if (args->r.error) return (NULL); return (str); } // Get a program call (program names & its arguments) until stop_charset. // Change args accordingly. // On success, return 0. On error, return the error status. static int read_call(t_parsing_args *args, const char *stop_charset) { t_call *r; t_list arguments; const char *str; arguments = list_createempty(args->mc); while (stream_read(&args->stream) && !char_isin(stream_read(&args->stream), stop_charset)) { str = read_string(args, stop_charset); if (args->r.error) return (args->r.error); if (!str) return (parse_error("EOF unexpected")); list_add(fatal_error, &arguments, (char *)str); skip_blank(&args->stream); } r = mem_alloc(fatal_error, args->mc, sizeof(t_call)); r->program = (char *)list_get(err_remember, &arguments, 0); if (err_get()) return (parse_error("program name expected")); r->argc = list_getsize(&arguments); r->argv = (char *const *)list_convert(fatal_error, args->mc, &arguments); list_add(fatal_error, &args->calls, (t_call *)r); args->got_first_call = true; return (0); } // Read a '<' or '<<' redirection from the file. // Change args accordingly. // On success, return 0. On error, return the error status. static int read_inputfile(t_parsing_args *args, const char *stop_charset) { const char *str; bool heredoc; if (args->r.input_fd != 0) return (parse_error("several input files")); if (!stream_read(&args->stream)) return (parse_error("EOF unexpected")); heredoc = stream_read(&args->stream) == '<'; if (heredoc) stream_pop(&args->stream); skip_blank(&args->stream); str = read_string(args, stop_charset); if (args->r.error) return (args->r.error); if (!str) return (parse_error("EOF unexpected")); if (heredoc) args->heredoc = str; else { args->r.input_fd = open(str, O_RDONLY); if (args->r.input_fd == -1) return (perror(str), errno); } return (0); } // Read a '>' or '>>' redirection from the file. // Change args accordingly. // On success, return 0. On error, return the error status. static int read_outputfile(t_parsing_args *args, const char *stop_charset) { const char *str; int flag; if (args->r.output_fd != 1) return (parse_error("several output files")); if (!stream_read(&args->stream)) return (parse_error("EOF unexpected")); flag = O_TRUNC; if (stream_read(&args->stream) == '>') { stream_pop(&args->stream); flag = O_APPEND; } skip_blank(&args->stream); str = read_string(args, stop_charset); if (args->r.error) return (args->r.error); if (!str) return (parse_error("EOF unexpected")); args->r.output_fd = open( str, O_WRONLY | O_CREAT | flag, 0666); if (args->r.output_fd == -1) return (perror(str), errno); return (0); } // (extension of read_element) static void read_element2(t_parsing_args *args, int *error, char c) { stream_pop(&args->stream); skip_blank(&args->stream); if (c == '|') { if (!args->got_first_call) *error = parse_error("pipe before any call to a program"); else *error = read_call(args, "<>|"); } else if (c == '>') *error = read_outputfile(args, "<>|"); else if (c == '<') *error = read_inputfile(args, "<>|"); else fatal_error_msg("internal error u.u"); } // Read an element from the stream (a call to a program or a redirection) // Change args accordingly. // On success, return 0. On error, return the error status. static int read_element(t_parsing_args *args) { char c; int error; error = 0; while (!error && stream_read(&args->stream)) { c = stream_read(&args->stream); if (char_isin(c, "|><")) read_element2(args, &error, c); else if (!args->got_first_call) error = read_call(args, "<>|"); else return (parse_error("'|', '>' or '<'' expected")); skip_blank(&args->stream); } return (error); } // Check if the command is a variable definition. // If possible, change 'variables' accordingly and return true. // If not, return false. static bool parse_variable_set_command( t_parsing_args *args, const char *command, t_list *variables) { t_variable var; streamstr_init(&args->stream, command); skip_blank(&args->stream); var.name = str_dup(fatal_error, args->mc, ""); read_only(args, &var.name, SYMBOL_CHARS); if (str_len(var.name) == 0 || stream_pop(&args->stream) != '=') return (false); var.value = read_string(args, "<>|/"); if (args->r.error) return (false); if (!var.value) var.value = ""; skip_blank(&args->stream); if (stream_read(&args->stream)) return (false); variables_set(variables, var); return (true); } // Read the command from the stream, and define args and args->r accordingly. static void read_command(t_parsing_args *args) { while (!args->r.error && stream_read(&args->stream)) { args->r.error = read_element(args); skip_blank(&args->stream); } if (!args->r.error) args->r.calls = (t_call *)list_convert_type( fatal_error, args->mc, &args->calls, sizeof(t_call)); } // Create a t_parsing_args shared between all the parse_command subfunctions. // Define every fields but .stream static t_parsing_args init_parsing_args(t_env *env, const t_memclass mc, t_list *variables) { const t_parsing_args r = { .r = { .error = 0, .empty = false, .input_fd = 0, .output_fd = 1, }, .mc = mc, .calls = list_createempty(mc), .got_first_call = false, .heredoc = NULL, .variables = variables, .env = env, }; return (r); } // - If the command string contains call(s), return the t_command to execute. // - If necessary, input the user for the heredoc. // - If the command string is a variable definition, define it and return // an "empty" t_command. // - If the command string is empty (or blank), return an "empty" t_command. // - If there is any error, return a t_command with .error != 0. t_command parse_command(t_env *env, const char *command) { t_parsing_args args; args = init_parsing_args(env, env->mc_command, env->variables); if (parse_variable_set_command(&args, command, env->variables)) { args.r.empty = true; return (args.r); } streamstr_init(&args.stream, command); skip_blank(&args.stream); if (!stream_read(&args.stream)) { args.r.empty = true; return (args.r); } read_command(&args); if (!args.r.error && args.heredoc) args.r.error = heredoc(env->mc_command, &args.r.input_fd, args.heredoc); return (args.r); }