bsh project

This commit is contained in:
brice.boisson 2022-02-08 18:50:03 +01:00
commit 7744ceaa33
109 changed files with 9536 additions and 0 deletions

79
.clang-format Normal file
View File

@ -0,0 +1,79 @@
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Right
AlignOperands: false
AlignTrailingComments: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: false
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: true
BinPackParameters: true
BreakBeforeBraces: Custom
BraceWrapping:
AfterEnum: true
AfterClass: true
AfterControlStatement: true
AfterFunction: true
AfterNamespace: true
AfterStruct: true
AfterUnion: true
AfterExternBlock: true
BeforeCatch: true
BeforeElse: true
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false
BreakBeforeBinaryOperators: NonAssignment
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeComma
BreakInheritanceList: BeforeComma
BreakStringLiterals: true
ColumnLimit: 80
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
Cpp11BracedListStyle: false
DerivePointerAlignment: false
FixNamespaceComments: true
ForEachMacros: ['ILIST_FOREACH', 'ILIST_FOREACH_ENTRY']
IncludeBlocks: Regroup
IncludeCategories:
- Regex: '<.*>'
Priority: 1
- Regex: '.*'
Priority: 2
IndentCaseLabels: false
IndentPPDirectives: AfterHash
IndentWidth: 4
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false
Language: Cpp
NamespaceIndentation: All
PointerAlignment: Right
ReflowComments: true
SortIncludes: true
SortUsingDeclarations: false
SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
TabWidth: 4
UseTab: Never

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
.DS_Store
*.o
tmp/
bsh
doc/
.vscode/
builddir/
vgcore.*

2638
Doxyfile Normal file

File diff suppressed because it is too large Load Diff

36
Makefile Normal file
View File

@ -0,0 +1,36 @@
all: rebuild
bsh: build
.PHONY: build
check: build
@echo "Checking..."
@python3 tests/moulinette.py --binary bsh --tests tests/*.yml
@echo "Done."
rebuild:
@echo "Rebuilding project..."
@rm -f bsh
@ninja -C builddir
@cp builddir/bsh .
@echo "Done."
build: clean
@echo "Building project..."
@rm -f bsh
@meson setup builddir
@ninja -C builddir
@cp builddir/bsh .
@echo "Done."
doc:
@echo "Generating documentation..."
@rm -rf doc
@doxygen
@echo "Done."
clean:
@echo "Cleaning up..."
@rm -rf builddir bsh doc vgcore.*
@echo "Done."

44
README.md Normal file
View File

@ -0,0 +1,44 @@
# Bsh
## Testsuite
First, you need to install the dependencies using pip:
```sh
➜ bsh ✗ pip install -r tests/requirements.txt
```
Make sure to have the bsh binary, and then execute the tests/moulinette.py file using python3
```sh
➜ bsh ✗ python3 tests/moulinette.py --binary bsh --tests tests/echo.yml
```
- ```--binary``` is the relative path of the bsh binary
- ```--tests``` is a list of all yaml files containing tests
A test is formatted like this in a yaml file:
```yml
- name: a name
input: an input in a single line (for example -> echo "Foo!")
file: a path to a file, relative from the root of the git
```
You can choose either input or file for a test. In the case you set a value for each of them, only input will be taken into account.
## Options
In order to debug the program, there are 2 options.
- ```--petty-print``` print the AST before each execution
- ```--verbose``` print some functions output during execution. For now, there is only the lexer output.
:warning: **The order is important**: ```--petty-print``` has to be before ```--verbose```
## Environment
```sh
➜ b-sh-bitarrays git:(main) env -i ./bsh
```
You need to execute the shell with this command, in order to start with an empty environment

8
build.ninja Normal file
View File

@ -0,0 +1,8 @@
rule call_make
command = make $out
build bsh: call_make
build clean: call_make
build check: call_make

77
meson.build Normal file
View File

@ -0,0 +1,77 @@
project(
'bsh',
'c',
version : '1.0',
default_options: [
'c_std=c99',
'warning_level=2',
'werror=true',
],
)
# add_project_arguments('-fsanitize=address', language : 'c')
# add_project_link_arguments('-fsanitize=address', language : 'c')
cflags = ['-D_GNU_SOURCE', '-D_POSIX_C_SOURCE=200809L']
add_project_arguments(cflags, language: 'c')
incdirs = [
include_directories('./src/lexer'),
include_directories('./src/parser'),
include_directories('./src/shell_input'),
include_directories('./src/ast_evaluation'),
include_directories('./src/builtins'),
include_directories('./src/variables'),
include_directories('./src/loops'),
include_directories('./src/functions'),
include_directories('./src/'),
]
common = [
'src/lexer/lexer.c',
'src/lexer/lexer_print.c',
'src/lexer/ionumbers.c',
'src/lexer/create_token.c',
'src/lexer/keywords.c',
'src/lexer/split_input.c',
'src/lexer/spaces.c',
'src/lexer/export.c',
'src/lexer/alias.c',
'src/parser/parser.c',
'src/parser/parser_high_level.c',
'src/parser/parser_conditions.c',
'src/parser/parser_commands.c',
'src/parser/parser_loops.c',
'src/parser/ast.c',
'src/ast_evaluation/pretty_print.c',
'src/ast_evaluation/ast_evaluation.c',
'src/ast_evaluation/builtin_exec.c',
'src/ast_evaluation/inner_exec.c',
'src/ast_evaluation/prog_exec.c',
'src/ast_evaluation/pipe.c',
'src/ast_evaluation/redir.c',
'src/ast_evaluation/redir_tools.c',
'src/shell_input/shell_input.c',
'src/builtins/builtins.c',
'src/variables/var_list.c',
'src/builtins/echo.c',
'src/builtins/cd.c',
'src/builtins/continue.c',
'src/builtins/break.c',
'src/builtins/dot.c',
'src/parser/parse_functions.c',
'src/loops/loop_stack.c',
'src/builtins/exit.c',
'src/builtins/export.c',
'src/functions/functions.c',
'src/builtins/unset.c',
]
token_printer = executable(
# name of the output executable
'bsh',
# source files for the executable
common + [ 'src/bsh.c' ],
# these are passed as -I
include_directories: incdirs
)

View File

@ -0,0 +1,467 @@
#include "ast_evaluation.h"
#include <stdio.h>
#include <string.h>
#include "builtins.h"
#include "parser.h"
#include "redir.h"
int evaluate_ast(struct ast *ast)
{
if (shell->exit || shell->ctn || shell->brk)
return shell->return_code;
if (!ast)
return 0;
if (ast->type == AST_IF)
{
if (!evaluate_ast(ast->condition))
{
if (shell->exit || shell->ctn || shell->brk)
return shell->return_code;
return evaluate_ast(ast->left_child);
}
else
{
if (shell->exit || shell->ctn || shell->brk)
return shell->return_code;
return evaluate_ast(ast->right_child);
}
}
if (ast->type == AST_CASE)
{
char **val = expand(ast->value, ast->enclosure);
char *arg = merge_arg(val);
int i = 0;
while (val[i])
free(val[i++]);
free(val);
ast = ast->left_child;
int res = 0;
int found = 0;
while (ast && ast->type == AST_CASE_SWITCH && !found)
{
int j = 0;
while (ast->value[j])
{
char *tmp = ast->value[j];
if (!strcmp(tmp, arg) || !strcmp(tmp, "*"))
{
res = evaluate_ast(ast->left_child);
if (shell->exit || shell->ctn || shell->brk)
{
free(arg);
return shell->return_code;
}
found = 1;
break;
}
j++;
}
ast = ast->right_child;
}
free(arg);
return res;
}
else if (ast->type == AST_FOR)
{
char **var;
push_loop(shell, ast);
if ((!ast->value[1] || !ast->value[2]))
{
if (shell->nb_args > 0)
{
enum quotes *enclosure =
calloc(shell->nb_args, sizeof(enum quotes));
char **tmp = calloc(2, sizeof(char *));
tmp[0] = find_elt_list(shell, "@");
var = split_arg(tmp, enclosure);
free(enclosure);
free(tmp);
}
else
var = NULL;
// wait impletation of env var
// enum quotes enclosure[1] = {Q_NONE};
// var = split_arg(shell->args, enclosure); // add var in array
}
else
var = split_arg(
ast->value + 2 /*expand(ast->value + 2, ast->enclosure + 2)*/,
ast->enclosure + 2);
if (var && ast->value)
{
int i = 0;
while (var[i])
{
int res = push_elt_list(shell, ast->value[0], var[i++]);
evaluate_ast(ast->left_child);
if (shell->ctn)
{
shell->ctn--;
if (shell->ctn > 0 && get_ast_loop(shell))
{
free_arg(var);
pop_loop(shell);
return res;
}
else if (shell->ctn)
shell->ctn = 0;
continue;
}
else if (shell->brk)
{
shell->brk--;
pop_loop(shell);
if (shell->brk > 0 && get_ast_loop(shell))
{
free_arg(var);
return res;
}
else if (shell->brk)
shell->brk = 0;
break;
}
if (shell->exit)
{
free_arg(var);
return shell->return_code;
}
}
}
free_arg(var);
if (get_ast_loop(shell) == ast)
pop_loop(shell);
int res =
evaluate_ast(ast->right_child); // check return code for a null for
if (shell->exit || shell->ctn || shell->brk)
return shell->return_code;
return res;
}
else if (ast->type == AST_WHILE)
{
int ret = 0;
push_loop(shell, ast);
while (!evaluate_ast(ast->condition))
{
if (shell->exit)
return shell->return_code;
ret = evaluate_ast(ast->left_child);
if (shell->ctn)
{
shell->ctn--;
if (shell->ctn > 0 && get_ast_loop(shell))
{
pop_loop(shell);
return ret;
}
else if (shell->ctn)
shell->ctn = 0;
continue;
}
else if (shell->brk)
{
shell->brk--;
pop_loop(shell);
if (shell->brk > 0 && get_ast_loop(shell))
return ret;
else if (shell->brk)
shell->brk = 0;
break;
}
if (shell->exit)
return shell->return_code;
}
if (get_ast_loop(shell) == ast)
pop_loop(shell);
return ret;
}
else if (ast->type == AST_UNTIL)
{
int ret = 0;
push_loop(shell, ast);
while (evaluate_ast(ast->condition))
{
if (shell->exit)
return shell->return_code;
ret = evaluate_ast(ast->left_child);
if (shell->ctn)
{
shell->ctn--;
if (shell->ctn > 0 && get_ast_loop(shell))
{
pop_loop(shell);
return ret;
}
else if (shell->ctn)
shell->ctn = 0;
continue;
}
else if (shell->brk)
{
shell->brk--;
pop_loop(shell);
if (shell->brk > 0 && get_ast_loop(shell))
return ret;
else if (shell->brk)
shell->brk = 0;
break;
}
if (shell->exit)
return shell->return_code;
}
if (get_ast_loop(shell) == ast)
pop_loop(shell);
return ret;
}
else if (ast->type == AST_AND || ast->type == AST_OR)
{
int prec = !evaluate_ast(ast->left_child);
while (ast->right_child
&& (ast->right_child->type == AST_OR
|| ast->right_child->type == AST_AND))
{
if (ast->type == AST_AND)
prec = prec && !evaluate_ast(ast->right_child->left_child);
else if (ast->type == AST_OR)
prec = prec || !evaluate_ast(ast->right_child->left_child);
/*if (shell->continue)
{
}
else if (shell->break)
{
}*/
if (shell->exit)
return shell->return_code;
ast = ast->right_child;
}
if (ast->type == AST_AND)
prec = prec && !evaluate_ast(ast->right_child);
else if (ast->type == AST_OR)
prec = prec || !evaluate_ast(ast->right_child);
if (shell->exit || shell->ctn || shell->brk)
return shell->return_code;
return !prec;
}
else if (ast->type == AST_REDIR)
{
int nb = 1;
struct ast *tmp = ast;
while (tmp->right_child->type == AST_REDIR)
{
tmp = tmp->right_child;
nb++;
}
char ***redirs = calloc(nb + 1, sizeof(char **));
if (!redirs)
return 1;
int i = 0;
tmp = ast;
while (tmp->right_child->type)
{
redirs[i] = calloc(4, sizeof(char *));
if (!redirs[i])
return 1; // error a recheck
int j = 0;
while (tmp->value[j])
{
redirs[i][j] = tmp->value[j];
j++;
}
tmp = tmp->right_child;
redirs[i++][j] = tmp->left_child->value[0];
}
redirs[i] = calloc(4, sizeof(char *));
if (!redirs[i])
return 1; // error a recheck
int j = 0;
while (tmp->value[j])
{
redirs[i][j] = tmp->value[j];
j++;
}
redirs[i][j] = tmp->right_child->value[0];
// call redir
exec_redirections(redirs);
int res = evaluate_ast(ast->left_child);
int k = 0;
while (redirs[k])
free(redirs[k++]);
free(redirs);
if (shell->exit || shell->ctn || shell->brk)
return shell->return_code;
return res;
// int fd = atoi_begining(char *s);
/*if (shell->verbose)
{
printf("%d\n", nb);
printf("%s\n", ast->left_child->value[0]);
}*/
}
else if (ast->type == AST_PIPE)
{
int nb = 1;
struct ast *tmp = ast;
while (tmp->right_child->type == AST_PIPE)
{
tmp = tmp->right_child;
nb++;
}
char ***redirs = calloc(nb + 2, sizeof(char **));
if (!redirs)
return 1;
enum quotes **enclosure = calloc(nb + 2, sizeof(enum quotes *));
if (!redirs)
return 1;
redirs[0] = ast->left_child->value;
enclosure[0] = ast->left_child->enclosure;
int i = 1;
while (ast->right_child->type == AST_PIPE)
{
enclosure[i] = ast->left_child->enclosure;
redirs[i++] = ast->left_child->value;
ast = ast->right_child;
}
/* int y = 0;
while (ast->left_child->value[y])
{
printf("1\n");
printf("%s\n", ast->right_child->value[y]);
printf("%d\n", ast->right_child->enclosure[y++]);
}*/
redirs[i] = ast->right_child->value;
enclosure[i] = ast->right_child->enclosure;
int res = exec_pipe(redirs, enclosure, nb);
push_int_elt_list(shell, "?", res);
return res;
}
else if (ast->type == AST_ASSIGNMENT)
{
if (ast->value)
{
char **var = expand(ast->value, ast->enclosure);
char *val = merge_arg(var);
push_elt_list(shell, ast->var_name, val);
free_arg(var);
free(val);
}
}
else if (ast->type == AST_FUNC)
{
if (ast->var_name)
push_elt_fun(shell, ast->var_name, ast->left_child);
else
evaluate_ast(ast->left_child);
/*new_var(shell, ast->value);
int res = ast_evaluation(ast->left_child);
del_stack(shell);
push_int_elt_list(shell, "?", res);
res = ast_evaluation(ast->right_child);
return res;*/
}
else if (ast->type == AST_SUBSHELL)
{
struct var *cpy = var_list_cpy(shell);
struct functions *fn_cpy = fun_list_cpy(shell);
int res = evaluate_ast(ast->left_child);
free_list_sub(shell->var_list);
free_fun_sub(shell);
shell->ctn = 0;
shell->brk = 0;
shell->exit = 0;
shell->var_list = cpy;
shell->functions = fn_cpy;
if (shell->exit || shell->ctn || shell->brk)
return shell->return_code;
return res;
}
else if (ast->type == AST_CMD_SUBSTITUTION)
{
struct var *cpy = var_list_cpy(shell);
struct functions *fn_cpy = fun_list_cpy(shell);
int save = dup(STDOUT_FILENO);
char *path = get_next_free_file();
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
dup2(fd, STDOUT_FILENO);
close(fd);
int res = evaluate_ast(ast->left_child);
dup2(save, STDOUT_FILENO);
char *cmd_val = get_file_in_var(path);
free(path);
free_list_sub(shell->var_list);
free_fun_sub(shell);
shell->ctn = 0;
shell->brk = 0;
shell->exit = 0;
shell->var_list = cpy;
shell->functions = fn_cpy;
/*struct ast *cmd = ast_new(AST_COMMAND);
cmd->value = calloc(2, sizeof(char *));
cmd->enclosure = calloc(2, sizeof(enum quotes));
cmd->value[0] = cmd_val;
cmd->enclosure[0] = Q_NONE;*/
parse_input(cmd_val, NULL);
res = shell->return_code;
free(cmd_val);
push_int_elt_list(shell, "?", res);
if (shell->exit || shell->ctn || shell->brk)
return shell->return_code;
return res;
}
else if (ast->type == AST_COMMAND)
{
char **val = expand(ast->value, ast->enclosure);
// val = split_arg(val, ast->enclosure);
if (!val)
return 1;
int res;
struct ast *block;
if (is_builtin(*(val)))
res = find_command(val, 1);
else if ((block = find_elt_fun(shell, *(ast->value))) != NULL)
{
new_var(shell, ast->value);
res = evaluate_ast(block);
del_stack(shell);
shell->ctn = 0;
shell->brk = 0;
shell->exit = 0;
}
else
res = call_exec(val);
char *tmp = val[0];
int pos = 0;
while (tmp)
{
free(tmp);
tmp = val[++pos];
}
free(val);
push_int_elt_list(shell, "?", res);
return res;
}
else if (ast->type == AST_NOT)
return !evaluate_ast(ast->left_child);
else if (ast->type == AST_LIST)
{
int r = evaluate_ast(ast->right_child);
// printf("%d\n", shell->exit);
if (shell->exit || shell->ctn || shell->brk)
{
// printf("%d\n", shell->return_code);
return shell->return_code;
}
if (ast->left_child && ast->left_child->type != AST_EOF)
return evaluate_ast(ast->left_child);
return r;
}
else
{
printf("%d, Not implemented yet\n", ast->type);
}
return 0;
}

View File

@ -0,0 +1,13 @@
#ifndef AST_EVALUATION_H
#define AST_EVALUATION_H
#include "../bsh.h"
#include "../parser/ast.h" // refaire plus propre
#include "ast_evaluation_tools.h"
#include "functions.h"
#include "loop_stack.h"
#include "var_list.h"
int evaluate_ast(struct ast *ast);
#endif /* !AST_EVALUATION_H */

View File

@ -0,0 +1,23 @@
#ifndef AST_EVALUATION_TOOLS_H
#define AST_EVALUATION_TOOLS_H
#include "bsh.h"
#include "ast.h"
#include "var_list.h"
// include builtin
extern struct shell *shell;
// int call_builtin(char *cmd);
int is_builtin(char *);
int call_exec(char **cmd);
int is_in(char **condition);
char **expand(char **arg, enum quotes *enclosure);
char **split_arg(char **arg, enum quotes *enclosure);
char *merge_arg(char **arg);
int atoi_begining(char *s);
int exec_pipe(char ***args, enum quotes **enclosure, int pipe_nb);
void free_arg(char **var);
char *get_next_free_file(void);
char *get_file_in_var(char *path);
#endif /* !AST_EVALUATION_TOOLS_H */

View File

@ -0,0 +1,65 @@
#include <string.h>
#include "ast_evaluation_tools.h"
#include "builtins.h"
/*
* The main objectif of this source code is to call the builtin implemented
* inside of the bsh project
*/
int is_builtin(char *cmd)
{
if (!strcmp(cmd, "echo"))
return 1;
if (!strcmp(cmd, "cd"))
return 1;
if (!strcmp(cmd, "break"))
return 1;
if (!strcmp(cmd, "continue"))
return 1;
if (!strcmp(cmd, "exit"))
return 1;
if (!strcmp(cmd, "export"))
return 1;
if (!strcmp(cmd, "unset"))
return 1;
if (!strcmp(cmd, "."))
return 1;
return 0;
}
/*int call_echo(char *cmd)
{
int i = 0;
// int flag = 0;
while (cmd[i] != '\0')
{
while (!(cmd[i] > ' '))
i++;
if (!strcmp(cmd, "-e", i))
if (i != 1 && i != 3)
flag++;
if (!strcmp(cmd, "-n", i))
if (i < 2)
flag += 2;
if (!strcmp(cmd, "-ne", i))
flag = 3;
}
printf("%s\n", cmd);
echo(cmd + i, 1);
return 0;
}*/
/*int call_builtin(char **cmd)
{
int i = 0;
while (cmd[i] != ' ')
i++;
if (!strncmp(cmd, "echo", i))
{
return call_echo(cmd + i);
}
return 0;
}*/

View File

