refactor pipeline: use a state machine for parsing

This allows for easier-to-norm code
This commit is contained in:
Khaïs COLIN 2025-02-21 16:04:47 +01:00
parent c9f8c5a4f9
commit 36df14e599
Signed by: logistic-bot
SSH key fingerprint: SHA256:RlpiqKeXpcPFZZ4y9Ou4xi2M8OhRJovIwDlbCaMsuAo
7 changed files with 322 additions and 60 deletions

View file

@ -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;
}

View file

@ -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 commands 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

View 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);
}

View 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

View 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;
}

View 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