From 38170c7211afb77ea4cc822e110a43f6937101a3 Mon Sep 17 00:00:00 2001 From: mcolonna Date: Mon, 27 May 2024 13:21:40 +0200 Subject: [PATCH] [ADD] manage variables * Also: - dev: - add docu - add fixes in TODO - add FIXME --- .gitignore | 1 + dev/TODO | 6 +- include/include.h | 11 +- src/main.c | 20 +-- src/parse_command.c | 312 +++++++++++++++++++++++++++++++++++--------- 5 files changed, 277 insertions(+), 73 deletions(-) diff --git a/.gitignore b/.gitignore index 8979ded..6c77ac2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /minishell +/dev/*.srctrl* # Prerequisites *.d diff --git a/dev/TODO b/dev/TODO index fbfb4d3..d1c50ca 100644 --- a/dev/TODO +++ b/dev/TODO @@ -6,7 +6,6 @@ - signals - ^C, ^D, ^\ - variables - - normal variables - environment variables - $? - builtins: @@ -17,3 +16,8 @@ - unset (with no options) - env (with no options or arguments) - exit (with no options) +- fix + - `$ |` throws 2 errors + - empty variables aren't freed + +(also search TODO and FIXME in the files) diff --git a/include/include.h b/include/include.h index 1a015d2..f2c372f 100644 --- a/include/include.h +++ b/include/include.h @@ -6,7 +6,7 @@ /* By: jschaft +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2024/04/23 14:15:12 by mcolonna #+# #+# */ -/* Updated: 2024/05/16 18:18:16 by mcolonna ### ########.fr */ +/* Updated: 2024/06/05 17:45:33 by mcolonna ### ########.fr */ /* */ /* ************************************************************************** */ @@ -35,6 +35,12 @@ const char *ask_command(t_memclass mc); ///// PARSE_COMMAND ///// +typedef struct s_variable +{ + const char *name; + const char *value; +} t_variable; + // Represents a call to a program (the program name and its arguments) typedef struct s_call { @@ -62,7 +68,8 @@ typedef struct s_pipes // Return the t_command representing the command given by the user. // If error, return a t_command wth the value .error = true. -t_command parse_command(const t_memclass mc, const char *command); +t_command parse_command(const t_memclass mc, const char *command, + t_list *variables); ///// EXECUTE COMMAND ///// diff --git a/src/main.c b/src/main.c index 8efcceb..86bebf5 100644 --- a/src/main.c +++ b/src/main.c @@ -6,7 +6,7 @@ /* By: jschaft +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2024/04/23 14:33:45 by mcolonna #+# #+# */ -/* Updated: 2024/05/17 14:39:43 by mcolonna ### ########.fr */ +/* Updated: 2024/06/05 17:45:43 by mcolonna ### ########.fr */ /* */ /* ************************************************************************** */ @@ -15,13 +15,13 @@ t_memclass g_mc; // Execute a command from a string. -static int do_command(char *const envp[], const char *str) +static int do_command(char *const envp[], t_list *variables, const char *str) { const t_memclass mc = mem_subclass(fatal_error, g_mc); t_command command; int r; - command = parse_command(mc, str); + command = parse_command(mc, str, variables); if (command.error || command.empty) return (command.error); r = execute_command(mc, command, envp); @@ -29,15 +29,16 @@ static int do_command(char *const envp[], const char *str) return (r); } -static void start(char *const envp[]) +static void start(char *const envp[], t_list *variables) { - do_command(envp, "clear"); - do_command(envp, "cat ./header"); - do_command(envp, "echo"); + do_command(envp, variables, "clear"); + do_command(envp, variables, "cat ./header"); + do_command(envp, variables, "echo"); } int main(const int argc, const char *argv[], char *const envp[]) { + t_list variables; t_memclass mc; const char *command_str; int errorstatus; @@ -46,13 +47,14 @@ int main(const int argc, const char *argv[], char *const envp[]) (void)argv; g_mc = NULL; g_mc = mem_newclass(fatal_error); - start(envp); + variables = list_createempty(g_mc); + start(envp, &variables); errorstatus = 0; while (true) { mc = mem_subclass(fatal_error, g_mc); command_str = ask_command(mc); - do_command(envp, command_str); + do_command(envp, &variables, command_str); } return (errorstatus); } diff --git a/src/parse_command.c b/src/parse_command.c index d29ca12..a4af678 100644 --- a/src/parse_command.c +++ b/src/parse_command.c @@ -6,12 +6,49 @@ /* By: mcolonna +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2024/04/24 13:47:40 by mcolonna #+# #+# */ -/* Updated: 2024/05/17 14:41:50 by mcolonna ### ########.fr */ +/* Updated: 2024/06/06 15:58:38 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. @@ -58,12 +95,13 @@ static int parse_error(const char *msg) // Global variables for all the parsing functions typedef struct s_parsing_args { - t_memclass mc; - t_command r; - t_stream stream; - t_list calls; - bool got_first_call; // got at least the first program call? - const char *heredoc; + 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_parsing_args; // Skip blank characters @@ -87,37 +125,139 @@ static char *str_addchar(t_err err, t_memclass mc, const char *str, char c) return (r); } -// Read the string, stop if the char is in stop_charset -// TODO variables if using " -static const char *get_string(t_parsing_args *args, const char *stop_charset) +// 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) { - char quote; - const t_memclass mc = mem_subclass(fatal_error, args->mc); - const char *stop_charset_2; - char *str; - - quote = '\0'; - if (char_isin(stream_read(&args->stream), "\"'")) - quote = stream_pop(&args->stream); - if (!quote) - stop_charset_2 = str_join(fatal_error, mc, stop_charset, " \n"); - else - stop_charset_2 = str_addchar(fatal_error, mc, - str_dup(fatal_error, mc, ""), quote); - str = str_dup(fatal_error, mc, ""); while (stream_read(&args->stream) - && !char_isin(stream_read(&args->stream), stop_charset_2)) - str = str_addchar(fatal_error, mc, str, stream_pop(&args->stream)); - if (quote) - if (!stream_pop(&args->stream)) - return (NULL); - str = str_dup(fatal_error, args->mc, str); - mem_freeall(mc); + && !char_isin(stream_read(&args->stream), stop_charset) + ) + *dest = str_addchar(fatal_error, 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(fatal_error, 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. +// FIXME Manage error (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 = str_dup(fatal_error, args->mc, ""); + read_only(args, &name, SYMBOL_CHARS); + value = variables_get(args->variables, name); + mem_free((char *)name); + tmp = *dest; + *dest = str_join(fatal_error, args->mc, *dest, value); + mem_free((char *)tmp); +} + +// Read a string without quotes. +// Append it to dest. +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); + } + } +} + +// 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 (!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)]+/ +// - /'.*'/ +// - /".*"/ +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"); + + 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); return (str); } // Get a program call (program names & its arguments) until stop_charset. -static int get_call(t_parsing_args *args, const char *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; @@ -127,7 +267,7 @@ static int get_call(t_parsing_args *args, const char *stop_charset) while (stream_read(&args->stream) && !char_isin(stream_read(&args->stream), stop_charset)) { - str = get_string(args, stop_charset); + str = read_string(args, stop_charset); if (!str) return (parse_error("EOF unexpected")); list_add(fatal_error, &arguments, @@ -146,7 +286,10 @@ static int get_call(t_parsing_args *args, const char *stop_charset) return (0); } -static int get_inputfile(t_parsing_args *args, const char *stop_charset) +// 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; @@ -159,7 +302,7 @@ static int get_inputfile(t_parsing_args *args, const char *stop_charset) if (heredoc) stream_pop(&args->stream); skip_blank(&args->stream); - str = get_string(args, stop_charset); + str = read_string(args, stop_charset); if (!str) return (parse_error("EOF unexpected")); if (heredoc) @@ -173,7 +316,10 @@ static int get_inputfile(t_parsing_args *args, const char *stop_charset) return (0); } -static int get_outputfile(t_parsing_args *args, const char *stop_charset) +// 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; @@ -189,7 +335,7 @@ static int get_outputfile(t_parsing_args *args, const char *stop_charset) flag = O_APPEND; } skip_blank(&args->stream); - str = get_string(args, stop_charset); + str = read_string(args, stop_charset); if (!str) return (parse_error("EOF unexpected")); args->r.output_fd = open( @@ -199,7 +345,8 @@ static int get_outputfile(t_parsing_args *args, const char *stop_charset) return (0); } -static void get_element2(t_parsing_args *args, int *error, char c) +// (extension of read_element) +static void read_element2(t_parsing_args *args, int *error, char c) { stream_pop(&args->stream); skip_blank(&args->stream); @@ -207,19 +354,20 @@ static void get_element2(t_parsing_args *args, int *error, char c) { if (!args->got_first_call) *error = parse_error("'|', '>' or '<' expected"); - *error = get_call(args, "<>|"); + *error = read_call(args, "<>|"); } else if (c == '>') - *error = get_outputfile(args, "<>|"); + *error = read_outputfile(args, "<>|"); else if (c == '<') - *error = get_inputfile(args, "<>|"); + *error = read_inputfile(args, "<>|"); else fatal_error_msg("internal error u.u"); } -// Read an element (a call to a program, a '< FILE' or a '> FILE') +// 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 get_element(t_parsing_args *args) +static int read_element(t_parsing_args *args) { char c; int error; @@ -229,9 +377,9 @@ static int get_element(t_parsing_args *args) { c = stream_read(&args->stream); if (char_isin(c, "|><")) - get_element2(args, &error, c); + read_element2(args, &error, c); else if (!args->got_first_call) - error = get_call(args, "<>|"); + error = read_call(args, "<>|"); else return (parse_error("'|', '>' or '<'' expected")); skip_blank(&args->stream); @@ -239,31 +387,82 @@ static int get_element(t_parsing_args *args) return (error); } -t_parsing_args init_parsing_args(const t_memclass mc) +// Check if the command is a variable definition. +// If possible, change 'variables' accordingly and return true. +// If not, return false. +// FIXME read_string not possible. +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, "<>|/"); + 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(const t_memclass mc, + t_list *variables) { const t_parsing_args r = { - .mc = mc, .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, }; return (r); } -t_command parse_command(const t_memclass mc, const char *command) +// - 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. +// FIXME error with heredoc and parse error +t_command parse_command(const t_memclass mc, const char *command, + t_list *variables) { t_parsing_args args; - int error; - error = 0; - args = init_parsing_args(mc); + args = init_parsing_args(mc, variables); + if (parse_variable_set_command(&args, command, variables)) + { + args.r.empty = true; + return (args.r); + } streamstr_init(&args.stream, command); skip_blank(&args.stream); if (!stream_read(&args.stream)) @@ -271,16 +470,7 @@ t_command parse_command(const t_memclass mc, const char *command) args.r.empty = true; return (args.r); } - while (!error && stream_read(&args.stream)) - { - error = get_element(&args); - skip_blank(&args.stream); - } - if (!error) - args.r.calls = (t_call *)list_convert_type( - fatal_error, args.mc, &args.calls, sizeof(t_call)); - if (error) - args.r.error = error; + read_command(&args); if (args.heredoc) heredoc(mc, &args.r.input_fd, args.heredoc); return (args.r);