@ -0,0 +1,374 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "ast_evaluation_tools.h"
char *get_next_free_file(void)
{
int i = 0;
char *file_name = calloc(25, sizeof(char));
sprintf(file_name, "/tmp/bsh_%d", i);
while (access(file_name, F_OK) == 0)
{
i++;
free(file_name);
file_name = calloc(25, sizeof(char));
sprintf(file_name, "/tmp/bsh_%d", i);
}
return file_name;
}
char *get_file_in_var(char *path)
{
FILE *file = fopen(path, "r");
if (!file)
return NULL;
fseek(file, 0, SEEK_END);
long size = ftell(file);
fseek(file, 0, SEEK_SET);
char *content = malloc(sizeof(char) * (size + 1));
fread(content, sizeof(char), size, file);
content[size] = '\0';
fclose(file);
return content;
}
void free_arg(char **var)
{
if (!var)
return;
int i = 0;
while (var[i])
free(var[i++]);
free(var);
}
int is_in(char **condition)
{
while ((*condition)[0] != '\0')
{
if (!strcmp(*condition, "in"))
return 1;
condition++;
}
return 0;
}
int is_char_name(char c)
{
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z')
|| (c >= 'a' && c <= 'z') || c == '_';
}
int is_special(char c)
{
return c == '#' || c == '?' || c == '*' || c == '@' || c == '$' || c == '!';
}
int expand_s(char **elt, char *s, enum quotes type)
{
int size = strlen(s);
char *new = calloc(size + 1, sizeof(char));
if (!new)
return 0;
strcpy(new, s);
if (type == Q_NONE)
{
int i = 0;
int i_new = 0;
while (s[i] != '\0')
{
if (s[i] == '$')
{
int begin = i;
int bracket = s[++i] == '{';
int offset = 1;
if (bracket)
{
offset++;
i++;
}
int start = i;
int size_var = 0;
if (is_special(s[i]))
{
i++;
size_var++;
}
else
{
while (is_char_name(s[i])/*s[i] != '\0' && s[i] != ' ' && s[i] != '\t'
&& s[i] != '$' && s[i] != '\\'
&& (!bracket || (bracket && s[i] != '}'))*/)
{
i++;
size_var++;
}
}
if (bracket)
i++;
if (i - begin != 1)
{
char *name = calloc(size_var + 1, sizeof(char));
strncpy(name, s + start, size_var);
name[size_var] = '\0';
char *var = find_elt_list(shell, name);
if (!var)
var = "";
int new_size = strlen(var);
size += begin - i + new_size;
char *tmp = realloc(new, (size + 1) * sizeof(char));
if (!tmp)
return 0;
new = tmp;
strcpy(new + i_new, var);
i_new += new_size;
free(name);
if (s[i] == '\0')
break;
if (s[i] == '$')
continue;
}
else
{
i--;
new[i_new++] = s[i++];
}
}
else if (s[i] == '\\')
{
i++;
new[i_new++] = s[i++];
}
else
new[i_new++] = s[i++];
}
new[i_new] = '\0';
}
else if (type == Q_DOUBLE)
{
int i = 0;
int i_new = 0;
while (s[i] != '\0')
{
if (s[i] == '$')
{
int begin = i;
int bracket = s[++i] == '{';
int offset = 1;
if (bracket)
{
offset++;
i++;
}
int start = i;
int size_var = 0;
if (is_special(s[i]))
{
i++;
size_var++;
}
else
{
while (is_char_name(s[i])/*s[i] != '\0' && s[i] != ' ' && s[i] != '\t'
&& s[i] != '$' && s[i] != '\\'
&& (!bracket || (bracket && s[i] != '}'))*/)
{
i++;
size_var++;
}
}
if (bracket)
i++;
if (i - begin != 1)
{
char *name = calloc(size_var + 1, sizeof(char));
strncpy(name, s + start, size_var);
name[size_var] = '\0';
char *var = find_elt_list(shell, name);
if (!var)
var = "";
int new_size = strlen(var);
size += begin - i + new_size;
char *tmp = realloc(new, (size + 1) * sizeof(char));
if (!tmp)
return 0;
new = tmp;
strcpy(new + i_new, var);
i_new += new_size;
free(name);
if (s[i] == '\0')
break;
if (s[i] == '$')
continue;
}
else
{
i--;
new[i_new++] = s[i++];
}
}
else if (s[i] == '\\')
{
i++;
if (s[i] == 'n')
{
i++;
new[i_new++] = '\n';
}
else if (s[i] == '\"')
{
i++;
new[i_new++] = '\"';
}
else if (s[i] == '\'')
{
i++;
new[i_new++] = '\'';
}
new[i_new++] = s[i];
if (s[i++] == '\0')
break;
}
else
new[i_new++] = s[i++];
}
new[i_new] = '\0';
}
*elt = new;
return 1;
}
int array_len(char **arr)
{
int i = 0;
while (arr[i] != NULL)
i++;
return i;
}
char **expand(char **arg, enum quotes *enclosure)
{
char **new = calloc(array_len(arg) + 1, sizeof(char *));
if (!new)
return NULL;
int ret_val = 1;
int i = 0;
while (arg[i] != NULL && ret_val)
{
ret_val = expand_s(new + i, arg[i], enclosure[i]);
i++;
}
new[i] = NULL;
if (!ret_val)
return NULL;
return new;
}
int str_in(char *s, char c)
{
int i = 0;
while (s[i] != '\0')
{
if (s[i++] == c)
return 1;
}
return 0;
}
char *merge_arg(char **arg)
{
if (!arg)
return NULL;
char *s = calloc(1, sizeof(char));
s[0] = '\0';
int size = 0;
int i = 0;
while (arg[i])
{
size += strlen(arg[i]);
char *tmp = realloc(s, (size + 1) * sizeof(char));
if (!tmp)
{
free(s);
return NULL;
}
s = tmp;
strcat(s, arg[i]);
i++;
}
return s;
}
char **split_arg(char **arg, enum quotes *enclosure)
{
if (!arg)
return NULL;
int size = array_len(arg) + 1;
char **new = calloc(size, sizeof(char *));
if (!new)
return NULL;
int ret_val = 1;
int i = 0;
int i_new = 0;
while (arg[i] != NULL && ret_val)
{
ret_val = expand_s(new + i_new, arg[i], enclosure[i]);
if (enclosure[i] == Q_NONE)
{
char *s = *(new + i_new);
int j = 0;
int start = 0;
while (s[j] != '\0')
{
start = 0;
if (str_in(shell->ifs, s[j]))
{
start = 1;
size++;
char **tmp = realloc(new, size * sizeof(char *));
if (!tmp)
return NULL;
new = tmp;
new[i_new + 1] =
calloc(strlen(new[i_new] + j + 1) + 1, sizeof(char));
strcpy(new[i_new + 1], new[i_new] + j + 1);
new[i_new][j] = '\0';
s = new[++i_new];
j = 0;
}
else
j++;
}
if (start == 0)
i_new++;
i++;
}
else
{
i++;
i_new++;
}
}
new[i_new] = NULL;
if (!ret_val)
return NULL;
return new;
}
int atoi_begining(char *s)
{
int i = 0;
int nb = 0;
while (s[i] != '\0')
{
if (s[i] < '0' || s[i] > '9')
break;
nb = nb * 10 + (s[i++] - '0');
}
if (nb == 0 && i == 0)
return -1;
return nb;
}

135
src/ast_evaluation/pipe.c Normal file
View File

@ -0,0 +1,135 @@
#include <err.h>
#include <errno.h>
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include "ast_evaluation_tools.h"
#include "builtins.h"
int exec_with_fork(char **cmd, int i, int pipe_nb, int ***fds)
{
int pid = fork();
if (pid == -1)
{
fprintf(stderr, "bsh: fork error\n");
}
else if (pid == 0)
{
if (i == 0)
{
dup2((*fds)[i][1], 1);
close((*fds)[i][0]);
close((*fds)[i][1]);
if (is_builtin(cmd[0]))
{
find_command(cmd, 1);
kill(getpid(), SIGKILL);
return shell->return_code;
}
else
execvp(cmd[0], cmd);
fprintf(stderr, "bsh: command not found: %s\n", cmd[0]);
kill(getpid(), SIGKILL);
return 127;
}
else if (i == pipe_nb)
{
dup2((*fds)[i - 1][0], 0);
close((*fds)[i - 1][0]);
close((*fds)[i - 1][1]);
if (is_builtin(cmd[0]))
{
find_command(cmd, 1);
kill(getpid(), SIGKILL);
return shell->return_code;
}
else
execvp(cmd[0], cmd);
fprintf(stderr, "bsh: command not found: %s\n", cmd[0]);
kill(getpid(), SIGKILL);
return 127;
}
else
{
dup2((*fds)[i - 1][0], 0);
dup2((*fds)[i][1], 1);
close((*fds)[i - 1][0]);
close((*fds)[i - 1][1]);
close((*fds)[i][0]);
close((*fds)[i][1]);
if (is_builtin(cmd[0]))
{
find_command(cmd, 1);
kill(getpid(), SIGKILL);
return shell->return_code;
}
else
execvp(cmd[0], cmd);
fprintf(stderr, "bsh: command not found: %s\n", cmd[0]);
kill(getpid(), SIGKILL);
return 127;
}
}
else
{
if (i != 0)
{
close((*fds)[i - 1][0]);
close((*fds)[i - 1][1]);
int wstatus;
if (waitpid(pid, &wstatus, 0) == -1)
shell->return_code = 1;
if (!WIFEXITED(wstatus))
shell->return_code = 127;
else
shell->return_code = WEXITSTATUS(wstatus);
}
return shell->return_code;
}
return shell->return_code;
}
int exec_pipe(char ***args, enum quotes **enclosure, int pipe_nb)
{
int res = 0;
int **fds = calloc(pipe_nb, sizeof(int *));
for (int i = 0; i < pipe_nb; i++)
fds[i] = calloc(2, sizeof(int));
for (int i = 0; i <= pipe_nb; i++)
{
if (i != pipe_nb)
{
if (pipe(fds[i]) == -1)
{
fprintf(stderr, "bsh: bad pipe\n");
}
}
char **val = expand(args[i], enclosure[i]);
// if (is_builtin(val[0]))
// res = exec_without_fork(val, i, pipe_nb, &fds);
// else
res = exec_with_fork(val, i, pipe_nb, &fds);
char *tmp = val[0];
int pos = 0;
while (tmp)
{
free(tmp);
tmp = val[++pos];
}
free(val);
}
for (int i = 0; i < pipe_nb; i++)
free(fds[i]);
free(fds);
free(args);
free(enclosure);
return res;
}

View File

@ -0,0 +1,148 @@
#include <stdio.h>
#include "ast.h"
#include "ast_evaluation_tools.h"
void pretty_print(struct ast *ast)
{
if (ast)
{
if (ast->type == AST_IF || ast->type == AST_FOR
|| ast->type == AST_WHILE || ast->type == AST_UNTIL)
{
if (ast->type == AST_IF)
{
printf("if { ");
pretty_print(ast->condition);
printf("}; then { ");
pretty_print(ast->left_child);
printf("}");
if (ast->right_child)
{
printf(" else { ");
pretty_print(ast->right_child);
printf("}");
}
}
else if (ast->type == AST_FOR)
{
printf("for { ");
for (int i = 0; ast->value[i]; i++)
{
printf("%s ", ast->value[i]);
}
printf("}; do { ");
pretty_print(ast->left_child);
printf("}");
}
else if (ast->type == AST_WHILE)
{
printf("while { ");
pretty_print(ast->condition);
printf("}; do { ");
pretty_print(ast->left_child);
printf("}");
}
else if (ast->type == AST_UNTIL)
{
printf("until { ");
pretty_print(ast->condition);
printf("}; do { ");
pretty_print(ast->left_child);
printf("}");
}
}
else if (ast->type == AST_AND)
{
printf("{ ");
pretty_print(ast->left_child);
printf("} && { ");
pretty_print(ast->right_child);
printf("}");
}
else if (ast->type == AST_OR)
{
printf("{ ");
pretty_print(ast->left_child);
printf("} OR { ");
pretty_print(ast->right_child);
printf("}");
}
else if (ast->type == AST_REDIR)
{
pretty_print(ast->left_child);
printf(" %s ", ast->value[0]);
pretty_print(ast->right_child);
}
else if (ast->type == AST_PIPE)
{
pretty_print(ast->left_child);
printf(" | ");
pretty_print(ast->right_child);
}
else if (ast->type == AST_COMMAND)
{
if (ast->value[0])
{
printf("%s", ast->value[0]);
for (size_t i = 1; ast->value[i] != NULL; i++)
printf(" %s", ast->value[i]);
}
}
else if (ast->type == AST_LIST)
{
pretty_print(ast->right_child);
printf("; ");
pretty_print(ast->left_child);
}
else if (ast->type == AST_NOT)
{
printf("! ");
pretty_print(ast->left_child);
}
else if (ast->type == AST_ASSIGNMENT)
{
printf("%s=", ast->var_name);
for (size_t i = 0; ast->value && ast->value[i] != NULL; i++)
printf("%s ", ast->value[i]);
}
else if (ast->type == AST_FUNC)
{
if (ast->var_name)
printf("%s() ", ast->var_name);
printf("{ ");
pretty_print(ast->left_child);
printf("}");
}
else if (ast->type == AST_CMD_SUBSTITUTION)
{
printf("$(");
pretty_print(ast->left_child);
printf(")");
}
else if (ast->type == AST_SUBSHELL)
{
printf("(");
pretty_print(ast->left_child);
printf(")");
}
else if (ast->type == AST_CASE)
{
printf("case %s in ", ast->value[0]);
pretty_print(ast->left_child);
printf("esac");
}
else if (ast->type == AST_CASE_SWITCH)
{
printf("%s", ast->value[0]);
for (int i = 1; ast->value[i]; i++)
printf("|%s", ast->value[i]);
printf(") ");
pretty_print(ast->left_child);
printf(";; ");
pretty_print(ast->right_child);
}
else if (ast->type == AST_EOF)
printf("EOF\n");
}
}

View File

@ -0,0 +1,44 @@
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#include "ast_evaluation_tools.h"
/*
* The main objectif of this source code is to call some programe of the
* computer using fork and exec
*/
int call_exec(char **cmd)
{
pid_t cpid = fork();
if (cpid == -1)
{
perror("bsh");
return 1;
}
else if (!cpid)
{
execvp(cmd[0], cmd);
fprintf(stderr, "bsh: command not found: %s\n", cmd[0]);
kill(getpid(), SIGKILL);
return 127;
}
else
{
int cstatus = 0;
if (waitpid(cpid, &cstatus, 0) == -1)
{
return 1;
}
if (!WIFEXITED(cstatus))
{
return 127;
}
return WEXITSTATUS(cstatus);
}
}

172
src/ast_evaluation/redir.c Normal file
View File

@ -0,0 +1,172 @@
#include "redir.h"
static bool is_int(char *str)
{
int i = 0;
while (str[i])
{
if (str[i] < '0' || str[i] > '9')
return false;
i++;
}
return true;
}
static int get_open_flags(char **redir)
{
char *type = redir[0][0] == '>' ? redir[0] : redir[1];
switch (type[1])
{
case 0:
case '|':
return O_CREAT | O_WRONLY | O_TRUNC;
case '>':
return O_CREAT | O_WRONLY | O_APPEND;
default:
return O_CREAT | O_WRONLY | O_TRUNC;
}
}
static bool readable(int fd)
{
if (fd >= 0 && fd <= 2)
return true;
int o_accmode = 0;
int rc = fcntl(fd, F_GETFL, &o_accmode);
if (rc == -1)
return false;
rc = (o_accmode & O_ACCMODE);
printf("%d\n", rc);
return (rc == O_RDONLY || rc == O_RDWR);
}
static bool writeable(int fd)
{
if (fd == 1 || fd == 2)
return true;
int o_accmode = 0;
int rc = fcntl(fd, F_GETFL, &o_accmode);
if (rc == -1)
return false;
rc = (o_accmode & O_ACCMODE);
return (rc == O_WRONLY || rc == O_RDWR);
}
static void setup_out_redir(char **redir)
{
char *filename = get_filename_from_redir(redir);
int fd = open(filename, get_open_flags(redir), 0644);
int ionumber = get_fd_from_redir(redir, true);
if (ionumber < 0)
{
close(fd);
return;
}
dup2(fd, ionumber);
close(fd);
}
static void setup_in_redir(char **redir)
{
char *filename = get_filename_from_redir(redir);
int fd = open(filename, O_RDONLY);
if (fd == -1)
{
fprintf(stderr, "bsh: %s: No such file or directory\n", filename);
return;
}
int ionumber = get_fd_from_redir(redir, false);
if (ionumber < 0)
{
close(fd);
return;
}
dup2(fd, ionumber);
close(fd);
}
static int is_out_dup(char **redir)
{
if (redir[0][1] == '&')
return redir[0][0] == '>';
return redir[1][0] == '>';
}
static void setup_dup_redir(char **redir)
{
int is_out = is_out_dup(redir);
int fd1 = get_fd_from_redir(redir, is_out);
char *filename = get_filename_from_redir(redir);
if (is_out)
{
if (!strcmp(filename, "-"))
close(fd1);
else if (is_int(filename))
{
int fd2 = atoi(filename);
if (fd2 == fd1)
return;
if (!writeable(fd2))
{
fprintf(stderr, "bsh: file descriptor %d is not writable\n",
fd2);
return;
}
int tmpout = dup(fd1);
dup2(fd2, tmpout);
close(tmpout);
}
}
else
{
if (!strcmp(filename, "-"))
close(fd1);
else if (is_int(filename))
{
int fd2 = atoi(filename);
if (fd2 == fd1)
return;
if (!readable(fd2))
{
fprintf(stderr, "bsh: file descriptor %d is not readable\n",
fd2);
return;
}
int tmpin = dup(fd1);
dup2(fd2, tmpin);
close(tmpin);
}
}
}
void exec_redirections(char ***redirs)
{
int redirs_pos = 0;
while (redirs[redirs_pos])
{
if (is_in_redir(redirs[redirs_pos]))
setup_in_redir(redirs[redirs_pos]);
if (is_out_redir(redirs[redirs_pos]))
setup_out_redir(redirs[redirs_pos]);
else
setup_dup_redir(redirs[redirs_pos]);
redirs_pos++;
}
}
// int main(void)
// {
// char **cmd = calloc(3, sizeof(char *));
// cmd[0] = "xargs";
// cmd[1] = "-0";
// char ***redirs = calloc(3, sizeof(char *));
// redirs[0] = calloc(4, sizeof(char *));
// redirs[0][0] = "3";
// redirs[0][1] = ">";
// redirs[0][2] = "test.txt";
// exec_redirections(cmd, redirs);
// free(redirs[0]);
// free(redirs[1]);
// free(redirs);
// free(cmd);
// }

View File

@ -0,0 +1,8 @@
#ifndef REDIR_H
#define REDIR_H
#include "redir_tools.h"
void exec_redirections(char ***redirs);
#endif // !REDIR_H

View File

@ -0,0 +1,56 @@
#include "redir_tools.h"
static bool is_int(char *str)
{
int i = 0;
while (str[i])
{
if (str[i] < '0' || str[i] > '9')
return false;
i++;
}
return true;
}
char *get_filename_from_redir(char **redir)
{
char *filename = NULL;
int len = 0;
while (redir[len])
len++;
filename = redir[len - 1];
return filename;
}
int get_fd_from_redir(char **redir, bool out_redir)
{
if (is_int(redir[0]))
{
int res = atoi(redir[0]);
if (res > 2)
{
fprintf(stderr, "bsh: bad file descriptor\n");
return -1;
}
return res;
}
return out_redir;
}
bool is_out_redir(char **redir)
{
if (redir[0][0] == '>')
return redir[0][1] != '&';
if (redir[1][0] == '>')
return redir[1][1] != '&';
return false;
}
bool is_in_redir(char **redir)
{
if (redir[0][0] == '<')
return redir[0][1] != '&';
if (redir[1][0] == '<')
return redir[1][1] != '&';
return false;
}

View File

@ -0,0 +1,23 @@
#ifndef REDIR_TOOLS_H
#define REDIR_TOOLS_H
// #define _GNU_SOURCE
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
char *get_filename_from_redir(char **redir);
int get_fd_from_redir(char **redir, bool out_redir);
bool is_out_redir(char **redir);
bool is_in_redir(char **redir);
#endif // !REDIR_TOOLS_H

128
src/bsh.c Normal file
View File

@ -0,0 +1,128 @@
#include "bsh.h"
#include <stdio.h>
#include <string.h>
#include "functions.h"
#include "lexer.h"
#include "loop_stack.h"
#include "shell_input.h"
#include "var_list.h"
struct shell *shell;
static void init_shell(int argc, char **argv)
{
shell = calloc(1, sizeof(struct shell));
shell->pid = getppid();
shell->pretty_print = argc > 1 ? !strcmp(argv[1], "--pretty-print") : false;
if (shell->pretty_print)
{
printf("Pretty print enabled\n");
argc--;
argv++;
}
shell->verbose = argc > 1 ? !strcmp(argv[1], "--verbose") : false;
if (shell->verbose)
printf("Verbose mode enabled\n");
shell->oldpwd = calloc(2048, sizeof(char));
if (getenv("OLDPWD"))
strcpy(shell->oldpwd, getenv("OLDPWD"));
else if (getcwd(shell->oldpwd, 2048) == NULL)
shell->exit = true;
shell->pwd = calloc(2048, sizeof(char));
if (!shell->exit && getcwd(shell->pwd, 2048) == NULL)
shell->exit = true;
// TODO: what are the shell parameters?
shell->args = NULL;
shell->nb_args = 0;
shell->ifs = calloc(100, sizeof(char));
strcpy(shell->ifs, " \t\n");
shell->uid = getuid();
shell->var_list = NULL;
shell->var_stack = NULL;
shell->functions = NULL;
shell->loop_stack = NULL;
shell->random_nb = NULL;
// append param shell->var_stack
}
void free_shell(void)
{
int i = 0;
while (shell->args && shell->args[i])
free(shell->args[i++]);
free(shell->args);
free_list(shell);
free_fun_sub(shell);
free(shell->oldpwd);
free(shell->pwd);
free(shell->ifs);
if (shell->random_nb)
free(shell->random_nb);
struct lexer_alias *alias = shell->alias_list;
while (alias)
{
struct lexer_alias *next = alias->next;
free(alias->name);
struct lexer_token *token = alias->value;
while (token)
{
struct lexer_token *next = token->next;
lexer_token_free(token);
token = next;
}
free(alias);
alias = next;
}
free_loop(shell);
free(shell);
}
void print_shell(void)
{
printf("bsh\n");
printf(" + oldpwd: %s\n", shell->oldpwd);
printf(" + pwd: %s\n", shell->pwd);
printf(" + args (%d):\n", shell->nb_args);
for (int i = 0; i < shell->nb_args; i++)
printf(" %s\n", shell->args[i]);
printf(" + ifs:");
for (int i = 0; shell->ifs[i] != '\0'; i++)
printf(" %d", shell->ifs[i]);
printf("\n");
printf(" + uid: %d\n", shell->uid);
printf(" + exit: %d\n", shell->exit);
printf(" + last_return_code: %d\n", shell->return_code);
printf("\n");
}
int main(int argc, char **argv)
{
init_shell(argc, argv);
if (shell->exit)
{
free_shell();
fprintf(stderr, "bsh: error during initialization.\n");
return 1;
}
// print_shell();
int res;
if (shell->pretty_print && shell->verbose)
res = get_input(argc - 2, argv + 2);
else if (shell->pretty_print || shell->verbose)
res = get_input(argc - 1, argv + 1);
else
res = get_input(argc, argv);
free_shell();
return res;
char *input = calloc(49, sizeof(char));
strcpy(input, "if test then bsh.c\n then echo 'bsh.c exists'\n fi");
struct lexer *lexer = lexer_create(input);
lexer_build(lexer);
lexer_free(lexer);
return (0);
}

