42_minishell/src/parse_command.c
2024-06-21 16:06:20 +02:00

505 lines
14 KiB
C

/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* parse_command.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: mcolonna <marvin@42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2024/04/24 13:47:40 by mcolonna #+# #+# */
/* Updated: 2024/06/21 15:44:51 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 = 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 = 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];
t_const_string line;
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");
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");
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;
tmp = 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) != '"')
{
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);
}