mirror of
https://codeberg.org/la-chouette/minishell.git
synced 2025-12-06 07:28:09 +01:00
refactor pipeline: use a state machine for parsing
This allows for easier-to-norm code
This commit is contained in:
parent
c9f8c5a4f9
commit
36df14e599
7 changed files with 322 additions and 60 deletions
|
|
@ -6,72 +6,23 @@
|
|||
/* By: khais <marvin@42.fr> +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2025/02/21 13:23:50 by khais #+# #+# */
|
||||
/* Updated: 2025/02/21 15:42:34 by khais ### ########.fr */
|
||||
/* Updated: 2025/02/24 15:03:25 by khais ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "pipeline.h"
|
||||
#include "pipeline_parse.h"
|
||||
#include "libft.h"
|
||||
#include "../../ft_errno.h"
|
||||
#include "../matchers/pipe.h"
|
||||
|
||||
static int pipeline_count_cmds(t_wordlist *words)
|
||||
{
|
||||
int count;
|
||||
|
||||
if (words == NULL)
|
||||
return (0);
|
||||
count = 1;
|
||||
while (words != NULL)
|
||||
{
|
||||
if (is_pipe(words->word))
|
||||
count++;
|
||||
words = words->next;
|
||||
}
|
||||
return (count);
|
||||
}
|
||||
|
||||
static t_worddesc *ignore_word(t_worddesc *current_word, t_wordlist **words)
|
||||
{
|
||||
worddesc_destroy(current_word);
|
||||
return (wordlist_pop(words));
|
||||
}
|
||||
|
||||
static t_pipeline *pipeline_parse_wordlist(t_pipeline *pipeline,
|
||||
t_wordlist *words)
|
||||
{
|
||||
int idx;
|
||||
t_wordlist *current_wordlist;
|
||||
t_worddesc *current_word;
|
||||
|
||||
idx = 0;
|
||||
current_wordlist = NULL;
|
||||
current_word = wordlist_pop(&words);
|
||||
while (current_word != NULL)
|
||||
{
|
||||
if (is_pipe(current_word))
|
||||
{
|
||||
pipeline->cmds[idx] = simple_cmd_from_wordlist(current_wordlist);
|
||||
if (pipeline->cmds[idx] == NULL)
|
||||
return (pipeline_destroy(pipeline), wordlist_destroy(words), NULL);
|
||||
current_wordlist = NULL;
|
||||
current_word = ignore_word(current_word, &words);
|
||||
if (is_pipe(current_word))
|
||||
return (pipeline_destroy(pipeline), wordlist_destroy(words), worddesc_destroy(current_word), ft_errno(FT_EUNEXPECTED_PIPE), NULL);
|
||||
idx++;
|
||||
}
|
||||
current_wordlist = wordlist_push(current_wordlist, current_word);
|
||||
current_word = wordlist_pop(&words);
|
||||
}
|
||||
pipeline->cmds[idx] = simple_cmd_from_wordlist(current_wordlist);
|
||||
if (pipeline->cmds[idx] == NULL)
|
||||
return (pipeline_destroy(pipeline), NULL);
|
||||
return (pipeline);
|
||||
}
|
||||
|
||||
/*
|
||||
** Parse a pipeline from the given wordlist.
|
||||
**
|
||||
** Return NULL and set ft_errno in case of error.
|
||||
*/
|
||||
t_pipeline *pipeline_from_wordlist(t_wordlist *words)
|
||||
{
|
||||
t_pipeline *pipeline;
|
||||
t_pipeline *pipeline;
|
||||
t_pipeline_builder builder;
|
||||
|
||||
if (words == NULL)
|
||||
return (NULL);
|
||||
|
|
@ -82,10 +33,16 @@ t_pipeline *pipeline_from_wordlist(t_wordlist *words)
|
|||
pipeline->cmds = ft_calloc(pipeline->num_cmd, sizeof(t_simple_cmd *));
|
||||
if (pipeline->cmds == NULL)
|
||||
return (NULL);
|
||||
pipeline = pipeline_parse_wordlist(pipeline, words);
|
||||
ft_bzero(&builder, sizeof(t_pipeline_builder));
|
||||
builder.words = words;
|
||||
builder.pipeline = pipeline;
|
||||
pipeline = pipeline_parse_wordlist(&builder);
|
||||
return (pipeline);
|
||||
}
|
||||
|
||||
/*
|
||||
** Destroy the given pipeline, freeing all memory.
|
||||
*/
|
||||
void pipeline_destroy(t_pipeline *pipeline)
|
||||
{
|
||||
int i;
|
||||
|
|
@ -101,3 +58,18 @@ void pipeline_destroy(t_pipeline *pipeline)
|
|||
free(pipeline->cmds);
|
||||
free(pipeline);
|
||||
}
|
||||
|
||||
/*
|
||||
** Free all memory for the given builder, set freed variables to NULL, and set
|
||||
** error to true
|
||||
*/
|
||||
void builder_destroy(t_pipeline_builder *builder)
|
||||
{
|
||||
pipeline_destroy(builder->pipeline);
|
||||
builder->pipeline = NULL;
|
||||
worddesc_destroy(builder->current_word);
|
||||
builder->current_word = NULL;
|
||||
wordlist_destroy(builder->words);
|
||||
builder->words = NULL;
|
||||
builder->error = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
/* By: khais <marvin@42.fr> +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2025/02/21 13:19:51 by khais #+# #+# */
|
||||
/* Updated: 2025/02/21 13:27:50 by khais ### ########.fr */
|
||||
/* Updated: 2025/02/24 15:02:35 by khais ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
|
|
@ -14,14 +14,85 @@
|
|||
# define PIPELINE_H
|
||||
|
||||
# include "../simple_cmd/simple_cmd.h"
|
||||
# include <stdbool.h>
|
||||
|
||||
/*
|
||||
** cf. 3.2.3 Pipelines
|
||||
**
|
||||
** A pipeline is a sequence of one or more commands separated by the control
|
||||
** operator '|'.
|
||||
**
|
||||
** The output of each command in the pipeline is connected via a pipe to the
|
||||
** input of the next command.
|
||||
**
|
||||
** That is, each command reads the previous command’s output.
|
||||
**
|
||||
** This connection is performed before any redirections specified by the first
|
||||
** command.
|
||||
**
|
||||
** The shell waits for all commands in the pipeline to complete before reading
|
||||
** the next command.
|
||||
**
|
||||
** Each command in a multi-command pipeline, where pipes are created, is
|
||||
** executed in its own _subshell_, which is a separate process.
|
||||
**
|
||||
** e.g.
|
||||
**
|
||||
** ```shell
|
||||
** export TT=1 | echo $TT
|
||||
** ```
|
||||
**
|
||||
** prints an empty string, because TT is unset in the second subshell.
|
||||
**
|
||||
** The exit status of a pipeline is the exit status of the last command in the
|
||||
** pipeline.
|
||||
**
|
||||
** The shell waits for all commands in the pipeline to terminate before
|
||||
** returning a value.
|
||||
*/
|
||||
typedef struct s_pipeline
|
||||
{
|
||||
/*
|
||||
** The commands in the pipeline
|
||||
*/
|
||||
t_simple_cmd **cmds;
|
||||
/*
|
||||
** The number of commands in this pipeline
|
||||
*/
|
||||
int num_cmd;
|
||||
} t_pipeline;
|
||||
|
||||
typedef struct s_pipeline_builder
|
||||
{
|
||||
/*
|
||||
** index of the command we are currently working on
|
||||
*/
|
||||
int idx;
|
||||
/*
|
||||
** wordlist that is being built up to be the wordlist of the command being
|
||||
** parsed
|
||||
*/
|
||||
t_wordlist *current_wordlist;
|
||||
/*
|
||||
** word that is currently being considered
|
||||
*/
|
||||
t_worddesc *current_word;
|
||||
/*
|
||||
** linked list of words that are being parsed
|
||||
*/
|
||||
t_wordlist *words;
|
||||
/*
|
||||
** pipeline that is currently being constructed
|
||||
*/
|
||||
t_pipeline *pipeline;
|
||||
/*
|
||||
** true if an error occured, which should interrupt processing
|
||||
*/
|
||||
bool error;
|
||||
} t_pipeline_builder;
|
||||
|
||||
t_pipeline *pipeline_from_wordlist(t_wordlist *words);
|
||||
void pipeline_destroy(t_pipeline *pipeline);
|
||||
void builder_destroy(t_pipeline_builder *builder);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
108
src/parser/pipeline/pipeline_parse.c
Normal file
108
src/parser/pipeline/pipeline_parse.c
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* pipeline_parse.c :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: khais <marvin@42.fr> +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2025/02/24 14:36:43 by khais #+# #+# */
|
||||
/* Updated: 2025/02/24 15:09:35 by khais ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "pipeline_parse.h"
|
||||
#include "pipeline_parse_baseops.h"
|
||||
#include "../../ft_errno.h"
|
||||
#include "../matchers/pipe.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
/*
|
||||
** count the number of commands in a pipeline from the given words.
|
||||
**
|
||||
** A command is separated from the next by a pipe token.
|
||||
**
|
||||
** Note that this does not handle errors such as repeated pipe tokens, or pipe
|
||||
** tokens at the wrong place.
|
||||
*/
|
||||
int pipeline_count_cmds(t_wordlist *words)
|
||||
{
|
||||
int count;
|
||||
|
||||
if (words == NULL)
|
||||
return (0);
|
||||
count = 1;
|
||||
while (words != NULL)
|
||||
{
|
||||
if (is_pipe(words->word))
|
||||
count++;
|
||||
words = words->next;
|
||||
}
|
||||
return (count);
|
||||
}
|
||||
|
||||
/*
|
||||
** Delimit a command in the pipeline.
|
||||
**
|
||||
** - Create a new simple command from the wordlist that has been accumulated
|
||||
** thus far.
|
||||
** - Reset the current wordlist.
|
||||
** - Ignore the current word, which is assumed to be a pipe token.
|
||||
** - Check if the new current word is a pipe token, and if it is, signal
|
||||
** FT_EUNEXPECTED_PIPE.
|
||||
**
|
||||
** Destroys the builder and sets error in case of error.
|
||||
*/
|
||||
static void pipeline_delimit(t_pipeline_builder *builder)
|
||||
{
|
||||
builder->pipeline->cmds[builder->idx]
|
||||
= simple_cmd_from_wordlist(builder->current_wordlist);
|
||||
if (builder->pipeline->cmds[builder->idx] == NULL)
|
||||
{
|
||||
builder_destroy(builder);
|
||||
return ;
|
||||
}
|
||||
builder->current_wordlist = NULL;
|
||||
ignore_word(builder);
|
||||
if (current_is_pipe(builder))
|
||||
{
|
||||
builder_destroy(builder);
|
||||
ft_errno(FT_EUNEXPECTED_PIPE);
|
||||
return ;
|
||||
}
|
||||
builder->idx++;
|
||||
}
|
||||
|
||||
/*
|
||||
** Parse a pipeline from a wordlist using the given builder.
|
||||
**
|
||||
** - Assumes that current word is unset.
|
||||
** - Load the next word.
|
||||
** - While there are still words remaining in the input stream
|
||||
** - If we have a pipe character
|
||||
** - Delimit the current command
|
||||
** - If there was an error
|
||||
** - Return NULL
|
||||
** - Push the current word to the accumulator for the next command
|
||||
** - Advance the state to the next word
|
||||
** - Since there is no pipe token at the end to delimit the last command,
|
||||
** delimit it now
|
||||
** - Return the pipeline. Since all input tokens have been consumed, no
|
||||
** additional memory freeing is required.
|
||||
** in case of error in pipeline_delimit, it will call destroy, which will set
|
||||
** builder->pipeline to NULL
|
||||
*/
|
||||
t_pipeline *pipeline_parse_wordlist(t_pipeline_builder *builder)
|
||||
{
|
||||
next_word(builder);
|
||||
while (!eof(builder))
|
||||
{
|
||||
if (current_is_pipe(builder))
|
||||
pipeline_delimit(builder);
|
||||
if (builder->error)
|
||||
return (NULL);
|
||||
push_word(builder);
|
||||
next_word(builder);
|
||||
}
|
||||
pipeline_delimit(builder);
|
||||
return (builder->pipeline);
|
||||
}
|
||||
21
src/parser/pipeline/pipeline_parse.h
Normal file
21
src/parser/pipeline/pipeline_parse.h
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* pipeline_parse.h :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: khais <marvin@42.fr> +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2025/02/24 14:34:55 by khais #+# #+# */
|
||||
/* Updated: 2025/02/24 15:01:46 by khais ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#ifndef PIPELINE_PARSE_H
|
||||
# define PIPELINE_PARSE_H
|
||||
|
||||
# include "pipeline.h"
|
||||
|
||||
int pipeline_count_cmds(t_wordlist *words);
|
||||
t_pipeline *pipeline_parse_wordlist(t_pipeline_builder *builder);
|
||||
|
||||
#endif // PIPELINE_PARSE_H
|
||||
65
src/parser/pipeline/pipeline_parse_baseops.c
Normal file
65
src/parser/pipeline/pipeline_parse_baseops.c
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* pipeline_parse_baseops.c :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: khais <marvin@42.fr> +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2025/02/24 15:06:33 by khais #+# #+# */
|
||||
/* Updated: 2025/02/24 15:10:09 by khais ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
#include "pipeline_parse_baseops.h"
|
||||
#include "../matchers/pipe.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
/*
|
||||
** Advance the state to the next word
|
||||
*/
|
||||
void next_word(t_pipeline_builder *builder)
|
||||
{
|
||||
builder->current_word = wordlist_pop(&builder->words);
|
||||
}
|
||||
|
||||
/*
|
||||
** If the current word is NULL, return true.
|
||||
**
|
||||
** Used to detect end of word stream.
|
||||
**
|
||||
** Assumes that next_word was previously called.
|
||||
*/
|
||||
bool eof(t_pipeline_builder *builder)
|
||||
{
|
||||
return (builder->current_word == NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
** return true if the given current word is a pipe operator "|"
|
||||
*/
|
||||
bool current_is_pipe(t_pipeline_builder *builder)
|
||||
{
|
||||
return (builder->current_word != NULL && is_pipe(builder->current_word));
|
||||
}
|
||||
|
||||
/*
|
||||
** Remove the current word from the wordstream and advance to the next word.
|
||||
*/
|
||||
void ignore_word(t_pipeline_builder *builder)
|
||||
{
|
||||
worddesc_destroy(builder->current_word);
|
||||
next_word(builder);
|
||||
}
|
||||
|
||||
/*
|
||||
** push the current word to the wordlist that will be used for the next simple
|
||||
** command.
|
||||
**
|
||||
** Resets current word to NULL.
|
||||
*/
|
||||
void push_word(t_pipeline_builder *builder)
|
||||
{
|
||||
builder->current_wordlist
|
||||
= wordlist_push(builder->current_wordlist, builder->current_word);
|
||||
builder->current_word = NULL;
|
||||
}
|
||||
23
src/parser/pipeline/pipeline_parse_baseops.h
Normal file
23
src/parser/pipeline/pipeline_parse_baseops.h
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/* ************************************************************************** */
|
||||
/* */
|
||||
/* ::: :::::::: */
|
||||
/* pipeline_parse_baseops.h :+: :+: :+: */
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: khais <marvin@42.fr> +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2025/02/24 15:05:16 by khais #+# #+# */
|
||||
/* Updated: 2025/02/24 15:09:01 by khais ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
#ifndef PIPELINE_PARSE_BASEOPS_H
|
||||
# define PIPELINE_PARSE_BASEOPS_H
|
||||
|
||||
# include "pipeline.h"
|
||||
|
||||
void ignore_word(t_pipeline_builder *builder);
|
||||
bool current_is_pipe(t_pipeline_builder *builder);
|
||||
void next_word(t_pipeline_builder *builder);
|
||||
bool eof(t_pipeline_builder *builder);
|
||||
void push_word(t_pipeline_builder *builder);
|
||||
|
||||
#endif // PIPELINE_PARSE_BASEOPS_H
|
||||
Loading…
Add table
Add a link
Reference in a new issue