71
src/bsh.h Normal file
View File

@ -0,0 +1,71 @@
#ifndef SHELL_H
#define SHELL_H
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
struct var
{
char *name;
char *value;
struct var *next;
};
struct var_stack
{
struct var *var_list;
struct var_stack *next;
};
struct loop_stack
{
struct ast *loop;
struct loop_stack *next;
};
struct functions
{
char *name;
struct ast *function;
struct functions *next;
};
struct shell
{
bool pretty_print;
char *oldpwd;
char *pwd;
bool exit;
char **args;
int nb_args;
char *ifs;
uid_t uid;
int return_code;
char *random_nb;
bool verbose;
bool interupt;
struct var_stack *var_stack;
struct var *var_list;
struct loop_stack *loop_stack;
struct functions *functions;
int ctn; // continue is a keyword
int brk; // break is a keyword
pid_t pid;
struct lexer_alias *alias_list;
};
/**
* @brief Print the shell structure.
*
*/
void print_shell(void);
/**
* @brief Free the shell structure.
*
*/
void free_shell(void);
#endif // !SHELL_H

31
src/builtins/break.c Normal file
View File

@ -0,0 +1,31 @@
#include "builtins.h"
static bool is_int(char *str)
{
int i = 0;
while (str[i])
{
if (str[i] < '0' || str[i] > '9')
return false;
i++;
}
return true;
}
int my_break(char **args)
{
if (args[1] == NULL)
{
shell->brk = 1;
return 1;
}
if (is_int(args[1]))
{
shell->brk = atoi(args[1]);
return shell->brk;
}
fprintf(stderr, "bsh: break: Illegal number: %s\n", args[1]);
return -1;
}

26
src/builtins/builtins.c Normal file
View File

@ -0,0 +1,26 @@
#include "builtins.h"
int find_command(char **args, int fd_write)
{
if (!strcmp(args[0], "echo"))
{
echo(args, fd_write);
return 0;
}
if (!strcmp(args[0], "cd"))
return cd(args);
if (!strcmp(args[0], "continue"))
return my_continue(args);
if (!strcmp(args[0], "break"))
return my_break(args);
if (!strcmp(args[0], "exit"))
return my_exit(args);
if (!strcmp(args[0], "export"))
return export(args);
if (!strcmp(args[0], "unset"))
return unset(args);
if (!strcmp(args[0], "."))
return dot(args);
else
return 1;
}

92
src/builtins/builtins.h Normal file
View File

@ -0,0 +1,92 @@
#ifndef BUILTINS_H
#define BUILTINS_H
#include <dirent.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include "bsh.h"
#include "lexer.h"
extern struct shell *shell;
/**
* @brief : Check which builtin command is received and execute it.
*
* @param toExecute : Builtin command starting from the bultin to the end
* of it's argument.
* @return int : 0 on success, 1 on failure.
*/
int find_command(char **toExecute, int fd_write);
/**
* @brief Execute cd command.
*
* @param args the list of arguments
* @return int return code
*/
int cd(char **args);
/**
* @brief The echo command.
*
* @param args the list of arguments
*/
void echo(char **args, int fd_write);
/**
* @brief @brief Modify the shell->exit and shell->return_code parameter in the
* global variable shell
*
* @param args the list of arguments
* @return int return 0 on success, 1 on failure
*/
int my_exit(char **args);
/**
* @brief Put a variable to the exported variable-list, if it doesn't exist, it
* creates it.
*
* @param args the list of arguments.
* @return int return 0 on success, 1 on failure.
*/
int export(char **args);
/**
* @brief The continue command.
*
* @param args the list of arguments.
* @return int the number of enclosing loops to continue. -1 on failure.
*/
int my_continue(char **args);
/**
* @brief The break command.
*
* @param args the list of arguments.
* @return int the number of enclosing loops to break. -1 on failure.
*/
int my_break(char **args);
/**
* @brief The unset command.
*
* @param args the list of arguments.
* @return int return 0 on success, -1 on failure.
*/
int unset(char **args);
/**
* @brief The dot builtin
*
* @param argv the list of arguments.
* @return int
*/
int dot(char **argv);
#endif

41
src/builtins/cd.c Normal file
View File

@ -0,0 +1,41 @@
#include "builtins.h"
#include "var_list.h"
int cd(char **args)
{
int len = 0;
while (args[len])
len++;
if (len < 2)
return 0;
if (!strcmp(args[1], "-"))
{
chdir(shell->oldpwd);
char *swap = shell->oldpwd;
shell->oldpwd = shell->pwd;
shell->pwd = swap;
printf("%s\n", shell->pwd);
setenv("OLDPWD", shell->oldpwd, 1);
setenv("PWD", shell->pwd, 1);
fflush(stdout);
return 0;
}
int error_chdir = chdir(args[1]);
if (error_chdir == -1)
{
fprintf(stderr, "bsh: cd: can't cd to %s\n", args[1]);
return 1;
}
shell->oldpwd = strcpy(shell->oldpwd, shell->pwd);
push_elt_list(shell, "OLDPWD", shell->pwd);
getcwd(shell->pwd, 2048);
setenv("OLDPWD", shell->oldpwd, 1);
setenv("PWD", shell->pwd, 1);
return 0;
}

31
src/builtins/continue.c Normal file
View File

@ -0,0 +1,31 @@
#include "builtins.h"
static bool is_int(char *str)
{
int i = 0;
while (str[i])
{
if (str[i] < '0' || str[i] > '9')
return false;
i++;
}
return true;
}
int my_continue(char **args)
{
if (args[1] == NULL)
{
shell->ctn = 1;
return 1;
}
if (is_int(args[1]))
{
shell->ctn = atoi(args[1]);
return shell->ctn;
}
fprintf(stderr, "bsh: continue: Illegal number: %s\n", args[1]);
return -1;
}

163
src/builtins/dot.c Normal file
View File

@ -0,0 +1,163 @@
#include <errno.h>
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "bsh.h"
#include "lexer.h"
#include "parser.h"
#include "shell_input.h"
#include "var_list.h"
extern struct shell *shell;
static char **split_path(char *path)
{
char *rest = path;
char *token;
char **paths = NULL;
int paths_nb = 0;
while ((token = strtok_r(rest, ":", &rest)) != NULL)
{
paths = realloc(paths, sizeof(char *) * (paths_nb + 2));
paths[paths_nb++] = strdup(token);
}
paths[paths_nb] = NULL;
return paths;
}
static char *find_in_path(const char *input)
{
char *path_tmp = getenv("PATH");
char *path = calloc(strlen(path_tmp) + 1, sizeof(char));
strcpy(path, path_tmp);
if (!path)
{
fprintf(stderr, "bsh: .: %s: not found\n", input);
shell->return_code = 2;
free(path);
return NULL;
}
char **path_single = split_path(path);
if (!path_single)
{
fprintf(stderr, "bsh: .: %s: not found\n", input);
shell->return_code = 2;
free(path);
return NULL;
}
int i = 0;
bool found = false;
char *final_path = NULL;
while (path_single[i] && !found)
{
char *full_path =
calloc(strlen(path_single[i]) + strlen(input) + 2, sizeof(char));
strcpy(full_path, path_single[i]);
strcat(full_path, "/");
strcat(full_path, input);
struct stat sb;
if (access(full_path, F_OK) == 0
&& (stat(path, &sb) == 0 && sb.st_mode & S_IXUSR))
{
found = true;
final_path = full_path;
continue;
}
free(full_path);
free(path_single[i]);
i++;
}
if (!final_path)
{
fprintf(stderr, "bsh: .: %s: not found\n", input);
shell->return_code = 2;
}
free(path);
free(path_single);
return final_path;
}
static char get_first_char(const char *arg)
{
int pos = 0;
while (arg[pos] != 0 && arg[pos] == '.')
pos++;
return arg[pos];
}
char *get_file_content(const char *path)
{
if (get_first_char(path) != '/')
path = find_in_path(path);
if (!path)
return NULL;
char *buffer = 0;
long length;
FILE *f = fopen(path, "r");
if (!f)
{
shell->return_code = 127;
fprintf(stderr, "bsh: .: Can't open %s\n", path);
return NULL;
}
struct stat sb;
if (!(stat(path, &sb) == 0 && sb.st_mode & S_IXUSR))
{
shell->return_code = 126;
fprintf(stderr, "bsh: %s: Permission denied\n", path);
fclose(f);
return NULL;
}
fseek(f, 0, SEEK_END);
length = ftell(f);
fseek(f, 0, SEEK_SET);
buffer = calloc(length + 1, sizeof(char));
if (buffer)
{
fread(buffer, 1, length, f);
}
fclose(f);
return buffer;
}
static void restore_shell(struct var *vars, struct var_stack *stack)
{
free_list(shell);
shell->var_list = vars;
shell->var_stack = stack;
}
int dot(char **argv)
{
struct var *save_var_list = shell->var_list;
struct var_stack *save_var_stack = shell->var_stack;
shell->var_list = NULL;
shell->var_stack = NULL;
char **args = calloc(2, sizeof(char *));
new_var(shell, args);
if (!argv[1])
{
free(args);
restore_shell(save_var_list, save_var_stack);
return 0;
}
char *buf = get_file_content(argv[1]);
if (!buf)
{
free(args);
restore_shell(save_var_list, save_var_stack);
return shell->return_code;
}
parse_input(buf, NULL);
free(buf);
free(args);
restore_shell(save_var_list, save_var_stack);
return shell->return_code;
}

84
src/builtins/echo.c Normal file
View File

@ -0,0 +1,84 @@
#include "builtins.h"
static void afterBackslash(char *toCheck, int *index, int fd_write)
{
*index += 1;
if (toCheck[*index] != '\0')
{
switch (toCheck[*index])
{
case '\\':
write(fd_write, "\\", 1);
break;
case 'n':
write(fd_write, "\n", 1);
break;
case 't':
write(fd_write, "\t", 1);
break;
default:
write(fd_write, "\\", 1);
write(fd_write, &(toCheck[*index]), 1);
break;
}
}
else
write(fd_write, "\\", 1);
}
void echo(char **args, int fd_write)
{
bool n_option = false;
bool e_option = false;
int start_print = 1;
for (; args[start_print] != NULL; start_print++)
{
if (!strcmp(args[start_print], "-n"))
n_option = true;
else if (!strcmp(args[start_print], "-e"))
e_option = true;
else if (!strcmp(args[start_print], "-ne")
|| !strcmp(args[start_print], "-en"))
{
e_option = true;
n_option = true;
}
else
break;
}
if (e_option)
{
for (; args[start_print] != NULL; start_print++)
{
for (int i = 0; args[start_print][i] != '\0'; i++)
{
if (args[start_print][i] == '\\')
afterBackslash(args[start_print], &i, fd_write);
else
write(fd_write, &(args[start_print][i]), 1);
}
if (args[start_print + 1] != NULL)
write(fd_write, " ", 1);
}
}
else
{
for (; args[start_print] != NULL; start_print++)
{
for (int i = 0; args[start_print][i] != '\0'; i++)
write(fd_write, &(args[start_print][i]), 1);
if (args[start_print + 1] != NULL)
write(fd_write, " ", 1);
}
}
if (!n_option)
printf("\n");
fflush(stdout);
}

36
src/builtins/exit.c Normal file
View File

@ -0,0 +1,36 @@
#include "builtins.h"
static bool is_int(char *str)
{
int i = 0;
while (str[i])
{
if (str[i] < '0' || str[i] > '9')
return false;
i++;
}
return true;
}
int my_exit(char **args)
{
if (args[1] == NULL)
shell->return_code = 0;
else
{
if (is_int(args[1]))
{
int return_code = atoi(args[1]);
shell->return_code = return_code % 256;
}
else
{
fprintf(stderr, "bsh: exit: Illegal number: %s\n", args[1]);
return 2;
}
}
shell->exit = true;
return 0;
}

40
src/builtins/export.c Normal file
View File

@ -0,0 +1,40 @@
#include "../variables/var_list.h"
#include "builtins.h"
int export(char **args)
{
if (args[1] == NULL)
{
fprintf(stderr,
"bsh: export: expected export <name> or export <name>=<value> "
"but got export\n");
return 0;
}
for (int i = 0; args[1][i] != '\0'; i++)
{
if ((args[1][i] >= 'a' && args[1][i] <= 'z')
|| (args[1][i] >= 'A' && args[1][i] <= 'Z')
|| (args[1][i] >= '0' && args[1][i] <= '9') || args[1][i] == '_')
continue;
fprintf(stderr, "bsh: export %s: bad variable name\n", args[1]);
return 2;
}
if (args[2] == NULL)
{
char *value = find_elt_list(shell, args[1]);
if (value != NULL)
{
setenv(args[1], value, 1);
}
}
else
{
setenv(args[1], args[2], 1);
push_elt_list(shell, args[1], args[2]);
}
return 0;
}

62
src/builtins/unset.c Normal file
View File

@ -0,0 +1,62 @@
#include "../functions/functions.h"
#include "../variables/var_list.h"
#include "builtins.h"
int unset(char **args)
{
bool v_option = false;
bool f_option = false;
int start_print = 1;
for (; args[start_print] != NULL; start_print++)
{
if (!strcmp(args[start_print], "-v"))
v_option = true;
else if (!strcmp(args[start_print], "-f"))
f_option = true;
else if (!strcmp(args[start_print], "-fv")
|| !strcmp(args[start_print], "-vf"))
{
v_option = true;
f_option = true;
}
else
{
if (args[start_print][0] == '-')
{
fprintf(stderr, "bsh: unset: Illegal option %s\n",
args[start_print]);
return 1;
}
break;
}
}
if (!v_option && !f_option)
v_option = true;
for (int i = 0; args[start_print][i] != '\0'; i++)
{
if ((args[start_print][i] >= 'a' && args[start_print][i] <= 'z')
|| (args[start_print][i] >= 'A' && args[start_print][i] <= 'Z')
|| (args[start_print][i] >= '0' && args[start_print][i] <= '9')
|| args[start_print][i] == '_')
continue;
fprintf(stderr, "bsh: unset: %s: bad variable name\n",
args[start_print]);
return 1;
}
if (v_option)
{
del_name(shell, args[start_print]);
unsetenv(args[start_print]);
}
if (f_option)
del_fun_name(shell, args[start_print]);
return 0;
}

109
src/functions/functions.c Normal file
View File

@ -0,0 +1,109 @@
#include "functions.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../bsh.h"
int push_elt_fun(struct shell *sh, char *name, struct ast *fun)
{
struct functions *tmp = sh->functions;
while (tmp && strcmp(tmp->name, name))
tmp = tmp->next;
if (tmp)
tmp->function = fun;
else
{
struct functions *new = calloc(1, sizeof(struct functions));
if (!new)
return 1;
new->name = calloc(strlen(name) + 1, sizeof(char));
if (!new->name)
{
free(new);
return 1;
}
strcpy(new->name, name);
new->function = fun;
new->next = sh->functions;
sh->functions = new;
}
return 0;
}
struct ast *find_elt_fun(struct shell *sh, char *name)
{
struct functions *tmp = sh->functions;
while (tmp && strcmp(tmp->name, name))
tmp = tmp->next;
// printf("%s\n", tmp->value);
if (tmp)
return tmp->function;
return NULL;
}
void free_fun_sub(struct shell *sh)
{
struct functions *fun = sh->functions;
while (fun)
{
struct functions *tmp = fun;
fun = fun->next;
free(tmp->name);
free(tmp);
}
}
int del_fun_name(struct shell *sh, char *name)
{
struct functions *actual = sh->functions;
struct functions *previous = sh->functions;
int index = 0;
while (actual)
{
if (!strcmp(actual->name, name))
{
if (index == 0)
sh->functions = actual->next;
else
previous->next = actual->next;
free(actual->name);
free(actual);
return 1;
}
index++;
previous = actual;
actual = actual->next;
}
return 0;
}
struct functions *fun_list_cpy(struct shell *sh)
{
struct functions *new = NULL;
struct functions *fun = sh->functions;
while (fun)
{
struct functions *tmp = new;
new = calloc(1, sizeof(struct functions));
if (!new)
return NULL;
new->name = calloc(strlen(fun->name) + 1, sizeof(char));
if (!new->name)
{
free(new);
return NULL;
}
strcpy(new->name, fun->name);
new->function = fun->function;
new->next = tmp;
fun = fun->next;
}
return new;
}

12
src/functions/functions.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef FUNCTIONS_H
#define FUNCTIONS_H
#include "bsh.h"
int push_elt_fun(struct shell *sh, char *name, struct ast *fun);
struct ast *find_elt_fun(struct shell *sh, char *name);
void free_fun_sub(struct shell *sh);
int del_fun_name(struct shell *sh, char *name);
struct functions *fun_list_cpy(struct shell *sh);
#endif

278
src/lexer/alias.c Normal file
View File

@ -0,0 +1,278 @@
#include "bsh.h"
#include "lexer_tools.h"
extern struct shell *shell;
struct lexer_alias *get_alias(char *name)
{
struct lexer_alias *head = shell->alias_list;
while (head)
{
if (head->name && !strcmp(head->name, name))
return head;
head = head->next;
}
return NULL;
}
static char *get_token_string(enum token_type type)
{
char *token_string[] = { "",
"if",
"else",
"elif",
"fi",
"then",
"do",
"done",
"while",
"until",
"for",
"in",
"&&",
"||",
";",
"\n",
"REDIR",
"IONUMBER",
"|",
"!",
"ASSIGNMENT_WORD",
"(",
"$(",
"`",
")",
"{",
"}",
"$",
"case",
"esac",
"WORD",
"WORD_DOUBLE_QUOTE",
"WORD_SINGLE_QUOTE",
"EOF",
" " };
return strdup(token_string[type]);
}
static char *get_alias_value(struct lexer_token *head)
{
char *res = NULL;
while (head)
{
size_t len = res ? strlen(res) : 0;
char *to_append =
head->value ? head->value : get_token_string(head->type);
res = realloc(res, sizeof(char) * (len + strlen(to_append) + 1));
res[len] = '\0';
strcat(res, to_append);
if (!head->value)
free(to_append);
head = head->next;
}
return res;
}
void lexer_append_alias(struct lexer *lexer, struct lexer_alias *alias)
{
struct lexer_token *token = alias->value;
while (token)
{
struct lexer_token *new = calloc(1, sizeof(struct lexer_token));
new->type = token->type;
if (token->value)
new->value = strdup(token->value);
else
new->value = NULL;
lexer_append(lexer, new);
token = token->next;
}
}
struct lexer_token *copy_lexer_alias(struct lexer_token *head)
{
struct lexer_token *new_head = calloc(1, sizeof(struct lexer_token));
new_head->type = head->type;
if (head->value)
new_head->value = strdup(head->value);
head = head->next;
struct lexer_token *current_tail = head;
while (head)
{
struct lexer_token *new_token = calloc(1, sizeof(struct lexer_token));
new_token->type = head->type;
if (head->value)
new_token->value = strdup(head->value);
current_tail->next = new_token;
current_tail = new_token;
head = head->next;
}
return new_head;
}
static void process_single_alias(struct lexer *lexer, char *name,
struct lexer_token *value)
{
if (value)
{
struct lexer_alias *alias = calloc(1, sizeof(struct lexer_alias));
alias->name = strdup(name);
if (value->type != TOKEN_NEWLINE && value->type != TOKEN_SEMICOLON
&& value->type != TOKEN_EOF)
alias->value = value;
else
{
struct lexer_token *token = calloc(1, sizeof(struct lexer_token));
token->type = TOKEN_WORD;
token->value = strdup("");
alias->value = token;
}
alias->next = lexer->alias_list;
lexer->alias_list = alias;
shell->return_code = 0;
}
else
{
struct lexer_alias *alias = get_alias(name);
if (!alias)
{
shell->return_code = 1;
fprintf(stderr, "bsh: alias: %s: not found\n", name);
}
else
{
char *a_value = get_alias_value(alias->value);
printf("%s='%s'\n", alias->name, a_value);
free(a_value);
shell->return_code = 0;
}
}
}
void process_alias(struct lexer_token *prev, struct lexer_token *head,
struct lexer *lexer)
{
shell->return_code = 0;
if (!head
|| (head->type != TOKEN_ALIAS && head->type != TOKEN_SPACE
&& head->type != TOKEN_ASSIGNMENT_WORD && head->type != TOKEN_WORD
&& head->type != TOKEN_WORD_SINGLE_QUOTE
&& head->type != TOKEN_WORD_DOUBLE_QUOTE))
{
if (head
&& (head->type == TOKEN_SEMICOLON || head->type == TOKEN_NEWLINE))
{
if (prev)
prev->next = head->next;
else
lexer->tokens = head->next;
lexer_token_free(head);
}
return;
}
struct lexer_token *name = head;
if (head->type == TOKEN_ALIAS && head->next)
{
name = head->next;
lexer_token_free(head);
}
while (name->type == TOKEN_SPACE)
{
struct lexer_token *next = name->next;
lexer_token_free(name);
name = next;
}
head = name;
if (!name || !name->value)
{
if (prev)
prev->next = NULL;
else
lexer->tokens = NULL;
return;
}
struct lexer_token *value = NULL;
struct lexer_token *end = head->next ? head->next : head;
if (name->type == TOKEN_ASSIGNMENT_WORD)
{
value = head->next;
while (value->type == TOKEN_SPACE)
{
struct lexer_token *next = value->next;
lexer_token_free(value);
value = next;
}
end = value;
if (value->next)
end = value->next;
struct lexer_token *previous = value;
while (end && end->type != TOKEN_SPACE && end->type != TOKEN_NEWLINE
&& end->type != TOKEN_EOF && end->type != TOKEN_SEMICOLON)
{
previous = end;
end = end->next;
}
previous->next = NULL;
if (prev)
prev->next = end;
else
lexer->tokens = end;
}
else
{
if (prev)
prev->next = end;
else
lexer->tokens = end;
}
process_single_alias(lexer, name->value, value);
lexer_token_free(name);
if (end && end->type == TOKEN_SEMICOLON)
end->type = TOKEN_SPACE;
process_alias(end, end ? end->next : NULL, lexer);
}
void process_unalias(struct lexer_token *prev, struct lexer_token *head,
struct lexer *lexer)
{
shell->return_code = 0;
while (head && head->type != TOKEN_SEMICOLON && head->type != TOKEN_NEWLINE
&& head->type != TOKEN_EOF)
{
if (head->type < TOKEN_WORD || head->type > TOKEN_WORD_DOUBLE_QUOTE)
{
struct lexer_token *next = head->next;
lexer_token_free(head);
head = next;
continue;
}
bool to_free = false;
if (!head->value)
{
head->value = get_token_string(head->type);
to_free = true;
}
struct lexer_alias *alias = get_alias(head->value);
if (alias)
{
free(alias->name);
alias->name = calloc(1, sizeof(char));
}
else
{
shell->return_code = 1;
fprintf(stderr, "bsh: alias: %s: not found\n", head->value);
}
if (to_free)
free(head->value);
struct lexer_token *next = head->next;
lexer_token_free(head);
head = next;
}
if (head && head->type == TOKEN_SEMICOLON)
head->type = TOKEN_SPACE;
if (prev)
prev->next = head;
else
lexer->tokens = head;
}

10
src/lexer/create_token.c Normal file
View File

@ -0,0 +1,10 @@
#include "lexer_tools.h"
void create_and_append_token(struct lexer *lexer, enum token_type type,
char *value)
{
struct lexer_token *token = calloc(1, sizeof(struct lexer_token));
token->type = type;
token->value = value;
lexer_append(lexer, token);
}

15
src/lexer/export.c Normal file
View File

@ -0,0 +1,15 @@
#include "lexer_tools.h"
void process_export(struct lexer *lexer)
{
struct lexer_token *token = lexer->tokens;
while (token && token->next)
{
if (token->value && !strcmp(token->value, "export")
&& token->next->type == TOKEN_ASSIGNMENT_WORD)
{
token->next->type = TOKEN_WORD;
}
token = token->next;
}
}

30
src/lexer/ionumbers.c Normal file
View File

@ -0,0 +1,30 @@
#include "lexer.h"
#include "lexer_tools.h"
bool is_int(char *input)
{
int i = 0;
while (input[i])
{
if (input[i] < '0' || input[i] > '9')
return false;
i++;
}
return true;
}
// void words_to_ionumber(struct lexer *lexer)
// {
// struct lexer_token *token = lexer->tokens;
// struct lexer_token *prev = lexer->tokens;
// while (token)
// {
// if (token->type == TOKEN_REDIR && prev && prev->type == TOKEN_WORD
// && is_int(prev->value))
// {
// prev->type = TOKEN_IONUMBER;
// }
// prev = token;
// token = token->next;
// }
// }

42
src/lexer/keywords.c Normal file
View File

@ -0,0 +1,42 @@
#include "lexer_tools.h"
bool is_keyword(char *word)
{
return (
!strcmp(word, "if") || !strcmp(word, "else") || !strcmp(word, "elif")
|| !strcmp(word, "fi") || !strcmp(word, "then") || !strcmp(word, "!")
|| !strcmp(word, "do") || !strcmp(word, "done") || !strcmp(word, "for")
|| !strcmp(word, "while") || !strcmp(word, "until")
|| !strcmp(word, "case") || !strcmp(word, "esac"));
}
enum token_type get_keyword(char *word)
{
if (!strcmp(word, "if"))
return TOKEN_IF;
if (!strcmp(word, "else"))
return TOKEN_ELSE;
if (!strcmp(word, "elif"))
return TOKEN_ELIF;
if (!strcmp(word, "fi"))
return TOKEN_FI;
if (!strcmp(word, "then"))
return TOKEN_THEN;
if (!strcmp(word, "!"))
return TOKEN_NOT;
if (!strcmp(word, "do"))
return TOKEN_DO;
if (!strcmp(word, "done"))
return TOKEN_DONE;
if (!strcmp(word, "for"))
return TOKEN_FOR;
if (!strcmp(word, "while"))
return TOKEN_WHILE;
if (!strcmp(word, "until"))
return TOKEN_UNTIL;
if (!strcmp(word, "case"))
return TOKEN_CASE;
if (!strcmp(word, "esac"))
return TOKEN_ESAC;
return TOKEN_ERROR;
}

461
src/lexer/lexer.c Normal file
View File

@ -0,0 +1,461 @@
#include "lexer.h"
#include "../bsh.h"
#include "lexer_tools.h"
extern struct shell *shell;
struct lexer_token *lexer_token_free(struct lexer_token *token)
{
free(token->value);
free(token);
return NULL;
}
struct lexer *lexer_create(char *input)
{
struct lexer *lexer = calloc(1, sizeof(struct lexer));
lexer->input = input;
lexer->tail = NULL;
lexer->head = NULL;
lexer->tokens = NULL;
return lexer;
}
struct lexer_token *lexer_peek(struct lexer *lexer)
{
return lexer->head;
}
struct lexer_token *lexer_pop(struct lexer *lexer)
{
struct lexer_token *token = lexer->head;
lexer->head = lexer->head->next;
return token;
}
void lexer_append(struct lexer *lexer, struct lexer_token *token)
{
token->next = NULL;
if (lexer->tail)
{
lexer->tail->next = token;
lexer->tail = token;
}
else
{
lexer->tokens = token;
lexer->tail = token;
}
}
void lexer_free(struct lexer *lexer)
{
struct lexer_token *token = lexer->tokens;
while (token)
{
struct lexer_token *next = token->next;
lexer_token_free(token);
token = next;
}
struct lexer_alias *alias = lexer->alias_list;
while (alias)
{
struct lexer_alias *next = alias->next;
free(alias->name);
struct lexer_token *token = alias->value;
while (token)
{
struct lexer_token *next = token->next;
lexer_token_free(token);
token = next;
}
free(alias);
alias = next;
}
lexer->alias_list = NULL;
lexer->head = NULL;
lexer->tail = NULL;
free(lexer);
}
static bool is_separator(char c)
{
return (c == ';' || c == '\n');
}
static enum token_type get_separator(char c)
{
if (c == ';')
return TOKEN_SEMICOLON;
if (c == '\n')
return TOKEN_NEWLINE;
return TOKEN_ERROR;
}
static bool is_quote(char c)
{
return (c == '\'' || c == '\"' || c == '`');
}
static enum token_type get_quote(char c)
{
if (c == '\'')
return TOKEN_WORD_SINGLE_QUOTE;
if (c == '\"')
return TOKEN_WORD_DOUBLE_QUOTE;
if (c == '`')
return TOKEN_BACKTICK;
return TOKEN_ERROR;
}
static void create_word_and_append(char *word, int word_pos, bool *in_cmd,
struct lexer *lexer,
enum token_type *word_type)
{
if (!word)
return;
word[word_pos] = 0;
struct lexer_alias *alias = get_alias(word);
if (alias && !lexer->alias)
{
lexer_append_alias(lexer, alias);
free(word);
return;
}
if (*word_type == TOKEN_WORD
&& (!strcmp(word, "alias") || !strcmp(word, "unalias")))
{
lexer->alias_prev = lexer->tail;
create_and_append_token(
lexer, !strcmp(word, "alias") ? TOKEN_ALIAS : TOKEN_UNALIAS, NULL);
lexer->alias = lexer->tail;
free(word);
*word_type = TOKEN_WORD;
return;
}
if (*word_type == TOKEN_WORD && (!strcmp(word, "in"))
&& ((!lexer->in_for && lexer->found_for) || lexer->found_case))
{
create_and_append_token(lexer, TOKEN_IN, NULL);
if (lexer->found_for)
lexer->in_for = true;
free(word);
return;
}
struct lexer_token *token = calloc(1, sizeof(struct lexer_token));
token->type = is_keyword(word) && !lexer->alias
&& (!(*in_cmd) || lexer->found_case
|| (lexer->found_for && !strcmp(word, "do")))
? get_keyword(word)
: *word_type;
if (token->type >= TOKEN_WORD && !lexer->found_case)
*in_cmd = true;
if (token->type == TOKEN_FOR)
lexer->found_for = true;
if (token->type == TOKEN_CASE)
lexer->found_case = true;
if (token->type == TOKEN_ESAC)
lexer->found_case = false;
token->value = word;
word = NULL;
word_pos = 0;
lexer_append(lexer, token);
}
static bool is_pipe(char c, char next)
{
return (c == '|' && next != '|');
}
static bool is_redir(char c1)
{
return (c1 == '<' || c1 == '>');
}
static char *get_redir(char c1, char c2)
{
char *res = calloc(3, sizeof(char));
if (c1 == '<')
{
res[0] = '<';
if (c2 == '&' || c2 == '>')
res[1] = c2;
}
if (c1 == '>')
{
res[0] = '>';
if (c2 == '&' || c2 == '>' || c2 == '|')
res[1] = c2;
}
return res;
}
static bool is_special(char c)
{
return (c == '(' || c == ')' || c == '{' || c == '}' || c == '$');
}
static enum token_type get_special(char c)
{
if (c == '(')
return TOKEN_PARENTHESIS_OPEN;
if (c == ')')
return TOKEN_PARENTHESIS_CLOSE;
if (c == '{')
return TOKEN_BRACE_OPEN;
if (c == '}')
return TOKEN_BRACE_CLOSE;
if (c == '$')
return TOKEN_DOLLAR;
return TOKEN_ERROR;
}
static bool is_word_alphanum(char *word, int len)
{
for (int i = 0; i < len; i++)
if (!((word[i] >= 'a' && word[i] <= 'z')
|| (word[i] >= 'A' && word[i] <= 'Z')
|| (word[i] >= '0' && word[i] <= '9') || word[i] == '_'))
return false;
return true;
}
static void word_lexer(struct lexer *lexer, char *input, bool *in_cmd,
enum token_type *word_type)
{
int j = 0;
char *word = NULL;
int word_pos = 0;
while (input[j])
{
if (input[j] == '\\')
{
word = realloc(word, (word_pos + 3) * sizeof(char));
word[word_pos++] = input[j++];
if (input[j] == 0)
break;
word[word_pos++] = input[j++];
if (input[j] == 0)
break;
}
if ((*word_type == TOKEN_WORD && is_separator(input[j]))
|| (is_pipe(input[j], input[j + 1]) && *word_type == TOKEN_WORD))
{
if (word)
{
create_word_and_append(word, word_pos, in_cmd, lexer,
word_type);
word = NULL;
word_pos = 0;
}
create_and_append_token(
lexer,
is_separator(input[j]) ? get_separator(input[j]) : TOKEN_PIPE,
NULL);
if (is_separator(input[j]))
{
if (lexer->alias != NULL && lexer->alias->next != lexer->tail)
{
if (lexer->alias->type == TOKEN_ALIAS)
process_alias(lexer->alias_prev, lexer->alias, lexer);
else
process_unalias(lexer->alias_prev, lexer->alias, lexer);
}
else if (lexer->alias)
{
if (lexer->alias_prev)
{
lexer_token_free(lexer->alias_prev->next);
lexer->alias_prev->next = lexer->tail;
}
else
{
lexer_token_free(lexer->alias);
lexer->tokens = lexer->tail;
}
}
if (input[j] == '\n')
{
struct lexer_alias *alias = lexer->alias_list;
while (alias)
{
struct lexer_alias *next = alias->next;
alias->next = shell->alias_list;
shell->alias_list = alias;
alias = next;
}
lexer->alias_list = NULL;
}
lexer->alias = NULL;
lexer->in_for = false;
lexer->found_for = false;
}
*in_cmd = false;
}
else if (*word_type == TOKEN_WORD
&& ((input[j] == '&' && input[j + 1] == '&')
|| (input[j] == '|' && input[j + 1] == '|')))
{
if (word)
{
create_word_and_append(word, word_pos, in_cmd, lexer,
word_type);
word = NULL;
word_pos = 0;
}
create_and_append_token(
lexer, input[j] == '&' ? TOKEN_AND : TOKEN_OR, NULL);
j++;
}
else if (*word_type == TOKEN_WORD && is_special(input[j]))
{
if (input[j] == '}' && lexer->in_variable)
{
word = realloc(word, (word_pos + 2) * sizeof(char));
word[word_pos++] = input[j];
lexer->in_variable = false;
}
else
{
if (word && (input[j] != '$' || input[j + 1] == '('))
{
create_word_and_append(word, word_pos, in_cmd, lexer,
word_type);
word = NULL;
word_pos = 0;
}
if (input[j] == '$')
{
if (input[j + 1] == '(')
{
*in_cmd = false;
j++;
create_and_append_token(lexer, TOKEN_SUBSTITUTION_OPEN,
NULL);
}
else
{
word = realloc(word, (word_pos + 3) * sizeof(char));
word[word_pos++] = input[j];
if (input[j + 1] == '{' || input[j + 1] == '$')
{
word[word_pos++] = input[++j];
lexer->in_variable = true;
}
}
}
else
{
if (input[j] == '{' || input[j] == '(')
*in_cmd = false;
create_and_append_token(lexer, get_special(input[j]), NULL);
}
}
}
else if (*word_type == TOKEN_WORD && is_redir(input[j]))
{
if (word)
{
word[word_pos] = 0;
if (is_int(word))
{
create_and_append_token(lexer, TOKEN_IONUMBER, word);
}
else
create_word_and_append(word, word_pos, in_cmd, lexer,
word_type);
word = NULL;
word_pos = 0;
}
create_and_append_token(lexer, TOKEN_REDIR,
get_redir(input[j], input[j + 1]));
if (input[j + 1] != 0)
j++;
}
else if (*word_type == TOKEN_WORD && input[j] == '='
&& (!lexer->tail || lexer->tail->type != TOKEN_ASSIGNMENT_WORD)
&& is_word_alphanum(word, word_pos))
{
if (word)
{
create_word_and_append(word, word_pos, in_cmd, lexer,
word_type);
word = NULL;
word_pos = 0;
lexer->tail->type = TOKEN_ASSIGNMENT_WORD;
}
}
else if (is_quote(input[j])
&& (*word_type == get_quote(input[j])
|| *word_type == TOKEN_WORD))
{
if (word)
{
create_word_and_append(word, word_pos, in_cmd, lexer,
word_type);
word = NULL;
word_pos = 0;
}
if (lexer->alias)
{
j++;
continue;
}
if (*word_type == TOKEN_WORD && input[j] != '`')
*word_type = get_quote(input[j]);
else if (*word_type == TOKEN_WORD && input[j] == '`')
{
create_and_append_token(lexer, TOKEN_BACKTICK, NULL);
}
else if (get_quote(input[j]) == *word_type)
{
*word_type = TOKEN_WORD;
}
}
else
{
word = realloc(word, (word_pos + 2) * sizeof(char));
word[word_pos++] = input[j];
}
j++;
}
if (word)
{
create_word_and_append(word, word_pos, in_cmd, lexer, word_type);
word = NULL;
word_pos = 0;
}
free(input);
}
void lexer_build(struct lexer *lexer)
{
bool in_cmd = false;
char **words = split_in_words(lexer->input);
enum token_type word_type = TOKEN_WORD;
for (int i = 0; words[i]; i++)
{
word_lexer(lexer, words[i], &in_cmd, &word_type);
create_and_append_token(lexer, TOKEN_SPACE, NULL);
}
if (word_type != TOKEN_WORD)
{
fprintf(stderr, "Error: quote <%c> is not terminated.\n",
word_type == TOKEN_WORD_SINGLE_QUOTE ? '\'' : '\"');
shell->return_code = 2;
shell->exit = true;
}
create_and_append_token(lexer, TOKEN_EOF, NULL);
process_spaces(lexer);
process_export(lexer);
if (shell->verbose)
lexer_print(lexer);
free(words);
lexer->head = lexer->tokens;
}
void lexer_go_back(struct lexer *lexer, struct lexer_token *token)
{
lexer->head = token;
}

155
src/lexer/lexer.h Normal file
View File

@ -0,0 +1,155 @@
#ifndef LEXER_H
#define LEXER_H
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
enum token_type
{
TOKEN_ERROR,
TOKEN_IF,
TOKEN_ELSE,
TOKEN_ELIF,
TOKEN_FI,
TOKEN_THEN,
TOKEN_DO,
TOKEN_DONE,
TOKEN_WHILE,
TOKEN_UNTIL,
TOKEN_FOR,
TOKEN_IN,
TOKEN_AND,
TOKEN_OR,
TOKEN_SEMICOLON,
TOKEN_NEWLINE,
TOKEN_REDIR,
TOKEN_IONUMBER,
TOKEN_PIPE,
TOKEN_NOT,
TOKEN_ASSIGNMENT_WORD,
TOKEN_PARENTHESIS_OPEN,
TOKEN_SUBSTITUTION_OPEN,
TOKEN_BACKTICK,
TOKEN_PARENTHESIS_CLOSE,
TOKEN_BRACE_OPEN,
TOKEN_BRACE_CLOSE,
TOKEN_DOLLAR,
TOKEN_CASE,
TOKEN_ESAC,
TOKEN_WORD,
TOKEN_WORD_DOUBLE_QUOTE,
TOKEN_WORD_SINGLE_QUOTE,
TOKEN_EOF,
TOKEN_SPACE,
TOKEN_ALIAS,
TOKEN_UNALIAS
};
struct lexer_token
{
enum token_type type;
char *value;
struct lexer_token *next;
};
struct lexer_alias
{
char *name;
struct lexer_token *value;
struct lexer_alias *next;
};
/**
* @brief free an allocated token.
*
* @param token the token to free.
*/
struct lexer_token *lexer_token_free(struct lexer_token *token);
/**
* @var lexer::input
* Member 'input' contains the input string.
* @var lexer::tokens
* Member 'tokens' contains the head of the list of tokens.
* @var lexer::tail
* Member 'tail' contains the last token of the list.
*/
struct lexer
{
char *input;
struct lexer_token *tokens;
struct lexer_token *tail;
struct lexer_token *head;
bool in_for;
bool in_variable;
bool found_for;
bool found_case;
struct lexer_token *alias;
struct lexer_token *alias_prev;
struct lexer_alias *alias_list;
};
/**
** @brief Allocate and init a new lexer.
** @param input the string to use as input stream.
*/
struct lexer *lexer_create(char *input);
/**
** @brief Fill the token list by creating all the tokens from
** the given string.
**
** @param lexer an empty lexer.
*/
void lexer_build(struct lexer *lexer);
/**
** @brief Return the next token without consume it.
**
** \return the next token from the input stream
** @param lexer the lexer to lex from
*/
struct lexer_token *lexer_peek(struct lexer *lexer);
/**
** @brief Return and consume the next token from the input stream.
**
** \return the next token from the input stream
** @param lexer the lexer to lex from
*/
struct lexer_token *lexer_pop(struct lexer *lexer);
/**
** @brief Append a new token to the token_list of the lexer.
**
** @param lexer the lexer.
** @param token the token to append.
*/
void lexer_append(struct lexer *lexer, struct lexer_token *token);
/**
** @brief Free a lexer, all the tokens and tokens values.
**
** @param lexer the lexer.
*/
void lexer_free(struct lexer *lexer);
/**
* @brief Set token as head of the token_list.
*
* @param lexer the lexer.
* @param token the new head of the token_list.
*/
void lexer_go_back(struct lexer *lexer, struct lexer_token *token);
/**
* @brief Print each token in the token_list.
*
* @param lexer a lexer.
*/
void lexer_print(struct lexer *lexer);
#endif // !LEXER_H

56
src/lexer/lexer_print.c Normal file
View File

@ -0,0 +1,56 @@
#include "lexer.h"
static char *get_token_string(enum token_type type)
{
char *token_string[] = { "ERROR",
"IF",
"ELSE",
"ELIF",
"FI",
"THEN",
"DO",
"DONE",
"WHILE",
"UNTIL",
"FOR",
"IN",
"&&",
"||",
"SEMICOLON",
"NEWLINE",
"REDIR",
"IONUMBER",
"PIPE",
"NOT",
"ASSIGNMENT_WORD",
"PARENTHESIS_OPEN",
"SUBSTITUTION",
"BACKTICK",
"PARENTHESIS_CLOSE",
"BRACE_OPEN",
"BRACE_CLOSE",
"DOLLAR",
"CASE",
"ESAC",
"WORD",
"WORD_DOUBLE_QUOTE",
"WORD_SINGLE_QUOTE",
"EOF",
"SPACE" };
return token_string[type];
}
void lexer_print(struct lexer *lexer)
{
printf("lexer output: ");
struct lexer_token *token = lexer->tokens;
while (token)
{
if (token->type != TOKEN_REDIR)
printf("%s ", get_token_string(token->type));
else
printf("%s ", token->value);
token = token->next;
}
printf("\n");
}

30
src/lexer/lexer_tools.h Normal file
View File

@ -0,0 +1,30 @@
#ifndef LEXER_TOOLS_H
#define LEXER_TOOLS_H
#include "lexer.h"
#include "spaces.h"
void create_and_append_token(struct lexer *lexer, enum token_type type,
char *value);
char **split_in_words(char *input);
enum token_type get_keyword(char *word);
bool is_keyword(char *word);
bool is_int(char *word);
void process_export(struct lexer *lexer);
void process_alias(struct lexer_token *prev, struct lexer_token *head,
struct lexer *lexer);
struct lexer_alias *get_alias(char *name);
void lexer_append_alias(struct lexer *lexer, struct lexer_alias *alias);
void process_unalias(struct lexer_token *prev, struct lexer_token *head,
struct lexer *lexer);
#endif // !LEXER_TOOLS_H

52
src/lexer/spaces.c Normal file
View File

@ -0,0 +1,52 @@
#include "spaces.h"
static bool is_word(enum token_type type)
{
int res =
type >= TOKEN_WORD_DOUBLE_QUOTE && type <= TOKEN_WORD_SINGLE_QUOTE;
return res;
}
void process_spaces(struct lexer *lexer)
{
struct lexer_token *token = lexer->tokens;
while (token
&& (token->type == TOKEN_SPACE || token->type == TOKEN_NEWLINE))
{
struct lexer_token *next = token->next;
lexer_token_free(token);
token = next;
lexer->tokens = token;
}
while (token && token->type != TOKEN_EOF)
{
struct lexer_token *next = token->next;
if (!next)
{
token = next;
continue;
}
if (next->type == TOKEN_SPACE)
{
while (next->type == TOKEN_SPACE)
{
struct lexer_token *tmp = next->next;
token->next = next->next;
free(next->value);
free(next);
next = tmp;
}
}
else if (is_word(token->type) && is_word(next->type)
&& next->type == token->type)
{
token->next = next->next;
token->value = realloc(
token->value, strlen(token->value) + strlen(next->value) + 2);
strcat(token->value, next->value);
free(next->value);
free(next);
}
token = token->next;
}
}

13
src/lexer/spaces.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef SPACES_H
#define SPACES_H
#include "lexer.h"
/**
* @brief Remove TOKEN_SPACE tokens from the token list.
*
* @param lexer the lexer.
*/
void process_spaces(struct lexer *lexer);
#endif // !SPACES_H

54
src/lexer/split_input.c Normal file
View File

@ -0,0 +1,54 @@
#include "lexer_tools.h"
char **split_in_words(char *input)
{
char *new = strdup(input);
char *save = new;
char **words = NULL;
int words_nb = 0;
char *word = NULL;
int i = 0;
int word_len = 0;
char quote = 0;
while (input[i])
{
if (input[i] == '\'' || input[i] == '"')
{
if (quote == input[i])
quote = 0;
else if (!quote)
quote = input[i];
}
if ((input[i] == ' ' || input[i] == '\t') && !quote)
{
if (word_len > 0)
{
word[word_len] = 0;
words = realloc(words, sizeof(char *) * (words_nb + 2));
words[words_nb] = word;
words_nb++;
word = NULL;
word_len = 0;
}
i++;
}
else
{
word = realloc(word, sizeof(char) * (word_len + 2));
word[word_len++] = input[i++];
}
}
if (word_len > 0)
{
word[word_len] = 0;
words = realloc(words, sizeof(char *) * (words_nb + 2));
words[words_nb] = word;
words_nb++;
}
if (!words)
words = realloc(words, sizeof(char *));
words[words_nb] = NULL;
free(save);
return words;
}

44
src/loops/loop_stack.c Normal file
View File

@ -0,0 +1,44 @@
#include "loop_stack.h"
#include <stdlib.h>
/* if (continue) -> ast for else -> normal exec
for check if CONTINUE -> get var list and index else -> new for add for in
stack end of loop unstack val unset continue
*/
int push_loop(struct shell *sh, struct ast *ast)
{
struct loop_stack *new = calloc(1, sizeof(struct loop_stack));
if (!new)
return 1;
new->loop = ast;
new->next = sh->loop_stack;
sh->loop_stack = new;
return 0;
}
struct ast *get_ast_loop(struct shell *sh)
{
if (sh->loop_stack)
return sh->loop_stack->loop;
return NULL;
}
void pop_loop(struct shell *sh)
{
struct loop_stack *tmp = sh->loop_stack;
if (tmp)
{
sh->loop_stack = tmp->next;
free(tmp);
}
}
void free_loop(struct shell *sh)
{
while (sh->loop_stack)
{
pop_loop(sh);
}
}

12
src/loops/loop_stack.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef LOOP_STACK_H
#define LOOP_STACK_H
#include "../bsh.h"
#include "ast.h"
int push_loop(struct shell *sh, struct ast *ast);
struct ast *get_ast_loop(struct shell *sh);
void pop_loop(struct shell *sh);
void free_loop(struct shell *sh);
#endif /* !LOOP_STACK_H */

43
src/parser/ast.c Normal file
View File

@ -0,0 +1,43 @@
#include "ast.h"
#include <stdlib.h>
struct ast *ast_new(enum ast_type type)
{
struct ast *new = calloc(1, sizeof(struct ast));
new->type = type;
return new;
}
void ast_free(struct ast *ast)
{
if (ast == NULL)
return;
if (ast->type == AST_COMMAND || ast->type == AST_FOR
|| ast->type == AST_ASSIGNMENT || ast->type == AST_CASE
|| ast->type == AST_CASE_SWITCH)
{
free(ast->value);
free(ast->enclosure);
}
else if (ast->type == AST_REDIR)
{
int i = 0;
while (ast->value[i])
free(ast->value[i++]);
free(ast->value);
free(ast->enclosure);
}
ast_free(ast->left_child);
ast->left_child = NULL;
ast_free(ast->right_child);
ast->right_child = NULL;
ast_free(ast->condition);
ast->condition = NULL;
free(ast);
}

56
src/parser/ast.h Normal file
View File

@ -0,0 +1,56 @@
#ifndef AST_H
#define AST_H
enum ast_type
{
AST_COMMAND = 0,
AST_LIST, // < ('\n')* and_or ((';'|'\n') ('\n')* and_or)* [(';'|'\n')
// ('\n')*]
AST_IF,
AST_FOR,
AST_WHILE,
AST_UNTIL,
AST_CASE,
AST_PIPE,
AST_OR,
AST_AND,
AST_NOT,
AST_REDIR,
AST_FUNC,
AST_EOF,
AST_ASSIGNMENT,
AST_CMD_SUBSTITUTION,
AST_SUBSHELL,
AST_CASE_SWITCH
};
enum quotes
{
Q_NONE = 0,
Q_DOUBLE,
Q_SINGLE,
Q_BACKTICK
};
struct ast
{
enum ast_type type;
char **value;
char *var_name;
enum quotes *enclosure;
struct ast *left_child;
struct ast *right_child;
struct ast *condition;
};
/**
** \brief Allocate a new ast with the given type
*/
struct ast *ast_new(enum ast_type type);
/**
** \brief Recursively free the given ast
*/
void ast_free(struct ast *ast);
#endif /* !AST_H */

View File

@ -0,0 +1,37 @@
#include "parser.h"
enum parser_status parse_funcdec(struct ast **ast, struct lexer *lexer)
{
struct lexer_token *tok = lexer_peek(lexer);
*ast = ast_new(AST_FUNC);
// Try WORD
if (tok->type != TOKEN_WORD)
return handle_parser_error(PARSER_ERROR, ast);
(*ast)->var_name = tok->value;
lexer_pop(lexer); // token WORD
// Try (
tok = lexer_peek(lexer);
if (tok->type != TOKEN_PARENTHESIS_OPEN)
return handle_parser_error(PARSER_ERROR, ast);
lexer_pop(lexer); // token (
// Try )
tok = lexer_peek(lexer);
if (tok->type != TOKEN_PARENTHESIS_CLOSE)
return handle_parser_error(PARSER_ERROR, ast);
lexer_pop(lexer); // token )
// Try ('\n')*
while ((tok = lexer_peek(lexer))->type == TOKEN_NEWLINE)
lexer_pop(lexer); // token \n
enum parser_status status_shell_cmd = parse_shell_command(ast, lexer);
if (status_shell_cmd == PARSER_ERROR)
return handle_parser_error(status_shell_cmd, ast);
return PARSER_OK;
}

117
src/parser/parser.c Normal file
View File

@ -0,0 +1,117 @@
#include "parser.h"
#include <err.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
int evaluate_ast(struct ast *ast);
enum parser_status handle_parser_error(enum parser_status status,
struct ast **res)
{
ast_free(*res);
*res = NULL;
return status;
}
static int display_parser_error(struct ast **res)
{
warnx("Parser: unexpected token");
ast_free(*res);
*res = NULL;
shell->return_code = 2;
return 2;
}
struct string_array_with_quotes merge_values(char **values_1, char **values_2,
enum quotes *q_1, enum quotes *q_2)
{
int length_1 = 0;
while (values_1 && values_1[length_1] != NULL)
length_1++;
int length_2 = 0;
while (values_2 && values_2[length_2] != NULL)
length_2++;
values_1 = realloc(values_1, (length_1 + 1 + length_2) * sizeof(char *));
q_1 = realloc(q_1, (length_1 + length_2) * sizeof(enum quotes));
for (int i = length_1; i < length_2 + length_1; i++)
{
values_1[i] = values_2[i - length_1];
q_1[i] = q_2[i - length_1];
}
values_1[length_1 + length_2] = NULL;
struct string_array_with_quotes res = { values_1, q_1 };
return res;
}
static enum parser_status add_eof_node(struct ast **ast)
{
struct ast *cur = *ast;
while (cur && cur->left_child && cur->type == AST_LIST)
{
cur = cur->left_child;
}
if (!cur || cur->type != AST_LIST)
return PARSER_ERROR;
cur->left_child = ast_new(AST_EOF);
return PARSER_OK;
}
int parse_input(char *input, struct ast **res)
{
struct lexer *lex = lexer_create(input);
lexer_build(lex);
if (shell->exit)
{
lexer_free(lex);
shell->exit = false;
return 2;
}
struct ast *ast = ast_new(AST_LIST);
// Try EOF
struct lexer_token *end = lexer_peek(lex);
if (end->type == TOKEN_EOF || end->type == TOKEN_NEWLINE)
{
ast_free(ast);
lexer_free(lex);
return PARSER_OK;
}
// Try compound_list EOF
if (parse_compound_list(&ast, lex) == PARSER_OK)
{
struct lexer_token *next = lexer_peek(lex);
if (next->type == TOKEN_EOF || next->type == TOKEN_NEWLINE)
{
if (add_eof_node(&ast) == PARSER_ERROR)
return display_parser_error(&ast);
if (shell->pretty_print)
pretty_print(ast);
if (!res)
{
int res_eval = evaluate_ast(ast);
shell->return_code = res_eval;
ast_free(ast);
lexer_free(lex);
}
else
{
*res = ast;
}
return 0;
}
}
lexer_free(lex);
return display_parser_error(&ast);
}

222
src/parser/parser.h Normal file
View File

@ -0,0 +1,222 @@
#ifndef PARSER_H
#define PARSER_H
#include "../bsh.h"
#include "../lexer/lexer.h"
#include "ast.h"
enum parser_status
{
PARSER_OK = 0,
PARSER_ERROR
};
struct string_array_with_quotes
{
char **value;
enum quotes *q;
};
extern struct shell *shell;
enum parser_status handle_parser_error(enum parser_status status,
struct ast **res);
struct string_array_with_quotes merge_values(char **values_1, char **values_2,
enum quotes *q_1,
enum quotes *q_2);
void pretty_print(struct ast *ast);
/**
** @brief Check if input grammar rule is respected
** >> input: compound_list EOF | compound_list 'newline' | 'newline' | EOF;
**
** @param ast the general ast to update
** @param lexer the lexer to read tokens from
** @return enum parser_status - current parser status
**/
int parse_input(char *input, struct ast **res);
/**
** @brief Check if compound_list grammar rule is respected
** >> compound_list: and_or (';' and_or)* [';'];
**
** @param ast the general ast to update
** @param lexer the lexer to read tokens from
** @return enum parser_status - current parser status
**/
enum parser_status parse_compound_list(struct ast **ast, struct lexer *l);
/**
** @brief Check if simple_command grammar rule is respected
** >> simple_command: WORD+
**
** @param ast the general ast to update
** @param lexer the lexer to read tokens from
** @return enum parser_status - current parser status
**/
enum parser_status parse_simple_command(struct ast **ast, struct lexer *l);
/**
** @brief Check if shell_command grammar rule is respected
** >> shell_command: '{' compound_list '}'
** | rule_for
** | rule_while
** | rule_until
** | rule_if
**
** @param ast the general ast to update
** @param lexer the lexer to read tokens from
** @return enum parser_status - current parser status
**/
enum parser_status parse_shell_command(struct ast **ast, struct lexer *l);
/**
** @brief Check if command grammar rule is respected
** >> command: simple_command | shell_command
**
** @param ast the general ast to update
** @param lexer the lexer to read tokens from
** @return enum parser_status - current parser status
**/
enum parser_status parse_command(struct ast **ast, struct lexer *l);
/**
** @brief Check if pipeline grammar rule is respected
** >> pipeline: command ('|' ('newline')* command)*
**
** @param ast the general ast to update
** @param lexer the lexer to read tokens from
** @return enum parser_status - current parser status
**/
enum parser_status parse_pipeline(struct ast **ast, struct lexer *l);
/**
** @brief Check if and_or grammar rule is respected
** >> and_or: pipeline (('&&'|'||') ('newline')* pipeline)*
**
** @param ast the general ast to update
** @param lexer the lexer to read tokens from
** @return enum parser_status - current parser status
**/
enum parser_status parse_and_or(struct ast **ast, struct lexer *l);
/**
** @brief Check if rule_if grammar rule is respected
** >> rule_if: If compound_list Then compound_list [else_clause] Fi
**
** @param ast the general ast to update
** @param lexer the lexer to read tokens from
** @return enum parser_status - current parser status
**/
enum parser_status parse_rule_if(struct ast **ast, struct lexer *l);
/**
** @brief Check if else_clause grammar rule is respected
** >> else_clause: Else compound_list | Elif compound_list Then compound_list
**
** @param ast the general ast to update
** @param lexer the lexer to read tokens from
** @return enum parser_status - current parser status
**/
enum parser_status parse_else_clause(struct ast **ast, struct lexer *l);
/**
** @brief Check if rule_case grammar rule is respected
** >> rule_case: Case WORD ('newline')* 'in' ('newline')* [case_clause] Esac
**
** @param ast the general ast to update
** @param lexer the lexer to read tokens from
** @return enum parser_status - current parser status
**/
enum parser_status parse_rule_case(struct ast **ast, struct lexer *l);
/**
** @brief Check if case_clause grammar rule is respected
** >> case_clause: case_item (';;' ('newline')* case_item)* [;;] ('newline')*
**
** @param ast the general ast to update
** @param lexer the lexer to read tokens from
** @return enum parser_status - current parser status
**/
enum parser_status parse_case_clause(struct ast **ast, struct lexer *l);
/**
** @brief Check if case_item grammar rule is respected
** >> case_item: ['('] WORD ('|' WORD)* ')' ('newline')* [ compound_list ]
**
** @param ast the general ast to update
** @param lexer the lexer to read tokens from
** @return enum parser_status - current parser status
**/
enum parser_status parse_case_item(struct ast **ast, struct lexer *l);
/**
** @brief Check if redirection grammar rule is respected
** >> redirection: [IONUMBER] '>' WORD
** | [IONUMBER] '<' WORD
** | [IONUMBER] '>&' WORD
** | [IONUMBER] '<&' WORD
** | [IONUMBER] '>>' WORD
** | [IONUMBER] '<>' WORD
** | [IONUMBER] '>|' WORD
**
** @param ast the general ast to update
** @param lexer the lexer to read tokens from
** @return enum parser_status - current parser status
**/
enum parser_status parse_redirection(struct ast **ast, struct lexer *lexer);
/**
** @brief Check if rule_while grammar rule is respected
** >> rule_while: While compound_list do_group
**
** @param ast the general ast to update
** @param lexer the lexer to read tokens from
** @return enum parser_status - current parser status
**/
enum parser_status parse_rule_while(struct ast **ast, struct lexer *lexer);
/**
** @brief Check if rule_until grammar rule is respected
** >> rule_until: Until compound_list do_group
**
** @param ast the general ast to update
** @param lexer the lexer to read tokens from
** @return enum parser_status - current parser status
**/
enum parser_status parse_rule_until(struct ast **ast, struct lexer *lexer);
/**
** @brief Check if do_group grammar rule is respected
** >> do_group: Do compound_list Done
**
** @param ast the general ast to update
** @param lexer the lexer to read tokens from
** @return enum parser_status - current parser status
**/
enum parser_status parse_do_group(struct ast **ast, struct lexer *lexer);
/**
** @brief Check if for grammar rule is respected
** >> for: For WORD ([';']|[('newline')* 'in' (WORD)* (';'|'newline')])
** ('newline')* do_group
**
** @param ast the general ast to update
** @param lexer the lexer to read tokens from
** @return enum parser_status - current parser status
**/
enum parser_status parse_for(struct ast **ast, struct lexer *lexer);
/**
** @brief Check if funcdec grammar rule is respected
** >> funcdec: WORD '(' ')' ('newline')* shell_command
**
** @param ast the general ast to update
** @param lexer the lexer to read tokens from
** @return enum parser_status - current parser status
**/
enum parser_status parse_funcdec(struct ast **ast, struct lexer *lexer);
#endif // !PARSER_H

View File

@ -0,0 +1,498 @@
#include "parser.h"
enum parser_status parse_redirection(struct ast **ast, struct lexer *lexer)
{
struct lexer_token *tok = lexer_peek(lexer);
char *fd = NULL;
char *type = NULL;
size_t len = 0;
// Try [IONUMBER]
if (tok->type == TOKEN_IONUMBER)
{
len = strlen(tok->value);
fd = realloc(fd, len + 1);
fd = strcpy(fd, tok->value);
fd[len] = '\0';
lexer_pop(lexer);
}
tok = lexer_peek(lexer);
if (tok->type != TOKEN_REDIR)
{
free(fd);
return handle_parser_error(PARSER_ERROR, ast);
}
len = strlen(tok->value);
type = calloc(len + 1, sizeof(char));
type = strcpy(type, tok->value);
type[len] = '\0';
char **redir_value = NULL;
*ast = ast_new(AST_REDIR);
if (fd)
{
redir_value = calloc(3, sizeof(char *));
redir_value[0] = fd;
redir_value[1] = type;
}
else
{
redir_value = calloc(2, sizeof(char *));
redir_value[0] = type;
}
(*ast)->value = redir_value;
enum quotes *enclosure = calloc(2, sizeof(enum quotes));
enclosure[0] = Q_SINGLE;
enclosure[1] = Q_SINGLE;
(*ast)->enclosure = enclosure;
lexer_pop(lexer);
tok = lexer_peek(lexer);
if (tok->type != TOKEN_WORD && tok->type != TOKEN_WORD_SINGLE_QUOTE
&& tok->type != TOKEN_WORD_DOUBLE_QUOTE)
return handle_parser_error(PARSER_ERROR, ast);
struct ast *ast_word = ast_new(AST_COMMAND);
char **word_value = calloc(2, sizeof(char *));
word_value[0] = tok->value;
enum quotes *word_q = calloc(1, sizeof(enum quotes));
if (tok->type == TOKEN_WORD)
word_q[0] = Q_NONE;
else if (tok->type == TOKEN_WORD_SINGLE_QUOTE)
word_q[0] = Q_SINGLE;
else if (tok->type == TOKEN_WORD_DOUBLE_QUOTE)
word_q[0] = Q_DOUBLE;
ast_word->value = word_value;
ast_word->enclosure = word_q;
(*ast)->right_child = ast_word;
lexer_pop(lexer);
return PARSER_OK;
}
/**
* @brief Check if simple_command grammar rule is respected
* >> prefix: ASSIGNMENT_WORD | redirection
*
* @param ast the general ast to update
* @param lexer the lexer to read tokens from
* @return enum parser_status - current parser status
*/
static enum parser_status parse_prefix(struct ast **ast, struct lexer *lexer)
{
// Try ASSIGNMENT_WORD
struct lexer_token *tok = lexer_peek(lexer);
if (tok->type == TOKEN_ASSIGNMENT_WORD)
{
*ast = ast_new(AST_ASSIGNMENT);
(*ast)->var_name = tok->value;
lexer_pop(lexer);
return PARSER_OK;
}
// Try redirection
enum parser_status status_redir = parse_redirection(ast, lexer);
if (status_redir == PARSER_ERROR)
return handle_parser_error(status_redir, ast);
return PARSER_OK;
}
static enum parser_status parse_element(struct ast **ast, struct lexer *lexer)
{
// Try WORD
struct lexer_token *tok = lexer_peek(lexer);
if (tok->type == TOKEN_WORD || tok->type == TOKEN_WORD_DOUBLE_QUOTE
|| tok->type == TOKEN_WORD_SINGLE_QUOTE)
{
*ast = ast_new(AST_COMMAND);
char **value = calloc(2, sizeof(char *));
value[0] = tok->value;
enum quotes *enclosure = calloc(1, sizeof(enum quotes));
if (tok->type == TOKEN_WORD)
enclosure[0] = Q_NONE;
else if (tok->type == TOKEN_WORD_DOUBLE_QUOTE)
enclosure[0] = Q_DOUBLE;
else
enclosure[0] = Q_SINGLE;
(*ast)->value = value;
(*ast)->enclosure = enclosure;
lexer_pop(lexer);
return PARSER_OK;
}
// Try redirection
enum parser_status status_redir = parse_redirection(ast, lexer);
if (status_redir == PARSER_ERROR)
return handle_parser_error(status_redir, ast);
return PARSER_OK;
}
enum parser_status parse_simple_command(struct ast **ast, struct lexer *lexer)
{
bool first_prefix = true;
struct lexer_token *save_tok = lexer_peek(lexer);
bool is_assignment = false;
// Try (prefix)*
struct ast *cur_prefix = NULL;
while (true)
{
struct ast *ast_prefix = NULL;
// Try prefix
enum parser_status status_prefix = parse_prefix(&ast_prefix, lexer);
if (status_prefix == PARSER_ERROR)
{
lexer_go_back(lexer, save_tok);
ast_free(ast_prefix);
break;
}
//? If we saw a variable and we get something else after other than a
//? word, then the assignment is incorrect but we go on and it will be
//? catched while executing
//! Should maybe be an error clause if is_assignment is already set to
//! true
is_assignment = false;
if (ast_prefix->type == AST_ASSIGNMENT)
is_assignment = true;
if (first_prefix)
{
*ast = ast_prefix;
first_prefix = false;
}
else
{
ast_prefix->left_child = cur_prefix->right_child;
cur_prefix->right_child = ast_prefix;
}
cur_prefix = ast_prefix;
save_tok = lexer_peek(lexer);
}
save_tok = lexer_peek(lexer);
// Try (element)+
bool first_element = true;
bool first_is_command = false;
while (true)
{
struct ast *ast_element = NULL;
enum parser_status status_element = parse_element(&ast_element, lexer);
if (status_element == PARSER_ERROR)
{
lexer_go_back(lexer, save_tok);
ast_free(ast_element);
break;
}
save_tok = lexer_peek(lexer);
// Test if element is WORD and parse it as argument of the previous
// command
if (ast_element->type == AST_COMMAND)
{
//? following words are consider in variable assignment
if (is_assignment)
{
struct string_array_with_quotes res =
merge_values(cur_prefix->value, ast_element->value,
cur_prefix->enclosure, ast_element->enclosure);
cur_prefix->value = res.value;
cur_prefix->enclosure = res.q;
ast_free(ast_element);
first_is_command = true; //! To be sure about
continue;
}
struct ast *last_command = NULL;
if ((first_element && first_prefix) || first_is_command)
{
first_is_command = true;
if (cur_prefix == NULL)
{
cur_prefix = ast_element;
first_element = false;
continue;
}
last_command = cur_prefix;
}
else
last_command = cur_prefix->right_child;
//? Merge char **value and enum quotes *enclosure from last_command
// and ast_element
struct string_array_with_quotes res =
merge_values(last_command->value, ast_element->value,
last_command->enclosure, ast_element->enclosure);
last_command->value = res.value;
last_command->enclosure = res.q;
ast_free(ast_element);
}
else
{
is_assignment = false;
if ((first_element && first_prefix) || first_is_command)
{
*ast = ast_element;
if (first_is_command)
(*ast)->left_child = cur_prefix;
}
else
{
ast_element->left_child = cur_prefix->right_child;
cur_prefix->right_child = ast_element;
}
first_is_command = false;
cur_prefix = ast_element;
}
first_element = false;
}
if (first_prefix)
{
//? first_element needs to be false, we are in (prefix)* (element)+
if (first_element)
return handle_parser_error(PARSER_ERROR, ast);
}
if (first_is_command)
*ast = cur_prefix;
return PARSER_OK;
}
enum parser_status parse_shell_command(struct ast **ast, struct lexer *lexer)
{
struct lexer_token *save_tok = lexer_peek(lexer);
// Try {
struct lexer_token *tok = lexer_peek(lexer);
if (tok->type == TOKEN_BRACE_OPEN)
{
lexer_pop(lexer); // token {
// Try compound_list
struct ast *ast_list = ast_new(AST_LIST);
enum parser_status status_compound_list =
parse_compound_list(&ast_list, lexer);
if (status_compound_list == PARSER_OK)
{
tok = lexer_peek(lexer);
if (tok->type == TOKEN_BRACE_CLOSE)
{
if (ast != NULL && *ast != NULL)
(*ast)->left_child = ast_list;
else
{
*ast = ast_new(AST_FUNC);
(*ast)->var_name = NULL;
(*ast)->left_child = ast_list;
}
lexer_pop(lexer); // token }
return PARSER_OK;
}
}
ast_free(ast_list);
lexer_go_back(lexer, save_tok);
}
// Try ( or $(
tok = lexer_peek(lexer);
if (tok->type == TOKEN_PARENTHESIS_OPEN
|| tok->type == TOKEN_SUBSTITUTION_OPEN || tok->type == TOKEN_BACKTICK)
{
struct lexer_token *tok_parenthesis = tok;
lexer_pop(lexer); // token (
// Try compound_list
struct ast *ast_list = ast_new(AST_LIST);
enum parser_status status_compound_list =
parse_compound_list(&ast_list, lexer);
if (status_compound_list == PARSER_OK)
{
tok = lexer_peek(lexer);
if ((tok_parenthesis->type != TOKEN_BACKTICK
&& tok->type == TOKEN_PARENTHESIS_CLOSE)
|| (tok_parenthesis->type == TOKEN_BACKTICK
&& tok->type == TOKEN_BACKTICK))
{
if (ast != NULL && *ast != NULL)
(*ast)->left_child = ast_list;
else
{
*ast =
ast_new(tok_parenthesis->type == TOKEN_SUBSTITUTION_OPEN
|| tok->type == TOKEN_BACKTICK
? AST_CMD_SUBSTITUTION
: AST_SUBSHELL);
(*ast)->left_child = ast_list;
}
lexer_pop(lexer); // token )
return PARSER_OK;
}
}
ast_free(ast_list);
lexer_go_back(lexer, save_tok);
}
// Try rule_for
enum parser_status status_command = parse_for(ast, lexer);
if (status_command == PARSER_OK)
return PARSER_OK;
lexer_go_back(lexer, save_tok);
// Try rule_while
status_command = parse_rule_while(ast, lexer);
if (status_command == PARSER_OK)
return PARSER_OK;
lexer_go_back(lexer, save_tok);
// Try rule_until
status_command = parse_rule_until(ast, lexer);
if (status_command == PARSER_OK)
return PARSER_OK;
lexer_go_back(lexer, save_tok);
// Try rule_case
status_command = parse_rule_case(ast, lexer);
if (status_command == PARSER_OK)
return PARSER_OK;
lexer_go_back(lexer, save_tok);
// Try rule_if
status_command = parse_rule_if(ast, lexer);
if (status_command == PARSER_OK)
return PARSER_OK;
lexer_go_back(lexer, save_tok);
return handle_parser_error(PARSER_ERROR, ast);
}
enum parser_status parse_command(struct ast **ast, struct lexer *lexer)
{
enum parser_status status;
// Save of current state of lexer because of | in grammar
struct lexer_token *save_tok = lexer_peek(lexer);
// Try fundec
struct ast *ast_fundec = NULL;
if ((status = parse_funcdec(&ast_fundec, lexer)) == PARSER_OK)
{
*ast = ast_fundec;
bool first_redir = true;
struct ast *cur_redir = NULL;
// Try (redirection)*
while (true)
{
struct lexer_token *save_tok = lexer_peek(lexer);
// Try redirection
struct ast *ast_redir = NULL;
enum parser_status status_redir =
parse_redirection(&ast_redir, lexer);
if (status_redir == PARSER_ERROR)
{
lexer_go_back(lexer, save_tok);
break;
}
if (first_redir)
{
ast_redir->left_child = *ast;
*ast = ast_redir;
first_redir = false;
cur_redir = *ast;
}
else
{
ast_redir->left_child = cur_redir->right_child;
cur_redir->right_child = ast_redir;
cur_redir = cur_redir->right_child;
}
}
return status;
}
ast_free(ast_fundec);
lexer_go_back(lexer, save_tok);
// Try simple_command
struct ast *ast_simple_command = NULL;
if ((status = parse_simple_command(&ast_simple_command, lexer))
== PARSER_OK)
{
*ast = ast_simple_command;
return status;
}
ast_free(ast_simple_command);
// Go back to lexer's state before simple_command exec
lexer_go_back(lexer, save_tok);
// Try shell_command
struct ast *ast_shell_command = NULL;
if ((status = parse_shell_command(&ast_shell_command, lexer)) == PARSER_OK)
{
*ast = ast_shell_command;
bool first_redir = true;
struct ast *cur_redir = NULL;
// Try (redirection)*
while (true)
{
struct lexer_token *save_tok = lexer_peek(lexer);
// Try redirection
struct ast *ast_redir = NULL;
enum parser_status status_redir =
parse_redirection(&ast_redir, lexer);
if (status_redir == PARSER_ERROR)
{
lexer_go_back(lexer, save_tok);
break;
}
if (first_redir)
{
ast_redir->left_child = *ast;
*ast = ast_redir;
first_redir = false;
cur_redir = *ast;
}
else
{
ast_redir->left_child = cur_redir->right_child;
cur_redir->right_child = ast_redir;
cur_redir = cur_redir->right_child;
}
}
return PARSER_OK;
}
ast_free(ast_shell_command);
return PARSER_ERROR;
}

View File

@ -0,0 +1,306 @@
#include "parser.h"
enum parser_status parse_rule_if(struct ast **ast, struct lexer *lexer)
{
// Check If
struct lexer_token *tok = lexer_peek(lexer);
if (tok->type != TOKEN_IF)
return handle_parser_error(PARSER_ERROR, ast);
lexer_pop(lexer); // token IF
*ast = ast_new(AST_IF);
// Check compound_list (condition)
struct ast *ast_condition = ast_new(AST_LIST);
enum parser_status status_compound_list =
parse_compound_list(&ast_condition, lexer);
// If status is ERROR, assignment is still legal to free everything
(*ast)->condition = ast_condition;
if (status_compound_list == PARSER_ERROR)
return handle_parser_error(status_compound_list, ast);
// Check Then
tok = lexer_peek(lexer);
if (tok->type != TOKEN_THEN)
return handle_parser_error(PARSER_ERROR, ast);
lexer_pop(lexer); // token THEN
// Check compound_list (true block)
struct ast *ast_true_block = ast_new(AST_LIST);
status_compound_list = parse_compound_list(&ast_true_block, lexer);
(*ast)->left_child = ast_true_block;
if (status_compound_list == PARSER_ERROR)
return handle_parser_error(status_compound_list, ast);
// Check First(else_clause) = {Else, Elif}
tok = lexer_peek(lexer);
if (tok->type == TOKEN_ELSE || tok->type == TOKEN_ELIF)
{
// Check else_clause
struct ast *ast_false_block = NULL;
status_compound_list = parse_else_clause(&ast_false_block, lexer);
(*ast)->right_child = ast_false_block;
if (status_compound_list == PARSER_ERROR)
return handle_parser_error(status_compound_list, ast);
}
// Check Fi
tok = lexer_peek(lexer);
if (tok->type != TOKEN_FI)
return handle_parser_error(PARSER_ERROR, ast);
lexer_pop(lexer); // token FI
return PARSER_OK;
}
enum parser_status parse_else_clause(struct ast **ast, struct lexer *lexer)
{
// Double check First(else_clause), should always be correct but safety
// first
struct lexer_token *tok = lexer_peek(lexer);
enum parser_status status = PARSER_OK;
if (tok->type == TOKEN_ELSE)
{
lexer_pop(lexer); // token ELSE
// Check compound_list
*ast = ast_new(AST_LIST);
status = parse_compound_list(ast, lexer);
if (status == PARSER_ERROR)
return handle_parser_error(status, ast);
}
else if (tok->type == TOKEN_ELIF)
{
lexer_pop(lexer); // token ELIF
(*ast) = ast_new(AST_IF);
// Check compound_list
struct ast *ast_elif_condition = ast_new(AST_LIST);
status = parse_compound_list(&ast_elif_condition, lexer);
(*ast)->condition = ast_elif_condition;
if (status == PARSER_ERROR)
return handle_parser_error(status, ast);
// Check Then
tok = lexer_peek(lexer);
if (tok->type != TOKEN_THEN)
return handle_parser_error(PARSER_ERROR, ast);
lexer_pop(lexer); // token THEN
// Check compound_list (true block)
struct ast *ast_true_block = ast_new(AST_LIST);
status = parse_compound_list(&ast_true_block, lexer);
(*ast)->left_child = ast_true_block;
if (status == PARSER_ERROR)
return handle_parser_error(status, ast);
// Check First(else_clause) = {Else, Elif}
tok = lexer_peek(lexer);
if (tok->type == TOKEN_ELSE || tok->type == TOKEN_ELIF)
{
struct ast *ast_false_block = NULL;
status = parse_else_clause(&ast_false_block, lexer);
(*ast)->right_child = ast_false_block;
if (status == PARSER_ERROR)
return handle_parser_error(status, ast);
}
}
else
return handle_parser_error(PARSER_ERROR, ast);
return PARSER_OK;
}
enum parser_status parse_rule_case(struct ast **ast, struct lexer *lexer)
{
struct lexer_token *tok = lexer_peek(lexer);
// Try Case
if (tok->type != TOKEN_CASE)
return handle_parser_error(PARSER_ERROR, ast);
lexer_pop(lexer); // token Case
// Try WORD
tok = lexer_peek(lexer);
if (tok->type != TOKEN_WORD && tok->type != TOKEN_WORD_DOUBLE_QUOTE
&& tok->type != TOKEN_WORD_SINGLE_QUOTE)
return handle_parser_error(PARSER_ERROR, ast);
char **val = calloc(2, sizeof(char *));
enum quotes *enclosure = calloc(1, sizeof(enum quotes));
enclosure[0] = tok->type - TOKEN_WORD;
val[0] = tok->value;
lexer_pop(lexer); // token WORD
*ast = ast_new(AST_CASE);
(*ast)->value = val;
(*ast)->enclosure = enclosure;
// Try ('\n')*
while ((tok = lexer_peek(lexer))->type == TOKEN_NEWLINE)
lexer_pop(lexer); // token \n
// Try in
tok = lexer_peek(lexer);
if (tok->type != TOKEN_IN)
return handle_parser_error(PARSER_ERROR, ast);
lexer_pop(lexer); // token in
// Try ('\n')*
while ((tok = lexer_peek(lexer))->type == TOKEN_NEWLINE)
lexer_pop(lexer); // token \n
// Check First(case_clause) = First(case_item) = {(, WORD}
tok = lexer_peek(lexer);
if (tok->type == TOKEN_PARENTHESIS_OPEN || tok->type == TOKEN_WORD
|| tok->type == TOKEN_WORD_SINGLE_QUOTE
|| tok->type == TOKEN_WORD_DOUBLE_QUOTE)
{
// Check case_clause
struct ast *ast_content_block = NULL;
enum parser_status status_case_clause =
parse_case_clause(&ast_content_block, lexer);
(*ast)->left_child = ast_content_block;
if (status_case_clause == PARSER_ERROR)
return handle_parser_error(status_case_clause, ast);
}
// Try Esac
tok = lexer_peek(lexer);
if (tok->type != TOKEN_ESAC)
return handle_parser_error(PARSER_ERROR, ast);
lexer_pop(lexer); // token Esac
return PARSER_OK;
}
enum parser_status parse_case_clause(struct ast **ast, struct lexer *lexer)
{
struct lexer_token *tok = lexer_peek(lexer);
// Try case_item
enum parser_status status_case_item = parse_case_item(ast, lexer);
if (status_case_item == PARSER_ERROR)
return handle_parser_error(status_case_item, ast);
// Try (';;' ('\n')* case_item)*
struct ast *cur_item = *ast;
struct lexer_token *save_tok = lexer_peek(lexer);
while (true)
{
tok = lexer_peek(lexer);
save_tok = tok;
// Try ;
if (tok->type != TOKEN_SEMICOLON)
break;
lexer_pop(lexer); // token ;
// Try ;
tok = lexer_peek(lexer);
if (tok->type == TOKEN_SEMICOLON)
lexer_pop(lexer); // token ;
// Try ('\n')*
while ((tok = lexer_peek(lexer))->type == TOKEN_NEWLINE)
lexer_pop(lexer); // token \n
// Try case_item
enum parser_status status_case_item =
parse_case_item(&(cur_item->right_child), lexer);
if (status_case_item == PARSER_ERROR)
{
lexer_go_back(lexer, save_tok);
break;
}
cur_item = cur_item->right_child;
}
tok = lexer_peek(lexer);
// Try ;;
if (tok->type == TOKEN_SEMICOLON)
{
lexer_pop(lexer); // token ;
tok = lexer_peek(lexer);
if (tok->type == TOKEN_SEMICOLON)
lexer_pop(lexer); // token ;
}
// Try ('\n')*
while ((tok = lexer_peek(lexer))->type == TOKEN_NEWLINE)
lexer_pop(lexer); // token \n
return PARSER_OK;
}
enum parser_status parse_case_item(struct ast **ast, struct lexer *lexer)
{
struct lexer_token *tok = lexer_peek(lexer);
// Try [(]
if (tok->type == TOKEN_PARENTHESIS_OPEN)
lexer_pop(lexer); // token (
// Try WORD
tok = lexer_peek(lexer);
if (tok->type != TOKEN_WORD && tok->type != TOKEN_WORD_SINGLE_QUOTE
&& tok->type != TOKEN_WORD_DOUBLE_QUOTE)
return handle_parser_error(PARSER_ERROR, ast);
*ast = ast_new(AST_CASE_SWITCH);
(*ast)->value = calloc(2, sizeof(char *));
(*ast)->value[0] = tok->value;
(*ast)->enclosure = calloc(1, sizeof(enum quotes));
(*ast)->enclosure[0] = tok->type - TOKEN_WORD;
size_t val_len = 1;
lexer_pop(lexer); // token WORD
// Try (| WORD)*
while (true)
{
// Try |
tok = lexer_peek(lexer);
if (tok->type != TOKEN_PIPE)
break;
lexer_pop(lexer); // token |
// Try WORD
tok = lexer_peek(lexer);
if (tok->type != TOKEN_WORD && tok->type != TOKEN_WORD_SINGLE_QUOTE
&& tok->type != TOKEN_WORD_DOUBLE_QUOTE)
return handle_parser_error(PARSER_ERROR, ast);
val_len++;
(*ast)->value = realloc((*ast)->value, (val_len + 1) * sizeof(char *));
(*ast)->value[val_len - 1] = tok->value;
(*ast)->value[val_len] = NULL;
(*ast)->enclosure =
realloc((*ast)->enclosure, val_len * sizeof(char *));
(*ast)->enclosure[val_len - 1] = tok->type - TOKEN_WORD;
lexer_pop(lexer); // token WORD
}
// Try )
if (tok->type != TOKEN_PARENTHESIS_CLOSE)
return handle_parser_error(PARSER_ERROR, ast);
lexer_pop(lexer); // token )
// Try ('\n')*
while ((tok = lexer_peek(lexer))->type == TOKEN_NEWLINE)
lexer_pop(lexer); // token \n
// Try [compound_list]
struct lexer_token *save_tok = lexer_peek(lexer);
struct ast *ast_compound_list = ast_new(AST_LIST);
enum parser_status status_compound_list =
parse_compound_list(&ast_compound_list, lexer);
if (status_compound_list == PARSER_ERROR)
{
ast_free(ast_compound_list);
lexer_go_back(lexer, save_tok);
}
else
{
(*ast)->left_child = ast_compound_list;
}
return PARSER_OK;
}

View File

@ -0,0 +1,198 @@
#include "parser.h"
enum parser_status parse_compound_list(struct ast **ast, struct lexer *lexer)
{
struct lexer_token *tok = lexer_peek(lexer);
// Try ('\n')*
while ((tok = lexer_peek(lexer))->type == TOKEN_NEWLINE)
lexer_pop(lexer); // token \n
// Try and_or
enum parser_status status_and_or =
parse_and_or(&(*ast)->right_child, lexer);
if (status_and_or == PARSER_ERROR)
return handle_parser_error(status_and_or, ast);
// Try (';' and_or)*
struct ast *cur_list_node = *ast;
while (true)
{
struct lexer_token *save_tok = lexer_peek(lexer);
if (save_tok->type != TOKEN_SEMICOLON
&& save_tok->type != TOKEN_NEWLINE)
break;
lexer_pop(lexer);
// Try ('\n')*
while ((tok = lexer_peek(lexer))->type == TOKEN_NEWLINE)
lexer_pop(lexer); // token \n
struct ast *new_list = ast_new(AST_LIST);
enum parser_status status = parse_and_or(&new_list->right_child, lexer);
if (status == PARSER_ERROR)
{
ast_free(new_list);
lexer_go_back(lexer, save_tok);
break;
}
cur_list_node->left_child = new_list;
cur_list_node = cur_list_node->left_child;
}
// Try [';'] and skip it if present
tok = lexer_peek(lexer);
if (tok->type == TOKEN_SEMICOLON || tok->type == TOKEN_NEWLINE)
{
lexer_pop(lexer);
// Try ('\n')*
while ((tok = lexer_peek(lexer))->type == TOKEN_NEWLINE)
lexer_pop(lexer); // token \n
}
return PARSER_OK;
}
enum parser_status parse_pipeline(struct ast **ast, struct lexer *lexer)
{
struct lexer_token *tok = lexer_peek(lexer);
// Try not
bool not = false;
if (tok->type == TOKEN_NOT)
{
not = true;
*ast = ast_new(AST_NOT);
lexer_pop(lexer);
}
// Try command
struct ast *last_command = NULL;
enum parser_status status_command = parse_command(&last_command, lexer);
if (status_command == PARSER_ERROR)
{
ast_free(*ast);
return handle_parser_error(status_command, &last_command);
}
// Try ('|' ('\n')* command)*
struct ast *cur_pipe = NULL;
bool first = true;
while (true)
{
// Try |
tok = lexer_peek(lexer);
if (tok->type != TOKEN_PIPE)
break;
lexer_pop(lexer); // token |
// Try ('\n')*
while ((tok = lexer_peek(lexer))->type == TOKEN_NEWLINE)
lexer_pop(lexer); // token \n
// Try command
struct ast *new_command;
status_command = parse_command(&new_command, lexer);
if (status_command == PARSER_ERROR)
{
ast_free(new_command);
if (first)
ast_free(last_command);
return handle_parser_error(status_command, ast);
}
struct ast *ast_pipe = ast_new(AST_PIPE);
//* Create new pipe and add command found before while loop in left
// child
if (first)
{
first = false;
ast_pipe->left_child = last_command;
if (not )
{
not = false;
(*ast)->left_child = ast_pipe;
}
else
*ast = ast_pipe;
}
else
{
cur_pipe->right_child = ast_pipe;
ast_pipe->left_child = last_command;
}
cur_pipe = ast_pipe;
last_command = new_command;
}
if (cur_pipe)
cur_pipe->right_child = last_command;
if (ast == NULL)
ast = &last_command;
else if ((first && !not ) || *ast == NULL)
{
*ast = last_command;
}
else if (not &&first)
(*ast)->left_child = last_command;
return PARSER_OK;
}
enum parser_status parse_and_or(struct ast **ast, struct lexer *lexer)
{
// Try pipeline
enum parser_status status_pipeline = parse_pipeline(ast, lexer);
if (status_pipeline == PARSER_ERROR)
return handle_parser_error(status_pipeline, ast);
// Try (('&&'|'||') ('\n')* pipeline)*
struct lexer_token *tok = lexer_peek(lexer);
bool first = true;
struct ast *last_op = NULL;
while (true)
{
// Try ('&&'| '||')
tok = lexer_peek(lexer);
bool is_and = true;
if (tok->type == TOKEN_AND)
is_and = true;
else if (tok->type == TOKEN_OR)
is_and = false;
else
break;
lexer_pop(lexer);
// Try ('\n')*
while ((tok = lexer_peek(lexer))->type == TOKEN_NEWLINE)
lexer_pop(lexer); // token '\n'
// Try pipeline
struct ast *new_command = NULL;
status_pipeline = parse_pipeline(&new_command, lexer);
if (status_pipeline == PARSER_ERROR)
{
ast_free(new_command);
return handle_parser_error(status_pipeline, ast);
}
if (first)
{
first = false;
struct ast *tmp = *ast;
*ast = ast_new(is_and ? AST_AND : AST_OR);
last_op = *ast;
last_op->left_child = tmp;
last_op->right_child = new_command;
}
else
{
struct ast *tmp_left_cmd = last_op->right_child;
last_op->right_child = ast_new(is_and ? AST_AND : AST_OR);
last_op = last_op->right_child;
last_op->left_child = tmp_left_cmd;
last_op->right_child = new_command;
}
}
return PARSER_OK;
}

201
src/parser/parser_loops.c Normal file
View File

@ -0,0 +1,201 @@
#include "parser.h"
enum parser_status parse_rule_while(struct ast **ast, struct lexer *lexer)
{
struct lexer_token *tok = lexer_peek(lexer);
// Try While
if (tok->type != TOKEN_WHILE)
return handle_parser_error(PARSER_ERROR, ast);
lexer_pop(lexer); // token While
// Try compound_list
*ast = ast_new(AST_WHILE);
struct ast *ast_condition = ast_new(AST_LIST);
enum parser_status status_compound_list =
parse_compound_list(&ast_condition, lexer);
// If status is ERROR, assignment is still legal to free everything
(*ast)->condition = ast_condition;
if (status_compound_list == PARSER_ERROR)
return handle_parser_error(status_compound_list, ast);
// Try do_group
struct ast *ast_do = NULL;
enum parser_status status_do_group = parse_do_group(&ast_do, lexer);
(*ast)->left_child = ast_do;
if (status_do_group == PARSER_ERROR)
return handle_parser_error(status_do_group, ast);
return PARSER_OK;
}
enum parser_status parse_rule_until(struct ast **ast, struct lexer *lexer)
{
struct lexer_token *tok = lexer_peek(lexer);
// Try Until
if (tok->type != TOKEN_UNTIL)
return handle_parser_error(PARSER_ERROR, ast);
lexer_pop(lexer); // token Until
// Try compound_list
*ast = ast_new(AST_UNTIL);
struct ast *ast_condition = ast_new(AST_LIST);
enum parser_status status_compound_list =
parse_compound_list(&ast_condition, lexer);
// If status is ERROR, assignment is still legal to free everything
(*ast)->condition = ast_condition;
if (status_compound_list == PARSER_ERROR)
return handle_parser_error(status_compound_list, ast);
// Try do_group
struct ast *ast_do = NULL;
enum parser_status status_do_group = parse_do_group(&ast_do, lexer);
(*ast)->left_child = ast_do;
if (status_do_group == PARSER_ERROR)
return handle_parser_error(status_do_group, ast);
return PARSER_OK;
}
enum parser_status parse_do_group(struct ast **ast, struct lexer *lexer)
{
struct lexer_token *tok = lexer_peek(lexer);
// Try Do
if (tok->type != TOKEN_DO)
return handle_parser_error(PARSER_ERROR, ast);
lexer_pop(lexer); // token While
// Try compound_list
struct ast *ast_body = ast_new(AST_LIST);
enum parser_status status_compound_list =
parse_compound_list(&ast_body, lexer);
*ast = ast_body;
if (status_compound_list == PARSER_ERROR)
return handle_parser_error(status_compound_list, ast);
// Try Done
tok = lexer_peek(lexer);
if (tok->type != TOKEN_DONE)
return handle_parser_error(PARSER_ERROR, ast);
lexer_pop(lexer); // token Done
return PARSER_OK;
}
enum parser_status parse_for(struct ast **ast, struct lexer *lexer)
{
struct lexer_token *tok = lexer_peek(lexer);
// Try For
if (tok->type != TOKEN_FOR)
return handle_parser_error(PARSER_ERROR, ast);
lexer_pop(lexer); // token For
*ast = ast_new(AST_FOR);
char **values = NULL;
enum quotes *enclosure = NULL;
size_t len = 0;
// Try WORD
tok = lexer_peek(lexer);
if (tok->type == TOKEN_WORD || tok->type == TOKEN_WORD_DOUBLE_QUOTE
|| tok->type == TOKEN_WORD_SINGLE_QUOTE)
{
len++;
values = realloc(values, (len + 1) * sizeof(char *));
values[len - 1] = tok->value;
values[len] = NULL;
enclosure = realloc(enclosure, len * sizeof(enum quotes));
enclosure[len - 1] = tok->type - TOKEN_WORD;
(*ast)->value = values;
(*ast)->enclosure = enclosure;
lexer_pop(lexer); // token WORD
}
else
return handle_parser_error(PARSER_ERROR, ast);
// Try [';']
tok = lexer_peek(lexer);
bool semi_colon = false;
if (tok->type == TOKEN_SEMICOLON)
{
semi_colon = true;
lexer_pop(lexer); // token ';'
}
// Try ('\n')*
while ((tok = lexer_peek(lexer))->type == TOKEN_NEWLINE)
lexer_pop(lexer); // token '\n'
// Try in from [('\n')* 'in' (WORD)* (';'|'\n')]
bool in = false;
if (tok->type == TOKEN_IN)
{
in = true;
lexer_pop(lexer); // token in
//? add 'in' in char **values
len++;
values = realloc(values, (len + 1) * sizeof(char *));
values[len - 1] = "in";
values[len] = NULL;
enclosure = realloc(enclosure, len * sizeof(enum quotes));
enclosure[len - 1] = 0;
(*ast)->value = values;
(*ast)->enclosure = enclosure;
//? already took left part of \ condition
if (semi_colon)
return handle_parser_error(PARSER_ERROR, ast);
//? try rest of condition
// Try WORD*
tok = lexer_peek(lexer);
while (tok->type == TOKEN_WORD || tok->type == TOKEN_WORD_DOUBLE_QUOTE
|| tok->type == TOKEN_WORD_SINGLE_QUOTE)
{
len++;
values = realloc(values, (len + 1) * sizeof(char *));
values[len - 1] = tok->value;
values[len] = NULL;
enclosure = realloc(enclosure, len * sizeof(enum quotes));
enclosure[len - 1] = tok->type - TOKEN_WORD;
(*ast)->value = values;
(*ast)->enclosure = enclosure;
lexer_pop(lexer); // token WORD
tok = lexer_peek(lexer);
}
if (tok->type == TOKEN_SEMICOLON)
lexer_pop(lexer); // token ';'
else if (tok->type == TOKEN_NEWLINE)
lexer_pop(lexer); // token '\n'
else
return handle_parser_error(PARSER_ERROR, ast);
}
if (in)
{
// Try ('\n')*
while ((tok = lexer_peek(lexer))->type == TOKEN_NEWLINE)
lexer_pop(lexer); // token '\n'
}
// Try do_group
struct ast *ast_do = NULL;
enum parser_status status_do_group = parse_do_group(&ast_do, lexer);
(*ast)->left_child = ast_do;
if (status_do_group == PARSER_ERROR)
return handle_parser_error(status_do_group, ast);
(*ast)->left_child = ast_do;
return PARSER_OK;
}

View File

@ -0,0 +1,135 @@
#include "shell_input.h"
#include "parser.h"
#include "var_list.h"
static int shell_prompt(void)
{
char *input = NULL;
size_t input_len = 0;
while (!shell->exit)
{
if (shell->return_code)
fprintf(stderr,
"\033[1m\033[31m➜ \033[1m\033[36mbsh \033[1m\033[33m✗ "
"\033[0;37m");
else
fprintf(stderr,
"\033[1m\033[32m➜ \033[1m\033[36mbsh \033[1m\033[33m✗ "
"\033[0;37m");
fflush(stderr);
int line = 0;
if (getline(&input, &input_len, stdin) < 1)
{
free(input);
input = NULL;
input_len = 0;
break;
}
if (!input)
{
if (!line)
fprintf(stderr, "\n");
continue;
}
if (!strcmp(input, "exit\n"))
{
shell->exit = 1;
free(input);
continue;
}
parse_input(input, NULL);
free(input);
input = NULL;
input_len = 0;
}
return shell->return_code;
}
static char *get_file_content(char *filename)
{
FILE *file = fopen(filename, "r");
if (!file)
{
fprintf(stderr, "bsh: Can't open %s\n", filename);
return NULL;
}
fseek(file, 0, SEEK_END);
long size = ftell(file);
fseek(file, 0, SEEK_SET);
char *content = calloc(size + 1, sizeof(char));
fread(content, 1, size, file);
content[size] = 0;
fclose(file);
return content;
}
static char **copy_args(char **argv)
{
int i = 0;
char **args = NULL;
while (argv[i])
{
args = realloc(args, (i + 2) * sizeof(char *));
args[i] = strdup(argv[i]);
i++;
}
shell->nb_args = i - 1;
if (args)
args[i] = NULL;
else
args = calloc(1, sizeof(char *));
return args;
}
int get_input(int argc, char **argv)
{
char *input = NULL;
size_t input_len = 0;
if (argc < 2)
{
shell->args = calloc(2, sizeof(char *));
shell->args[0] = strdup(argv[0]);
new_var(shell, shell->args);
int c;
if (!isatty(STDIN_FILENO))
{
while (read(STDIN_FILENO, &c, 1) > 0)
{
if (c == EOF)
break;
input = realloc(input, (input_len + 2) * sizeof(char));
input[input_len++] = c;
}
}
else
return shell_prompt();
}
else
{
int i = 1;
if (!strcmp(argv[i], "-c"))
{
shell->args = calloc(2, sizeof(char *));
shell->args[0] = strdup(argv[0]);
new_var(shell, shell->args);
input = strdup(argv[++i]);
}
else
{
input = get_file_content(argv[i]);
shell->args = copy_args(argv + 1);
new_var(shell, shell->args);
}
if (!input)
return 1;
input_len = strlen(input);
}
if (input)
{
input[input_len] = '\0';
parse_input(input, NULL);
free(input);
}
return shell->return_code;
}

View File

@ -0,0 +1,26 @@
#ifndef SHELL_INPUT_H
#define SHELL_INPUT_H
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#include "../bsh.h"
extern struct shell *shell;
/**
* @brief Get the input for the shell and send it to the lexer
*
* @param argc number of arguments
* @param argv list of arguments
* @return int
*/
int get_input(int argc, char **argv);
#endif // !SHELL_INPUT_H

327
src/variables/var_list.c Normal file
View File

@ -0,0 +1,327 @@
#include "var_list.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "../bsh.h"
/*struct var *init_list(void)
{
struct var *new = calloc(1, sizeof(struct var));
if (!new)
return NULL;
new->name = NULL;
new->value = NULL;
new->next = NULL;
}*/
/*int add_elt_list(struct shell *sh, char *name, char *value)
{
struct var *new = calloc(1, sizeof(struct var));
if (!new)
return 1;
new->name = calloc(strlen(name) + 1, sizeof(char));
if (!new->name)
{
free(new);
return 1;
}
strcpy(new->name, name);
new->value = calloc(strlen(value) + 1, sizeof(char));
if (!new->value)
{
free(new->name);
free(new);
return 1;
}
strcpy(new->value, value);
new->next = sh->var_list;
sh->var_list = new;
return 0;
}*/
void my_itoa(int nb, char *buff)
{
sprintf(buff, "%d", nb);
}
char *generate_rand(struct shell *sh)
{
srand(time(NULL));
int rnd = rand() % 32768;
if (!sh->random_nb)
sh->random_nb = calloc(6, sizeof(char));
my_itoa(rnd, sh->random_nb);
return sh->random_nb;
}
int is_number(char *str)
{
if (str[0] == '0')
return 0;
int i = 0;
while (str[i] != '\0')
{
if (str[i] < '0' || str[i++] > '0')
return 0;
}
return 1;
}
int is_param(char *str, char *c)
{
int i = 0;
if (is_number(str))
return 1;
while (str[i] != '\0')
{
if (str[i++] == *c)
return 1 && c[1] == '\0';
}
return 0;
}
int new_var(struct shell *sh, char **arg)
{
struct var_stack *s = calloc(1, sizeof(struct var));
s->var_list = NULL;
s->next = sh->var_stack;
sh->var_stack = s;
int i = 1;
char *nb = calloc(21, sizeof(char));
int size = 1;
char *var = calloc(size, sizeof(char));
var[0] = '\0';
while (arg[i])
{
if (i != 0)
{
size += strlen(arg[i]) + 1;
var = realloc(var, size * sizeof(char));
strcat(var, arg[i]);
if (arg[i + 1])
strcat(var, " ");
}
my_itoa(i, nb);
push_elt_list(sh, nb, arg[i++]);
}
push_elt_list(sh, "0", sh->args[0]);
push_int_elt_list(sh, "#", i - 1);
push_int_elt_list(sh, "?", 0);
push_elt_list(sh, "@", var);
push_elt_list(sh, "*", var);
push_int_elt_list(sh, "$", sh->pid);
push_int_elt_list(sh, "UID", sh->uid);
push_elt_list(sh, "OLDPWD", sh->oldpwd);
push_elt_list(sh, "IFS", sh->ifs);
free(nb);
free(var);
return 0;
}
int push_elt_list(struct shell *sh, char *name, char *value)
{
int param = is_param("*@#?$", name);
char *new_content = calloc(strlen(value) + 1, sizeof(char));
if (!new_content)
return 1;
strcpy(new_content, value);
struct var *tmp = NULL;
if (param)
tmp = sh->var_stack->var_list;
else
tmp = sh->var_list;
while (tmp && strcmp(tmp->name, name))
tmp = tmp->next;
if (tmp)
{
free(tmp->value);
tmp->value = new_content;
}
else
{
struct var *new = calloc(1, sizeof(struct var));
if (!new)
return 1;
new->name = calloc(strlen(name) + 1, sizeof(char));
if (!new->name)
{
free(new);
free(new_content);
return 1;
}
strcpy(new->name, name);
new->value = new_content;
if (param)
{
new->next = sh->var_stack->var_list;
sh->var_stack->var_list = new;
}
else
{
new->next = sh->var_list;
sh->var_list = new;
}
}
return 0;
}
int push_int_elt_list(struct shell *sh, char *name, int val)
{
char *value = calloc(21, sizeof(char));
my_itoa(val, value);
int param = is_param("*@#?$", name);
char *new_content = calloc(strlen(value) + 1, sizeof(char));
if (!new_content)
return 1;
strcpy(new_content, value);
struct var *tmp = NULL;
if (param)
tmp = sh->var_stack->var_list;
else
tmp = sh->var_list;
while (tmp && strcmp(tmp->name, name))
tmp = tmp->next;
if (tmp)
{
free(tmp->value);
tmp->value = new_content;
}
else
{
struct var *new = calloc(1, sizeof(struct var));
if (!new)
return 1;
new->name = calloc(strlen(name) + 1, sizeof(char));
if (!new->name)
{
free(new);
free(new_content);
return 1;
}
strcpy(new->name, name);
new->value = new_content;
if (param)
{
new->next = sh->var_stack->var_list;
sh->var_stack->var_list = new;
}
else
{
new->next = sh->var_list;
sh->var_list = new;
}
}
free(value);
return 0;
}
char *find_elt_list(struct shell *sh, char *name)
{
int param = is_param("*@#?$", name);
if (!strcmp("RANDOM", name))
return generate_rand(sh);
struct var *tmp = NULL;
if (param)
tmp = sh->var_stack->var_list;
else
tmp = sh->var_list;
while (tmp && strcmp(tmp->name, name))
tmp = tmp->next;
// printf("%s\n", tmp->value);
if (tmp)
return tmp->value;
return NULL;
}
void free_list_sub(struct var *l)
{
struct var *list = l;
while (list)
{
struct var *tmp = list;
list = list->next;
free(tmp->name);
free(tmp->value);
free(tmp);
}
}
void del_stack(struct shell *sh)
{
struct var_stack *s = sh->var_stack;
sh->var_stack = s->next;
free_list_sub(s->var_list);
free(s);
}
void free_list(struct shell *sh)
{
while (sh->var_stack)
del_stack(sh);
free_list_sub(sh->var_list);
}
int del_name(struct shell *sh, char *name)
{
struct var *actual = sh->var_list;
struct var *previous = sh->var_list;
int index = 0;
while (actual)
{
if (!strcmp(actual->name, name))
{
if (index == 0)
sh->var_list = actual->next;
else
previous->next = actual->next;
free(actual->name);
free(actual->value);
free(actual);
return 1;
}
index++;
previous = actual;
actual = actual->next;
}
return 0;
}
struct var *var_list_cpy(struct shell *sh)
{
struct var *new = NULL;
struct var *list = sh->var_list;
while (list)
{
struct var *tmp = new;
new = calloc(1, sizeof(struct var));
if (!new)
return NULL;
char *new_content = calloc(strlen(list->value) + 1, sizeof(char));
if (!new_content)
{
free(new);
return NULL;
}
strcpy(new_content, list->value);
new->name = calloc(strlen(list->name) + 1, sizeof(char));
if (!new->name)
{
free(new);
free(new_content);
return NULL;
}
strcpy(new->name, list->name);
new->value = new_content;
new->next = tmp;
list = list->next;
}
return new;
}

17
src/variables/var_list.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef VAR_LIST_H
#define VAR_LIST_H
#include "../bsh.h"
int push_elt_list(struct shell *sh, char *name, char *value);
char *find_elt_list(struct shell *sh, char *name);
void free_list(struct shell *sh);
int push_int_elt_list(struct shell *sh, char *name, int val);
void del_stack(struct shell *sh);
void free_list_sub(struct var *list);
int new_var(struct shell *sh, char **arg);
int del_name(struct shell *sh, char *name);
struct var *var_list_cpy(struct shell *sh);
// int change_elt_list(struct shell *sh, char *name, char *value);
#endif /* !VAR_LIST_H */

2
tests/alias.yml Normal file
View File

@ -0,0 +1,2 @@
- name: Basic
file: tests/aliases/basic.sh

2
tests/aliases/basic.sh Normal file
View File

@ -0,0 +1,2 @@
alias bar=ls
bar

View File

@ -0,0 +1,5 @@
for i in ee oo kk; do
echo $i
break
done
echo "done"

View File

@ -0,0 +1,12 @@
for i in ee oo uu;
echo $i
while echo 11; do
until grep; do
for j in aa bb cc;
echo $j
break 4
done
done
done
done
echo "done"

View File

@ -0,0 +1,12 @@
for i in ee oo uu;
echo $i
while echo 11; do
until grep; do
for j in aa bb cc;
echo $j
break 5
done
done
done
done
echo "done"

35
tests/case.yml Normal file
View File

@ -0,0 +1,35 @@
- name: exit in case
input: a=33; case 22 in ($a) echo oui;; (22) echo non; exit;; esac; echo oui
- name: basic
file: tests/case/basic.sh
- name: all_case
input: "case 'yoyoy' in bruh) echo error;; *) echo ayaya;; esac"
- name: no_match
input: "case 'yoyoy' in bruh) echo error;; efefefef) echo ayaya;; esac; echo 'this is the first line'"
- name: no_case_clause
input: "case 'yoyoy' in esac"
- name: no_ending_semicolons
input: "case 'yoyoy' in bruh) echo error;; yoyoy) echo ayaya; esac"
- name: case_in_case
input: "case test in test) case second in s) echo false;; (second) echo ok;; esac; echo woow!;; esac"
- name: hard_semicolon
input: "case test in bruh) echo error;; test) echo ayaya;; a) echo a; esac"
- name: if_in_case
input: "case test in bruh) echo error;; test) if true; then echo ok; fi;; a) echo a; esac"
- name: case_in_function
file: tests/case/case_in_function.sh
- name: if_in_case_in_if
file: tests/case/if_in_case_in_if.sh
- name: multiple_condition_case
file: tests/case/multiple_condition_case.sh

6
tests/case/basic.sh Normal file
View File

@ -0,0 +1,6 @@
case test in
te)
echo wrong;;
"test")
echo ok ;;
esac

View File

@ -0,0 +1,16 @@
fun()
{
case fun in
f)
echo nope;;
*)
echo nice;;
esac
}
case call in
call)
fun;;
c)
echo what?;;
esac

View File

@ -0,0 +1,13 @@
a=true
if $a
then
case $a in
false)
echo false;;
(true)
echo $a;;
esac
fi
echo end

View File

@ -0,0 +1,4 @@
case test in
te|test|aaaaaaaaa)
echo true;;
esac

59
tests/cd.yml Normal file
View File

@ -0,0 +1,59 @@
- name: Basic folder
input: cd src; pwd
- name: Multiple folders
input: cd src; cd builtins; cd ..; pwd
- name: Long path
input: cd src/../tests/../; pwd
- name: Long path, doesn't work
input: cd src/builtins/Makefile/ ; pwd
- name: Wrong folder
input: cd jenexistepas; pwd
- name: File
input: cd Makefile; pwd
- name: Folder then a dash
input: cd src; cd -; pwd
- name: Root
input: cd ./; pwd
- name: Root then folder
input: cd ./src; pwd
- name: Root then folder then reroot
input: cd ./src; cd ./; pwd
- name: Root after folder
input: cd src/./; pwd
- name: Root then dash
input: cd ./; cd -; pwd
- name: Root folder then dash
input: cd ./src/builtins; cd -; pwd
- name: Root folder inexistant
input: cd ./jenexistepas; pwd
- name: Root folder is a file
input: cd ./Makefile; pwd
- name: Double root
input: cd ././; pwd
- name: Multiple dash
input: cd -----; pwd
- name: Chained dash
input: cd /tmp; cd /root; echo start; cd -; pwd; cd -; pwd; cd -; pwd; cd -; pwd; cd -; pwd; cd -; echo end; pwd
- name: Go back but too far
input: cd ../../../../../../../../../../../../../../tmp; pwd; cd /home; cd -; pwd;
- name: Go back (absolute) but too far
input: cd /../../../../../../../../../../../../../../tmp; pwd; cd /home; cd -; pwd;

View File

@ -0,0 +1,8 @@
- name: Basic
file: tests/cmd_substitution/basic.sh
- name: Backticks
file: tests/cmd_substitution/backticks.sh
- name: With while loops
file: tests/cmd_substitution/with_while_loop.sh

View File

@ -0,0 +1,4 @@
if `echo true`; then
echo ok
fi
`echo false`

View File

@ -0,0 +1,3 @@
if $(echo true); then
echo ok
fi

View File

@ -0,0 +1,3 @@
while $(echo false); do
echo nope
done

26
tests/command_list.yml Normal file
View File

@ -0,0 +1,26 @@
- name: Two commands separated by semicolon
input: echo Hello; echo World!;
- name: Two commands, semicolon at the end
input: echo Hello; echo World!;
- name: Two commands, no space between semicolon and word
input: echo "Hello";echo "World!"
- name: Only semicolon
input: ;
- name: Many commands
input: ls;pwd;cat Makefile;echo "Test";
- name: Two semicolons
input: ;;
- name: One command and two semicolons
input: echo;;
- name: Many semicolons with 2 commands
input: echo salut;;;;; pwd;
- name: Wrong commands and multiple semicolons
input: a;a;a;a;;a;a;;;;a;;a;a;a;;a;

View File

@ -0,0 +1,8 @@
- name: 1 break
file: tests/break_and_continue/1_break.sh
- name: 4 break
file: tests/break_and_continue/4_break.sh
- name: 5 break 4 loop
file: tests/break_and_continue/5_break_4_loop.sh

74
tests/echo.yml Normal file
View File

@ -0,0 +1,74 @@
- name: Basic words
input: echo "Cut, Copy and Paste!"
- name: Basic -n
input: echo -n "Cut, Copy and Paste!"
- name: Basic words
input: echo "-n"
- name: Multiple Words
input: echo "Cut, Copy and Paste!" "Cut, Copy and Paste!"
- name: Multiple Words 2²
input: echo "Cut, Copy and Paste!" "Quentin" "Charles"
- name: Word without quote
input: echo nathan
- name: Sentence without quote
input: echo Je suis une phrase sans quote
- name: Sentence without quote with option
input: echo -n Je suis une phrase sans quote
- name: Sentence without quote with multiple spaces
input: echo Je suis une phrase sans quote
- name: Sentence without quote with multiple spaces with option
input: echo -n Je suis une phrase sans quote
- name: Sentence without quote with multiple spaces and a lost option
input: echo Je suis une -n phrase sans quote
- name: Echo empty
input: echo
- name: Echo empty with -n
input: echo -n
- name: Echo empty with -n and empty quote
input: echo -n ""
- name: Echo empty quote
input: echo ""
- name: Echo real backlash n
input: echo "\n"
- name: Echo wrong option
input: echo -a
- name: Echo wrong option followed by real one
input: echo -a -n
- name: Echo wrong option preceded by real one
input: echo -a -n
- name: Option in quote with word
input: echo "-n salut"
- name: Option in quote with word and mutliple spaces
input: echo "-n salut"
- name: Option in quote with no other word inside quote
input: echo "-n" "salut"
- name: Option in quote with space inside
input: echo "-n " "salut"
- name: Long argument
input: echo Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in
- name: 10 paragraphs
file: tests/echo/very_long.sh

19
tests/echo/very_long.sh Normal file
View File

@ -0,0 +1,19 @@
echo "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque quam, varius et nunc nec, malesuada pellentesque risus. Morbi nunc augue, sollicitudin eu tincidunt id, vulputate quis nisl. Etiam quis risus id mauris blandit vestibulum. Suspendisse ullamcorper tortor id sapien iaculis, vel tincidunt orci consequat. Proin rhoncus dui ut accumsan tincidunt. Nullam luctus rutrum dui ut ornare. Fusce elit arcu, faucibus et nisl efficitur, dignissim commodo nibh. Nam imperdiet pretium massa, quis laoreet felis vestibulum maximus. Suspendisse imperdiet lacus vel diam feugiat molestie. Vestibulum et mauris porta est commodo porttitor. Curabitur egestas vitae sapien sit amet dignissim. Ut fringilla cursus urna non accumsan. Nullam vulputate id quam vel finibus. Morbi rutrum tincidunt venenatis. Curabitur lacus lectus, feugiat eget mi semper, pharetra aliquam arcu.
Sed semper tempor turpis non pretium. In vestibulum neque non risus vulputate ullamcorper. Proin mauris lectus, rhoncus at facilisis id, condimentum non est. Nulla purus velit, accumsan sit amet massa eu, auctor pulvinar lectus. Ut id justo sapien. Integer scelerisque, justo et aliquam malesuada, urna nisl bibendum magna, nec euismod justo leo id augue. Aliquam et velit eget enim pretium euismod. Nunc consectetur vel augue ac luctus. In porttitor vulputate lectus nec ultrices. Mauris elementum lectus id ligula convallis, semper dignissim urna ullamcorper. Praesent convallis nisl in tortor malesuada ornare. Quisque eu nulla finibus, blandit metus eu, porttitor purus. Donec vitae lacus nec turpis semper pellentesque non id tortor. Pellentesque quis libero sagittis, ultrices massa in, scelerisque est. Mauris semper dui turpis.
In finibus risus ac lorem pulvinar, et congue est eleifend. Curabitur finibus eu ante a rutrum. Sed mollis neque in finibus rhoncus. Praesent blandit maximus velit a ornare. Nunc suscipit ligula eget lacus posuere, vel egestas metus ornare. Vestibulum vel rutrum quam. Ut justo dolor, viverra in rhoncus sed, ultrices eget quam. Proin aliquet nunc sed aliquam tempus. Vivamus accumsan est sed libero aliquet interdum. Aenean rhoncus turpis in nisi vulputate pretium. Quisque vel accumsan nulla. Aliquam euismod massa eget dui semper aliquam. Proin massa nunc, placerat sed ante in, tincidunt convallis metus. Etiam quis sem lacinia velit condimentum lobortis suscipit non enim. Aliquam vel condimentum dui. Nunc eget pharetra odio.
Nam in purus tincidunt, porttitor turpis sit amet, pharetra velit. Nunc eu tempor ligula. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Etiam a varius quam. Mauris nec cursus nisl. In velit tellus, cursus non mi sit amet, sagittis semper enim. Phasellus elementum purus nibh, vel finibus urna vulputate non. Aenean tortor nunc, faucibus et imperdiet eu, blandit at dui. Suspendisse tempor auctor sodales. Donec hendrerit nibh quis augue consequat, quis dapibus quam faucibus.
Cras maximus hendrerit velit non porta. In hac habitasse platea dictumst. Aliquam nec consectetur erat. Cras sodales sit amet dolor id congue. Mauris et aliquam risus, nec aliquam nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Sed sed turpis finibus, feugiat nibh vel, interdum ligula. Donec sit amet nunc rutrum, efficitur erat eu, mattis justo. Phasellus sit amet scelerisque ex. Suspendisse fringilla tempus risus, efficitur aliquet ex tincidunt eget.
Sed eleifend, odio vitae rutrum convallis, nisl dui mollis magna, a euismod erat elit quis erat. Phasellus convallis vulputate ante, a convallis risus. Curabitur imperdiet quam non aliquam tempor. Integer maximus lacus elit, vehicula pulvinar nunc elementum tincidunt. In tincidunt ante sit amet est aliquet porta. Pellentesque dictum egestas purus, sit amet lobortis ex tincidunt condimentum. Maecenas vulputate orci a diam fermentum, ac rutrum mauris pellentesque. In non blandit justo, in scelerisque sem.
Quisque lobortis pretium velit, sit amet sollicitudin tortor bibendum in. Integer posuere imperdiet erat, vel euismod mi mollis in. Donec felis nunc, dictum vitae sodales vel, suscipit vitae nisi. Nam convallis non ex ut facilisis. Nunc maximus facilisis arcu eget faucibus. Sed luctus orci sit amet commodo vestibulum. Donec est arcu, porta ac justo at, lobortis interdum felis. Sed finibus ante nec maximus mollis.
Nam tristique turpis quis congue suscipit. Nam vel tincidunt leo. Sed laoreet lectus nec ipsum dictum sollicitudin. Proin tincidunt sed augue sit amet posuere. Vestibulum vel rutrum nisl. Sed lectus nisl, lobortis eu lobortis vel, bibendum sed enim. Phasellus consectetur neque id tristique maximus. Donec quam sapien, rhoncus id maximus sit amet, bibendum vitae orci. Suspendisse potenti. Phasellus ut varius sapien. Vivamus facilisis in ligula nec accumsan. Phasellus sem neque, dictum et commodo vitae, vulputate nec ipsum. Pellentesque vitae tincidunt lorem.
Etiam dignissim lectus sed nunc elementum, quis ullamcorper turpis sodales. Pellentesque est sapien, cursus et est ut, posuere scelerisque diam. Nullam non nisi id ipsum elementum varius et ut magna. Donec laoreet quis tellus eu vehicula. Quisque et fermentum ligula. Nam rutrum sollicitudin mollis. Aliquam erat volutpat. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Suspendisse ullamcorper ut felis nec porttitor. Quisque scelerisque quis magna vel facilisis. Sed congue nisl id enim consectetur egestas. Suspendisse semper diam ex, sed vehicula neque maximus at. Maecenas sit amet ex vitae quam tristique vestibulum vel a tellus.
Praesent ultrices risus ut mauris auctor suscipit. In suscipit finibus massa, non finibus lacus ultricies sed. Vestibulum rutrum feugiat massa, at efficitur sapien volutpat blandit. Nunc eget erat sit amet libero ultricies ornare. Nunc imperdiet, magna a sodales tempor, mauris velit molestie velit, id interdum eros arcu quis odio. Quisque odio urna, auctor sit amet leo id, consequat vestibulum tellus. Etiam vulputate nunc eget turpis convallis iaculis. In nulla sapien, pretium ut sagittis id, eleifend sit amet nibh. Duis feugiat iaculis dapibus. Fusce blandit sapien id convallis faucibus. Phasellus suscipit mauris commodo bibendum condimentum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Praesent aliquam fringilla mi. Sed et diam ipsum. Morbi ac ante id erat pretium congue eget id lectus. Proin laoreet augue eget est finibus, nec volutpat nulla porttitor."

29
tests/expansion.yml Normal file
View File

@ -0,0 +1,29 @@
- name: basic variable
input: first=test; a="first value is $first"; echo "$a"
- name: basic variable no expand
input: first=test; a='first value is $first'; echo $a
- name: basic variable expand then no expand
input: first=test; a="first value is $first"; echo '$a $first'
- name: basic variable with weird quotes
input: first='test'; a="'first value is $first"; echo '"$a $first'
- name: basic variable with weird quotes reversed
input: first='test'; a="'first value is $first"; echo "'$a $first"
- name: basic variable with weird quotes escaped
input: first='test'; a="'first value is $first"; echo '\"$a $first'
- name: basic variable with undefined variable
input: first='test'; a='first value is $i $first'; echo "$a $first"
- name: basic variable with quotes in quotes
input: first=""; a='first value is $i $first'; echo "$a $first"
- name: wrong basic variable with quotes in quotes
input: first="""; a='first value is $i $first'; echo "$a $first"
- name: basic variable with undefined variable and weird quotes
input: first="$first"; a="'first value is "$first"'"; echo ''$a "$first''

17
tests/for.yml Normal file
View File

@ -0,0 +1,17 @@
- name: Basic for
file: tests/for/basic.sh
- name: For in for
file: tests/for/for_in_for.sh
- name: For with variables
file: tests/for/for_in_variables.sh
- name: For mixed ';' and '\n'
file: tests/for/mixed.sh
- name: For no args no in
file: tests/for/no_args_no_in.sh
- name: Multiple for
file: tests/for/multiple.sh

4
tests/for/basic.sh Executable file
View File

@ -0,0 +1,4 @@
for i in salut test
do
echo $i
done

9
tests/for/for_in_for.sh Normal file
View File

@ -0,0 +1,9 @@
for i in salut mec
do
echo -n "$i "
for j in "bonjour" toi
do
echo -n $j
done
echo
done

View File

@ -0,0 +1,6 @@
a=bonjour
b="encore un mot"
for i in $a $b
do
echo $i
done

3
tests/for/mixed.sh Normal file
View File

@ -0,0 +1,3 @@
for i in salut toi je suis un for genial; do
echo $i
done

13
tests/for/multiple.sh Normal file
View File

@ -0,0 +1,13 @@
for a in arg1 arg2 arg3
do
echo $a
done
for a in a b c d e f g
do
echo $a
done
for g in in for while; do
echo $g
done

8
tests/for/no_args_no_in.sh Executable file
View File

@ -0,0 +1,8 @@
for salut
do
echo $salut
done
echo $?
echo $#
echo $@
echo $*

26
tests/functions.yml Normal file
View File

@ -0,0 +1,26 @@
- name: Basic
file: ./tests/functions/basic.sh
- name: Arguments
file: tests/functions/arguments.sh
- name: Funtion in function
file: tests/functions/function_in_function.sh
- name: Arguments hard
file: tests/functions/arguments_hard.sh
- name: Mutilple calls with simple args
file: tests/functions/multiple_calls.sh
- name: Recursive calls
file: tests/functions/recursive_call.sh
- name: Declaration in if
file: tests/functions/declaration_in_if.sh
- name: Use function in other command
file: tests/functions/use_fun_in_other_cmd.sh
- name: Variable in function
file: tests/functions/variable_in_fun.sh

View File

@ -0,0 +1,8 @@
foo()
{
echo $@
echo $#
echo $2
}
foo salut je suis un argument

View File

@ -0,0 +1,9 @@
foo()
{
echo $@
}
foo arguments de la fonction
echo $@
foo "d'autres" arguments
exit 4

6
tests/functions/basic.sh Normal file
View File

@ -0,0 +1,6 @@
foo()
{
echo "foo"
}
foo

View File

@ -0,0 +1,8 @@
if true
then
foo() {
echo 'I am in foo function'
}
fi
foo

View File

@ -0,0 +1,11 @@
foo()
{
bar()
{
echo "bar"
echo $@
}
}
foo salut je suis un argument
bar salut

View File

@ -0,0 +1,9 @@
foo()
{
echo "this is a test"
echo $@
}
foo salut
foo
foo foo foo; foo a

View File

@ -0,0 +1,11 @@
test()
{
if $@; then
test false
else
echo end
fi
}
test true
echo "did this work ?"

View File

@ -0,0 +1,6 @@
fun() {
echo "fun"
}
echo fun
echo; fun

View File

@ -0,0 +1,11 @@
a=12
fun()
{
a=42
echo "$a"
}
echo $a
fun
echo $a

67
tests/moulinette.py Normal file
View File

@ -0,0 +1,67 @@
import argparse
from pathlib import Path
from difflib import unified_diff
import subprocess as sp
import yaml
from dataclasses import dataclass
@dataclass
class TestCase:
def __init__(self, name, input = None, file = None):
self.name = name
self.input = input
self.file = file
def diff(expected: str, actual: str) -> str:
expected_lines = expected.splitlines(keepends=True)
actual_lines = actual.splitlines(keepends=True)
return ''.join(unified_diff(actual_lines, expected_lines, fromfile='actual', tofile='expected'))
def run_shell(shell: str, stdin: str) -> sp.CompletedProcess:
return sp.run([shell], input=stdin, capture_output=True, text=True, env={})
def perform_check(expected: sp.CompletedProcess, actual: sp.CompletedProcess):
assert expected.returncode == actual.returncode, f"EXIT_CODE_ERR: expected {expected.returncode}, got {actual.returncode}"
assert expected.stdout == actual.stdout, f"STDOUT_ERR\n{diff(expected.stdout, actual.stdout)}"
assert len(expected.stderr) == len(actual.stderr) or (len(expected.stderr) != 0 and len(actual.stderr) != 0), f"STDERR_EMPTY"
if __name__ == '__main__':
parser = argparse.ArgumentParser("bshTestSuite")
parser.add_argument('--binary', required=True, type=Path)
parser.add_argument('--tests', required=True, nargs='+', default=[])
args = parser.parse_args()
binary_path = args.binary.absolute()
tests=0
errors=0
for test in args.tests:
with open(test, 'r') as file:
testsuite = [TestCase(**testcase) for testcase in yaml.safe_load(file)]
print(f"\033[6m## \033[0m\033[34m{test}\033[97m")
for testcase in testsuite:
tests += 1
input = testcase.input
if (testcase.file):
with open(testcase.file, 'r') as file:
input = file.read()
actual = run_shell(binary_path, input)
expected = run_shell("dash", input)
try:
perform_check(expected, actual)
except AssertionError as e:
print(f"\033[1m[ \033[31mKO\033[97m ] \033[0m{testcase.name}\n{e}")
errors += 1
else:
print(f"\033[1m[ \033[92mOK\033[97m ] \033[0m{testcase.name}")
print("")
if (errors > 1):
s = 's'
else:
s = ''
print(f"\n\033[33m✗ \033[34m{tests}\033[97m tests performed, \033[31m{errors}\033[97m error{s}\033[0m")
exit(errors)

35
tests/pipes.yml Normal file
View File

@ -0,0 +1,35 @@
- name: basic
input: ls | grep Makefile
- name: double pipe
input: ls | grep Makefile | grep Makefile
- name: expand
input: a="Makefile"; ls | grep "$a"
- name: multiple file
input: ls -R src | grep src
- name: multiple file globing failed
input: ls -R src | grep *.c
- name: multiple file globing
input: ls -R src | grep .c
- name: pipe builtin pass
input: echo "test|pass" | grep pass
- name: pipe builtin pass
input: echo "test|pass" | grep failed
- name: 3 pipes
input: cat Makefile | grep "@" | grep "echo"
- name: most used
input: cat /root/.bash_history | cut -d' ' -f1 | sort | uniq -c | tr -s ' ' | cut -c 2- | sort -nr | head
- name: no builtins
input: ls | grep -E "*.c"
- name: mutltiple no builtins
input: ls | grep "s" | grep "bui"

1
tests/requirements.txt Normal file
View File

@ -0,0 +1 @@
pyyaml

Some files were not shown because too many files have changed in this diff Show More