Compare commits

..

No commits in common. "dev" and "lists" have entirely different histories.
dev ... lists

99 changed files with 400 additions and 8270 deletions

50
.gitignore vendored
View file

@ -1,7 +1,5 @@
# Project specific
42sh
testsuite
debug
# Prerequisites
*.d
@ -134,51 +132,3 @@ $RECYCLE.BIN/
*.msm
*.msp
*.lnk
# Autotools (since it disappeared for some reason)
Makefile.in
/ar-lib
/mdate-sh
/py-compile
/test-driver
/ylwrap
.deps/
.dirstamp
autom4te.cache
/autoscan.log
/autoscan-*.log
/aclocal.m4
/compile
/config.cache
/config.guess
/config.h.in
/config.log
/config.status
/config.sub
/configure
/configure.scan
/depcomp
/install-sh
/missing
/stamp-h1
/libtool
/ltmain.sh
.libs/
/texinfo.tex
m4/libtool.m4
m4/ltoptions.m4
m4/ltsugar.m4
m4/ltversion.m4
m4/lt~obsolete.m4
Makefile
*.svg
.venv/
*.py

View file

@ -1,59 +1,31 @@
# 42sh - A POSIX shell with a bad name
42sh is a project aiming to implement a POSIX-compliant shell written in C with only the standard library.
Source de is fully documented with the doxygen format so you can easily understand how the project works by exploring it.
> **Note** This is a school project, therefore it probably won't interest you if you are looking for something useful.
42sh is a shcool project aiming to implement a POSIX compliant shell in C.
## Getting started
TODO
### Build
run this command:
`autoreconf --force --verbose --install`
TODO
### Test
run this command:
`./configure CFLAGS='-std=c99 -Werror -Wall -Wextra -Wvla'`
then:
`make`
#### Build with ASan
run this command:
`./configure CFLAGS='-std=c99 -Werror -Wall -Wextra -Wvla -g -fsanitize=address'`
or for MacOS (Jean Here):
`./configure CFLAGS='-std=c99 -Werror -Wall -Wextra -Wvla -I/opt/homebrew/include' LDFLAGS='-L/opt/homebrew/lib'`
then:
`make check`
## Project status
### Implemented features
* **Command Execution:** `$PATH` search and binary execution (via `fork` and `execvp`) with error return code handling.
* **Built-ins:** Native implementation of `echo`, `cd`, `exit`, `export`, `unset`, `set`, `.`, `true`, `false`, as well as loop management with `break` and `continue`.
* **Control Structures:** * Conditions: `if / then / elif / else / fi`.
* Loops: `while`, `until` and `for`.
* **Logical Operators:** Command chaining with `&&`, `||` and negation with `!`.
* **Pipelines and Redirections:** * Full management of pipes `|` to connect the output of one process to the input of another.
* Single and multiple redirections: `>`, `<`, `>>`, `>&`, `<&`, `<>`.
* **Variables Management:** Assignment, variable expansion, and special variables handling like `$?` (return code of the last command).
* **Command Grouping:** Execution blocks `{ ... }` and subshells creation `( ... )`.
* **Quoting:** Support for weak (`"`) and strong (`'`) quoting for special characters escaping.
## Architecture
The shell operates on a classic compilation/interpretation pipeline:
1. **Lexer (Lexical Analysis):** Reads standard input (or script) character by character and generates a stream of "Tokens" (Words, Operators, Redirections).
2. **Parser (Syntax Analysis):** Syntax analyzer that transforms the token stream into a complex Abstract Syntax Tree (AST). This module strictly manages the nesting of control structures and enforces the rigid grammar of the Shell Command Language.
3. **Execution (AST Traversal):** The tree is traversed recursively. Redirections modify file descriptors (`dup2`), child processes are created (`fork`), and commands are executed.
TODO
## Authors
- Guillem George
- Matteo Flebus
- Jean Herail
- Jean Hérail
- William Valenduc
- Guillem George
## Project status
WIP
## TODO
# Autotools
implement functions in all .c files to see if everything compiles.

View file

@ -1,47 +0,0 @@
#!/usr/bin/env bash
# build_only.sh - Automates build for 42sh with pretty output (no tests)
set -e
# Colors
GREEN="\033[1;32m"
RED="\033[1;31m"
YELLOW="\033[1;33m"
RESET="\033[0m"
VERBOSE=0
if [[ "$1" == "--verbose" ]]; then
VERBOSE=1
fi
run_cmd() {
local desc="$1"
shift
if [[ $VERBOSE -eq 1 ]]; then
echo -e "${YELLOW}${desc}...${RESET}"
"$@"
else
echo -ne "${YELLOW}${desc}...${RESET}"
"$@" &> /dev/null && echo -e " ${GREEN}done${RESET}" || { echo -e " ${RED}failed${RESET}"; exit 1; }
fi
}
run_cmd "Running autoreconf" autoreconf --force --verbose --install
if [[ "$(uname)" == "Darwin" ]]; then
run_cmd "Configuring for MacOS" ./configure CFLAGS='-std=c99 -Werror -Wall -Wextra -Wvla -I/opt/homebrew/include' LDFLAGS='-L/opt/homebrew/lib'
else
run_cmd "Configuring for Linux" ./configure CFLAGS='-std=c99 -Werror -Wall -Wextra -Wvla -g -fsanitize=address'
fi
run_cmd "Cleaning build" make clean
if [[ $VERBOSE -eq 1 ]]; then
echo -e "${YELLOW}Building...${RESET}"
make
else
echo -ne "${YELLOW}Building...${RESET}"
echo
make
fi

View file

@ -1,46 +0,0 @@
#!/usr/bin/env bash
# build_and_test.sh - Automates build and test for 42sh with pretty output
set -e
# Colors
GREEN="\033[1;32m"
RED="\033[1;31m"
YELLOW="\033[1;33m"
RESET="\033[0m"
VERBOSE=0
if [[ "$1" == "--verbose" ]]; then
VERBOSE=1
fi
run_cmd() {
local desc="$1"
shift
if [[ $VERBOSE -eq 1 ]]; then
echo -e "${YELLOW}${desc}...${RESET}"
"$@"
else
echo -ne "${YELLOW}${desc}...${RESET}"
"$@" &> /dev/null && echo -e " ${GREEN}done${RESET}" || { echo -e " ${RED}failed${RESET}"; exit 1; }
fi
}
run_cmd "Running autoreconf" autoreconf --force --verbose --install
if [[ "$(uname)" == "Darwin" ]]; then
run_cmd "Configuring for MacOS" ./configure CFLAGS='-std=c99 -Werror -Wall -Wextra -Wvla -I/opt/homebrew/include' LDFLAGS='-L/opt/homebrew/lib'
else
run_cmd "Configuring for Linux" ./configure CFLAGS='-std=c99 -Werror -Wall -Wextra -Wvla -g -fsanitize=address'
fi
run_cmd "Cleaning build" make clean
if [[ $VERBOSE -eq 1 ]]; then
echo -e "${YELLOW}Running tests...${RESET}"
make check
else
echo -ne "${YELLOW}Running tests...${RESET}"
echo
make check
fi

View file

@ -1,9 +1,6 @@
# Init the 42sh project
AC_INIT([42sh], [1.0], [matteo.flebus@epita.fr])
# FLAGS="-std=c99 -pedantic -Werror -Wall -Wextra -Wvla"
# AC_SUBST([FLAGS])
# Setup Automake
AM_INIT_AUTOMAKE([subdir-objects] [foreign])
@ -26,15 +23,8 @@ AC_PROG_CC
AC_CONFIG_FILES([
Makefile
src/Makefile
src/ast/Makefile
src/parser/Makefile
src/lexer/Makefile
src/io_backend/Makefile
src/execution/Makefile
src/expansion/Makefile
src/utils/Makefile
])
# TODO add tests Makefile here
# tests/Makefile
# tests/unit/Makefile
# tests/unit/utils/Makefile
AC_OUTPUT

6
src/42sh.c Normal file
View file

@ -0,0 +1,6 @@
// all includes
int main(int argc, char **argv)
{
return 0;
}

View file

@ -1,62 +1,25 @@
# define the subdirectories
SUBDIRS = \
parser \
lexer \
io_backend \
execution \
expansion \
utils
ast \
parser \
lexer \
io_backend \
execution \
expansion
# + utils if needed
bin_PROGRAMS = 42sh
42sh_CFLAGS = -std=c99 -Werror -Wall -Wextra -Wvla
42sh_SOURCES = main.c
42sh_SOURCES = 42sh.c
42sh_CPPFLAGS = -I%D%
42sh_CFLAGS = -std=c99 -pedantic -Werror -Wall -Wextra -Wvla
42sh_LDADD = \
parser/libparser.a \
lexer/liblexer.a \
io_backend/libio_backend.a \
utils/libutils.a \
execution/libexecution.a \
expansion/libexpansion.a
# ================ TESTS ================
check:
../tests/wrap.sh # "$(COVERAGE)" "$(OUTPUT_FILE)"
# ------------- Unit tests --------------
check_PROGRAMS = testsuite
#testsuite_CFLAGS = $(42sh_CFLAGS)
#testsuite_CFLAGS += $(CRITERION_CFLAGS)
testsuite_SOURCES = ../tests/unit/utils/utils_tests.c \
../tests/unit/expansion/parse_var.c \
../tests/unit/utils/args.c \
../tests/unit/utils/hash_map.c \
../tests/unit/utils/insert_into.c
# ../tests/unit/lexer/lexer_tests.c
# ../tests/unit/expansion/expand.c
# ../tests/unit/io_backend/io_backend.c
testsuite_CPPFLAGS = $(42sh_CPPFLAGS)
testsuite_LDADD = $(42sh_LDADD) -lcriterion
# ------------- asan debug --------------
# check_PROGRAMS += debug
#debug_CFLAGS = $(42sh_CFLAGS)
#debug_CFLAGS += -fsanitize=address -g
# debug_SOURCES = $(42sh_SOURCES)
#
# debug_CPPFLAGS = $(42sh_CPPFLAGS)
#
# debug_LDADD = $(42sh_LDADD)
ast/libast.a \
parser/libparser.a \
lexer/liblexer.a \
io_backend/libio_backend.a \
expansion/libexpansion.a \
execution/libexecution.a

11
src/ast/Makefile.am Normal file
View file

@ -0,0 +1,11 @@
lib_LIBRARIES = libast.a
libast_a_SOURCES = \
ast.c \
ast.h
libast_a_CPPFLAGS = -I$(top_srcdir)/src
libast_a_CFLAGS = -std=c99 -pedantic -Werror -Wall -Wextra -Wvla
noinst_LIBRARIES = libast.a

0
src/ast/ast.c Normal file
View file

0
src/ast/ast.h Normal file
View file

View file

@ -2,10 +2,10 @@ lib_LIBRARIES = libexecution.a
libexecution_a_SOURCES = \
execution.c \
execution.h \
execution_helpers.c \
execution_helpers.h
execution.h
libexecution_a_CPPFLAGS = -I$(top_srcdir)/src
libexecution_a_CFLAGS = -std=c99 -pedantic -Werror -Wall -Wextra -Wvla
noinst_LIBRARIES = libexecution.a

View file

@ -1,73 +0,0 @@
#define _POSIX_C_SOURCE 200809L
#include "execution.h"
#include <fcntl.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "../expansion/expansion.h"
#include "../utils/hash_map/hash_map.h"
#include "../utils/vars/vars.h"
// Refactored: delegates to helpers in execution_helpers.c
#include "execution_helpers.h"
int execution(struct ast *ast, struct hash_map *vars)
{
if (!ast)
return 0;
int res;
switch (ast->type)
{
case AST_VOID:
case AST_END:
res = 0;
break;
case AST_CMD: {
struct ast_command *command = ast_get_command(ast);
if (!expand(command, vars))
fprintf(stderr, "Error: Variable expansion failed\n");
res = exec_ast_command(command, vars);
break;
}
case AST_IF:
res = exec_ast_if(ast_get_if(ast), vars);
break;
case AST_LIST:
res = exec_ast_list(ast_get_list(ast), vars);
break;
case AST_AND_OR:
res = exec_ast_and_or(ast_get_and_or(ast), vars);
break;
case AST_LOOP:
res = exec_ast_loop(ast_get_loop(ast), vars);
break;
case AST_SUBSHELL:
res = exec_ast_subshell(ast_get_subshell(ast), vars);
break;
default:
res = 127;
break;
}
if (res == EXEC_SIGNAL_EXIT)
{
char *exit_val_str = get_var(vars, "EXIT_VALUE");
if (exit_val_str == NULL)
{
fprintf(
stderr,
"Internal error: could not retrieve return value from exit\n");
return 2;
}
return atoi(exit_val_str);
}
else
{
return res;
}
}

View file

@ -1,16 +0,0 @@
#ifndef EXECUTION_H
#define EXECUTION_H
#include "../utils/ast/ast.h"
#include "../utils/hash_map/hash_map.h"
#include "../utils/lists/lists.h"
/**
* @brief Execute the AST
*
* @param ast Pointer to the AST structure
* @return int Execution status code of the last command
*/
int execution(struct ast *ast, struct hash_map *vars);
#endif /* ! EXECUTION_H */

View file

@ -1,597 +0,0 @@
#define _POSIX_C_SOURCE 200809L
#include "execution_helpers.h"
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#include "../utils/hash_map/hash_map.h"
#include "../utils/lists/lists.h"
#include "../utils/vars/vars.h"
#include "execution.h"
// === Static functions
static int open_redir_file(const struct ast_redir *redir, int *flags, int *mode)
{
*mode = 0644;
if (redir->type == AST_REDIR_TYPE_GREAT
|| redir->type == AST_REDIR_TYPE_CLOBBER)
{
*flags = O_WRONLY | O_CREAT | O_TRUNC;
return open(redir->filename, *flags, *mode);
}
else if (redir->type == AST_REDIR_TYPE_DGREAT)
{
*flags = O_WRONLY | O_CREAT | O_APPEND;
return open(redir->filename, *flags, *mode);
}
else if (redir->type == AST_REDIR_TYPE_LESS)
{
*flags = O_RDONLY;
return open(redir->filename, *flags);
}
return -3; // not a file open
}
static int set_all_redir(struct list *redir_list)
{
while (redir_list)
{
struct ast *redir_node = (struct ast *)redir_list->data;
struct ast_redir *redir = ast_get_redir(redir_node);
int target_fd;
if (redir->io_number != -1)
{
target_fd = redir->io_number;
}
else
{
if (redir->type == AST_REDIR_TYPE_LESS
|| redir->type == AST_REDIR_TYPE_LESSGREAT
|| redir->type == AST_REDIR_TYPE_LESSAND)
{
target_fd = 0;
}
else
{
target_fd = 1;
}
}
redir->saved_fd = dup(target_fd);
int new_fd = -1;
int flags = 0;
int mode = 0644;
if (redir->type == AST_REDIR_TYPE_GREAT
|| redir->type == AST_REDIR_TYPE_CLOBBER
|| redir->type == AST_REDIR_TYPE_DGREAT
|| redir->type == AST_REDIR_TYPE_LESS)
{
new_fd = open_redir_file(redir, &flags, &mode);
if (new_fd == -1)
{
perror("open");
return -1;
}
}
else if (redir->type == AST_REDIR_TYPE_GREATAND
|| redir->type == AST_REDIR_TYPE_LESSAND)
{
new_fd = atoi(redir->filename);
}
if (dup2(new_fd, target_fd) == -1)
{
perror("dup2");
if (new_fd != -1)
{
close(new_fd);
}
return -1;
}
if (new_fd != -1)
{
close(new_fd);
}
redir_list = redir_list->next;
}
return 0;
}
static char **list_to_argv(struct list *command_list)
{
size_t len = 0;
struct list *cur = command_list;
while (cur)
{
len++;
cur = cur->next;
}
char **argv = calloc(len + 1, sizeof(char *));
if (!argv)
return NULL;
cur = command_list;
for (size_t i = 0; i < len; i++)
{
argv[i] = (char *)cur->data;
cur = cur->next;
}
argv[len] = NULL;
return argv;
}
/*
* @brief parses string and returns the represented (unsigned) number
* @return the number contained by the string or -1 if number is invalid
*/
static int atou(char *str)
{
if (str == NULL || *str == '\0')
return -1;
int result = 0;
size_t i = 0;
while (str[i] != '\0')
{
if (str[i] < '0' || str[i] > '9')
return -1;
result *= 10;
result += str[i] - '0';
i++;
}
return result;
}
static int try_builtin(char **argv, struct hash_map *vars);
static int builtin_break(char **argv);
static int builtin_continue(char **argv);
static int builtin_unset(char **argv, struct hash_map *vars);
static int exec_assignment(struct list *assignment_list, struct hash_map *vars)
{
while (assignment_list != NULL)
{
if (!ast_is_assignment(assignment_list->data))
{
fprintf(stderr, "list of assignements contains something else");
return 1;
}
struct ast_assignment *assignment =
ast_get_assignment(assignment_list->data);
if (assignment->global)
{
setenv(assignment->name, assignment->value, 1);
}
set_var_copy(vars, assignment->name, assignment->value);
assignment_list = assignment_list->next;
}
return 0;
}
// === Functions
int exec_ast_command(struct ast_command *command, struct hash_map *vars)
{
exec_assignment(command->assignments, vars);
set_all_redir(command->redirections);
if (!command || !(command->command))
{
return 1;
}
char **argv = list_to_argv(command->command);
if (!argv || !(argv[0]))
{
free(argv);
unset_all_redir(command->redirections);
return 0;
}
int builtin_ret = try_builtin(argv, vars);
if (builtin_ret != -1)
{
free(argv);
unset_all_redir(command->redirections);
return builtin_ret;
}
pid_t pid = fork();
if (pid < 0)
{
perror("fork");
free(argv);
unset_all_redir(command->redirections);
return 1;
}
if (pid == 0)
{
execvp(argv[0], argv);
perror("execvp");
unset_all_redir(command->redirections);
_exit(127);
}
int status = 0;
waitpid(pid, &status, 0);
free(argv);
unset_all_redir(command->redirections);
if (WIFEXITED(status))
{
return WEXITSTATUS(status);
}
return 1;
}
int exec_ast_if(struct ast_if *if_node, struct hash_map *vars)
{
if (if_node == NULL)
return 2;
int cond = execution(if_node->condition, vars);
if (cond == EXEC_SIGNAL_BREAK || cond == EXEC_SIGNAL_CONTINUE
|| cond == EXEC_SIGNAL_EXIT)
return cond;
if (cond == 0)
{
int r = execution(if_node->then_clause, vars);
return r;
}
else
{
int r = execution(if_node->else_clause, vars);
return r;
}
}
int exec_ast_subshell(struct ast_subshell *subshell_node,
struct hash_map *vars)
{
pid_t pid = fork();
if (pid < 0)
{
perror("fork");
return 1;
}
if (pid == 0)
{
int res = execution(subshell_node->child, vars);
_exit(res);
}
int status = 0;
waitpid(pid, &status, 0);
if (WIFEXITED(status))
{
return WEXITSTATUS(status);
}
return 1;
}
int exec_ast_list(struct ast_list *list_node, struct hash_map *vars)
{
struct list *cur = list_node->children;
int ret = 0;
while (cur)
{
struct ast *child = (struct ast *)cur->data;
if (!ast_is_void(child))
{
int child_ret = execution(child, vars);
if (child_ret == EXEC_SIGNAL_BREAK
|| child_ret == EXEC_SIGNAL_CONTINUE
|| child_ret == EXEC_SIGNAL_EXIT)
return child_ret;
ret = child_ret;
}
cur = cur->next;
}
return ret;
}
int exec_ast_and_or(struct ast_and_or *ao_node, struct hash_map *vars)
{
int left_ret = execution(ao_node->left, vars);
if (left_ret == EXEC_SIGNAL_BREAK || left_ret == EXEC_SIGNAL_CONTINUE
|| left_ret == EXEC_SIGNAL_EXIT)
return left_ret;
if (ao_node->type == AST_AND_OR_TYPE_AND)
{
if (left_ret == 0)
{
int right_ret = execution(ao_node->right, vars);
return right_ret;
}
return left_ret;
}
else
{
if (left_ret != 0)
{
int right_ret = execution(ao_node->right, vars);
return right_ret;
}
return left_ret;
}
}
void unset_all_redir(struct list *redir_list)
{
while (redir_list)
{
struct ast *redir_node = (struct ast *)redir_list->data;
struct ast_redir *redir = ast_get_redir(redir_node);
int target_fd;
if (redir->io_number != -1)
{
target_fd = redir->io_number;
}
else
{
if (redir->type == AST_REDIR_TYPE_LESS
|| redir->type == AST_REDIR_TYPE_LESSGREAT
|| redir->type == AST_REDIR_TYPE_LESSAND)
{
target_fd = 0;
}
else
{
target_fd = 1;
}
}
if (redir->saved_fd != -1)
{
dup2(redir->saved_fd, target_fd);
close(redir->saved_fd);
redir->saved_fd = -1;
}
redir_list = redir_list->next;
}
}
int exec_ast_loop(struct ast_loop *loop_node, struct hash_map *vars)
{
int res = 0;
while (execution(loop_node->condition, vars) == 0)
{
res = execution(loop_node->body, vars);
if (res == EXEC_SIGNAL_BREAK)
{
res = 0;
break;
}
else if (res == EXEC_SIGNAL_CONTINUE)
{
res = 0;
continue;
}
}
return res;
}
// --- Builtins ---
static void print_with_escapes(const char *str)
{
while (*str)
{
if (*str == '\\')
{
str++;
if (*str == 'n')
putchar('\n');
else if (*str == 't')
putchar('\t');
else if (*str == '\\')
putchar('\\');
else if (*str == 'a')
putchar('\a');
else if (*str == 'b')
putchar('\b');
else if (*str == 'f')
putchar('\f');
else if (*str == 'r')
putchar('\r');
else if (*str == 'v')
putchar('\v');
else if (*str == 'c')
return; // stop printing
else
{
// unrecognized escape, print "\"" and the char
putchar('\\');
if (*str)
putchar(*str);
}
}
else
{
putchar(*str);
}
str++;
}
}
static int builtin_echo(char **argv)
{
bool newline = true;
bool interpret_escapes = false;
int i = 1;
// Parse options
while (argv[i] && argv[i][0] == '-')
{
char *opt = argv[i] + 1; // skip "-"
bool valid_option = false;
while (*opt)
{
if (*opt == 'n')
{
newline = false;
valid_option = true;
}
else if (*opt == 'e')
{
interpret_escapes = true;
valid_option = true;
}
else if (*opt == 'E')
{
interpret_escapes = false;
valid_option = true;
}
else
{
// invalid option so euh treat as regular argument
valid_option = false;
break;
}
opt++;
}
if (!valid_option)
break; // stop parsing options
i++;
}
// Print arguments
for (; argv[i]; i++)
{
if (interpret_escapes)
print_with_escapes(argv[i]);
else
printf("%s", argv[i]);
if (argv[i + 1])
printf(" ");
}
if (newline)
printf("\n");
fflush(stdout);
return 0;
}
static int builtin_break(char **argv)
{
(void)argv;
return EXEC_SIGNAL_BREAK;
}
static int builtin_continue(char **argv)
{
(void)argv;
return EXEC_SIGNAL_CONTINUE;
}
static int builtin_true(char **argv)
{
(void)argv;
return 0;
}
static int builtin_false(char **argv)
{
(void)argv;
return 1;
}
static int builtin_exit(char **argv, struct hash_map *vars)
{
int exit_val = 0;
if (argv[1])
{
exit_val = atou(argv[1]);
if (exit_val == -1)
{
fprintf(stderr, "exit: Illegal number %s\n", argv[1]);
return 2;
}
}
set_var_int(vars, "EXIT_VALUE", exit_val);
return EXEC_SIGNAL_EXIT;
}
static int builtin_cd(char **argv, struct hash_map *vars)
{
const char *path = argv[1];
if (!path)
{
path = getenv("HOME");
if (!path)
{
fprintf(stderr, "cd: HOME not set\n");
return 1;
}
}
// char *pwd = getcwd("", "");
char *pwd = get_var_or_env(vars, "PWD");
if (chdir(path) != 0)
{
perror("cd");
return 1;
}
set_var_copy(vars, "OLD_PWD", pwd);
set_var_copy(vars, "PWD", path);
return 0;
}
static int builtin_unset(char **argv, struct hash_map *vars)
{
if (!argv)
return 0;
for (int i = 1; argv[i]; i++)
{
const char *name = argv[i];
if (name == NULL || name[0] == '\0')
continue;
// remove from shell variables
hash_map_remove(vars, name);
// remove from environment variables
unsetenv(name);
}
return 0;
}
/**
* @brief Tries to execute a builtin command if the command matches a builtin
*
* @param argv Array of command arguments
* @return int Exit status of the builtin command, or -1 if not a builtin
*/
static int try_builtin(char **argv, struct hash_map *vars)
{
if (!argv || !argv[0])
return 0;
if (strcmp(argv[0], "echo") == 0)
return builtin_echo(argv);
if (strcmp(argv[0], "unset") == 0)
return builtin_unset(argv, vars);
if (strcmp(argv[0], "true") == 0)
return builtin_true(argv);
if (strcmp(argv[0], "false") == 0)
return builtin_false(argv);
if (strcmp(argv[0], "break") == 0)
return builtin_break(argv);
if (strcmp(argv[0], "continue") == 0)
return builtin_continue(argv);
if (strcmp(argv[0], "exit") == 0)
return builtin_exit(argv, vars);
if (strcmp(argv[0], "cd") == 0)
return builtin_cd(argv, vars);
return -1;
}

View file

@ -1,21 +0,0 @@
#ifndef EXECUTION_HELPERS_H
#define EXECUTION_HELPERS_H
#include "../utils/ast/ast.h"
#include "../utils/hash_map/hash_map.h"
// Special execution signals used internally to implement loop control
#define EXEC_SIGNAL_CONTINUE (-2)
#define EXEC_SIGNAL_BREAK (-3)
#define EXEC_SIGNAL_EXIT (-4)
int exec_ast_command(struct ast_command *command, struct hash_map *vars);
int exec_ast_if(struct ast_if *if_node, struct hash_map *vars);
int exec_ast_list(struct ast_list *list_node, struct hash_map *vars);
int exec_ast_and_or(struct ast_and_or *ao_node, struct hash_map *vars);
int exec_ast_loop(struct ast_loop *loop_node, struct hash_map *vars);
int exec_ast_subshell(struct ast_subshell *subshell_node,
struct hash_map *vars);
void unset_all_redir(struct list *redir_list);
#endif // EXECUTION_HELPERS_H

View file

@ -6,4 +6,6 @@ libexpansion_a_SOURCES = \
libexpansion_a_CPPFLAGS = -I$(top_srcdir)/src
libexpansion_a_CFLAGS = -std=c99 -pedantic -Werror -Wall -Wextra -Wvla
noinst_LIBRARIES = libexpansion.a

View file

@ -1,228 +0,0 @@
#define _POSIX_C_SOURCE 200809L
#include "expansion.h"
#include <ctype.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../utils/ast/ast.h"
#include "../utils/hash_map/hash_map.h"
#include "../utils/string_utils/string_utils.h"
#include "../utils/vars/vars.h"
static bool is_var_start_char(char c)
{
return isalpha(c) || c == '_';
}
static bool is_var_char(char c)
{
return isalnum(c) || c == '_';
}
static bool is_special_var_char(char c)
{
return c == '@' || c == '*' || c == '?' || c == '$' || isdigit(c)
|| c == '#';
}
size_t parse_var_name(char *str, char **res)
{
char *brace = NULL;
size_t i = 1; // skip the '$'
if (str[i] == '{' && str[i + 1] != 0 && str[i + 1] != '}')
{
if (is_special_var_char(str[i + 1]) && str[i + 2] == '}')
{
// Special variable like ${1}, ${?}
*res = strndup(str + i + 1, 1);
return 4; // length of ${X}
}
brace = str + i;
i++; // skip the '{'
}
else if (is_special_var_char(str[i]))
{
*res = strndup(str + i, 1);
return 2; // length of $X
}
if (!is_var_start_char(str[i]))
{
// Not a valid variable start
*res = NULL;
return 0;
}
while (1)
{
if (str[i] == '}' && *brace == '{')
{
*res = strndup(str + 2, i - 2);
return i + 1;
}
else if (!is_var_char(str[i]))
{
if (brace != NULL)
{
// Missing closing '}'
*res = NULL;
return 0;
}
break;
}
i++;
}
*res = strndup(str + 1, i - 1);
return i;
}
static bool expand_var(char **str, size_t pos, const struct hash_map *vars)
{
char *var_name = NULL;
size_t r = parse_var_name(*str + pos, &var_name);
if (r > 0 && var_name != NULL)
{
char *value;
char rnd_str[10]; // max 5 digits + null terminator
if (strcmp(var_name, "RANDOM") == 0)
{
short rnd = short_random();
snprintf(rnd_str, 10, "%d", rnd);
value = rnd_str;
}
else
{
value = get_var_or_env(vars, var_name);
if (value == NULL)
// Undefined variable: expand to empty string
value = "";
}
char *p = insert_into(*str, value, pos, r);
free(var_name);
if (p == NULL)
{
// error: insertion failed
return false;
}
*str = p;
return true;
}
return false;
}
size_t parse_subshell_str(char *str, char **res)
{
size_t i = 1; // skip the '('
int paren_count = 1;
if (str[i] == ')')
{
// empty subshell
*res = NULL;
return 0;
}
while (str[i] != 0)
{
if (str[i] == '(')
paren_count++;
else if (str[i] == ')')
{
paren_count--;
if (paren_count == 0)
{
*res = strndup(str + 1, i - 1);
return i + 1;
}
}
i++;
}
// error: parenthesis not closed
*res = NULL;
return 0;
}
bool expand(struct ast_command *command, const struct hash_map *vars)
{
if (command == NULL)
return false;
char *str;
size_t len;
enum quote_state quotes;
struct list *l = command->command;
while (l != NULL)
{
quotes = NO_QUOTE;
str = (char *)l->data;
len = strlen(str);
for (size_t i = 0; str[i] != 0; i++)
{
if (str[i] == '\'' || str[i] == '\"')
{
if (quotes == NO_QUOTE)
{
quotes = (str[i] == '\'') ? SINGLE_QUOTE : DOUBLE_QUOTE;
}
else if ((quotes == SINGLE_QUOTE && str[i] == '\'')
|| (quotes == DOUBLE_QUOTE && str[i] == '\"'))
{
quotes = NO_QUOTE;
}
else
{
// inside the other quote type, do nothing
continue;
}
// remove quote
memmove(str + i, str + i + 1, strlen(str + i + 1) + 1);
i--;
}
else if (quotes == SINGLE_QUOTE)
{
continue; // do nothing
}
else if (str[i] == '$' && str[i + 1] != 0 && !isspace(str[i + 1]))
{
// variable expansion
bool r = expand_var(&str, i, vars);
if (r == false || str == NULL)
return false;
i--; // -1 because loop will increment i
}
}
// if (quotes != NO_QUOTE)
// {
// // error: quote not closed
// fprintf(stderr, "Error: quote not closed in string: %s\n", str);
// return false;
// }
if (len != strlen(str))
{
char *new_str = realloc(str, strlen(str) + 1);
if (new_str == NULL)
// error: realloc fail
return false;
l->data = new_str;
}
l = l->next;
}
return true;
}

View file

@ -1,43 +0,0 @@
#ifndef EXPANSION_H
#define EXPANSION_H
#include <stdbool.h>
#include <stddef.h>
#include "../utils/ast/ast.h"
#include "../utils/hash_map/hash_map.h"
enum quote_state
{
NO_QUOTE,
SINGLE_QUOTE,
DOUBLE_QUOTE
};
/**
* Parse a variable from a string starting with '$'.
* @param str The input string starting with '$'. It must start with '$'.
* @param res Pointer to a char pointer that will be set to the extracted
* variable name.
* @return The number of characters processed in the input string.
*/
size_t parse_var_name(char *str, char **res);
/**
* Parse a subshell string enclosed in parentheses.
* @param str The input string starting with '('. It must start with '('.
* @param res Pointer to a char pointer that will be set to the extracted
* subshell string.
* @return The number of characters processed in the input string.
*/
size_t parse_subshell_str(char *str, char **res);
/**
* Expand variables in an AST command using the provided variable map.
* @param command The AST command to expand.
* @param vars The hash map containing variables.
* @return A new AST command with variables expanded, or NULL on error.
*/
bool expand(struct ast_command *command, const struct hash_map *vars);
#endif /* ! EXPANSION_H */

View file

@ -6,4 +6,6 @@ libio_backend_a_SOURCES = \
libio_backend_a_CPPFLAGS = -I$(top_srcdir)/src
libio_backend_a_CFLAGS = -std=c99 -pedantic -Werror -Wall -Wextra -Wvla
noinst_LIBRARIES = libio_backend.a

View file

@ -1,138 +1,34 @@
#define _POSIX_C_SOURCE 200809L
#include "io_backend.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// === Static variables
#include "../utils/args/args.h"
static struct iob_context context;
static FILE *input = NULL;
static char *stream_buf = NULL;
static size_t stream_buf_size = 0;
static enum iob_state state = IOB_STATE_NOT_INITIALIZED;
// === Functions
static FILE *input;
int iob_init(struct iob_context *ctx)
{
if (state != IOB_STATE_NOT_INITIALIZED)
return IOB_ERROR_MODULE_ALREADY_INITIALIZED;
context = *ctx;
switch (context.mode)
{
case IOB_MODE_STDIN:
IOB_MODE_STDIN:
input = stdin;
state = IOB_STATE_READY;
return 0;
case IOB_MODE_SCRIPT:
IOB_MODE_SCRIPT:
if (context.args == NULL)
return IOB_ERROR_BAD_ARG;
return -2;
input = fopen(context.args, "r");
if (input == NULL)
return IOB_ERROR_CANNOT_OPEN_FILE;
state = IOB_STATE_READY;
return 0;
return -4;
case IOB_MODE_CMD:
if (context.args == NULL)
return IOB_ERROR_BAD_ARG;
state = IOB_STATE_READY;
return 0;
IOB_MODE_CMD:
if (context.args != NULL)
return -2;
else
return 0;
default:
return IOB_ERROR_BAD_ARG;
return -1;
}
}
void iob_close(void)
{
fclose(input);
if ((context.mode == IOB_MODE_STDIN || context.mode == IOB_MODE_SCRIPT)
&& stream_buf != NULL)
{
free(stream_buf);
stream_buf_size = 0;
}
state = IOB_STATE_NOT_INITIALIZED;
}
ssize_t stream_read(char **stream)
{
// Check args
if (stream == NULL)
return IOB_ERROR_BAD_ARG;
// Check env
if (state == IOB_STATE_NOT_INITIALIZED)
return IOB_ERROR_MODULE_NOT_INITIALIZED;
if (state == IOB_STATE_FINISHED)
return 0;
if (state == IOB_STATE_ERROR)
return IOB_ERROR_GENERIC;
// Use input
if (context.mode == IOB_MODE_STDIN || context.mode == IOB_MODE_SCRIPT)
{
ssize_t nread = getline(&stream_buf, &stream_buf_size, input);
if (nread == -1)
{
state = IOB_STATE_FINISHED;
// MAGNIFICO
// malloc(1);
*stream_buf = EOF;
*stream = stream_buf;
return 1;
}
else if (nread < 0)
state = IOB_STATE_ERROR;
*stream = stream_buf;
return nread;
}
// Use args
else if (context.mode == IOB_MODE_CMD)
{
*stream = context.args;
size_t len = strlen(context.args);
context.args[len] = EOF;
return len + 1;
}
else
{
*stream = NULL;
return IOB_ERROR_GENERIC;
}
}
int iob_config_from_args(struct args_options *args, struct iob_context *ctx)
{
switch (args->type)
{
case INPUT_STDIN:
ctx->mode = IOB_MODE_STDIN;
ctx->args = NULL;
break;
case INPUT_FILE:
ctx->mode = IOB_MODE_SCRIPT;
ctx->args = (char *)args->input_source;
break;
case INPUT_CMD:
ctx->mode = IOB_MODE_CMD;
ctx->args = (char *)args->input_source;
break;
default:
return IOB_ERROR_GENERIC;
}
return 0;
}

View file

@ -3,71 +3,44 @@
#include <sys/types.h>
#include "../utils/args/args.h"
// Error codes
#define IOB_ERROR_GENERIC -1
#define IOB_ERROR_BAD_ARG -2
#define IOB_ERROR_MODULE_NOT_INITIALIZED -3
#define IOB_ERROR_MODULE_ALREADY_INITIALIZED -4
#define IOB_ERROR_CANNOT_OPEN_FILE -5
enum iob_mode
{
enum iob_mode {
IOB_MODE_NULL = 0,
IOB_MODE_STDIN,
IOB_MODE_SCRIPT,
IOB_MODE_CMD
};
enum iob_state
{
IOB_STATE_NOT_INITIALIZED,
IOB_STATE_READY,
IOB_STATE_FINISHED,
IOB_STATE_ERROR
};
/* @struct iob_context
* @var mode
* @var args contains
* the script name when mode is set to IOB_MODE_SCRIPT,
* the command to execute when mode is set to IOB_MODE_CMD
* the script name when mode is set to IOB_SCRIPT,
* the command to execute when mode is set to IOB_CMD,
*/
struct iob_context
{
struct iob_context {
enum iob_mode mode;
char *args;
char* args;
};
/**
* @brief Converts struct arg_options to iob_context
*
* @param args The arguments options struct
* @param ctx The IO Backend context struct to populate
* @return int 0 on success, negative value on error
*/
int iob_config_from_args(struct args_options *args, struct iob_context *ctx);
/*
* @brief Initializes the IO Backend module
*
*
* @param context contains the input mode and the args
* @return 0 on success, the corresponding error code otherwise
*/
int iob_init(struct iob_context *context);
/* @brief Closes the opened buffers and the module gracefully
*/
void iob_close(void);
/* @brief reads at most one line of the input and stores it into *stream
/* TODO
*
*
*
* @param stream is a pointer that will be set to a string to parse
* @return the number of read characters if positive,
* zero if finished (reached EOF),
* the error code otherwise
*/
ssize_t stream_read(char **stream);
void iob_close();
/*i TODO
*
*
*
*/
ssize_t stream_read(char** stream);
#endif /* ! IO_BACKEND_H */

View file

@ -2,8 +2,10 @@ lib_LIBRARIES = liblexer.a
liblexer_a_SOURCES = \
lexer.c \
lexer_utils.c
lexer.h
liblexer_a_CPPFLAGS = -I$(top_srcdir)/src
liblexer_a_CFLAGS = -std=c99 -pedantic -Werror -Wall -Wextra -Wvla
noinst_LIBRARIES = liblexer.a

View file

@ -1,218 +0,0 @@
#include "lexer.h"
#include <ctype.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../io_backend/io_backend.h"
#include "../utils/string_utils/string_utils.h"
#include "lexer_utils.h"
/* @brief: sets the ctx->current_token to [tok].
* this function is called by token_peek().
*/
static void update_current_token(struct token *tok, struct lexer_context *ctx)
{
ctx->current_token = tok;
}
/* @brief: frees the last token and sets it to [tok].
* Also sets ctx->current_token to NULL.
* this function is called by token_pop().
*/
static void update_previous_token(struct token *tok, struct lexer_context *ctx)
{
free_token(&ctx->previous_token);
ctx->previous_token = tok;
}
/* @brief: updates the current position in the stream.
* [stream] += [i]
* Also frees the last sent token, and sets it to ctx->current_token.
* Current token is then set to NULL.
* This function is called by token_pop().
*/
static void save_state(char *stream, ssize_t i, struct lexer_context *ctx)
{
ctx->remaining_chars -= i;
ctx->end_previous_token = stream + i;
update_previous_token(ctx->current_token, ctx);
update_current_token(NULL, ctx);
}
/*
* @brief: Updates the lexing_mode to LEXER_NORMAL
* if the SECOND quote is found at stream[i].
* Updates the lexing_mode to the corresponding quote type
* if the FIRST quote of any type is found.
*
* @return: true if an update was done. false otherwise.
*/
static bool update_lexing_mode(char *stream, ssize_t i,
enum lexing_mode *lexing_mode)
{
enum lexing_mode mode_before_update = *lexing_mode;
// FIRST quote
if (*lexing_mode == LEXER_NORMAL)
{
if (stream[i] == '"')
*lexing_mode = LEXER_DOUBLE_QUOTE;
if (stream[i] == '\'')
*lexing_mode = LEXER_QUOTE;
}
// SECOND quote
else
{
if (*lexing_mode == LEXER_QUOTE && stream[i] == '\'')
*lexing_mode = LEXER_NORMAL;
if (*lexing_mode == LEXER_DOUBLE_QUOTE && stream[i] == '"')
*lexing_mode = LEXER_NORMAL;
}
return *lexing_mode != mode_before_update;
}
/* @brief: updates the flags only_digits and has_equal.
* according to the character at stream[i].
*/
static void update_flags(char *stream, ssize_t i, struct token_info *info)
{
if (stream[i] == '=' && !info->has_equal)
{
if (i == 0)
{
perror("Syntax error: word start with a '='");
return;
}
else
info->has_equal = true;
}
else if (!isdigit(stream[i]) && info->only_digits)
{
info->only_digits = false;
}
}
struct token *peek_token(struct lexer_context *ctx)
{
stream_init(ctx);
// Usefull to know if we are inside a quote or double quote
enum lexing_mode lexing_mode = LEXER_NORMAL;
struct token_info info = { true, 0 };
char *stream = ctx->end_previous_token;
ssize_t i = 0;
// we already created the upcoming token during the previous call to peek()
if (ctx->current_token != NULL)
{
return ctx->current_token;
}
while (i < ctx->remaining_chars)
{
// true if we didn't encounter a quote of any type at stream[i]
// AND we are not inside quotes
if (!update_lexing_mode(stream, i, &lexing_mode)
&& lexing_mode == LEXER_NORMAL)
{
update_flags(stream, i, &info);
if (is_special_char(stream, i))
{
if (i == 0) // where we create spe_char token
i += len_op_sepchar(stream, i);
break;
}
if (isblank(stream[i]))
{
break;
}
}
else if (stream[i] == EOF)
{
fprintf(stderr, "Lexing error: unmatched quote\n");
// error handling
return NULL;
}
i++;
}
struct token *tok = new_token(stream, i, &info);
// if token is comment, we don't want it
if (tok->type == TOKEN_COMMENT)
{
// Find next newline or EOF.
go_end_of_line(ctx);
free_token(&tok);
tok = peek_token(ctx);
}
update_current_token(tok, ctx);
return tok;
}
struct token *pop_token(struct lexer_context *ctx)
{
stream_init(ctx);
// Usefull to know if we are inside a quote or double quote
enum lexing_mode lexing_mode = LEXER_NORMAL;
struct token_info info = { true, 0 };
char *stream = ctx->end_previous_token;
ssize_t i = 0;
if (ctx->current_token != NULL && ctx->current_token->type == TOKEN_EOF)
{
// we reached end of input, frees all the token still allocated.
free_token(&ctx->previous_token);
free_token(&ctx->current_token);
return NULL;
}
while (i < ctx->remaining_chars)
{
// true if we didn't encounter a quote of any type at stream[i]
// AND we are not inside quotes
if (!update_lexing_mode(stream, i, &lexing_mode)
&& lexing_mode == LEXER_NORMAL)
{
update_flags(stream, i, &info);
if (is_special_char(stream, i))
{
if (i == 0) // where we create spe_char token
i += len_op_sepchar(stream, i);
break;
}
if (isblank(stream[i]))
{
break;
}
}
else if (stream[i] == EOF)
{
fprintf(stderr, "Lexing error: unmatched quote\n");
// error handling
return NULL;
}
i++;
}
// just in case peek() was not called before poping.
// (this should never happen)
if (ctx->current_token == NULL)
{
ctx->current_token = new_token(stream, i, &info);
}
save_state(stream, i, ctx);
return ctx->previous_token;
}

View file

@ -1,37 +0,0 @@
#ifndef LEXER_H
#define LEXER_H
#include <sys/types.h>
#include "lexer_utils.h"
/*
* @brief returns true if token is a redir type, false otherwise
*/
bool is_token_redir(struct token *token);
/*
* @brief: returns the next (newly allocated) token without consuming it.
* if end of input is reached, returns a token of type TOKEN_EOF.
*/
struct token *peek_token(struct lexer_context *ctx);
/*
* @brief: returns the next (newly allocated) token and consumes it.
* if end of input is reached, returns a token of type TOKEN_EOF.
* It also frees the last token created if there was one.
*
* @warning: if the last returned token was a token EOF, it frees it
* and returns NULL. This means that after peeking a token EOF
* in the parser, there must be EXACTLY ONE call to pop_token().
*/
struct token *pop_token(struct lexer_context *ctx);
/* @note: maybe usefull for subshells.
*
* @warning: NOT IMPLEMENTED.
*/
struct token *get_token_str(void);
#endif /* ! LEXER_H */

View file

@ -1,351 +0,0 @@
#include "lexer_utils.h"
#include <stdlib.h>
#include <string.h>
#include "../io_backend/io_backend.h"
#include "../utils/string_utils/string_utils.h"
/* @brief: if a special character is found at [begin],
* [tok->token_type] is set accordingly
*/
static void set_token_spechar(struct token *tok, char *begin, ssize_t size)
{
if (size != 1)
return;
switch (begin[0])
{
case EOF:
tok->type = TOKEN_EOF;
break;
case ';':
tok->type = TOKEN_SEMICOLON;
break;
case '\n':
tok->type = TOKEN_NEWLINE;
break;
case '`':
tok->type = TOKEN_GRAVE;
break;
case '#':
tok->type = TOKEN_COMMENT;
break;
case '\\':
tok->type = TOKEN_BACKSLASH;
break;
case '(':
tok->type = TOKEN_LEFT_PAREN;
break;
case ')':
tok->type = TOKEN_RIGHT_PAREN;
break;
case '{':
tok->type = TOKEN_LEFT_BRACKET;
break;
case '}':
tok->type = TOKEN_RIGHT_BRACKET;
break;
case '*':
tok->type = TOKEN_STAR;
break;
default:
break;
}
}
/* @brief: if a keyword is found at [begin],
* [tok->token_type] is set accordingly
*/
static void set_token_keyword(struct token *tok, char *begin, ssize_t size)
{
if (tok->type != TOKEN_NULL || size == 0)
return;
if (strncmp(begin, "!", size) == 0 && size == 1)
tok->type = TOKEN_NEGATION;
else if (strncmp(begin, "if", size) == 0 && size == 2)
tok->type = TOKEN_IF;
else if (strncmp(begin, "fi", size) == 0 && size == 2)
tok->type = TOKEN_FI;
else if (strncmp(begin, "then", size) == 0 && size == 4)
tok->type = TOKEN_THEN;
else if (strncmp(begin, "else", size) == 0 && size == 4)
tok->type = TOKEN_ELSE;
else if (strncmp(begin, "elif", size) == 0 && size == 4)
tok->type = TOKEN_ELIF;
else if (strncmp(begin, "for", size) == 0 && size == 3)
tok->type = TOKEN_FOR;
else if (strncmp(begin, "while", size) == 0 && size == 5)
tok->type = TOKEN_WHILE;
else if (strncmp(begin, "until", size) == 0 && size == 5)
tok->type = TOKEN_UNTIL;
else if (strncmp(begin, "do", size) == 0 && size == 2)
tok->type = TOKEN_DO;
else if (strncmp(begin, "done", size) == 0 && size == 4)
tok->type = TOKEN_DONE;
else if (strncmp(begin, "export", size) == 0 && size == 6)
tok->type = TOKEN_EXPORT;
// no keywords found.
if (tok->type == TOKEN_NULL)
return;
tok->data = calloc(size + 1, sizeof(char));
if (tok->data == NULL)
{
perror("could not allocate memory in lexer");
return;
}
strncpy(tok->data, begin, size);
}
/* @brief: if an operator is found at [begin],
* [tok->token_type] is set accordingly
*/
static void set_token_operator(struct token *tok, char *begin, ssize_t size)
{
if (tok->type != TOKEN_NULL)
return;
if (strncmp(begin, "&&", size) == 0 && size == 2)
{
tok->type = TOKEN_AND;
}
else if (strncmp(begin, "||", size) == 0 && size == 2)
{
tok->type = TOKEN_OR;
}
else if (strncmp(begin, ">", size) == 0 && size == 1)
{
tok->type = TOKEN_REDIR_RIGHT;
}
else if (strncmp(begin, "<", size) == 0 && size == 1)
{
tok->type = TOKEN_REDIR_LEFT;
}
else if (strncmp(begin, ">>", size) == 0 && size == 2)
{
tok->type = TOKEN_REDIR_DOUBLE_RIGHT;
}
else if (strncmp(begin, ">&", size) == 0 && size == 2)
{
tok->type = TOKEN_REDIR_RIGHT_AMP;
}
else if (strncmp(begin, ">|", size) == 0 && size == 2)
{
tok->type = TOKEN_REDIR_RIGHT_PIPE;
}
else if (strncmp(begin, "<&", size) == 0 && size == 2)
{
tok->type = TOKEN_REDIR_LEFT_AMP;
}
else if (strncmp(begin, "<>", size) == 0 && size == 2)
{
tok->type = TOKEN_REDIR_LEFT_RIGHT;
}
else if (strncmp(begin, "|", size) == 0 && size == 1)
{
tok->type = TOKEN_PIPE;
}
}
/* @brief: if token_type has not yet been set, then it is a TOKEN_WORD
* Also allocates the data and fills it.
*/
static void set_token_word(struct token *tok, char *begin, ssize_t size)
{
if (tok->type == TOKEN_NULL && size != 0)
{
tok->type = TOKEN_WORD;
tok->data = calloc(size + 1, sizeof(char));
if (tok->data == NULL)
return;
strncpy(tok->data, begin, size);
}
}
/* @brief: Sets the token to an assignment_word
* Also allocates the data and fills it.
*/
static void set_token_assignment(struct token *tok, char *begin, ssize_t size)
{
if (tok->type == TOKEN_NULL && size != 0)
{
tok->type = TOKEN_ASSIGNMENT_WORD;
tok->data = calloc(size + 1, sizeof(char));
if (tok->data == NULL)
return;
strncpy(tok->data, begin, size);
}
}
/* @brief: Sets the token to an IO number
* Also allocates the data and fills it.
*/
static void set_token_ION(struct token *tok, char *begin, ssize_t size)
{
if (tok->type == TOKEN_NULL && size != 0)
{
tok->type = TOKEN_IONUMBER;
tok->data = calloc(size + 1, sizeof(char));
if (tok->data == NULL)
return;
strncpy(tok->data, begin, size);
}
}
/* @brief: check if [c] is a delimiter for end of line.
* @return: true if [c] == '\n' or EOF. false otherwise.
*/
static bool is_end_of_line(char c)
{
return c == EOF || c == '\n';
}
// === Functions
bool is_token_redir(struct token *token)
{
switch (token->type)
{
case TOKEN_REDIR_LEFT:
case TOKEN_REDIR_RIGHT:
case TOKEN_REDIR_LEFT_RIGHT:
case TOKEN_REDIR_DOUBLE_RIGHT:
case TOKEN_REDIR_LEFT_AMP:
case TOKEN_REDIR_RIGHT_AMP:
case TOKEN_REDIR_RIGHT_PIPE:
return true;
default:
return false;
}
}
bool is_special_char(char *stream, ssize_t i)
{
char c = stream[i];
if (c == EOF)
return true;
if (i > 0 && c == '#' && stream[i - 1] == '$')
return false; // the edge case of $#
if (i > 0 && stream[i - 1] == '\\')
return false; // TODO handle backslash better
// this doesnt work, ex : echo \\#comment
// (need to count the previous consequtive backslashes)
char special_chars[] = "\n'\"`;#|&(){}<>*";
return strchr(special_chars, c) != NULL;
}
struct token *new_token(char *begin, ssize_t size, struct token_info *info)
{
struct token *tok = calloc(1, sizeof(struct token));
if (tok == NULL)
return NULL;
if (info->only_digits)
set_token_ION(tok, begin, size);
else if (info->has_equal)
set_token_assignment(tok, begin, size);
else
{
set_token_keyword(tok, begin, size);
set_token_operator(tok, begin, size);
set_token_spechar(tok, begin, size);
set_token_word(tok, begin, size);
}
return tok;
}
void destroy_lexer_context(struct lexer_context *ctx)
{
struct token *prev = ctx->previous_token;
struct token *cur = ctx->current_token;
if (ctx == NULL)
return;
if (prev != NULL)
free_token(&prev);
if (cur != NULL)
free_token(&cur);
free(ctx);
}
void free_token(struct token **tok)
{
if (tok == NULL || *tok == NULL)
return;
if ((*tok)->data != NULL)
free((*tok)->data);
free(*tok);
*tok = NULL;
}
void stream_init(struct lexer_context *ctx)
{
char *stream;
if (ctx->remaining_chars == 0) // at the begining
{
ctx->remaining_chars = stream_read(&stream);
}
else
{
stream = ctx->end_previous_token;
}
char *trimed_stream = trim_blank_left(stream);
ctx->remaining_chars -= trimed_stream - stream;
ctx->end_previous_token = trimed_stream;
}
ssize_t len_op_sepchar(char *stream, ssize_t i)
{
if (!is_special_char(stream, i))
return -1; // should never happen
// OR
if (stream[i] == '|' && stream[i + 1] == '|')
return 2;
// AND
if (stream[i] == '&' && stream[i + 1] == '&')
return 2;
// special chars
if (stream[i] != '>' && stream[i] != '<')
return 1;
// REDIRS
if (stream[i] == '<')
{
if (stream[i + 1] == '&' || stream[i + 1] == '>')
return 2; // <&, <>
}
else if (stream[i + 1] == '>' || stream[i + 1] == '|'
|| stream[i + 1] == '&')
return 2; // >>, >&, >|
return 1; // >, <
}
void go_end_of_line(struct lexer_context *ctx)
{
if (ctx == NULL || ctx->end_previous_token == NULL)
return;
ssize_t i = 0;
while (!is_end_of_line(ctx->end_previous_token[i]))
{
i++;
}
ctx->end_previous_token += i;
ctx->remaining_chars -= i;
}
void get_next_stream(struct lexer_context *ctx)
{
ctx->remaining_chars = 0;
stream_init(ctx);
}

View file

@ -1,146 +0,0 @@
#ifndef LEXER_UTILS_H
#define LEXER_UTILS_H
#include <stdbool.h>
#include <stddef.h>
#include <sys/types.h>
struct lexer_context
{
char *end_previous_token;
ssize_t remaining_chars;
struct token *previous_token;
struct token *current_token;
};
/* @brief: frees all fields of ctx and sets ctx to NULL.
*/
void destroy_lexer_context(struct lexer_context *ctx);
enum lexing_mode
{
LEXER_NORMAL,
LEXER_QUOTE,
LEXER_DOUBLE_QUOTE
};
enum token_type
{
// Blanks
TOKEN_NULL = 0,
TOKEN_EOF,
TOKEN_NEWLINE,
// words
TOKEN_WORD,
TOKEN_ASSIGNMENT_WORD,
// Special characters
TOKEN_GRAVE,
TOKEN_SEMICOLON,
TOKEN_COMMENT,
TOKEN_STAR,
TOKEN_BACKSLASH,
TOKEN_DOLLAR,
TOKEN_LEFT_PAREN,
TOKEN_RIGHT_PAREN,
TOKEN_LEFT_BRACKET,
TOKEN_RIGHT_BRACKET,
TOKEN_PIPE,
TOKEN_NEGATION,
// Redirections
TOKEN_REDIR_LEFT,
TOKEN_REDIR_RIGHT,
TOKEN_REDIR_LEFT_RIGHT,
TOKEN_REDIR_DOUBLE_RIGHT,
TOKEN_REDIR_LEFT_AMP,
TOKEN_REDIR_RIGHT_AMP,
TOKEN_REDIR_RIGHT_PIPE,
TOKEN_IONUMBER,
// Keywords
TOKEN_IF,
TOKEN_THEN,
TOKEN_ELSE,
TOKEN_FI,
TOKEN_ELIF,
TOKEN_AND,
TOKEN_OR,
TOKEN_FOR,
TOKEN_WHILE,
TOKEN_UNTIL,
TOKEN_CASE,
TOKEN_EXPORT,
TOKEN_DO,
TOKEN_DONE
};
struct token
{
enum token_type type;
char *data;
};
// used to give info from lexing when creating a new token.
struct token_info
{
// usefull to detect IO numbers.
// tells us if we only lexed digits in current token.
bool only_digits;
// usefull to detect assignments, and syntax errors with '='.
bool has_equal;
};
/* @return: true if a special character from the grammar was found at stream[i],
* false otherwise.
*/
bool is_special_char(char *stream, ssize_t i);
/* @brief: return a newly allocated token, with the type corresponding
* to the info given in arguments.
* The data contains [size] char, starting from [begin].
*
* @return: NULL on error, a token otherwise.
*/
struct token *new_token(char *begin, ssize_t size, struct token_info *info);
/* @brief: frees the token given in argument
*/
void free_token(struct token **tok);
/*
* @brief: checks if the stream used for the last token creation is empty.
* If it is, it calls stream_read() from IO_backend,
* and sets [remaining_chars].
* If not, it starts from the end of the last token.
* Also trims left blanks before returning.
*
* @return: char* stream from which we tokenise.
*/
void stream_init(struct lexer_context *ctx);
/* @brief: finds the next '\n' or EOF character,
* starting at [ctx->end_previous_token],
* and updates the stream and remaining_chars accordingly.
*
* @note: Daft Punk. bang.
*/
void go_end_of_line(struct lexer_context *ctx);
/* @brief: this function is called when we found a special character
* in the stream. This can either be an operator (ig '>>' or '<&' etc),
* or a special char (ig '\' or '#' etc).
* @return: the length of the operator/special char found (can be 1, 2 or 3).
* -1 on error.
*/
ssize_t len_op_sepchar(char *stream, ssize_t i);
/* @brief: drops the current stream and asks IOB for a new one
*/
void get_next_stream(struct lexer_context *ctx);
#endif /* LEXER_UTILS_H */

View file

@ -1,71 +0,0 @@
// === Includes
#include <stdio.h>
#include <stdlib.h>
#include "execution/execution.h"
#include "io_backend/io_backend.h"
#include "lexer/lexer.h"
#include "parser/parser.h"
#include "utils/args/args.h"
#include "utils/main_loop/main_loop.h"
#include "utils/vars/vars.h"
int main(int argc, char **argv)
{
struct hash_map *vars = vars_init();
if (vars == NULL)
{
fprintf(stderr, "Error: Failed to initialize variables hash map\n");
return ERR_MALLOC;
}
// Create the options struct (with argument handler)
struct args_options options;
int return_code = args_handler(argc, argv, &options, vars);
if (return_code != 0)
{
print_usage(stderr, argv[0]);
return err_input(&vars, NULL);
}
// args_print(&options);
// Initialize variables hash map
// Create the IO-Backend context struct
struct iob_context io_context = { 0 };
// Convert args_options to iob_context
return_code = iob_config_from_args(&options, &io_context);
if (return_code != 0)
{
fprintf(stderr,
"Error: Failed to configure IO Backend from arguments\n");
return err_input(&vars, NULL);
}
// Init IO Backend (with the context struct)
return_code = iob_init(&io_context);
if (return_code != 0)
{
fprintf(stderr,
"Error: IO Backend initialization failed with code %d\n",
return_code);
return err_input(&vars, NULL);
}
// init lexer context
struct lexer_context *ctx = calloc(1, sizeof(struct lexer_context));
// init parser
if (!parser_init())
{
perror("parser initialization failed.");
return err_input(&vars, ctx);
}
return_code = main_loop(ctx, vars);
parser_close();
return return_code;
}

View file

@ -2,10 +2,10 @@ lib_LIBRARIES = libparser.a
libparser_a_SOURCES = \
parser.c \
grammar.c \
grammar_basic.c \
grammar_advanced.c
parser.h
libparser_a_CPPFLAGS = -I$(top_srcdir)/src
libparser_a_CFLAGS = -std=c99 -pedantic -Werror -Wall -Wextra -Wvla
noinst_LIBRARIES = libparser.a

View file

@ -1,299 +0,0 @@
// === Includes
#include "grammar.h"
#include <stdio.h>
#include <stdlib.h>
#include "grammar_basic.h"
// === Static variables
// rule-indexed array containing firsts
static struct firsts_list *firsts_map = NULL;
// === Static functions
/* @brief Add a token to a rule's firsts (in firsts_map)
*
* @arg rule the rule to which add a first
* @arg token the token to add to the rule's firsts
* @return true on success, false on error
*/
static bool add_first(enum rule rule, enum token_type token)
{
struct firsts_list *item = &firsts_map[rule];
if (item->tokens != NULL)
{
// Check for duplicates
for (size_t i = 0; i < item->list_length; i++)
{
if (item->tokens[i] == token)
return true;
}
// Append
item->list_length++;
item->tokens = realloc(item->tokens,
(item->list_length) * sizeof(enum token_type));
}
else
{
// Create entry
item->list_length = 1;
item->tokens = calloc(1, sizeof(enum token_type));
}
// Check for alloc error
if (item->tokens == NULL)
{
item->list_length = 0;
return false;
}
// Fill
item->tokens[item->list_length - 1] = token;
return true;
}
/* @brief Add a list of tokens to a rule's firsts (in firsts_map)
*
* @arg rule the rule to which add a first
* @arg tokens_list the list of tokens to add to the rule's firsts
* @return true on success, false on error
*/
static bool add_firsts(enum rule rule, struct firsts_list *tokens_list)
{
for (size_t i = 0; i < tokens_list->list_length; i++)
{
bool res = add_first(rule, tokens_list->tokens[i]);
if (!res)
return false;
}
return true;
}
/* @brief initializes the firsts_map static variable (does not populate it)
* @return true on success, false on error
*/
static bool init_firsts_map(void)
{
firsts_map = calloc(NUMBER_OF_RULES, sizeof(struct firsts_list));
if (firsts_map == NULL)
{
perror("Internal error: couldn't create the firsts_map (is your memory "
"full ?)");
return false;
}
return true;
}
static void add_first_redir(void)
{
add_first(RULE_REDIRECTION, TOKEN_IONUMBER);
add_first(RULE_REDIRECTION, TOKEN_REDIR_LEFT);
add_first(RULE_REDIRECTION, TOKEN_REDIR_RIGHT);
add_first(RULE_REDIRECTION, TOKEN_REDIR_LEFT_RIGHT);
add_first(RULE_REDIRECTION, TOKEN_REDIR_DOUBLE_RIGHT);
add_first(RULE_REDIRECTION, TOKEN_REDIR_LEFT_AMP);
add_first(RULE_REDIRECTION, TOKEN_REDIR_RIGHT_AMP);
add_first(RULE_REDIRECTION, TOKEN_REDIR_RIGHT_PIPE);
}
// Adds only direct tokens to rules firsts into the firsts map
static void add_firsts_tokens(void)
{
// Redirection
add_first_redir();
// %RIP Matteo 30/01/2026
// %RAX Guillem 30/01/2026 hehe
// If
add_first(RULE_IF, TOKEN_IF);
// Else clause
add_first(RULE_ELSE_CLAUSE, TOKEN_ELSE);
add_first(RULE_ELSE_CLAUSE, TOKEN_ELIF);
// For
add_first(RULE_FOR, TOKEN_FOR);
// While
add_first(RULE_WHILE, TOKEN_WHILE);
// Until
add_first(RULE_UNTIL, TOKEN_UNTIL);
// Case
add_first(RULE_CASE, TOKEN_CASE);
// Case item
add_first(RULE_CASE_ITEM, TOKEN_LEFT_PAREN);
add_first(RULE_CASE_ITEM, TOKEN_WORD);
// Shell command
add_first(RULE_SHELL_COMMAND, TOKEN_LEFT_BRACKET);
add_first(RULE_SHELL_COMMAND, TOKEN_LEFT_PAREN);
// Simple command
add_first(RULE_SIMPLE_COMMAND, TOKEN_WORD);
add_first(RULE_SIMPLE_COMMAND, TOKEN_EXPORT);
// Element
add_first(RULE_ELEMENT, TOKEN_WORD);
add_first(RULE_ELEMENT, TOKEN_ASSIGNMENT_WORD);
// Prefix
add_first(RULE_PREFIX, TOKEN_ASSIGNMENT_WORD);
// Pipeline
add_first(RULE_PIPELINE, TOKEN_WORD);
// Compound list
add_first(RULE_COMPOUND_LIST, TOKEN_NEWLINE);
// Input
add_first(RULE_INPUT, TOKEN_NEWLINE);
add_first(RULE_INPUT, TOKEN_EOF);
// Funcdec
add_first(RULE_FUNCDEC, TOKEN_WORD);
}
// Adds only firsts that depend on other rules to the firsts map
// WARNING order matters
static void add_firsts_rec(void)
{
// Case clause
add_firsts(RULE_CASE_CLAUSE, first(RULE_CASE_ITEM));
// Element
add_firsts(RULE_ELEMENT, first(RULE_REDIRECTION));
// Prefix
add_firsts(RULE_PREFIX, first(RULE_REDIRECTION));
// Shell command
add_firsts(RULE_SHELL_COMMAND, first(RULE_IF));
add_firsts(RULE_SHELL_COMMAND, first(RULE_FOR));
add_firsts(RULE_SHELL_COMMAND, first(RULE_WHILE));
add_firsts(RULE_SHELL_COMMAND, first(RULE_UNTIL));
add_firsts(RULE_SHELL_COMMAND, first(RULE_CASE));
// Simple command
add_firsts(RULE_SIMPLE_COMMAND, first(RULE_PREFIX));
// Command
add_firsts(RULE_COMMAND, first(RULE_SIMPLE_COMMAND));
add_firsts(RULE_COMMAND, first(RULE_SHELL_COMMAND));
add_firsts(RULE_COMMAND, first(RULE_FUNCDEC));
// Pipeline
add_firsts(RULE_PIPELINE, first(RULE_COMMAND));
// And Or
add_firsts(RULE_AND_OR, first(RULE_PIPELINE));
// Compound list
add_firsts(RULE_COMPOUND_LIST, first(RULE_AND_OR));
// List
add_firsts(RULE_LIST, first(RULE_AND_OR));
// Input
add_firsts(RULE_INPUT, first(RULE_LIST));
}
// === Functions
int grammar_init(void)
{
// Initialize the firsts map
bool success = init_firsts_map();
if (success != true)
return false;
// Populate the firsts map
add_firsts_tokens();
add_firsts_rec();
return true;
}
void grammar_close(void)
{
// Deep free firsts map
for (int i = 0; i < NUMBER_OF_RULES; i++)
{
if (firsts_map[i].tokens != NULL)
{
free(firsts_map[i].tokens);
}
}
free(firsts_map);
firsts_map = NULL;
}
struct firsts_list *first(enum rule rule)
{
if (firsts_map == NULL || firsts_map[rule].tokens == NULL)
{
perror("Internal error: attempted to get the firsts of a rule without "
"properly initializing the firsts map");
return NULL;
}
return &firsts_map[rule];
}
bool is_first(struct token token, enum rule rule)
{
struct firsts_list *firsts = &firsts_map[rule];
for (size_t i = 0; i < firsts->list_length; i++)
{
if (firsts->tokens[i] == token.type)
return true;
}
return false;
}
struct ast *parse_input(struct lexer_context *ctx)
{
struct token *token = PEEK_TOKEN();
if (token->type == TOKEN_EOF)
{
POP_TOKEN();
return ast_create_end();
}
if (token->type == TOKEN_NEWLINE)
{
POP_TOKEN();
return ast_create_list(NULL);
}
struct ast *ast = parse_list(ctx);
token = PEEK_TOKEN();
if (ast == NULL)
{
if (token != NULL && token->type == TOKEN_EOF)
{
POP_TOKEN();
}
return NULL;
}
if (token->type == TOKEN_NEWLINE || token->type == TOKEN_EOF)
{
POP_TOKEN();
return ast;
}
perror("Syntax error: expected newline or EOF after list");
ast_free(&ast);
return NULL;
}

View file

@ -1,103 +0,0 @@
#ifndef GRAMMAR_H
#define GRAMMAR_H
#include <stdbool.h>
#include "../lexer/lexer.h"
// === Macros
#define PEEK_TOKEN() \
peek_token(ctx); \
if (token == NULL) \
{ \
perror("Internal error: cannot get the following token"); \
return NULL; \
}
#define POP_TOKEN() \
pop_token(ctx); \
if (token == NULL) \
{ \
perror("Internal error: cannot get the following token"); \
return NULL; \
}
// === Structures
enum rule
{
RULE_NULL = 0,
RULE_INPUT,
RULE_LIST,
RULE_AND_OR,
RULE_PIPELINE,
RULE_COMMAND,
RULE_SIMPLE_COMMAND,
RULE_SHELL_COMMAND,
RULE_IF,
RULE_COMPOUND_LIST,
RULE_ELSE_CLAUSE,
RULE_ELEMENT,
RULE_REDIRECTION,
RULE_PREFIX,
RULE_FUNCDEC,
RULE_WHILE,
RULE_UNTIL,
RULE_FOR,
RULE_CASE,
RULE_CASE_CLAUSE,
RULE_CASE_ITEM,
NUMBER_OF_RULES
};
struct firsts_list
{
enum token_type *tokens; // Heap allocated array
size_t list_length;
};
// === Functions
/*
* @brief Initializes the grammar submodule
* @return PARSER_INIT_SUCCESS on success PARSER_INIT_ERROR on error
* @warning Do not use outside the parser
*/
int grammar_init(void);
/*
* @brief Closes the grammar submodule
* @warning Do not use outside the parser
*/
void grammar_close(void);
/*
* @brief get the first accepted tokens of a rule
*
* @arg r the rule
* @return the accepted tokens as a firsts_list struct
*/
struct firsts_list *first(enum rule r);
/*
* @brief tells is token belong to the firsts of a specific rule
*
* @arg token
* @arg rule
* @return true if token belongs to rule's firsts, false otherwise
*/
bool is_first(struct token token, enum rule rule);
/* @brief Acts as the entry point of the parser, calls parse_list
*
* @code input = list '\n'
* | list EOF
* | '\n'
* | EOF
* ;
* @first first(list), '\n', EOF
*/
struct ast *parse_input(struct lexer_context *ctx);
#endif /* ! GRAMMAR_H */

View file

@ -1,240 +0,0 @@
#define _POSIX_C_SOURCE 200809L
#include "grammar_advanced.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "grammar.h"
#include "grammar_basic.h"
// === Static functions
static enum ast_redir_type redir_tok_to_ast_type(enum token_type tok_type)
{
switch (tok_type)
{
case TOKEN_REDIR_LEFT:
return AST_REDIR_TYPE_LESS;
case TOKEN_REDIR_RIGHT:
return AST_REDIR_TYPE_GREAT;
case TOKEN_REDIR_DOUBLE_RIGHT:
return AST_REDIR_TYPE_DGREAT;
case TOKEN_REDIR_LEFT_RIGHT:
return AST_REDIR_TYPE_LESSGREAT;
case TOKEN_REDIR_LEFT_AMP:
return AST_REDIR_TYPE_LESSAND;
case TOKEN_REDIR_RIGHT_AMP:
return AST_REDIR_TYPE_GREATAND;
case TOKEN_REDIR_RIGHT_PIPE:
return AST_REDIR_TYPE_CLOBBER;
default:
return AST_REDIR_TYPE_NULL;
}
}
/*
* @brief parses a while/until loop starting with the condition
* (after the while/until keyword)
* @arg negate_condition Set to true for until loops, false for while loops
*/
static struct ast *parse_loop(struct lexer_context *ctx, bool negate_condition)
{
// condition
struct ast *condition = parse_compound_list(ctx);
if (condition == NULL)
return NULL;
if (negate_condition)
{
condition =
ast_create_neg(true, condition); // TODO check result (beware to not
// exceed the function lines limit)
}
struct token *token = PEEK_TOKEN();
// 'do'
if (token->type != TOKEN_DO)
{
ast_free(&condition);
perror("Syntax error: expected the 'do' keyowrd but got a different "
"token");
return NULL;
}
POP_TOKEN();
token = PEEK_TOKEN();
// body
struct ast *body = parse_compound_list(ctx);
if (body == NULL)
{
ast_free(&condition);
return NULL;
}
token = PEEK_TOKEN();
// 'done'
if (token->type != TOKEN_DONE)
{
ast_free(&condition);
perror("Syntax error: expected the 'done' keyowrd but got a different "
"token");
return NULL;
}
POP_TOKEN();
struct ast *result = ast_create_loop(condition, body);
if (result == NULL)
{
ast_free(&condition);
ast_free(&body);
perror("Internal error: could not create ast node (is your memory full "
"?)");
return NULL;
}
return result;
}
// === Functions
struct ast *parse_redirection(struct lexer_context *ctx)
{
struct token *token = PEEK_TOKEN();
int io_number = -1;
if (token->type == TOKEN_IONUMBER)
{
io_number = atoi(token->data);
POP_TOKEN();
token = PEEK_TOKEN();
}
if (!is_token_redir(token))
{
perror("Syntax error: expected a redirection token but got something "
"else");
return NULL;
}
enum ast_redir_type redir_type = redir_tok_to_ast_type(token->type);
POP_TOKEN();
token = PEEK_TOKEN();
if (token->type != TOKEN_WORD)
{
perror("Syntax error: expected a word after redirection");
return NULL;
}
char *target = strdup(token->data);
POP_TOKEN();
return ast_create_redir(target, io_number, redir_type);
}
struct ast *parse_prefix(struct lexer_context *ctx)
{
struct token *token = PEEK_TOKEN();
if (token->type == TOKEN_ASSIGNMENT_WORD)
{
token = POP_TOKEN();
return ast_create_assignment(token->data, false);
}
else if (is_first(*token, RULE_REDIRECTION))
return parse_redirection(ctx);
else
{
perror("Syntax error: expected a prefix (redirection or assignment)");
return NULL;
}
}
struct ast *parse_funcdec(struct lexer_context *ctx)
{
struct token *token = PEEK_TOKEN();
struct ast *value = NULL;
char *func_name = NULL;
if (token->type != TOKEN_WORD)
{
return NULL;
}
// word -> func name
POP_TOKEN();
func_name = strdup(token->data);
// (
token = PEEK_TOKEN();
if (token->type != TOKEN_LEFT_PAREN)
{
free(func_name);
return NULL;
}
POP_TOKEN();
// )
token = PEEK_TOKEN();
if (token->type != TOKEN_RIGHT_PAREN)
{
free(func_name);
return NULL;
}
POP_TOKEN();
token = PEEK_TOKEN();
// { \n }
while (token->type == TOKEN_NEWLINE)
{
POP_TOKEN();
token = PEEK_TOKEN();
}
// shell_command -> value
value = parse_shell_command(ctx);
if (value == NULL)
{
free(func_name);
return NULL;
}
return ast_create_function(func_name, value);
}
struct ast *parse_for(struct lexer_context *ctx)
{
(void)ctx;
perror("Error: usage of a not implemented function (parse_for)");
return NULL;
}
struct ast *parse_while(struct lexer_context *ctx)
{
struct token *token = PEEK_TOKEN();
// 'while'
if (token->type != TOKEN_WHILE)
{
perror(
"Internal error: expected a TOKEN_WHILE but got a different type");
return NULL;
}
POP_TOKEN();
return parse_loop(ctx, false);
}
struct ast *parse_until(struct lexer_context *ctx)
{
struct token *token = PEEK_TOKEN();
// 'until'
if (token->type != TOKEN_UNTIL)
{
perror(
"Internal error: expected a TOKEN_UNTIL but got a different type");
return NULL;
}
POP_TOKEN();
return parse_loop(ctx, true);
}

View file

@ -1,68 +0,0 @@
#ifndef GRAMMAR_ADVANCED_H
#define GRAMMAR_ADVANCED_H
#include "grammar.h"
// === Functions
/*
* @brief parses a redirection rule
*
* @code redirection = [IONUMBER] ( '>' | '<' | '>>' | '>&' | '<&' | '>|' | '<>'
* ) WORD ;
*
* @first TOKEN_IONUMBER, TOKEN_REDIRECTION
*/
struct ast *parse_redirection(struct lexer_context *ctx);
/*
* @brief parses a prefix rule
*
* @code prefix = redirection ;
*
* @first first(redirection)
*/
struct ast *parse_prefix(struct lexer_context *ctx);
/*
* @brief parses a funcdec rule
* @warning Work in progress
*
* @code funcdec = WORD '(' ')' {'\n'} shell_command ;
*
* @first WORD
*/
struct ast *parse_funcdec(struct lexer_context *ctx);
/*
* @brief parses a for rule
*
* @code rule_for = 'for' WORD
* ( [';'] | [ {'\n'} 'in' { WORD } ( ';' | '\n' ) ] )
* {'\n'} 'do' compound_list 'done' ;
*
* @first TOKEN_FOR
*/
struct ast *parse_for(struct lexer_context *ctx);
/*
* @brief parses a while rule
* @warning NOT IMPLEMENTED
*
* @code rule_while = 'while' compound_list 'do' compound_list 'done' ;
*
* @first TOKEN_WHILE
*/
struct ast *parse_while(struct lexer_context *ctx);
/*
* @brief parses an until rule
* @warning NOT IMPLEMENTED
*
* @code rule_until = 'until' compound_list 'do' compound_list 'done' ;
*
* @first TOKEN_UNTIL
*/
struct ast *parse_until(struct lexer_context *ctx);
#endif /* ! GRAMMAR_ADVANCED_H */

View file

@ -1,627 +0,0 @@
#define _POSIX_C_SOURCE 200809L
#include "grammar_basic.h"
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "../utils/lists/lists.h"
#include "grammar.h"
#include "grammar_advanced.h"
// === Static functions
static enum ast_and_or_type and_or_tok_to_ast(enum token_type tok_type)
{
switch (tok_type)
{
case TOKEN_AND:
return AST_AND_OR_TYPE_AND;
case TOKEN_OR:
return AST_AND_OR_TYPE_OR;
default:
fprintf(stderr, "and_or impossible to init, wrong token type");
return AST_AND_OR_NULL;
}
}
/* @brief: frees all the arguments. (helper func)
* @return: NULL.
*/
static void *err_if_rule(struct ast **cond, struct ast **then_clause,
struct ast **else_clause)
{
ast_free(cond);
ast_free(then_clause);
ast_free(else_clause);
return NULL;
}
/* @brief: frees command_elements and redirections lists (helper func)
* @return: NULL
*/
static void *err_s_com(struct list *command_elements, struct list *redirections,
struct list *assignments)
{
list_deep_destroy(command_elements);
list_deep_destroy(redirections);
list_deep_destroy(assignments);
return NULL;
}
/* @brief: used when export keyword is found, and expects an assignment after.
* @return: an ast_assignment with the field [global] set to true.
*/
static struct ast *parse_export(struct lexer_context *ctx)
{
struct token *token = PEEK_TOKEN();
if (token->type != TOKEN_EXPORT)
{
fprintf(stderr, "expected the export keyword in parse_export");
return NULL;
}
// export
POP_TOKEN();
token = PEEK_TOKEN();
if (token->type != TOKEN_ASSIGNMENT_WORD)
{
fprintf(stderr, "in parser: export must be followed by 'x=y'");
return NULL;
}
// assignment
POP_TOKEN();
return ast_create_assignment(token->data, true);
}
// === Functions
struct ast *parse_list(struct lexer_context *ctx)
{
struct list *result_list = NULL;
struct ast *current_node = NULL;
struct token *token = PEEK_TOKEN();
// and_or
current_node = parse_and_or(ctx);
if (current_node == NULL)
return NULL;
result_list = list_append(result_list, current_node);
// Following and_or commands
token = PEEK_TOKEN();
while (token->type == TOKEN_SEMICOLON)
{
// Forward
POP_TOKEN();
token = PEEK_TOKEN();
// TODO seems a little akward (not fully compliant with the grammar)
// but it's time consuming to rewrite to only cover edge cases.
// So it'll probably stay like that for now
if (is_first(*token, RULE_AND_OR))
{
current_node = parse_and_or(ctx);
if (current_node == NULL)
{
struct ast *tmp = ast_create_list(result_list);
ast_free(&tmp);
return NULL;
}
result_list = list_append(result_list, current_node);
token = PEEK_TOKEN();
}
}
return ast_create_list(result_list);
}
struct ast *parse_and_or(struct lexer_context *ctx)
{
struct ast *result = parse_pipeline(ctx);
if (result == NULL)
return NULL;
struct token *token = PEEK_TOKEN();
while (token->type == TOKEN_AND || token->type == TOKEN_OR)
{
// Build AST (left part)
enum ast_and_or_type type = and_or_tok_to_ast(token->type);
struct ast *left = result;
POP_TOKEN();
token = PEEK_TOKEN();
// Skip newlines
while (token->type == TOKEN_NEWLINE)
{
token = POP_TOKEN();
token = PEEK_TOKEN();
}
// Right part
struct ast *right = parse_pipeline(ctx);
if (right == NULL)
{
ast_free(&left);
return NULL;
}
token = PEEK_TOKEN();
result = ast_create_and_or(left, right, type);
if (result == NULL)
{
ast_free(&left);
ast_free(&right);
return NULL;
}
}
return result;
}
struct ast *parse_pipeline(struct lexer_context *ctx)
{
bool negation = false;
struct token *token = PEEK_TOKEN();
// Eventual '!'
if (token->type == TOKEN_NEGATION)
{
negation = true;
POP_TOKEN();
token = PEEK_TOKEN();
}
// command rule
struct ast *left = parse_command(ctx);
token = PEEK_TOKEN();
if (negation)
{
left = ast_create_neg(negation, left);
}
// Pipes
while (token->type == TOKEN_PIPE)
{
POP_TOKEN();
token = PEEK_TOKEN();
// skip newlines
while (token->type == TOKEN_NEWLINE)
{
POP_TOKEN();
token = PEEK_TOKEN();
}
// command rule
struct ast *right = parse_command(ctx);
token = PEEK_TOKEN();
// Create AST
left = ast_create_pipe(left, right);
}
return left;
}
struct ast *parse_command(struct lexer_context *ctx)
{
struct token *token = PEEK_TOKEN();
struct ast *result = NULL;
if (is_first(*token, RULE_SIMPLE_COMMAND))
{
result = parse_simple_command(ctx);
}
else if (is_first(*token, RULE_SHELL_COMMAND))
{
result = parse_shell_command(ctx);
}
else if (is_first(*token, RULE_FUNCDEC))
{
result = parse_funcdec(ctx);
}
else
{
perror("Syntax error: unexpected token");
return NULL;
}
return result;
}
struct ast *parse_simple_command(struct lexer_context *ctx)
{
struct list *command_elements = NULL;
struct list *redirections = NULL; // list of redirection ASTs
struct list *assignments = NULL;
bool has_prefix = false;
struct token *token = PEEK_TOKEN();
if (is_first(*token, RULE_PREFIX))
{
has_prefix = true;
while (is_first(*token, RULE_PREFIX))
{
struct ast *prefix = parse_prefix(ctx);
if (prefix == NULL)
{
return err_s_com(command_elements, redirections, assignments);
}
if (prefix->type == AST_ASSIGNMENT)
{
assignments = list_append(assignments, prefix);
}
else if (prefix->type == AST_REDIR)
{
redirections = list_append(redirections, prefix);
}
token = PEEK_TOKEN();
}
}
if (token->type != TOKEN_WORD)
{
if (!has_prefix && token->type != TOKEN_EXPORT)
{
perror("Expected a command but got a different token type");
return err_s_com(command_elements, redirections, assignments);
}
if (token->type == TOKEN_EXPORT)
{
struct ast *assignment_export = parse_export(ctx);
if (assignment_export == NULL)
return err_s_com(command_elements, redirections, assignments);
assignments = list_append(assignments, assignment_export);
}
}
else // TOKEN WORD
{
char *command = strdup(token->data);
command_elements = list_append(command_elements, command);
POP_TOKEN();
}
token = PEEK_TOKEN();
// Eventual elements
while (is_first(*token, RULE_ELEMENT))
{
// Get element
struct ast *element = parse_element(ctx);
if (element == NULL)
{
return err_s_com(command_elements, redirections, assignments);
}
// Get element type
if (ast_is_word(element))
{
// Extract word
struct ast_word *element_word = ast_get_word(element);
char *word = element_word->word;
element_word->word = NULL; // Prevents word to be freed
ast_free(&element);
command_elements = list_append(command_elements, word);
}
else if (ast_is_redir(element))
{
// append redirections to the list of redirections
redirections = list_append(redirections, element);
}
else
{
perror("Internal error: unexpected return value from "
"parse_element in parse_simple_command");
return err_s_com(command_elements, redirections, assignments);
}
// Forward
token = PEEK_TOKEN();
}
struct ast *result =
ast_create_command(command_elements, redirections, assignments);
if (result == NULL)
{
return err_s_com(command_elements, redirections, assignments);
}
return result;
}
struct ast *parse_element(struct lexer_context *ctx)
{
struct token *token = PEEK_TOKEN();
if (token->type == TOKEN_WORD || token->type == TOKEN_ASSIGNMENT_WORD)
{
POP_TOKEN();
struct ast *result = ast_create_word(token->data);
if (result == NULL)
{
perror("Internal error: could not create ast node (is your memory "
"full ?)");
return NULL;
}
return result;
}
else if (token->type == TOKEN_IONUMBER || is_token_redir(token))
{
return parse_redirection(ctx);
}
else if (token->type == TOKEN_EXPORT)
{
return parse_export(ctx);
}
else
{
perror("Syntax error: unexpected token at parse_element");
return NULL;
}
}
struct ast *parse_shell_command(struct lexer_context *ctx)
{
struct token *token = PEEK_TOKEN();
struct ast *result = NULL;
// '{'
if (token->type == TOKEN_LEFT_BRACKET)
{
POP_TOKEN();
result = parse_compound_list(ctx);
if (result == NULL)
return NULL;
// '}'
token = PEEK_TOKEN();
if (token->type == TOKEN_LEFT_BRACKET)
{
ast_free(&result);
perror("Syntax error: bracket mismatch");
return NULL;
}
POP_TOKEN();
return result;
}
// '('
else if (token->type == TOKEN_LEFT_PAREN)
{
POP_TOKEN();
result = parse_compound_list(ctx);
if (result == NULL)
return NULL;
// ')'
token = PEEK_TOKEN();
if (token->type == TOKEN_LEFT_PAREN)
{
ast_free(&result);
perror("Syntax error: parenthesis mismatch");
return NULL;
}
POP_TOKEN();
return ast_create_subshell(result);
}
else if (is_first(*token, RULE_IF))
{
return parse_if_rule(ctx);
}
else if (is_first(*token, RULE_WHILE))
{
return parse_while(ctx);
}
else if (is_first(*token, RULE_UNTIL))
{
return parse_until(ctx);
}
// TODO for and case
else
{
perror("Syntax error: unexpected token in parse_shell_command");
return NULL;
}
}
struct ast *parse_if_rule(struct lexer_context *ctx)
{
// If keyword
struct token *token = POP_TOKEN();
if (token->type != TOKEN_IF)
{
perror("Internal error: expected a if rule but token has different "
"type");
return NULL;
}
// Condition content
struct ast *condition_content = parse_compound_list(ctx);
if (condition_content == NULL)
return NULL;
token = PEEK_TOKEN();
// Then keyword
if (token->type != TOKEN_THEN)
{
perror("Syntax error: Expected the 'then' keyword but token has "
"different type");
return err_if_rule(&condition_content, NULL, NULL);
}
POP_TOKEN();
// Then content
struct ast *then_content = parse_compound_list(ctx);
if (then_content == NULL)
{
return err_if_rule(&condition_content, &then_content, NULL);
}
token = PEEK_TOKEN();
struct ast *else_content = NULL;
// Eventual else/elif clause(s)
if (is_first(*token, RULE_ELSE_CLAUSE))
{
else_content = parse_else_clause(ctx);
if (else_content == NULL)
{
return err_if_rule(&condition_content, &then_content, NULL);
}
token = PEEK_TOKEN();
}
// Fi keyword
if (token->type != TOKEN_FI)
{
perror("Expected the 'fi' keyword but token has different type");
return err_if_rule(&condition_content, &then_content, &else_content);
}
POP_TOKEN();
// Result
struct ast *result =
ast_create_if(condition_content, then_content, else_content);
if (result == NULL)
{
perror("Internal error: could not create a new AST (AST_IF)");
return err_if_rule(&condition_content, &then_content, &else_content);
}
return result;
}
struct ast *parse_compound_list(struct lexer_context *ctx)
{
struct list *result_list = NULL; // ast* list
struct ast *current_cmd = NULL;
struct token *token = PEEK_TOKEN();
// Skip newlines
while (token->type == TOKEN_NEWLINE)
{
POP_TOKEN();
token = PEEK_TOKEN();
}
// And/or
current_cmd = parse_and_or(ctx);
if (current_cmd == NULL)
return NULL;
result_list = list_append(result_list, current_cmd);
token = PEEK_TOKEN();
// Following commands
while (token->type == TOKEN_SEMICOLON || token->type == TOKEN_NEWLINE)
{
POP_TOKEN();
token = PEEK_TOKEN();
// Skip newlines
while (token->type == TOKEN_NEWLINE)
{
POP_TOKEN();
token = PEEK_TOKEN();
}
// And/or
if (is_first(*token, RULE_AND_OR))
{
current_cmd = parse_and_or(ctx);
if (current_cmd == NULL)
return NULL;
result_list = list_append(result_list, current_cmd);
token = PEEK_TOKEN();
}
}
// Eventual semicolon
if (token->type == TOKEN_SEMICOLON)
{
POP_TOKEN();
token = PEEK_TOKEN();
}
// Skip newlines
while (token->type == TOKEN_NEWLINE)
{
POP_TOKEN();
token = PEEK_TOKEN();
}
struct ast *result = ast_create_list(result_list);
return result;
}
struct ast *parse_else_clause(struct lexer_context *ctx)
{
struct token *token = PEEK_TOKEN();
// Eventual elif content
while (token->type == TOKEN_ELIF)
{
// Condition
token = POP_TOKEN();
struct ast *condition = parse_compound_list(ctx);
// Then keyword
token = POP_TOKEN();
if (token->type != TOKEN_THEN)
{
perror("Expected the 'then' keyword but got a different token "
"type");
return NULL;
}
struct ast *then_content = parse_compound_list(ctx);
if (then_content == NULL)
{
ast_free(&condition);
return NULL;
}
token = PEEK_TOKEN();
// Eventual else clause (recursive)
struct ast *else_content = NULL;
if (token->type == TOKEN_ELSE || token->type == TOKEN_ELIF)
{
else_content = parse_else_clause(ctx);
if (else_content == NULL)
{
ast_free(&then_content);
ast_free(&condition);
return NULL;
}
}
else
{
else_content = ast_create_void();
}
return ast_create_if(condition, then_content, else_content);
}
// Eventual else content
struct ast *result = NULL;
if (token->type == TOKEN_ELSE)
{
token = POP_TOKEN();
result = parse_compound_list(ctx);
if (result == NULL)
return NULL;
}
else
{
result = ast_create_void();
}
return result;
}

View file

@ -1,116 +0,0 @@
#ifndef GRAMMAR_BASIC_H
#define GRAMMAR_BASIC_H
#include "../lexer/lexer.h"
#include "../utils/ast/ast.h"
// === Functions
/*
* @brief parses a list of [and_or] rules separated by semicolons and that
* ends by a newline
*
* @code list = and_or { ';' and_or } [ ';' ] ;
*
* @first first(and_or)
*/
struct ast *parse_list(struct lexer_context *ctx);
/*
* @brief Only parses a pipeline rule for the moment
*
* @code and_or = pipeline { ( '&&' | '||' ) {'\n'} pipeline } ;
*
* @first first(pipeline)
*/
struct ast *parse_and_or(struct lexer_context *ctx);
/*
* @brief Only parses a command rule for the moment
*
* @code pipeline = ['!'] command { '|' {'\n'} command } ;
*
* @first '!', first(command)
*/
struct ast *parse_pipeline(struct lexer_context *ctx);
/*
* @brief Parses a simple command rule or a shell command rule depending on
* the first token.
* @note
* TOKEN_WORD => simple_command
* TOKEN_IF => shell_command
*
* @code command = simple_command
* | shell_command
*
* ;
* @first first(simple_command), first(shell_command)
*/
struct ast *parse_command(struct lexer_context *ctx);
/*
* @brief Parses a simple list of words (command and arguments)
* ending by a separator
*
* @code simple_command = WORD { element } ;
*
* @first WORD
*/
struct ast *parse_simple_command(struct lexer_context *ctx);
/*
* @brief Parses an element rule
*
* @code element = WORD
* | redirection
* ;
*
* @first WORD, first(redirection)
*/
struct ast *parse_element(struct lexer_context *ctx);
/*
* @brief Only parses if rules for the moment
*
* @code shell_command = '{' compound_list '}'
* | '(' compound_list ')'
* | if_rule
* ;
*
* @first first(if_rule)
*/
struct ast *parse_shell_command(struct lexer_context *ctx);
/*
* @brief Parses a if rule (condition, then-clause, elif-clause, else-clause)
*
* @code if_rule = 'if' compound_list 'then' compound_list [else_clause] 'fi' ;
*
* @first TOKEN_IF
*/
struct ast *parse_if_rule(struct lexer_context *ctx);
/*
* @brief parses commands inside if/else clauses and returns the corresponding
* AST list
*
* @code compound_list = {'\n'} and_or { ( ';' | '\n' ) {'\n'} and_or } [';']
* {'\n'} ;
*
* @first TOKEN_NEWLINE, first(and_or)
*/
struct ast *parse_compound_list(struct lexer_context *ctx);
/*
* @brief parses an else clause rule (inside if)
*
* @code else_clause = 'else' compound_list
* | 'elif' compound_list 'then' compound_list [else_clause]
* ;
*
* @first TOKEN_ELSE, TOKEN_ELIF
*/
struct ast *parse_else_clause(struct lexer_context *ctx);
#endif /* ! GRAMMAR_BASIC_H */

View file

@ -1,67 +0,0 @@
#include "parser.h"
#include <stdio.h>
#include "grammar.h"
// === Static variables
static enum parser_state state = PARSER_STATE_NOT_INITIALIZED;
// === Functions
int parser_init(void)
{
if (state == PARSER_STATE_READY)
{
perror("Internal error: tried to initialize the parser module twice.");
return false;
}
int success = grammar_init();
if (success == false)
return false;
state = PARSER_STATE_READY;
return true;
}
void parser_close(void)
{
if (state != PARSER_STATE_READY)
{
perror("trying to close parser which was not opened");
}
state = PARSER_STATE_CLOSED;
// TODO close grammar
}
struct ast *get_ast(struct lexer_context *ctx)
{
if (ctx == NULL)
{
perror("Internal error: called parser with no lexer context (NULL "
"pointer). Aborting.");
return NULL;
}
if (state == PARSER_STATE_NOT_INITIALIZED)
{
perror("Internal error: attempted to call parser without initializing "
"it. Aborting.");
return NULL;
}
if (state == PARSER_STATE_CLOSED)
{
perror("Internal error: attempted to call parser after closing it. "
"Aborting.");
return NULL;
}
return parse_input(ctx);
}
// TODO
struct ast *get_ast_str(char *command)
{
(void)command;
return NULL;
}

View file

@ -1,45 +0,0 @@
#ifndef PARSER_H
#define PARSER_H
#include <stdbool.h>
#include "../lexer/lexer.h"
#include "../utils/ast/ast.h"
enum parser_state
{
PARSER_STATE_NOT_INITIALIZED = 0,
PARSER_STATE_READY,
PARSER_STATE_CLOSED
};
/* @brief Initializes the parser module
* @warning parser needs to be closed after use with parser_close()
*
* @return Returns false on error and true on success
*/
int parser_init(void);
/* @brief Closes the parser module after use
*/
void parser_close(void);
/* @brief Builds the AST representation of the next command to execute.
*
* @return Returns the AST representation of the next command to execute.
* If there is no command left to execute, retuns an AST_END node.
*
* @warning NOT IMPLEMENTED
*/
struct ast *get_ast(struct lexer_context *ctx);
/* @brief Builds the AST representation of the given command string.
*
* @return Returns the AST representation of the given command string.
* Returns an AST_END node if the given command is empty.
*
* @warning NOT IMPLEMENTED
*/
struct ast *get_ast_str(char *command);
#endif /* ! PARSER_H */

View file

@ -1,30 +0,0 @@
lib_LIBRARIES = libutils.a
libutils_a_SOURCES = \
lists/lists1.c \
lists/lists2.c \
lists/lists3.c \
hash_map/hash_map.c \
string_utils/string_utils.c \
ast/ast.c \
ast/ast_if.c \
ast/ast_command.c \
ast/ast_list.c \
ast/ast_and_or.c \
ast/ast_redir.c \
ast/ast_void.c \
ast/ast_end.c \
ast/ast_word.c \
ast/ast_neg.c \
ast/ast_pipe.c \
ast/ast_loop.c \
args/args.c \
vars/vars.c \
main_loop/main_loop.c \
ast/ast_assignment.c \
ast/ast_subshell.c \
ast/ast_function.c
libutils_a_CPPFLAGS = -I$(top_srcdir)/src
noinst_LIBRARIES = libutils.a

View file

@ -1,155 +0,0 @@
#define _POSIX_C_SOURCE 200809L
#include "./args.h"
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../lists/lists.h"
#include "../string_utils/string_utils.h"
#include "../vars/vars.h"
static void strlen_acc(void *acc, void *data)
{
size_t *len = (size_t *)acc;
char *str = (char *)data;
*len += strlen(str);
}
static char *concat_list_str(struct list *list)
{
char *res = NULL;
size_t total_len = list_length(list) + 1; // + spaces + null terminator
list_fold(list, &total_len, strlen_acc);
res = malloc(total_len);
if (res != NULL)
{
res[0] = 0;
for (struct list *it = list; it != NULL; it = it->next)
{
strcat(res, (char *)it->data);
if (it->next != NULL)
strcat(res, " ");
}
res[total_len - 1] = 0;
}
return res;
}
static void args_in_var(struct hash_map *vars, struct list *args_list)
{
int arg_index = 1;
char index_str[11];
for (struct list *it = args_list; it != NULL; it = it->next)
{
int_to_str(arg_index++, index_str);
set_var_copy(vars, index_str, (char *)it->data);
}
char *concated_args = concat_list_str(args_list);
set_var_copy(vars, "*", concated_args);
char *key = strdup("@");
set_var(vars, key, concated_args, NULL);
// key and concated_args consumed by hash_map
int_to_str(arg_index - 1, index_str);
set_var_copy(vars, "#", index_str);
}
int args_handler(int argc, char **argv, struct args_options *options,
struct hash_map *vars)
{
options->type = INPUT_UNDEFINED;
options->input_source = NULL;
// options->pretty_print = false;
options->verbose = false;
struct list *args_list = NULL;
set_var_copy(vars, "0", argv[0]);
for (int i = 1; i < argc; i++)
{
/* if (strcmp(argv[i], "--pretty-print") == 0)
{
options->pretty_print = true;
} */
if (strcmp(argv[i], "--verbose") == 0)
{
options->verbose = true;
}
else if (strcmp(argv[i], "-c") == 0)
{
if (options->type != INPUT_UNDEFINED)
{
fprintf(stderr, "Multiple input sources specified: %s\n",
argv[i + 1]);
return 1;
}
else if (i + 1 >= argc)
{
fprintf(stderr, "No command provided after -c\n");
return 1;
}
options->type = INPUT_CMD;
options->input_source = argv[i + 1];
i++;
}
else if (argv[i][0] == '-')
{
fprintf(stderr, "Unknown option: %s\n", argv[i]);
return 1;
}
else if (options->type == INPUT_UNDEFINED)
{
options->type = INPUT_FILE;
options->input_source = argv[i];
}
else
{
// All remaining arguments are treated as additional arguments
args_list = list_append(args_list, argv[i]);
continue;
}
}
args_in_var(vars, args_list);
list_destroy(&args_list);
if (options->type == INPUT_UNDEFINED)
options->type = INPUT_STDIN;
return 0;
}
void args_print(struct args_options *options)
{
printf("Input type: %s\n",
options->type == INPUT_CMD ? "COMMAND"
: options->type == INPUT_FILE ? "FILE"
: options->type == INPUT_STDIN ? "STDIN"
: "UNDEFINED");
printf("Input source: %s\n",
options->input_source ? options->input_source : "NULL");
// printf("Pretty print: %s\n", options->pretty_print ? "true" : "false");
printf("Verbose: %s\n", options->verbose ? "true" : "false");
}
void print_usage(FILE *std, const char *program_name)
{
fprintf(std, "Usage: %s [OPTIONS] [SCRIPT] [ARGUMENTS...]\n", program_name);
fprintf(std, "Options:\n");
fprintf(std, " -c [SCRIPT] Execute the given command string.\n");
// fprintf(std, " --pretty-print Enable pretty printing of
// outputs.\n");
fprintf(std, " --verbose Enable verbose mode.\n");
fprintf(std,
"If no SCRIPT is provided, input is read from standard input.\n");
}

View file

@ -1,52 +0,0 @@
#ifndef ARGS_H
#define ARGS_H
#include <stdbool.h>
#include <stdio.h>
#include "../hash_map/hash_map.h"
enum input_type
{
INPUT_UNDEFINED,
INPUT_FILE,
INPUT_CMD,
INPUT_STDIN
};
struct args_options
{
/** Source of the input, filename or command string depending on type, NULL
* if INPUT_STDIN */
const char *input_source;
/** Type of the input source */
enum input_type type;
/** Enable or disable pretty printing of outputs */
// bool pretty_print;
/** Enable or disable verbose mode */
bool verbose;
};
/**
* Handles command-line arguments and populates the args_options structure.
* @param argc The argument count.
* @param argv The argument vector.
* @param options Pointer to args_options structure to be populated.
* @param vars Pointer to the variables hash map.
* @return 0 on success, non-zero on failure.
*/
int args_handler(int argc, char **argv, struct args_options *options,
struct hash_map *vars);
/** Prints the parsed arguments for debugging purposes.
* @param options Pointer to args_options structure containing parsed options.
*/
void args_print(struct args_options *options);
/** Prints the usage information for the program.
* @param std The output stream to print to (e.g., stdout or stderr).
* @param program_name The name of the program.
*/
void print_usage(FILE *std, const char *program_name);
#endif /* ARGS_H */

View file

@ -1,174 +0,0 @@
#define _POSIX_C_SOURCE 200809L
#include "ast.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
void ast_free(struct ast **node)
{
if (node == NULL || *node == NULL)
{
fprintf(stderr,
"WARNING: Internal error: failed to free AST node (NULL argument)");
return;
}
switch ((*node)->type)
{
case AST_IF:
ast_free_if(ast_get_if(*node));
break;
case AST_CMD:
ast_free_command(ast_get_command(*node));
break;
case AST_LIST:
ast_free_list(ast_get_list(*node));
break;
case AST_AND_OR:
ast_free_and_or(ast_get_and_or(*node));
break;
case AST_REDIR:
ast_free_redir(ast_get_redir(*node));
break;
case AST_PIPE:
ast_free_pipe(ast_get_pipe(*node));
break;
case AST_WORD:
ast_free_word(ast_get_word(*node));
break;
case AST_ASSIGNMENT:
ast_free_assignment(ast_get_assignment(*node));
break;
case AST_NEG:
ast_free_neg(ast_get_neg(*node));
break;
case AST_LOOP:
ast_free_loop(ast_get_loop(*node));
break;
case AST_FUNCTION:
ast_free_function(ast_get_function(*node));
break;
case AST_SUBSHELL:
ast_free_subshell(ast_get_subshell(*node));
break;
case AST_VOID:
case AST_END:
break;
default:
fprintf(stderr, "WARNING: Internal error:"
" failed to free an AST node (Unknown type)");
return;
}
free(*node);
*node = NULL;
}
struct ast *ast_create(enum ast_type type, void *data)
{
struct ast *node = malloc(sizeof(struct ast));
if (!node)
return NULL;
node->type = type;
node->data = data;
return node;
}
/* // TODO handle new types (AST_WORD, AST_PIPE, etc.)
static void ast_print_dot_recursive(struct ast *node, FILE *out)
{
if (!node)
return;
switch (node->type)
{
case AST_LIST: {
struct ast_list *ast_list = ast_get_list(node);
fprintf(out, " node%p [label=\"LIST\"];\n", (void *)node);
struct list *elt = ast_list->children;
while (elt != NULL)
{
struct ast *child = (struct ast *)elt->data;
fprintf(out, " node%p -> node%p;\n", (void *)node, (void *)child);
ast_print_dot_recursive(child, out);
elt = elt->next;
}
break;
}
case AST_IF: {
struct ast_if *if_data = ast_get_if(node);
fprintf(out, " node%p [label=\"IF\"];\n", (void *)node);
if (if_data->condition)
{
fprintf(out, " node%p -> node%p;\n", (void *)node,
(void *)if_data->condition);
fprintf(out,
" node%p [fillcolor=\"lightyellow\", style=\"filled\"];\n",
(void *)if_data->condition);
ast_print_dot_recursive(if_data->condition, out);
if (if_data->then_clause)
{
fprintf(out, " node%p -> node%p [label=\"true\"];\n",
(void *)if_data->condition,
(void *)if_data->then_clause);
ast_print_dot_recursive(if_data->then_clause, out);
}
if (if_data->else_clause)
{
fprintf(out, " node%p -> node%p [label=\"false\"];\n",
(void *)if_data->condition,
(void *)if_data->else_clause);
ast_print_dot_recursive(if_data->else_clause, out);
}
}
break;
}
case AST_CMD: {
struct ast_command *command_data = ast_get_command(node);
fprintf(out, " node%p [label=\"", (void *)node);
struct list *l = command_data->command;
while (l)
{
fprintf(out, "%s", (char *)l->data);
if (l->next)
fprintf(out, " ");
l = l->next;
}
fprintf(out, "\"];\n");
break;
}
case AST_END:
fprintf(out, " node%p [label=\"END\"];\n", (void *)node);
break;
default:
break;
}
}
void ast_print_dot(struct ast *ast)
{
if (!ast)
return;
FILE *dot_pipe = popen("dot -Tsvg -o ast.svg", "w");
if (!dot_pipe)
{
return;
}
fprintf(dot_pipe, "digraph AST {\n");
ast_print_dot_recursive(ast, dot_pipe);
fprintf(dot_pipe, "}\n");
pclose(dot_pipe);
}
*/

View file

@ -1,20 +0,0 @@
#ifndef AST_H
#define AST_H
#include "ast_and_or.h"
#include "ast_assignment.h"
#include "ast_base.h"
#include "ast_command.h"
#include "ast_end.h"
#include "ast_function.h"
#include "ast_if.h"
#include "ast_list.h"
#include "ast_loop.h"
#include "ast_neg.h"
#include "ast_pipe.h"
#include "ast_redir.h"
#include "ast_void.h"
#include "ast_word.h"
#include "ast_subshell.h"
#endif /* ! AST_H */

View file

@ -1,37 +0,0 @@
#include "ast_and_or.h"
#include <stdlib.h>
bool ast_is_and_or(struct ast *node)
{
return node != NULL && node->type == AST_AND_OR;
}
struct ast_and_or *ast_get_and_or(struct ast *node)
{
if (ast_is_and_or(node))
return (struct ast_and_or *)node->data;
return NULL;
}
struct ast *ast_create_and_or(struct ast *left, struct ast *right,
enum ast_and_or_type type)
{
struct ast_and_or *and_or = malloc(sizeof(struct ast_and_or));
if (!and_or)
return NULL;
and_or->left = left;
and_or->right = right;
and_or->type = type;
return ast_create(AST_AND_OR, and_or);
}
void ast_free_and_or(struct ast_and_or *and_or)
{
if (!and_or)
return;
ast_free(&and_or->left);
ast_free(&and_or->right);
free(and_or);
}

View file

@ -1,26 +0,0 @@
#ifndef AST_AND_OR_H
#define AST_AND_OR_H
#include "ast_base.h"
enum ast_and_or_type
{
AST_AND_OR_NULL,
AST_AND_OR_TYPE_AND,
AST_AND_OR_TYPE_OR
};
struct ast_and_or
{
struct ast *left;
struct ast *right;
enum ast_and_or_type type;
};
bool ast_is_and_or(struct ast *node);
struct ast_and_or *ast_get_and_or(struct ast *node);
struct ast *ast_create_and_or(struct ast *left, struct ast *right,
enum ast_and_or_type type);
void ast_free_and_or(struct ast_and_or *and_or);
#endif /* ! AST_AND_OR_H */

View file

@ -1,56 +0,0 @@
#define _POSIX_C_SOURCE 200809L
#include "ast_assignment.h"
#include <stdlib.h>
#include <string.h>
bool ast_is_assignment(struct ast *node)
{
return node != NULL && node->type == AST_ASSIGNMENT;
}
struct ast_assignment *ast_get_assignment(struct ast *node)
{
if (node == NULL || node->type != AST_ASSIGNMENT)
return NULL;
return (struct ast_assignment *)node->data;
}
/* @brief: splits the assignement 'name=value' into 2 parts,
* and fills the fields of ast_assignment with it.
*/
static void init_assignments(struct ast_assignment *ast_assignment,
char *assignment)
{
if (assignment == NULL)
return;
char *split_pos = strchr(assignment, '=');
if (split_pos == NULL)
return;
*split_pos = '\0';
ast_assignment->name = strdup(assignment);
ast_assignment->value = strdup(split_pos + 1);
}
struct ast *ast_create_assignment(char *assignment, bool global)
{
struct ast_assignment *assignment_data =
calloc(1, sizeof(struct ast_assignment));
if (!assignment_data)
return NULL;
init_assignments(assignment_data, assignment);
assignment_data->global = global;
return ast_create(AST_ASSIGNMENT, assignment_data);
}
void ast_free_assignment(struct ast_assignment *assignment_data)
{
if (assignment_data == NULL)
return;
free(assignment_data->name);
free(assignment_data->value);
free(assignment_data);
}

View file

@ -1,18 +0,0 @@
#ifndef AST_ASSIGNMENT_H
#define AST_ASSIGNMENT_H
#include "ast_base.h"
struct ast_assignment
{
char *name;
char *value;
bool global;
};
bool ast_is_assignment(struct ast *node);
struct ast_assignment *ast_get_assignment(struct ast *node);
struct ast *ast_create_assignment(char *assignment, bool global);
void ast_free_assignment(struct ast_assignment *assignment_data);
#endif /* ! AST_ASSIGNMENT_H */

View file

@ -1,52 +0,0 @@
#ifndef AST_BASE_H
#define AST_BASE_H
#include <stdbool.h>
#include <stdlib.h>
enum ast_type
{
AST_END,
AST_LIST,
AST_IF,
AST_AND_OR,
AST_REDIR,
AST_VOID,
AST_CMD,
AST_WORD,
AST_PIPE,
AST_NEG,
AST_LOOP,
AST_ASSIGNMENT,
AST_FUNCTION,
AST_SUBSHELL
};
struct ast
{
enum ast_type type;
/**
* Data associated with this AST node. It can be one of the following:
* - NULL (AST_END)
* - struct ast_if* (AST_IF)
* - struct ast_command* (AST_CMD)
* - struct ast_and_or* (AST_AND_OR)
* - struct ast_redir* (AST_REDIR)
* - and a lot more now...
*/
void *data;
};
/* @brief: returns an ast* with corresponding data and type.
*
* @note: this function should only be called by ast_create_[TYPE] functions.
*/
struct ast *ast_create(enum ast_type type, void *data);
/* @brief: frees the given ast. If ast is NULL, does nothing.
*
*/
void ast_free(struct ast **node);
#endif /* ! AST_BASE_H */

View file

@ -1,43 +0,0 @@
#include "ast_command.h"
#include <stdbool.h>
#include <stdlib.h>
#include "../lists/lists.h"
#include "ast_list.h"
struct ast *ast_create_command(struct list *command, struct list *redirections,
struct list *assignments)
{
struct ast_command *command_data = malloc(sizeof(struct ast_command));
if (!command_data)
return NULL;
command_data->command = command;
command_data->redirections = redirections;
command_data->assignments = assignments;
return ast_create(AST_CMD, command_data);
}
struct ast_command *ast_get_command(struct ast *node)
{
if (node == NULL || node->type != AST_CMD)
return NULL;
return (struct ast_command *)node->data;
}
bool ast_is_command(struct ast *node)
{
return node != NULL && node->type == AST_CMD;
}
void ast_free_command(struct ast_command *command_data)
{
if (command_data == NULL)
return;
list_deep_destroy(command_data->command);
ast_list_deep_destroy(command_data->redirections);
ast_list_deep_destroy(command_data->assignments);
free(command_data);
}

View file

@ -1,36 +0,0 @@
#ifndef AST_COMMAND_H
#define AST_COMMAND_H
#include "../lists/lists.h"
#include "ast_base.h"
struct ast_command
{
struct list *command; // A list of words (char*)
struct list *redirections; // A list of ASTs, all ast_redir
struct list *assignments; // A list of ASTs, all ast_assignment
};
/**
* Checks if the given AST node is a command.
*/
bool ast_is_command(struct ast *node);
/**
* Retrieves the command data from the given AST node.
* Assumes that the node is of type AST_CMD.
*/
struct ast_command *ast_get_command(struct ast *node);
/**
* Creates a new AST node representing a command.
*/
struct ast *ast_create_command(struct list *command, struct list *redirections,
struct list *assignments);
/*
* @brief: frees the given ast_command and sets the pointer to NULL.
*/
void ast_free_command(struct ast_command *command_data);
#endif /* ! AST_COMMAND_H */

View file

@ -1,14 +0,0 @@
#include "ast_end.h"
#include <stdbool.h>
#include <stdlib.h>
bool ast_is_end(struct ast *node)
{
return node != NULL && node->type == AST_END;
}
struct ast *ast_create_end(void)
{
return ast_create(AST_END, NULL);
}

View file

@ -1,18 +0,0 @@
#ifndef AST_END_H
#define AST_END_H
#include "../lists/lists.h"
#include "ast_base.h"
/**
* Checks if the given AST node is of type AST_END.
*/
bool ast_is_end(struct ast *node);
/**
* Creates a new AST node representing the end of input.
* WARNING: data will be a NULL pointer
*/
struct ast *ast_create_end(void);
#endif /* ! AST_END_H */

View file

@ -1,42 +0,0 @@
#include "ast_function.h"
#include <stdlib.h>
#include <string.h>
#include "ast_base.h"
bool ast_is_function(struct ast *node)
{
return node != NULL && node->type == AST_FUNCTION;
}
struct ast_function *ast_get_function(struct ast *node)
{
if (!ast_is_function(node))
return NULL;
return (struct ast_function *)node->data;
}
struct ast *ast_create_function(char *name, struct ast *value)
{
struct ast_function *function_data = malloc(sizeof(struct ast_function));
if (!function_data)
return NULL;
function_data->name = name;
function_data->value = value;
return ast_create(AST_FUNCTION, function_data);
}
void ast_free_function(struct ast_function *function_data)
{
if (function_data)
{
free(function_data->name);
// WARNING: this ast will be stored in the function hashmap.
// thus, it will be freed from the hashmap.
// ast_free(&function_data->value);
free(function_data);
}
}

View file

@ -1,33 +0,0 @@
#ifndef AST_FUNCTION_H
#define AST_FUNCTION_H
#include <stdbool.h>
struct ast_function
{
char *name;
struct ast *value;
};
/**
* @brief: Checks if the given AST node is an ast_function
*/
bool ast_is_function(struct ast *node);
/**
* @brief: Retrieves the function data from the given AST node.
* Assumes that the node is of type AST_function.
*/
struct ast_function *ast_get_function(struct ast *node);
/**
* @brief: Creates a new AST node representing an AST_function
* @warning: name must be already allocated.
*/
struct ast *ast_create_function(char *name, struct ast *value);
/*
* @brief: frees the given ast_function and sets the pointer to NULL.
*/
void ast_free_function(struct ast_function *function_data);
#endif /* AST_FUNCTION_H */

View file

@ -1,42 +0,0 @@
#include "ast_if.h"
#include <stdbool.h>
#include <stdlib.h>
struct ast *ast_create_if(struct ast *condition, struct ast *then_clause,
struct ast *else_clause)
{
struct ast_if *if_data = malloc(sizeof(struct ast_if));
if (!if_data)
return NULL;
if_data->condition = condition;
if_data->then_clause = then_clause;
if_data->else_clause = else_clause;
return ast_create(AST_IF, if_data);
}
struct ast_if *ast_get_if(struct ast *node)
{
if (node == NULL || node->type != AST_IF)
return NULL;
return node->data;
}
bool ast_is_if(struct ast *node)
{
return node != NULL && node->type == AST_IF;
}
void ast_free_if(struct ast_if *if_data)
{
if (if_data == NULL)
return;
ast_free(&if_data->condition);
ast_free(&if_data->then_clause);
ast_free(&if_data->else_clause);
free(if_data);
}

View file

@ -1,34 +0,0 @@
#ifndef AST_IF_H
#define AST_IF_H
#include "ast_base.h"
struct ast_if
{
struct ast *condition;
struct ast *then_clause;
struct ast *else_clause;
};
/**
* Checks if the given AST node is an if statement.
*/
bool ast_is_if(struct ast *node);
/**
* Retrieves the if statement data from the given AST node.
* Assumes that the node is of type AST_IF.
*/
struct ast_if *ast_get_if(struct ast *node);
/**
* Creates a new AST node representing an if statement.
*/
struct ast *ast_create_if(struct ast *condition, struct ast *then_clause,
struct ast *else_clause);
/*
* @brief: frees the given ast_if and sets the pointer to NULL.
*/
void ast_free_if(struct ast_if *if_data);
#endif /* ! AST_IF_H */

View file

@ -1,48 +0,0 @@
#include "ast_list.h"
struct ast *ast_create_list(struct list *list)
{
struct ast_list *ast_list = malloc(sizeof(struct ast_list));
if (ast_list == NULL)
return NULL;
ast_list->children = list;
return ast_create(AST_LIST, ast_list);
}
struct ast_list *ast_get_list(struct ast *node)
{
if (node == NULL)
return NULL;
return node->data;
}
bool ast_is_list(struct ast *node)
{
return node != NULL && node->type == AST_LIST;
}
void ast_free_list(struct ast_list *ast_list)
{
if (ast_list == NULL)
return;
ast_list_deep_destroy(ast_list->children);
free(ast_list);
}
void ast_list_deep_destroy(struct list *l)
{
struct list *elt = l;
struct list *next_elt;
while (elt != NULL)
{
next_elt = elt->next;
struct ast *node = (struct ast *)elt->data;
ast_free(&node);
free(elt);
elt = next_elt;
}
}

View file

@ -1,43 +0,0 @@
#ifndef AST_LIST_H
#define AST_LIST_H
#include "../lists/lists.h"
#include "ast_base.h"
struct ast_list
{
struct list *children; // A list of ASTs (ast*)
};
/*
** Release the memory used by the AST list and its AST children
** Does nothing if `list` is `NULL`.
*
* @warning: this function should NEVER be used on a list containing
* anything else than ASTs.
*/
void ast_list_deep_destroy(struct list *l);
/**
* Creates a new AST node representing a list of ASTs
*/
struct ast *ast_create_list(struct list *ast_list);
/**
* Retrieves the command data from the given AST node.
* Assumes that the node is of type AST_LIST.
*/
struct ast_list *ast_get_list(struct ast *node);
/**
* Checks if the given AST node is a command.
*/
bool ast_is_list(struct ast *node);
/* @brief: frees the given ast list.
*
* @warning: should only be called by ast_free() function.
*/
void ast_free_list(struct ast_list *ast_list);
#endif /* ! AST_LIST_H */

View file

@ -1,37 +0,0 @@
#include "ast_loop.h"
#include <stdbool.h>
#include <stdlib.h>
struct ast *ast_create_loop(struct ast *condition, struct ast *body)
{
struct ast_loop *node_data = malloc(sizeof(struct ast_loop));
if (!node_data)
return NULL;
node_data->condition = condition;
node_data->body = body;
return ast_create(AST_LOOP, node_data);
}
struct ast_loop *ast_get_loop(struct ast *node)
{
if (node == NULL || node->type != AST_LOOP)
return NULL;
return (struct ast_loop *)node->data;
}
bool ast_is_loop(struct ast *node)
{
return node != NULL && node->type == AST_LOOP;
}
void ast_free_loop(struct ast_loop *loop_data)
{
if (loop_data == NULL)
return;
ast_free(&loop_data->condition);
ast_free(&loop_data->body);
free(loop_data);
}

View file

@ -1,34 +0,0 @@
#ifndef AST_LOOP_H
#define AST_LOOP_H
#include "ast_base.h"
struct ast_loop
{
// Repeat body while condition is true
struct ast *condition;
struct ast *body;
};
/**
* Checks if the given AST node is a loop.
*/
bool ast_is_loop(struct ast *node);
/**
* Retrieves the loop data from the given AST node.
* Assumes that the node is of type AST_LOOP.
*/
struct ast_loop *ast_get_loop(struct ast *node);
/**
* Creates a new AST node representing a loop.
*/
struct ast *ast_create_loop(struct ast *condition, struct ast *body);
/*
* @brief: frees the given ast_loop and sets the pointer to NULL.
*/
void ast_free_loop(struct ast_loop *loop_node);
#endif /* ! AST_LOOP_H */

View file

@ -1,34 +0,0 @@
#include "ast_neg.h"
#include <stdlib.h>
bool ast_is_neg(struct ast *node)
{
return node != NULL && node->type == AST_NEG;
}
struct ast_neg *ast_get_neg(struct ast *node)
{
if (ast_is_neg(node))
return node->data;
return NULL;
}
struct ast *ast_create_neg(bool negation, struct ast *child)
{
struct ast_neg *node = malloc(sizeof(struct ast_neg));
if (!node)
return NULL;
node->negation = negation;
node->child = child;
return ast_create(AST_NEG, node);
}
void ast_free_neg(struct ast_neg *node)
{
if (!node)
return;
ast_free(&node->child);
free(node);
}

View file

@ -1,17 +0,0 @@
#ifndef AST_NEG_H
#define AST_NEG_H
#include "ast_base.h"
struct ast_neg
{
bool negation; // True negates the child's output
struct ast *child;
};
bool ast_is_neg(struct ast *node);
struct ast_neg *ast_get_neg(struct ast *node);
struct ast *ast_create_neg(bool negation, struct ast *child);
void ast_free_neg(struct ast_neg *node);
#endif /* ! AST_NEG_H */

View file

@ -1,35 +0,0 @@
#include "ast_pipe.h"
#include <stdlib.h>
bool ast_is_pipe(struct ast *node)
{
return node != NULL && node->type == AST_REDIR;
}
struct ast_pipe *ast_get_pipe(struct ast *node)
{
if (ast_is_pipe(node))
return node->data;
return NULL;
}
struct ast *ast_create_pipe(struct ast *left, struct ast *right)
{
struct ast_pipe *node = malloc(sizeof(struct ast_pipe));
if (!node)
return NULL;
node->left = left;
node->right = right;
return ast_create(AST_PIPE, node);
}
void ast_free_pipe(struct ast_pipe *node)
{
if (!node)
return;
ast_free(&node->left);
ast_free(&node->right);
free(node);
}

View file

@ -1,18 +0,0 @@
#ifndef AST_PIPE_H
#define AST_PIPE_H
#include "ast_base.h"
struct ast_pipe
{
struct ast *left;
struct ast *right;
// Output of left will be redirected to right stdin
};
bool ast_is_pipe(struct ast *node);
struct ast_pipe *ast_get_pipe(struct ast *node);
struct ast *ast_create_pipe(struct ast *left, struct ast *right);
void ast_free_pipe(struct ast_pipe *node);
#endif /* ! AST_PIPE_H */

View file

@ -1,38 +0,0 @@
#include "ast_redir.h"
#include <stdlib.h>
bool ast_is_redir(struct ast *node)
{
return node != NULL && node->type == AST_REDIR;
}
struct ast_redir *ast_get_redir(struct ast *node)
{
if (ast_is_redir(node))
return (struct ast_redir *)node->data;
return NULL;
}
struct ast *ast_create_redir(char *filename, int io_number,
enum ast_redir_type type)
{
struct ast_redir *redir = malloc(sizeof(struct ast_redir));
if (!redir)
return NULL;
redir->filename =
filename; // Takes ownership? Usually yes in simple ASTs, or dup. Let's
// assume pointer copy for now, but user must manage memory.
redir->io_number = io_number;
redir->type = type;
return ast_create(AST_REDIR, redir);
}
void ast_free_redir(struct ast_redir *redir)
{
if (!redir)
return;
free(redir->filename);
free(redir);
}

View file

@ -1,33 +0,0 @@
#ifndef AST_REDIR_H
#define AST_REDIR_H
#include "ast_base.h"
enum ast_redir_type
{
AST_REDIR_TYPE_NULL,
AST_REDIR_TYPE_LESS, // <
AST_REDIR_TYPE_GREAT, // >
AST_REDIR_TYPE_LESSGREAT, // <>
AST_REDIR_TYPE_DGREAT, // >>
AST_REDIR_TYPE_LESSAND, // <&
AST_REDIR_TYPE_GREATAND, // >&
AST_REDIR_TYPE_CLOBBER // >|
};
struct ast_redir
{
char *filename;
int io_number; // The FD being redirected (default -1 if not specified,
// implies 0 or 1 based on type)
enum ast_redir_type type;
int saved_fd; // To store the original FD for restoration (-1 before save)
};
bool ast_is_redir(struct ast *node);
struct ast_redir *ast_get_redir(struct ast *node);
struct ast *ast_create_redir(char *filename, int io_number,
enum ast_redir_type type);
void ast_free_redir(struct ast_redir *redir);
#endif /* ! AST_REDIR_H */

View file

@ -1,31 +0,0 @@
#include "ast_subshell.h"
bool ast_is_subshell(struct ast *node)
{
return node != NULL && node->type == AST_SUBSHELL;
}
struct ast_subshell *ast_get_subshell(struct ast *node)
{
if (ast_is_subshell(node))
return (struct ast_subshell *)node->data;
return NULL;
}
struct ast *ast_create_subshell(struct ast *child)
{
struct ast_subshell *subshell = calloc(1, sizeof(struct ast_subshell));
if (subshell == NULL)
return NULL;
subshell->child = child;
return ast_create(AST_SUBSHELL, subshell);
}
void ast_free_subshell(struct ast_subshell *subshell)
{
if (!subshell)
return;
ast_free(&subshell->child);
free(subshell);
}

View file

@ -1,19 +0,0 @@
#ifndef AST_SUBSHELL_H
#define AST_SUBSHELL_H
#include "ast_base.h"
struct ast_subshell
{
struct ast *child;
};
bool ast_is_subshell(struct ast *node);
struct ast_subshell *ast_get_subshell(struct ast *node);
struct ast *ast_create_subshell(struct ast *child);
void ast_free_subshell(struct ast_subshell *subshell);
#endif /* ! AST_SUBSHELL_H */

View file

@ -1,14 +0,0 @@
#include "ast_void.h"
#include <stdbool.h>
#include <stdlib.h>
bool ast_is_void(struct ast *node)
{
return node != NULL && node->type == AST_VOID;
}
struct ast *ast_create_void(void)
{
return ast_create(AST_VOID, NULL);
}

View file

@ -1,18 +0,0 @@
#ifndef AST_VOID_H
#define AST_VOID_H
#include "../lists/lists.h"
#include "ast_base.h"
/**
* Checks if the given AST node is of type AST_VOID.
*/
bool ast_is_void(struct ast *node);
/**
* Creates a new AST node representing NOTHING
* WARNING: data will be a NULL pointer
*/
struct ast *ast_create_void(void);
#endif /* ! AST_VOID_H */

View file

@ -1,49 +0,0 @@
#define _POSIX_C_SOURCE 200809L
#include "ast_word.h"
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
struct ast *ast_create_word(char *word)
{
struct ast_word *ast_node = malloc(sizeof(struct ast_word));
if (ast_node == NULL)
return NULL;
ast_node->type = AST_WORD;
ast_node->word = strdup(word);
struct ast *res = ast_create(AST_WORD, ast_node);
if (res == NULL)
{
free(ast_node->word);
free(ast_node);
return NULL;
}
return res;
}
struct ast_word *ast_get_word(struct ast *node)
{
if (node == NULL || node->type != AST_WORD)
return NULL;
return node->data;
}
bool ast_is_word(struct ast *node)
{
return node && node->type == AST_WORD;
}
void ast_free_word(struct ast_word *ast_node)
{
if (ast_node == NULL)
return;
if (ast_node->word != NULL)
free(ast_node->word);
free(ast_node);
}

View file

@ -1,33 +0,0 @@
#ifndef AST_WORD_H
#define AST_WORD_H
#include "ast_base.h"
struct ast_word
{
enum ast_type type;
char *word;
};
/**
* Checks if the given AST node is a command.
*/
bool ast_is_word(struct ast *node);
/**
* Retrieves the command data from the given AST node.
* Assumes that the node is of type AST_CMD.
*/
struct ast_word *ast_get_word(struct ast *node);
/**
* Creates a new AST node representing a command.
*/
struct ast *ast_create_word(char *word);
/*
* @brief: frees the given ast_command and sets the pointer to NULL.
*/
void ast_free_word(struct ast_word *ast_node);
#endif /* ! AST_WORD_H */

View file

@ -1,216 +0,0 @@
#include "hash_map.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../ast/ast.h"
/*
** Hash the key using FNV-1a 32 bits hash algorithm.
*/
static size_t hash(const char *key)
{
if (key == NULL)
return 0;
uint32_t hash = 2166136261; // FNV offset basis
uint32_t prime = 16777619; // FNV prime
while (*key != 0)
{
hash ^= *key;
hash *= prime;
key++;
}
return hash;
}
static void destroy_pair_list(struct pair_list **p)
{
free((char *)(*p)->key);
free((*p)->value);
free((*p));
*p = NULL;
}
static void destroy_pair_list_ast(struct pair_list **p)
{
free((char *)(*p)->key);
ast_free((*p)->value);
free((*p));
*p = NULL;
}
struct hash_map *hash_map_init(size_t size)
{
struct hash_map *p = malloc(sizeof(struct hash_map));
if (p != NULL)
{
p->data = calloc(size, sizeof(struct pair_list *));
if (p->data == NULL)
{
free(p);
return NULL;
}
p->size = size;
}
return p;
}
bool hash_map_insert(struct hash_map *hash_map, const char *key, void *value,
bool *updated)
{
if (hash_map == NULL || hash_map->size == 0 || key == NULL || value == NULL)
return false;
size_t h = hash(key);
struct pair_list **l = hash_map->data;
size_t i = h % hash_map->size;
if (l[i] != NULL)
{
// check if key is in linked list
struct pair_list *iter = l[i];
while (iter)
{
if (strcmp(iter->key, key) == 0)
{
// update
free(iter->value);
iter->value = value;
if (updated)
*updated = true;
return true;
}
iter = iter->next;
}
// if not found => collision
}
struct pair_list *p = malloc(sizeof(struct pair_list));
if (p == NULL)
return false;
if (updated)
*updated = false;
p->key = key;
p->value = value;
p->next = l[i];
l[i] = p;
l[i] = p;
return true;
}
void hash_map_free(struct hash_map **hash_map)
{
struct pair_list *l;
struct pair_list *prev;
if (hash_map != NULL && *hash_map != NULL)
{
for (size_t i = 0; i < (*hash_map)->size; i++)
{
l = (*hash_map)->data[i];
while (l != NULL)
{
prev = l;
l = l->next;
destroy_pair_list(&prev);
}
}
free((*hash_map)->data);
free(*hash_map);
*hash_map = NULL;
}
}
void hash_map_free_ast(struct hash_map **hash_map)
{
struct pair_list *l;
struct pair_list *prev;
if (hash_map != NULL && *hash_map != NULL)
{
for (size_t i = 0; i < (*hash_map)->size; i++)
{
l = (*hash_map)->data[i];
while (l != NULL)
{
prev = l;
l = l->next;
destroy_pair_list_ast(&prev);
}
}
free((*hash_map)->data);
free(*hash_map);
*hash_map = NULL;
}
}
void hash_map_foreach(struct hash_map *hash_map,
void (*fn)(const char *, const void *))
{
if (hash_map == NULL)
return;
struct pair_list *iter;
for (size_t i = 0; i < hash_map->size; i++)
{
iter = hash_map->data[i];
while (iter != NULL)
{
fn(iter->key, iter->value);
iter = iter->next;
}
}
}
const void *hash_map_get(const struct hash_map *hash_map, const char *key)
{
if (hash_map == NULL || hash_map->size == 0)
return NULL;
size_t h = hash(key);
size_t i = h % hash_map->size;
struct pair_list *l = hash_map->data[i];
while (l != NULL)
{
if (strcmp(l->key, key) == 0)
return l->value;
l = l->next;
}
return NULL;
}
bool hash_map_remove(struct hash_map *hash_map, const char *key)
{
if (hash_map == NULL || hash_map->size == 0)
return false;
size_t h = hash(key);
size_t i = h % hash_map->size;
struct pair_list *l = hash_map->data[i];
struct pair_list *p = NULL;
while (l != NULL)
{
if (strcmp(l->key, key) == 0)
{
if (p != NULL)
p->next = l->next;
else
hash_map->data[i] = l->next;
destroy_pair_list(&l);
return true;
}
p = l;
l = l->next;
}
return false;
}

View file

@ -1,47 +0,0 @@
#ifndef HASH_MAP_H
#define HASH_MAP_H
#include <stdbool.h>
#include <stddef.h>
struct pair_list
{
const char *key;
void *value;
struct pair_list *next;
};
struct hash_map
{
struct pair_list **data;
size_t size;
};
struct hash_map *hash_map_init(size_t size);
/**
* @brief Inserts a key-value pair into the hash map. Key and value are expected
* to be on the heap and will be managed by the hash map. It means they are
* consumed by this function. If the key already exists, its value is updated
* and the key is not consumed so it must be freed by the caller.
*
* @param hash_map The hash map.
* @param key The key to insert.
* @param value The value to insert.
* @param updated If not NULL, set to true if the key was already present and
* updated, false if the key was newly inserted.
* @return true on success, false on failure.
*/
bool hash_map_insert(struct hash_map *hash_map, const char *key, void *value,
bool *updated);
void hash_map_free(struct hash_map **hash_map);
void hash_map_foreach(struct hash_map *hash_map,
void (*fn)(const char *, const void *));
const void *hash_map_get(const struct hash_map *hash_map, const char *key);
bool hash_map_remove(struct hash_map *hash_map, const char *key);
#endif /* ! HASH_MAP_H */

306
src/utils/lists/lists.c Normal file
View file

@ -0,0 +1,306 @@
#include "lists.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct list *list_prepend(struct list *list, void *value)
{
struct list *new_elt = malloc(sizeof(struct list));
if (new_elt == NULL)
{
return NULL;
}
new_elt->next = list;
new_elt->data = value;
return new_elt;
}
size_t list_length(struct list *list)
{
size_t len = 0;
while (list != NULL)
{
len++;
list = list->next;
}
return len;
}
void list_print(struct list *list)
{
if (list == NULL)
{
return;
}
while (list != NULL)
{
if (list->next != NULL)
{
printf("%p ", list->data);
}
else
{
printf("%p\n", list->data);
}
list = list->next;
}
}
void list_destroy(struct list *list)
{
struct list *elt = list;
struct list *next_elt;
while (elt != NULL)
{
next_elt = elt->next;
free(elt);
elt = next_elt;
}
}
struct list *list_append(struct list *list, void *value)
{
if (list == NULL)
{
struct list *new_elt = malloc(sizeof(struct list));
new_elt->data = value;
new_elt->next = NULL;
return new_elt;
}
struct list *elt = list;
while (elt->next != NULL)
{
elt = elt->next;
}
struct list *new_elt = malloc(sizeof(struct list));
new_elt->data = value;
new_elt->next = NULL;
elt->next = new_elt;
return list;
}
/*
*
*******************
* Advanced *
*******************
*
*/
struct list *list_insert(struct list *list, void *value, size_t index)
{
if (list == NULL || index == 0)
{
struct list *new_elt = malloc(sizeof(struct list));
new_elt->data = value;
new_elt->next = list;
return new_elt;
}
struct list *elt = list;
for (size_t i = 0; i < index - 1; i++)
{
if (elt->next == NULL)
{
break;
}
elt = elt->next;
}
struct list *new_elt = malloc(sizeof(struct list));
new_elt->data = value;
new_elt->next = elt->next;
elt->next = new_elt;
return list;
}
struct list *list_remove(struct list *list, size_t index)
{
struct list *elt = list;
struct list *prev_elt;
if (index == 0)
{
struct list *res = elt->next;
free(elt);
return res;
}
for (size_t i = 0; i < index; i++)
{
if (elt == NULL)
{
return list;
}
prev_elt = elt;
elt = elt->next;
}
if (elt == NULL)
{
return list;
}
prev_elt->next = elt->next;
free(elt);
return list;
}
int list_find(struct list *list, void *value)
{
if (list == NULL)
{
return -1;
}
int res = 0;
while (list->data != value)
{
list = list->next;
res++;
if (list == NULL)
{
return -1;
}
}
return res;
}
struct list *list_concat(struct list *list, struct list *list2)
{
if (list == NULL)
{
return list2;
}
struct list *elt = list;
while (elt->next != NULL)
{
elt = elt->next;
}
elt->next = list2;
return list;
}
/*
*
******************
* Ultimate *
******************
*
*/
static void swap_next(struct list *elt)
{
void *c = elt->next->data;
elt->next->data = elt->data;
elt->data = c;
}
struct list *list_sort(struct list *list)
{
// Bubble sort go !
if (list == NULL)
{
return list;
}
struct list *elt = list;
int len = 0;
while (elt->next != NULL)
{
if (elt->data > elt->next->data)
{
swap_next(elt);
}
elt = elt->next;
len++;
}
for (int i = 1; i < len; i++)
{
elt = list;
while (elt->next != NULL)
{
if (elt->data > elt->next->data)
{
swap_next(elt);
}
elt = elt->next;
}
}
return list;
}
// Old proto
// WARNING no malloc/free allowed (moulinette issue)
struct list *list_reverse(struct list *list)
{
if (list == NULL)
{
return list;
}
// Get len
struct list *elt = list;
int len = 0;
while (elt->next != NULL)
{
len++;
elt = elt->next;
}
elt = list;
// Bring each elt to end
for (int i = 0; i < len; i++)
{
elt = list;
for (int j = 0; j < len - i; j++)
{
swap_next(elt);
elt = elt->next;
}
}
return list;
}
struct list *list_split(struct list *list, size_t index)
{
struct list *elt = list;
for (size_t i = 0; i < index; i++)
{
if (elt == NULL)
{
return NULL;
}
elt = elt->next;
}
if (elt == NULL)
{
return NULL;
}
struct list *res = elt->next;
elt->next = NULL;
return res;
}
void list_deep_destroy(struct list *l)
{
struct list *elt = l;
struct list *next_elt;
while (elt != NULL)
{
next_elt = elt->next;
free(elt->data);
free(elt);
elt = next_elt;
}
}

View file

@ -31,7 +31,7 @@ void list_print(struct list *list);
** Release the memory used by the list.
** Does nothing if `list` is `NULL`.
*/
void list_destroy(struct list **list);
void list_destroy(struct list *list);
/*
** Release the memory used by the list and its content
@ -107,9 +107,4 @@ int list_find(struct list *list, void *value);
// struct list *list_split(struct list *list, size_t index);
// END PROTO list_split
/**
* @brief: Folds the list from left to right using func and an accumulator.
*/
void list_fold(struct list *list, void *acc, void (*func)(void *, void *));
#endif /* ! LISTS_H */

View file

@ -1,88 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "lists.h"
struct list *list_prepend(struct list *list, void *value)
{
struct list *new_elt = malloc(sizeof(struct list));
if (new_elt == NULL)
{
return NULL;
}
new_elt->next = list;
new_elt->data = value;
return new_elt;
}
size_t list_length(struct list *list)
{
size_t len = 0;
while (list != NULL)
{
len++;
list = list->next;
}
return len;
}
void list_print(struct list *list)
{
if (list == NULL)
{
return;
}
while (list != NULL)
{
if (list->next != NULL)
{
printf("%p ", list->data);
}
else
{
printf("%p\n", list->data);
}
list = list->next;
}
}
void list_destroy(struct list **list)
{
struct list *elt = *list;
struct list *next_elt;
while (elt != NULL)
{
next_elt = elt->next;
free(elt);
elt = next_elt;
}
*list = NULL;
}
struct list *list_append(struct list *list, void *value)
{
if (list == NULL)
{
struct list *new_elt = malloc(sizeof(struct list));
new_elt->data = value;
new_elt->next = NULL;
return new_elt;
}
struct list *elt = list;
while (elt->next != NULL)
{
elt = elt->next;
}
struct list *new_elt = malloc(sizeof(struct list));
new_elt->data = value;
new_elt->next = NULL;
elt->next = new_elt;
return list;
}

View file

@ -1,107 +0,0 @@
#include <stdlib.h>
#include "lists.h"
/*
*
*******************
* Advanced *
*******************
*
*/
struct list *list_insert(struct list *list, void *value, size_t index)
{
if (list == NULL || index == 0)
{
struct list *new_elt = malloc(sizeof(struct list));
new_elt->data = value;
new_elt->next = list;
return new_elt;
}
struct list *elt = list;
for (size_t i = 0; i < index - 1; i++)
{
if (elt->next == NULL)
{
break;
}
elt = elt->next;
}
struct list *new_elt = malloc(sizeof(struct list));
new_elt->data = value;
new_elt->next = elt->next;
elt->next = new_elt;
return list;
}
struct list *list_remove(struct list *list, size_t index)
{
struct list *elt = list;
struct list *prev_elt;
if (index == 0)
{
struct list *res = elt->next;
free(elt);
return res;
}
for (size_t i = 0; i < index; i++)
{
if (elt == NULL)
{
return list;
}
prev_elt = elt;
elt = elt->next;
}
if (elt == NULL)
{
return list;
}
prev_elt->next = elt->next;
free(elt);
return list;
}
int list_find(struct list *list, void *value)
{
if (list == NULL)
{
return -1;
}
int res = 0;
while (list->data != value)
{
list = list->next;
res++;
if (list == NULL)
{
return -1;
}
}
return res;
}
struct list *list_concat(struct list *list, struct list *list2)
{
if (list == NULL)
{
return list2;
}
struct list *elt = list;
while (elt->next != NULL)
{
elt = elt->next;
}
elt->next = list2;
return list;
}

View file

@ -1,127 +0,0 @@
#include <stdlib.h>
#include "lists.h"
/*
*
******************
* Ultimate *
******************
*
*/
static void swap_next(struct list *elt)
{
void *c = elt->next->data;
elt->next->data = elt->data;
elt->data = c;
}
struct list *list_sort(struct list *list)
{
// Bubble sort go !
if (list == NULL)
{
return list;
}
struct list *elt = list;
int len = 0;
while (elt->next != NULL)
{
if (elt->data > elt->next->data)
{
swap_next(elt);
}
elt = elt->next;
len++;
}
for (int i = 1; i < len; i++)
{
elt = list;
while (elt->next != NULL)
{
if (elt->data > elt->next->data)
{
swap_next(elt);
}
elt = elt->next;
}
}
return list;
}
// Old proto
// WARNING no malloc/free allowed (moulinette issue)
struct list *list_reverse(struct list *list)
{
if (list == NULL)
{
return list;
}
// Get len
struct list *elt = list;
int len = 0;
while (elt->next != NULL)
{
len++;
elt = elt->next;
}
elt = list;
// Bring each elt to end
for (int i = 0; i < len; i++)
{
elt = list;
for (int j = 0; j < len - i; j++)
{
swap_next(elt);
elt = elt->next;
}
}
return list;
}
struct list *list_split(struct list *list, size_t index)
{
struct list *elt = list;
for (size_t i = 0; i < index; i++)
{
if (elt == NULL)
{
return NULL;
}
elt = elt->next;
}
if (elt == NULL)
{
return NULL;
}
struct list *res = elt->next;
elt->next = NULL;
return res;
}
void list_deep_destroy(struct list *l)
{
struct list *elt = l;
struct list *next_elt;
while (elt != NULL)
{
next_elt = elt->next;
free(elt->data);
free(elt);
elt = next_elt;
}
}
void list_fold(struct list *list, void *acc, void (*func)(void *, void *))
{
struct list *elt = list;
while (elt != NULL)
{
func(acc, elt->data);
elt = elt->next;
}
}

View file

@ -1,111 +0,0 @@
#define _POSIX_C_SOURCE 200809L
#include "main_loop.h"
// === Includes
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "../../execution/execution.h"
#include "../../io_backend/io_backend.h"
#include "../../lexer/lexer.h"
#include "../../parser/parser.h"
#include "../args/args.h"
#include "../vars/vars.h"
// === Functions
int err_input(struct hash_map **vars, struct lexer_context *ctx)
{
hash_map_free(vars);
destroy_lexer_context(ctx);
return ERR_INPUT_PROCESSING;
}
int main_loop(struct lexer_context *ctx, struct hash_map *vars)
{
int return_code = SUCCESS;
// Retrieve and build first AST
struct ast *command_ast = get_ast(ctx);
// Main parse-execute loop
while (command_ast != NULL && command_ast->type != AST_END)
{
if (command_ast->type != AST_VOID)
{
// Execute AST
return_code = execution(command_ast, vars);
// set $? variable
set_var_int(vars, "?", return_code);
}
ast_free(&command_ast);
// Retrieve and build next AST
command_ast = get_ast(ctx);
}
if (command_ast == NULL)
return err_input(&vars, ctx);
// === free
ast_free(&command_ast);
hash_map_free(&vars);
destroy_lexer_context(ctx);
return return_code;
}
/* @brief: initializes a lexer context from a command string.
* @return: pointer to the lexer context, or NULL on failure.
*/
static struct lexer_context *lexer_init_from_string(char *command)
{
// Create a lexer context from the command string
struct lexer_context *ctx = calloc(1, sizeof(struct lexer_context));
if (ctx == NULL)
return NULL;
ctx->end_previous_token = strdup(command);
if (ctx->end_previous_token == NULL)
{
free(ctx);
return NULL;
}
ctx->remaining_chars = strlen(command);
return ctx;
}
int start_subshell(struct hash_map **parent_vars, char *command)
{
int fd = fork();
if (fd < 0)
return ERR_GENERIC;
else if (fd == 0) // Child process
{
struct lexer_context *ctx = lexer_init_from_string(command);
if (ctx == NULL)
return ERR_MALLOC;
int return_code = main_loop(ctx, *parent_vars);
exit(return_code);
}
else // Parent process
{
int status;
if (waitpid(fd, &status, 0) == -1)
return ERR_GENERIC;
if (WIFEXITED(status))
return WEXITSTATUS(status);
else
return ERR_GENERIC;
}
}

View file

@ -1,31 +0,0 @@
#ifndef MAIN_LOOP_H
#define MAIN_LOOP_H
#include "../../utils/vars/vars.h"
#include "../../lexer/lexer.h"
// === Error codes
#define SUCCESS 0
#define ERR_INPUT_PROCESSING 2
#define ERR_MALLOC 3
#define ERR_GENERIC 4
/* @brief: main loop called from main.
* @return: exit code.
*/
int main_loop(struct lexer_context *ctx, struct hash_map *vars);
/*
* @brief: frees the hash map and lexer context.
* @return: ERR_INPUT_PROCESSING.
*/
int err_input(struct hash_map **vars, struct lexer_context *ctx);
/*
* @brief: starts a subshell and builds the intern lexer context
* from the string.
* @return: exit code of the subshell.
*/
int start_subshell(struct hash_map **parent_vars, char *command);
#endif /* MAIN_LOOP_H */

View file

@ -1,53 +0,0 @@
#include "string_utils.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *trim_blank_left(char *str)
{
if (str == NULL)
return NULL;
while (*str != '\0' && isblank(*str))
str++;
return str;
}
char *insert_into(char *dest, const char *src, size_t pos, size_t len)
{
size_t res_len = strlen(dest);
size_t prefix_len = pos;
size_t suffix_len = res_len - (pos + len);
size_t src_len = strlen(src);
size_t new_len = prefix_len + src_len + suffix_len;
if (dest == NULL || src == NULL || pos + len > res_len)
return NULL;
if (res_len < new_len)
{
char *p = realloc(dest, new_len + 1);
if (p == NULL)
return NULL; // allocation failure
dest = p;
}
memmove(dest + pos + src_len, dest + pos + len, suffix_len);
memcpy(dest + pos, src, src_len);
dest[new_len] = 0;
if (res_len > new_len)
return realloc(dest, new_len + 1);
return dest;
}
void int_to_str(int value, char *buffer)
{
if (buffer == NULL)
return;
snprintf(buffer, 11, "%d", value);
}

View file

@ -1,30 +0,0 @@
#ifndef STRING_UTILS_H
#define STRING_UTILS_H
#include <stddef.h>
/*
* @brief trims leading blank characters (space and tab) from the input string.
* @param str input string to be trimmed.
* @return pointer to the first non-blank character in the string. If the
* string consists entirely of blank characters, returns a pointer to the null
* terminator at the end of the string.
*/
char *trim_blank_left(char *str);
/**
* Inserts a substring into a destination string at a specified position,
* replacing a specified length of characters.
*/
char *insert_into(char *dest, const char *src, size_t pos, size_t len);
/**
* Converts an integer to its string representation.
* @param value The integer value to convert.
* @param buffer A character array where the resulting string will be stored.
* The buffer must be at least 11 bytes long to accommodate the largest
* 32-bit integer and the null terminator.
*/
void int_to_str(int value, char *buffer);
#endif /* STRING_UTILS_H */

View file

@ -1,86 +0,0 @@
#define _POSIX_C_SOURCE 200809L
#include "vars.h"
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "../hash_map/hash_map.h"
#include "../string_utils/string_utils.h"
#define VARS_INITIAL_SIZE 16
static void vars_default(struct hash_map *vars)
{
set_var_copy(vars, "?", "0");
pid_t pid = getpid();
char pid_str[11];
int_to_str(pid, pid_str);
set_var_copy(vars, "$", pid_str);
}
struct hash_map *vars_init(void)
{
struct hash_map *vars = hash_map_init(VARS_INITIAL_SIZE);
if (vars != NULL)
{
vars_default(vars);
char uid_str[11];
int_to_str((int)getuid(), uid_str);
set_var_copy(vars, "UID", uid_str);
}
return vars;
}
short short_random(void)
{
static bool seeded = false;
if (!seeded)
{
srand((unsigned)time(NULL));
seeded = true;
}
return (short)(rand() & 0x7FFF); // force 16 bits positive
}
char *get_var(const struct hash_map *vars, const char *key)
{
return (char *)hash_map_get(vars, key);
}
char *get_var_or_env(const struct hash_map *vars, const char *key)
{
char *value = (char *)hash_map_get(vars, key);
if (value == NULL)
value = getenv(key);
return value;
}
bool set_var(struct hash_map *vars, const char *key, const char *value,
bool *updated)
{
return hash_map_insert(vars, key, (void *)value, updated);
}
bool set_var_copy(struct hash_map *vars, const char *key, const char *value)
{
char *key_copy = strdup(key);
char *value_copy = strdup(value);
bool updated;
bool res = set_var(vars, key_copy, value_copy, &updated);
if (updated || !res)
free(key_copy);
if (!res)
free(value_copy);
return res;
}
bool set_var_int(struct hash_map *vars, const char *key, int value)
{
char value_str[11];
int_to_str(value, value_str);
return set_var_copy(vars, key, value_str);
}

View file

@ -1,49 +0,0 @@
#ifndef VARS_H
#define VARS_H
#include <stdbool.h>
#include "../hash_map/hash_map.h"
/**
* Initialize a new variables hash map.
*/
struct hash_map *vars_init(void);
/**
* Generate a random short integer (16 bits positive [0-32767]).
*/
short short_random(void);
/**
* Get the value of a variable, NULL if not found.
*/
char *get_var(const struct hash_map *vars, const char *key);
/**
* Get the value of a variable, from the environment if not found in vars,
* NULL if not found in either.
*/
char *get_var_or_env(const struct hash_map *vars, const char *key);
/**
* Set the value of a variable. Key and value ownership are transferred to
* the hash_map and need to be on the heap. Returns true on success, false on
* failure.
*/
bool set_var(struct hash_map *vars, const char *key, const char *value,
bool *updated);
/**
* Same as set_var, but makes copies of key and value so you don't have to worry
* about their memory management. Returns true on success, false on failure.
*/
bool set_var_copy(struct hash_map *vars, const char *key, const char *value);
/**
* Set the value of a variable to an integer. Behavior is similar to
* set_var_copy.
*/
bool set_var_int(struct hash_map *vars, const char *key, int value);
#endif /* ! VARS_H */

View file

@ -1,17 +0,0 @@
true
echo 'true =' $?
false
echo 'false =' $?
false && true
echo 'false && true =' $?
true && false
echo 'true && false =' $?
true || false
echo 'true || false =' $?
false || true
echo 'false || true =' $?

View file

@ -1,21 +0,0 @@
func()
{
echo hello
}
arg_func()
{
echo first argument is "$1"
}
func_in_func()
{
func
}
func_one_line() { echo "this is on one line"; }
func
arg_func "HERE"
func_in_func
func_one_line

View file

@ -1,20 +0,0 @@
echo "starting tests"
while false;
do
echo "should NOT be printed"
done
a='yes'
while [ "$a" -eq "yes" ];
do
a="no"
echo "should be printed only once"
done;
while true;
do
echo "yes"
done;
echo "tests done"

View file

@ -1,461 +0,0 @@
#!/bin/sh
###################
# Variables #
###################
executable="$BIN_PATH"
ref_executable="dash"
tmp_script="/tmp/test_script.sh"
output="/tmp/42sh_tests.output"
ref_output="/tmp/42sh_tests_ref.output"
total_tests=0
errors_count=0
timeouts_count=0
##################
# Colors #
##################
# Reset
Color_Off='\033[0m' # Text Reset
# Regular Colors
Black='\033[0;30m' # Black
Red='\033[0;31m' # Red
Green='\033[0;32m' # Green
Yellow='\033[0;33m' # Yellow
Blue='\033[0;34m' # Blue
Purple='\033[0;35m' # Purple
Cyan='\033[0;36m' # Cyan
White='\033[0;37m' # White
# Bold
BBlack='\033[1;30m' # Black
BRed='\033[1;31m' # Red
BGreen='\033[1;32m' # Green
BYellow='\033[1;33m' # Yellow
BBlue='\033[1;34m' # Blue
BPurple='\033[1;35m' # Purple
BCyan='\033[1;36m' # Cyan
BWhite='\033[1;37m' # White
# Underline
UBlack='\033[4;30m' # Black
URed='\033[4;31m' # Red
UGreen='\033[4;32m' # Green
UYellow='\033[4;33m' # Yellow
UBlue='\033[4;34m' # Blue
UPurple='\033[4;35m' # Purple
UCyan='\033[4;36m' # Cyan
UWhite='\033[4;37m' # White
# Background
On_Black='\033[40m' # Black
On_Red='\033[41m' # Red
On_Green='\033[42m' # Green
On_Yellow='\033[43m' # Yellow
On_Blue='\033[44m' # Blue
On_Purple='\033[45m' # Purple
On_Cyan='\033[46m' # Cyan
On_White='\033[47m' # White
# High Intensity
IBlack='\033[0;90m' # Black
IRed='\033[0;91m' # Red
IGreen='\033[0;92m' # Green
IYellow='\033[0;93m' # Yellow
IBlue='\033[0;94m' # Blue
IPurple='\033[0;95m' # Purple
ICyan='\033[0;96m' # Cyan
IWhite='\033[0;97m' # White
# Bold High Intensity
BIBlack='\033[1;90m' # Black
BIRed='\033[1;91m' # Red
BIGreen='\033[1;92m' # Green
BIYellow='\033[1;93m' # Yellow
BIBlue='\033[1;94m' # Blue
BIPurple='\033[1;95m' # Purple
BICyan='\033[1;96m' # Cyan
BIWhite='\033[1;97m' # White
# High Intensity backgrounds
On_IBlack='\033[0;100m' # Black
On_IRed='\033[0;101m' # Red
On_IGreen='\033[0;102m' # Green
On_IYellow='\033[0;103m' # Yellow
On_IBlue='\033[0;104m' # Blue
On_IPurple='\033[0;105m' # Purple
On_ICyan='\033[0;106m' # Cyan
On_IWhite='\033[0;107m' # White
##################
# Wrappers #
##################
# @arg test command
# @arg actual code
# @arg ref code
check_result() {
command="$1"
actual_code="$2"
ref_code="$3"
test_failed=0
# Check return code
if [[ "$actual_code" -eq 124 ]]; then
echo -e $BRed "TIMEOUT" $Color_Off
echo -e ' ' "on '$command'"
echo -e ' ' "Expected code $ref_code but got $actual_code"
((timeouts_count++))
test_failed=1
elif [[ "$actual_code" -ne "$ref_code" ]]; then
echo -e $BRed "FAILED" $Color_Off
echo -e ' ' "on '$command'"
echo -e ' ' "Expected code $ref_code but got $actual_code"
test_failed=1
# Check output
elif [[ $(diff $output $ref_output > /dev/null) -ne 0 ]]; then
echo -e $BRed "FAILED" $Color_Off
echo -e ' ' "on '$command'"
echo -e ' ' "Output is not the one expected"
test_failed=1
else
echo -e $BGreen OK $Color_Off
fi
if [[ "$test_failed" -eq 1 ]]; then
((errors_count++))
fi
}
# @arg test name
# @arg input string
test_str() {
# Check input
if [[ -z "$1" || -z "$2" ]]; then
echo -e $BRed "\n\n" "Issue in the testsuite: test_str: One or more argument is empty" $Color_Off
exit 2
fi
echo -e $BBlue "================== $1 ==================" $Color_Off
echo -e "$2" > $tmp_script
# Arg
echo -e -n $Blue "= [ARG] " $Color_Off
timeout 0.2 $executable -c "$2" &> $output
actual_code=$?
$ref_executable -c "$2" &> $ref_output
ref_code=$?
((total_tests++))
check_result "$2" "$actual_code" "$ref_code"
# Script
echo -e -n $Blue "= [SCRIPT]" $Color_Off
timeout 0.2 $executable "$tmp_script" &> $output
actual_code=$?
$ref_executable "$tmp_script" &> $ref_output
ref_code=$?
((total_tests++))
check_result "$2" "$actual_code" "$ref_code"
# Stdin
echo -e -n $Blue "= [STDIN] " $Color_Off
timeout 0.2 $executable < "$tmp_script" &> $output
actual_code=$?
$ref_executable < "$tmp_script" &> $ref_output
ref_code=$?
((total_tests++))
check_result "$2" "$actual_code" "$ref_code"
echo -e "\n"
# echo -e $BBlue "===========================================" $Color_Off "\n"
}
# @arg test name
# @arg input script
test_script() {
# Check input
if [[ -z "$1" || -z "$2" ]]; then
echo -e $BRed "\n\n" "Issue in the testsuite: test_script: One or more argument is empty" $Color_Off
exit 2
fi
if [[ ! -f "$2" ]]; then
echo -e $BRed "\n\n" "Issue in the testsuite: test_script: Second argument is not a file" $Color_Off
exit 2
fi
echo -e $BBlue "================== $1 ==================" $Color_Off
# Script
echo -e -n "= [SCRIPT] "
timeout 0.2 $executable "$tmp_script" &> $output
actual_code=$?
$ref_executable "$tmp_script" &> $ref_output
ref_code=$?
((total_tests++))
check_result "$2" "$actual_code" "$ref_code"
# Stdin
echo -e -n "= [STDIN] "
timeout 0.2 $executable < "$tmp_script" &> $output
actual_code=$?
$ref_executable < "$2" &> $ref_output
ref_code=$?
((total_tests++))
check_result "$tmp_script" "$actual_code" "$ref_code"
echo -e "\n"
# echo -e $BBlue "===========================================" $Color_Off "\n"
}
summarize() {
# Compute statistics
(( passed_tests = $total_tests - $errors_count ))
(( tests_percentage = 100 * $passed_tests / $total_tests ))
# Adapt color depending on results
# Percentage
if [[ tests_percentage -gt 80 ]]; then
coverage_color=$BGreen
elif [[ tests_percentage -gt 50 ]]; then
coverage_color=$BYellow
elif [[ tests_percentage -gt 30 ]]; then
coverage_color=$BOrange
else
coverage_color=$BRed
fi
# Timeouts
if [[ $timeouts_count -eq 0 ]]; then
timeouts_color=$BGreen
else
timeouts_color=$BRed
fi
# Print
echo -e $BWhite "\n\n""===========" $UWhite"Summary"$Color_Off "\n"
echo -e " Passed $coverage_color$passed_tests/$total_tests$Color_Off tests ($coverage_color$tests_percentage%$Color_Off)"
echo -e " Got $timeouts_color$timeouts_count timeout(s)$Color_Off"
if [ "$OUTPUT_FILE" != "" ]; then
echo $tests_percentage > "$OUTPUT_FILE";
fi
echo
}
# ***********************************************************
#################
# Tests #
#################
echo -e "\n\n""===$BGreen TestsuitatorX Ultra Pro Max+ 365 Premium Gris Sidéral" "\n\n"$Color_Off
echo -e "\n$BBlue=== Builtins ===$Color_Off"
# echo
test_str "Hello" "echo Hello"
test_str "Hello there" "echo Hello there;"
test_str "Hello there;" "echo Hello there;"
test_str "Hello; there;" "echo Hello; echo there;"
test_str "'Hello'" "echo 'Hello there'"
test_str "Hello;Hello" "echo Hello; echo Wesh attends quoi; echo pouquoi je suis une ligne en dessous"
test_str "Echo -n" "echo -n Hello"
test_str "Echo -e" "echo -e 'Hello\nThere'"
test_str "Empty echo" "echo"
test_str "Spaced echo" " echo spaced "
test_str "Exit 0" "exit 0"
test_str "Exit 1" "exit 1"
test_str "True" "true"
test_str "False" "false"
test_str "cd basic" "cd /tmp; pwd"
test_str "cd maison" "cd; pwd"
test_str "Alias basic" "alias foo=echo; foo bar"
test_str "cat $0" "alias foo=echo; foo bar"
echo -e "\n$BBlue=== Programs ===$Color_Off"
test_str "LS" "ls"
test_str "Wrong ls" "sl"
test_str "IPA ma gueule" "ip a"
test_str "Wrong ls" "sl --bachibouzouk"
test_str "ls a b c" "ls a b c"
test_str "ls -a --best" "ls -a --best"
test_str "ls -a --help" "ls -a --help"
echo -e "\n$BBlue=== Quotes ===$Color_Off"
test_str "Single quotes" "echo 'Single Quote'"
test_str "Double quotes" "echo \"Double Quote\""
test_str "Mixed quotes 1" "echo \"Mixed 'Quotes'\""
test_str "Mixed quotes 2" "echo 'Mixed \"Quotes\"'"
test_str "Escaped double quote" "echo \"Escaped \\\"Quote\\\"\""
test_str "Variable in double quotes" "VAR=val; echo \"Variable \$VAR\""
test_str "Variable in single quotes" "VAR=val; echo 'Variable \$VAR'"
test_str "Backslash in double quotes" "echo \"Backslash \\\\\""
test_str "Newline in string" "echo \"New\nline\""
test_str "Empty quotes" "echo '' \"\""
test_str "Concatenated quotes" "echo 'a'\"b\"'c'"
echo -e "\n$BBlue=== Comments ===$Color_Off"
test_str "Hello commentaire" "echo Hello # Commentaire"
test_str "Comment only" "# Comment only"
test_str "Comment after space" "echo foo #bar"
test_str "Hash inside word" "echo foo#bar"
test_str "Comment with special chars" "# echo 'hidden' $PATH"
echo -e "\n$BBlue=== Pipelines ===$Color_Off"
test_str "Simple pipe" "echo Hello | cat"
test_str "Double pipe" "echo Hello | rev | rev" # Pas mal non ? c'est frenssé
test_str "Pipe with grep" "echo 'a\nb\nc' | grep b"
test_str "Pipe exit code (là c'est dur)" "true | false | true"
test_str "Pipe sequence" "echo a | echo b"
test_str "Pipe with chiottes" "ls | wc -l"
echo -e "\n$BBlue=== Redirections ===$Color_Off"
test_str "Redirect output" "echo hello > /tmp/test_redir; cat /tmp/test_redir; rm /tmp/test_redir"
test_str "Append output" "echo Hello > /tmp/test_redir; echo World >> /tmp/test_redir; cat /tmp/test_redir; rm /tmp/test_redir"
test_str "Redirect input" "echo Hello > /tmp/test_in; cat < /tmp/test_in; rm /tmp/test_in"
test_str "Redirect stderr" "echo Error >&2"
test_str "Redirect to null" "echo Hello > /dev/null"
test_str "Redirect both" "ls > /dev/null 2> /dev/null"
test_str "Redirect fd" "echo foo 2>&1"
test_str "Clobbering" "echo foo > file; echo bar > file; cat file; rm file"
test_str "Pipe and redirect" "echo foo | cat > file; cat file; rm file"
test_str "Heredoc basic" "cat << EOF\nhello\nEOF"
echo -e "\n$BBlue=== And/Or ===$Color_Off"
test_str "AND true" "true && echo Oui"
test_str "AND false" "false && echo Non"
test_str "OR true" "true || echo Non"
test_str "OR false" "false || echo Oui"
test_str "AND OR mixed 1" "true && false || echo Oui"
test_str "AND OR mixed 2" "false || true && echo Oui"
test_str "Sequence AND" "echo a && echo b"
test_str "Sequence OR" "echo a || echo b"
test_str "Negation true" "! true"
test_str "Negation false" "! false"
echo -e "\n$BBlue=== If ===$Color_Off"
test_str "If true" "if true; then echo Yes; fi"
test_str "If false else" "if false; then echo No; else echo Yes; fi"
test_str "If elif" "if false; then echo No; elif true; then echo Yes; fi"
test_str "Nested if" "if true; then if true; then echo Nested; fi; fi"
test_str "If multiple commands" "if true; then echo a; echo b; fi"
test_str "If complex condition" "if true && true; then echo Yes; fi"
test_str "If with negation" "if ! false; then echo Yes; fi"
test_str "If faut aller niquer sa mere" "if false; ! false; then echo Embrasse moi; fi"
echo -e "\n$BBlue=== Loops ===$Color_Off"
test_str "While false" "while false; do false; done"
test_str "While(false) true" "while false; do true; done"
test_str "Until(true) false" "until true; do false; done"
test_str "Until true" "until true; do true; done"
# test_str "While var" "a=2; while [ \$a -eq 2 ]; do \$a=3; done"
test_str "While arithmetic" "i=0; while [ \$i -lt 3 ]; do echo \$i; i=\$((i+1)); done"
test_str "Until arithmetic" "i=0; until [ \$i -ge 3 ]; do echo \$i; i=\$((i+1)); done"
test_str "While break" "while true; do echo break; break; done"
test_str "While continue" "i=0; while [ \$i -lt 3 ]; do i=\$((i+1)); if [ \$i -eq 2 ]; then continue; fi; echo \$i; done"
test_str "For loop basic" "for i in a b c; do echo \$i; done"
test_str "For loop glob" "for i in *; do echo \$i; done"
test_str "For loop break" "for i in 1 2 3; do break; done"
test_str "Azy continue la con de ta mère" "for i in 1 2 3; do continue; done"
test_str "For loop empty" "for i in; do echo \$i; done"
test_str "For loop variable" "VAR='a b'; for i in \$VAR; do echo \$i; done"
echo -e "\n$BBlue=== Case ===$Color_Off"
test_str "Case simple" "case a in a) echo Yes;; esac"
test_str "Case basique" "case z in a) echo No;; *) echo Default;; esac"
test_str "Case multiple patterns" "case a in a|b) echo Yes;; esac"
test_str "Case no match (c pcq t'es moche)" "case z in a) echo No;; esac"
test_str "Case with variable" "v=foo; case \$v in foo) echo Yes;; esac"
test_str "Case nested" "case a in a) case b in b) echo Nested;; esac;; esac"
echo -e "\n$BBlue=== Variables ===$Color_Off"
test_str "Set and get" "var=value; echo \$var"
test_str "Braces" "var=value; echo \${var}"
test_str "Multi-word value" "var='a b'; echo \$var"
test_str "Unset" "var=value; unset var; echo \$var"
test_str "Export" "export VAR=val; env | grep VAR"
test_str "Assignment return" "a=1"
test_str "Multiple assignment (ouais askip c possible)" "a=1 b=2; echo \$a \$b"
test_str "Default value" "unset v; echo \${v:-default}"
test_str "Assign default" "unset v; echo \${v:=default}; echo \$v"
test_str "Alternative value" "v=val; echo \${v:+alt}"
test_str "Use default if unset" "echo \${unset_var-default}"
test_str "\$@" "echo \$@"
test_str "\$*" "echo \$*"
test_str "\$?" "echo \$?"
test_str "\$$" "echo \$$"
test_str "\$1" "echo \$1"
test_str "\$2" "echo \$2"
test_str "\${10}" "echo \${10}"
test_str "\$#" "echo \$#"
test_str "\$RANDOM" "echo \$RANDOM"
test_str "\$UID" "echo \$UID"
test_str "\$OLDPWD" "echo \$OLDPWD"
test_str "\$PWD" "echo \$PWD"
test_str "\$IFS" "echo \$IFS"
test_str "Default exit status" "echo \$?"
test_str "Exit status" "true; echo \$?"
test_str "PID" "echo \$\$"
test_str "Arg count" "echo \$#"
echo -e "\n$BBlue=== Arithmetic expansions de fou furieux ===$Color_Off"
test_str "Arithmetic add" "echo \$((1 + 1))"
test_str "Arithmetic mul" "echo \$((2 * 3))"
test_str "Arithmetic div" "echo \$((10 / 2))"
test_str "Arithmetic nested" "echo \$(( (1+2) * 3 ))"
test_str "Arithmetic mod" "echo \$(( 5 % 2 ))"
test_str "Arithmetic var" "var=1; echo \$(( var + 1 ))"
test_str "Command subst" "echo \$(echo command)"
test_str "Backticks" "echo \`echo backticks\`"
test_str "Tilde" "echo ~"
test_str "Length" "v=abc; echo \${#v}"
echo -e "\n$BBlue=== Subshells du démon ===$Color_Off"
test_str "Subshell basic" "(echo a; echo b)"
test_str "Subshell isolation" "var=1; (var=2; echo \$var); echo \$var"
test_str "Subshell exit" "(exit 1); echo \$?"
test_str "Grouping basic" "{ echo a; echo b; }"
test_str "Grouping side effect" "var=1; { var=2; }; echo \$var"
test_str "Nested subshells" "( ( echo nested ) )"
test_str "Subshell redirect" "(echo a) > /tmp/sub; cat /tmp/sub; rm /tmp/sub"
test_str "Group redirect" "{ echo a; } > /tmp/grp; cat /tmp/grp; rm /tmp/grp"
summarize

View file

@ -1,272 +0,0 @@
#define _POSIX_C_SOURCE 200809L
#include <criterion/criterion.h>
#include <criterion/new/assert.h>
#include <stdlib.h>
#include <unistd.h>
#include "../../../src/expansion/expansion.h"
#include "../../../src/utils/ast/ast.h"
#include "../../../src/utils/hash_map/hash_map.h"
#include "../../../src/utils/vars/vars.h"
TestSuite(expand);
Test(expand, no_expansion)
{
char str[] = "echo something";
char *str_heap = strdup(str);
struct list *list = list_append(NULL, str_heap);
struct ast *ast = ast_create_command(list, NULL, NULL);
struct ast_command *ast_command = ast_get_command(ast);
bool ret = expand(ast_command, NULL);
cr_expect(ret, "expansion failed with %s", str);
cr_expect_str_eq((char *)ast_command->command->data, "echo something",
"String without variables should remain unchanged");
ast_free(&ast);
}
Test(expand, single_quotes_no_expansion)
{
char str[] = "echo '$VAR'";
char *str_heap = strdup(str);
struct list *list = list_append(NULL, str_heap);
struct ast *ast = ast_create_command(list, NULL, NULL);
struct ast_command *ast_command = ast_get_command(ast);
struct hash_map *vars = vars_init();
set_var_copy(vars, "VAR", "expanded");
bool ret = expand(ast_command, vars);
cr_expect(ret, "expansion failed with %s", str);
cr_expect_str_eq((char *)ast_command->command->data, "echo $VAR",
"Variable should not expand inside single quotes");
ast_free(&ast);
hash_map_free(&vars);
}
Test(expand, single_dollar)
{
char str[] = "echo $ sign";
char *str_heap = strdup(str);
struct list *list = list_append(NULL, str_heap);
struct ast *ast = ast_create_command(list, NULL, NULL);
struct ast_command *ast_command = ast_get_command(ast);
struct hash_map *vars = vars_init();
set_var_copy(vars, "VAR", "expanded");
bool ret = expand(ast_command, vars);
cr_expect(ret, "expansion failed with %s", str);
cr_expect_str_eq((char *)ast_command->command->data, "echo $ sign",
"Variable should not expand inside single quotes");
ast_free(&ast);
hash_map_free(&vars);
}
Test(expand, empty_braces_no_expansion)
{
char str[] = "echo ${}";
char *str_heap = strdup(str);
struct list *list = list_append(NULL, str_heap);
struct ast *ast = ast_create_command(list, NULL, NULL);
struct ast_command *ast_command = ast_get_command(ast);
struct hash_map *vars = vars_init();
set_var_copy(vars, "VAR", "expanded");
bool ret = expand(ast_command, vars);
cr_expect(ret == false, "expansion should fail with %s", str);
ast_free(&ast);
hash_map_free(&vars);
}
Test(expand, basic_expansion)
{
char str[] = "echo $VAR";
char *str_heap = strdup(str);
struct list *list = list_append(NULL, str_heap);
struct ast *ast = ast_create_command(list, NULL, NULL);
struct ast_command *ast_command = ast_get_command(ast);
struct hash_map *vars = vars_init();
set_var_copy(vars, "VAR", "expanded");
bool ret = expand(ast_command, vars);
cr_expect(ret, "expansion failed with %s", str);
cr_expect_str_eq((char *)ast_command->command->data, "echo expanded",
"Variable should expand correctly");
ast_free(&ast);
hash_map_free(&vars);
}
Test(expand, multiple_expansion)
{
char str[] = "echo $VAR1 $VAR2 ${VAR3}";
char *str_heap = strdup(str);
struct list *list = list_append(NULL, str_heap);
struct ast *ast = ast_create_command(list, NULL, NULL);
struct ast_command *ast_command = ast_get_command(ast);
struct hash_map *vars = vars_init();
set_var_copy(vars, "VAR1", "expanded");
set_var_copy(vars, "VAR2", "values");
set_var_copy(vars, "VAR3", "here");
bool ret = expand(ast_command, vars);
cr_expect(ret, "expansion failed with %s", str);
cr_expect_str_eq((char *)ast_command->command->data,
"echo expanded values here",
"Multiple variables should expand correctly");
ast_free(&ast);
hash_map_free(&vars);
}
Test(expand, env_variable)
{
char str[] = "echo $MY_ENV_VAR";
char *str_heap = strdup(str);
struct list *list = list_append(NULL, str_heap);
struct ast *ast = ast_create_command(list, NULL, NULL);
struct ast_command *ast_command = ast_get_command(ast);
setenv("MY_ENV_VAR", "environment", 0);
bool ret = expand(ast_command, NULL);
cr_expect(ret, "expansion failed with %s", str);
cr_expect_str_eq((char *)ast_command->command->data, "echo environment",
"Environment variable should expand correctly");
ast_free(&ast);
}
Test(expand, undefined_variable)
{
char str[] = "echo $UNDEFINED";
char *str_heap = strdup(str);
struct list *list = list_append(NULL, str_heap);
struct ast *ast = ast_create_command(list, NULL, NULL);
struct ast_command *ast_command = ast_get_command(ast);
struct hash_map *vars = vars_init();
bool ret = expand(ast_command, vars);
cr_expect(ret, "expansion failed with %s", str);
cr_expect_str_eq((char *)ast_command->command->data, "echo ",
"Undefined variable should expand to empty string");
ast_free(&ast);
hash_map_free(&vars);
}
Test(expand, nested_expansion)
{
char str[] = "echo $B";
char *str_heap = strdup(str);
struct list *list = list_append(NULL, str_heap);
struct ast *ast = ast_create_command(list, NULL, NULL);
struct ast_command *ast_command = ast_get_command(ast);
struct hash_map *vars = vars_init();
set_var_copy(vars, "A", "expanded");
set_var_copy(vars, "B", "$A");
bool ret = expand(ast_command, vars);
cr_expect(ret, "expansion failed with %s", str);
cr_expect_str_eq((char *)ast_command->command->data, "echo expanded",
"Nested variable should expand correctly");
ast_free(&ast);
hash_map_free(&vars);
}
// Test(expand, mixed_quotes_expansion)
// {
// char str[] = "echo \"$VAR1 and '$VAR2'\"";
// char *str_heap = strdup(str);
// struct list *list = list_append(NULL, str_heap);
// struct ast *ast = ast_create_command(list, NULL, NULL);
// struct ast_command *ast_command = ast_get_command(ast);
// struct hash_map *vars = vars_init();
// set_var_copy(vars, "VAR1", "expanded");
// set_var_copy(vars, "VAR2", "not_expanded");
// bool ret = expand(ast_command, vars);
// cr_expect(ret, "expansion failed with %s", str);
// cr_expect_str_eq((char *)ast_command->command->data,
// "echo \"expanded and $VAR2\"",
// "Variable in double quotes should expand, while variable "
// "in single quotes should not");
// ast_free(&ast);
// hash_map_free(&vars);
// }
Test(expand, adjacent_variables)
{
char str[] = "echo $VAR1$VAR2";
char *str_heap = strdup(str);
struct list *list = list_append(NULL, str_heap);
struct ast *ast = ast_create_command(list, NULL, NULL);
struct ast_command *ast_command = ast_get_command(ast);
struct hash_map *vars = vars_init();
set_var_copy(vars, "VAR1", "hello");
set_var_copy(vars, "VAR2", "world");
bool ret = expand(ast_command, vars);
cr_expect(ret, "expansion failed with %s", str);
cr_expect_str_eq((char *)ast_command->command->data, "echo helloworld",
"Adjacent variables should expand correctly");
ast_free(&ast);
hash_map_free(&vars);
}
Test(expand, random)
{
char str[] = "$RANDOM";
char *str_heap = strdup(str);
struct list *list = list_append(NULL, str_heap);
struct ast *ast = ast_create_command(list, NULL, NULL);
struct ast_command *ast_command = ast_get_command(ast);
bool ret = expand(ast_command, NULL);
cr_expect(ret, "expansion failed with %s", str);
int rnd = atoi((char *)ast_command->command->data);
cr_assert(rnd >= 0 && rnd <= 32767,
"RANDOM variable should expand to a value between 0 and 32767");
ast_free(&ast);
}
Test(expand, pid)
{
char str[] = "$$";
char *str_heap = strdup(str);
struct list *list = list_append(NULL, str_heap);
struct ast *ast = ast_create_command(list, NULL, NULL);
struct ast_command *ast_command = ast_get_command(ast);
struct hash_map *vars = vars_init();
bool ret = expand(ast_command, vars);
cr_expect(ret, "expansion failed with %s", str);
int pid = atoi((char *)ast_command->command->data);
cr_assert(pid == getpid(),
"$$ variable should expand to the pid of the process");
ast_free(&ast);
hash_map_free(&vars);
}
Test(expand, default_last_exit_code)
{
char str[] = "$?";
char *str_heap = strdup(str);
struct list *list = list_append(NULL, str_heap);
struct ast *ast = ast_create_command(list, NULL, NULL);
struct ast_command *ast_command = ast_get_command(ast);
struct hash_map *vars = vars_init();
bool ret = expand(ast_command, vars);
cr_expect(ret, "expansion failed with %s", str);
int code = atoi((char *)ast_command->command->data);
cr_assert(code == 0,
"$? variable should expand to the last exit code (default 0)");
ast_free(&ast);
hash_map_free(&vars);
}

View file

@ -1,72 +0,0 @@
#include <criterion/criterion.h>
#include <criterion/new/assert.h>
#include "../../../src/expansion/expansion.h"
TestSuite(parse_subshell_str);
Test(parse_subshell_str, basic_subshell)
{
char *input = "(ls -l)";
char *extracted_var = NULL;
size_t r = parse_subshell_str(input, &extracted_var);
cr_expect(r == 7);
cr_expect_str_eq(extracted_var, "ls -l");
free(extracted_var);
}
Test(parse_subshell_str, multi_basic_subshell)
{
char *input = "(echo hello) and (echo world)";
char *extracted_var = NULL;
size_t r = parse_subshell_str(input, &extracted_var);
cr_expect(r == 12);
cr_expect_str_eq(extracted_var, "echo hello");
free(extracted_var);
input += r + 5; // skip " and "
r = parse_subshell_str(input, &extracted_var);
cr_expect(r == 12);
cr_expect_str_eq(extracted_var, "echo world");
free(extracted_var);
}
Test(parse_subshell_str, incomplete_braces)
{
char *input = "(echo hello";
char *extracted_var = NULL;
size_t r = parse_subshell_str(input, &extracted_var);
cr_expect(r == 0);
cr_expect(extracted_var == NULL);
}
Test(parse_subshell_str, empty_braces)
{
char *input = "()";
char *extracted_var = NULL;
size_t r = parse_subshell_str(input, &extracted_var);
cr_expect(r == 0);
cr_expect(extracted_var == NULL);
}
Test(parse_subshell_str, nested_subshell)
{
char *input = "(echo (nested))";
char *extracted_var = NULL;
size_t r = parse_subshell_str(input, &extracted_var);
cr_expect(r == 15);
cr_expect_str_eq(extracted_var, "echo (nested)");
free(extracted_var);
char *nested = input + 6; // point to the nested subshell
r = parse_subshell_str(nested, &extracted_var);
cr_expect(r == 8);
cr_expect_str_eq(extracted_var, "nested");
free(extracted_var);
}

View file

@ -1,143 +0,0 @@
#include <criterion/criterion.h>
#include <criterion/new/assert.h>
#include "../../../src/expansion/expansion.h"
TestSuite(parse_var_name);
Test(parse_var_name, basic_variable)
{
char *input = "$MY_VAR";
char *extracted_var = NULL;
size_t r = parse_var_name(input, &extracted_var);
cr_expect(r == 7);
cr_expect_str_eq(extracted_var, "MY_VAR");
free(extracted_var);
}
Test(parse_var_name, multi_basic_variable)
{
char *input = "$MY$VAR";
char *extracted_var = NULL;
size_t r = parse_var_name(input, &extracted_var);
cr_expect(r == 3);
cr_expect_str_eq(extracted_var, "MY");
free(extracted_var);
input += r;
r = parse_var_name(input, &extracted_var);
cr_expect(r == 4);
cr_expect_str_eq(extracted_var, "VAR");
free(extracted_var);
}
Test(parse_var_name, variable_with_braces)
{
char *input = "${MY_VAR}";
char *extracted_var = NULL;
size_t r = parse_var_name(input, &extracted_var);
cr_expect(r == 9);
cr_expect_str_eq(extracted_var, "MY_VAR");
free(extracted_var);
}
Test(parse_var_name, special_variable)
{
char *input = "$1";
char *extracted_var = NULL;
size_t r = parse_var_name(input, &extracted_var);
cr_expect(r == 2);
cr_expect_str_eq(extracted_var, "1");
free(extracted_var);
}
Test(parse_var_name, special_variable_with_braces)
{
char *input = "${1}";
char *extracted_var = NULL;
size_t r = parse_var_name(input, &extracted_var);
cr_expect(r == 4);
cr_expect_str_eq(extracted_var, "1");
free(extracted_var);
}
Test(parse_var_name, incomplete_braces)
{
char *input = "${MY_VAR";
char *extracted_var = NULL;
size_t r = parse_var_name(input, &extracted_var);
cr_expect(r == 0);
cr_expect(extracted_var == NULL);
}
Test(parse_var_name, empty_braces)
{
char *input = "${}";
char *extracted_var = NULL;
size_t r = parse_var_name(input, &extracted_var);
cr_expect(r == 0);
cr_expect(extracted_var == NULL);
}
Test(parse_var_name, dollar_sign_only)
{
char *input = "$";
char *extracted_var = NULL;
size_t r = parse_var_name(input, &extracted_var);
cr_expect(r == 0);
cr_expect(extracted_var == NULL);
}
Test(parse_var_name, variable_followed_by_dollar)
{
char *input = "$MY$VAR$";
char *extracted_var = NULL;
size_t r = parse_var_name(input, &extracted_var);
cr_expect(r == 3);
cr_expect_str_eq(extracted_var, "MY");
free(extracted_var);
input += r;
r = parse_var_name(input, &extracted_var);
cr_expect(r == 4);
cr_expect_str_eq(extracted_var, "VAR");
free(extracted_var);
input += r;
r = parse_var_name(input, &extracted_var);
cr_expect(r == 0);
cr_expect(extracted_var == NULL);
}
Test(parse_var_name, special_variable_followed_by_text)
{
char *input = "$1VAR";
char *extracted_var = NULL;
size_t r = parse_var_name(input, &extracted_var);
cr_expect(r == 2);
cr_expect_str_eq(extracted_var, "1");
free(extracted_var);
}
Test(parse_var_name, bad_variable_with_braces)
{
char *input = "${1VAR}";
char *extracted_var = NULL;
size_t r = parse_var_name(input, &extracted_var);
cr_expect(r == 0);
cr_expect(extracted_var == NULL);
}

View file

@ -1,96 +0,0 @@
#include <criterion/criterion.h>
#include <criterion/new/assert.h>
#include <io_backend/io_backend.h>
#include <stdio.h>
TestSuite(IO_Backend);
// IOB Init
Test(IO_Backend, init_null)
{
struct iob_context ctx = { .mode = IOB_MODE_NULL, .args = NULL };
int actual = iob_init(&ctx);
int expected = IOB_ERROR_BAD_ARG;
cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual);
}
Test(IO_Backend, init_stdin)
{
struct iob_context ctx = { .mode = IOB_MODE_STDIN, .args = NULL };
int actual = iob_init(&ctx);
int expected = 0;
cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual);
iob_close();
}
// WARNING: this one could fail because of iob_close in the previous test
// Same applies for other tests
Test(IO_Backend, init_script)
{
char *script_name = "script.tmp";
struct iob_context ctx = { .mode = IOB_MODE_SCRIPT, .args = script_name };
// Create file
FILE *f = fopen(script_name, "w");
fclose(f);
int actual = iob_init(&ctx);
int expected = 0;
cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual);
iob_close();
remove(script_name);
}
Test(IO_Backend, init_script_not_a_file)
{
char *script_name = "not_a_file.tmp";
struct iob_context ctx = { .mode = IOB_MODE_SCRIPT, .args = script_name };
int actual = iob_init(&ctx);
int expected = IOB_ERROR_CANNOT_OPEN_FILE;
cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual);
}
Test(IO_Backend, init_script_null)
{
struct iob_context ctx = { .mode = IOB_MODE_SCRIPT, .args = NULL };
int actual = iob_init(&ctx);
int expected = IOB_ERROR_CANNOT_OPEN_FILE;
cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual);
}
Test(IO_Backend, init_cmd)
{
char *cmd = "iamacommand --yesido";
struct iob_context ctx = { .mode = IOB_MODE_CMD, .args = cmd };
int actual = iob_init(&ctx);
int expected = 0;
cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual);
iob_close();
}
Test(IO_Backend, init_cmd_null)
{
struct iob_context ctx = { .mode = IOB_MODE_CMD, .args = NULL };
int actual = iob_init(&ctx);
int expected = IOB_ERROR_BAD_ARG;
cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual);
}
Test(IO_Backend, init_already_init)
{
char *cmd = "iamacommand --yesido";
struct iob_context ctx = { .mode = IOB_MODE_CMD, .args = cmd };
iob_init(&ctx);
int actual = iob_init(&ctx);
int expected = IOB_ERROR_MODULE_ALREADY_INITIALIZED;
cr_expect(actual == expected, "Expected: %d. Got: %d", expected, actual);
iob_close();
}
Test(IO_Backend, close_not_init)
{
iob_close(); // Shouldn't do anything
}
// IOB Stream
// TODO

View file

@ -1,62 +0,0 @@
#include <criterion/criterion.h>
#include <criterion/new/assert.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include "io_backend/io_backend.h"
#include "lexer/lexer.h"
TestSuite(peek_token);
Test(peek_token, basic_empty)
{
// simulates input
char command[] = "";
struct iob_context context = { IOB_MODE_CMD, command };
iob_init(&context);
struct lexer_context ctx = { .end_previous_token = NULL,
.remaining_chars = 0,
.only_digits = false,
.previous_token = NULL,
.current_token = NULL };
// test
struct token *tok = peek_token(&ctx);
// expected
enum token_type type_expected = TOKEN_EOF;
char *string_expected = NULL;
cr_assert(tok->data == string_expected);
cr_assert(type_expected == tok->type);
free_token(&tok);
}
Test(peek_token, basic_word)
{
// simulates input
char command[] = "hello";
struct iob_context context = { IOB_MODE_CMD, command };
iob_init(&context);
struct lexer_context ctx = { .end_previous_token = NULL,
.remaining_chars = 0,
.only_digits = false,
.previous_token = NULL,
.current_token = NULL };
// test
struct token *tok = peek_token(&ctx);
// expected
enum token_type type_expected = TOKEN_WORD;
char string_expected[] = "hello";
cr_assert(eq(str, string_expected, tok->data));
cr_assert(type_expected == tok->type);
free_token(&tok);
}

View file

@ -1,178 +0,0 @@
#include "../../../src/utils/args/args.h"
#include <criterion/criterion.h>
#include <criterion/new/assert.h>
#include <criterion/redirect.h>
#include "../../../src/utils/hash_map/hash_map.h"
#include "../../../src/utils/vars/vars.h"
TestSuite(utils_args);
Test(utils_args, basic_command)
{
int argc = 3;
struct args_options options;
char *input[] = { "program", "-c", "echo Hello, World!" };
struct hash_map *vars = vars_init();
int r = args_handler(argc, input, &options, vars);
cr_expect(r == 0);
// cr_expect(options.pretty_print == false);
cr_expect(options.verbose == false);
cr_expect(options.type == INPUT_CMD);
cr_expect(eq(options.input_source, "echo Hello, World!"));
hash_map_free(&vars);
}
Test(utils_args, basic_command_with_flags)
{
int argc = 5;
struct args_options options;
/* char *input[] = { "program", "--pretty-print", "-c", "echo Hello,
World!",
"--verbose" };*/
char *input[] = { "program", "-c", "echo Hello, World!", "--verbose" };
struct hash_map *vars = vars_init();
int r = args_handler(argc, input, &options, vars);
cr_expect(r == 0);
// cr_expect(options.pretty_print == true);
cr_expect(options.verbose == true);
cr_expect(options.type == INPUT_CMD);
cr_expect(eq(options.input_source, "echo Hello, World!"));
hash_map_free(&vars);
}
Test(utils_args, basic_file_input)
{
int argc = 2;
struct args_options options;
char *input[] = { "program", "input.txt" };
struct hash_map *vars = vars_init();
int r = args_handler(argc, input, &options, vars);
cr_expect(r == 0);
// cr_expect(options.pretty_print == false);
cr_expect(options.verbose == false);
cr_expect(options.type == INPUT_FILE);
cr_expect(eq(options.input_source, "input.txt"));
hash_map_free(&vars);
}
Test(utils_args, basic_file_input_with_flags)
{
int argc = 4;
struct args_options options;
// char *input[] = { "program", "--verbose", "input.txt", "--pretty-print"
// };
char *input[] = { "program", "--verbose", "input.txt" };
struct hash_map *vars = vars_init();
int r = args_handler(argc, input, &options, vars);
cr_expect(r == 0);
// cr_expect(options.pretty_print == true);
cr_expect(options.verbose == true);
cr_expect(options.type == INPUT_FILE);
cr_expect(eq(options.input_source, "input.txt"));
hash_map_free(&vars);
}
Test(utils_args, basic_stdin_input)
{
int argc = 1;
struct args_options options;
char *input[] = { "program" };
struct hash_map *vars = vars_init();
int r = args_handler(argc, input, &options, vars);
cr_expect(r == 0);
// cr_expect(options.pretty_print == false);
cr_expect(options.verbose == false);
cr_expect(options.type == INPUT_STDIN);
cr_expect(options.input_source == NULL);
hash_map_free(&vars);
}
Test(utils_args, pretty_print_and_verbose_flags)
{
int argc = 3;
struct args_options options;
// char *input[] = { "program", "--pretty-print", "--verbose" };
char *input[] = { "program", "--verbose" };
struct hash_map *vars = vars_init();
int r = args_handler(argc, input, &options, vars);
cr_expect(r == 0);
// cr_expect(options.pretty_print == true);
cr_expect(options.verbose == true);
cr_expect(options.type == INPUT_STDIN);
cr_expect(options.input_source == NULL);
hash_map_free(&vars);
}
Test(utils_args, missing_command_after_c, .init = cr_redirect_stderr)
{
int argc = 2;
struct args_options options;
char *input[] = { "program", "-c" };
struct hash_map *vars = vars_init();
int r = args_handler(argc, input, &options, vars);
cr_expect(r != 0);
cr_assert_stderr_eq_str("No command provided after -c\n");
hash_map_free(&vars);
}
Test(utils_args, unknown_option, .init = cr_redirect_stderr)
{
int argc = 2;
struct args_options options;
char *input[] = { "program", "--unknown" };
struct hash_map *vars = vars_init();
int r = args_handler(argc, input, &options, vars);
cr_expect(r != 0);
cr_assert_stderr_eq_str("Unknown option: --unknown\n");
hash_map_free(&vars);
}
Test(utils_args, multiple_command, .init = cr_redirect_stderr)
{
int argc = 4;
struct args_options options;
char *input[] = { "program", "exec.sh", "-c", "echo World" };
struct hash_map *vars = vars_init();
int r = args_handler(argc, input, &options, vars);
cr_expect(r != 0);
cr_assert_stderr_eq_str("Multiple input sources specified: echo World\n");
hash_map_free(&vars);
}
Test(utils_args, command_with_additional_arguments)
{
int argc = 5;
struct args_options options;
char *input[] = { "program", "-c", "echo Hello", "arg1", "arg2" };
struct hash_map *vars = vars_init();
int r = args_handler(argc, input, &options, vars);
cr_expect(r == 0);
cr_expect(options.type == INPUT_CMD);
cr_expect(eq(options.input_source, "echo Hello"));
cr_expect_str_eq(get_var(vars, "0"), "program");
cr_expect_str_eq(get_var(vars, "1"), "arg1");
cr_expect_str_eq(get_var(vars, "2"), "arg2");
hash_map_free(&vars);
}

View file

@ -1,217 +0,0 @@
#define _POSIX_C_SOURCE 200809L
#include "../../../src/utils/hash_map/hash_map.h"
#include <criterion/criterion.h>
#include <criterion/new/assert.h>
#include <criterion/redirect.h>
#include <stdio.h>
TestSuite(utils_hash_map);
Test(utils_hash_map, init_free)
{
struct hash_map *map = hash_map_init(10);
cr_expect_not_null(map);
cr_expect_eq(map->size, 10);
hash_map_free(&map);
}
Test(utils_hash_map, insert_basic)
{
struct hash_map *map = hash_map_init(10);
cr_expect_not_null(map);
cr_expect_eq(map->size, 10);
bool updated = false;
bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), &updated);
cr_expect_eq(res, true);
cr_expect_eq(updated, false);
hash_map_free(&map);
}
Test(utils_hash_map, insert_multiple)
{
struct hash_map *map = hash_map_init(10);
cr_expect_not_null(map);
cr_expect_eq(map->size, 10);
bool updated = false;
bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), &updated);
cr_expect_eq(res, true);
cr_expect_eq(updated, false);
res = hash_map_insert(map, strdup("key2"), strdup("value2"), &updated);
cr_expect_eq(res, true);
cr_expect_eq(updated, false);
hash_map_free(&map);
}
Test(utils_hash_map, insert_update)
{
struct hash_map *map = hash_map_init(10);
cr_expect_not_null(map);
cr_expect_eq(map->size, 10);
bool updated = false;
bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), &updated);
cr_expect_eq(res, true);
cr_expect_eq(updated, false);
res = hash_map_insert(map, "key1", strdup("value2"), &updated);
cr_expect_eq(res, true);
cr_expect_eq(updated, true);
hash_map_free(&map);
}
Test(utils_hash_map, insert_update_multiple)
{
struct hash_map *map = hash_map_init(10);
cr_expect_not_null(map);
cr_expect_eq(map->size, 10);
bool updated = false;
bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), &updated);
cr_expect_eq(res, true);
cr_expect_eq(updated, false);
res = hash_map_insert(map, strdup("key2"), strdup("value2"), &updated);
cr_expect_eq(res, true);
cr_expect_eq(updated, false);
res = hash_map_insert(map, "key1", strdup("value2"), &updated);
cr_expect_eq(res, true);
cr_expect_eq(updated, true);
res = hash_map_insert(map, "key1", strdup("value3"), &updated);
cr_expect_eq(res, true);
cr_expect_eq(updated, true);
hash_map_free(&map);
}
Test(utils_hash_map, get_basic)
{
struct hash_map *map = hash_map_init(10);
cr_expect_not_null(map);
cr_expect_eq(map->size, 10);
bool updated = false;
bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), &updated);
cr_expect_eq(res, true);
cr_expect_eq(updated, false);
char *value = (char *)hash_map_get(map, "key1");
cr_expect_str_eq(value, "value1");
hash_map_free(&map);
}
Test(utils_hash_map, get_after_update)
{
struct hash_map *map = hash_map_init(10);
cr_expect_not_null(map);
cr_expect_eq(map->size, 10);
bool updated = false;
bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), &updated);
cr_expect_eq(res, true);
cr_expect_eq(updated, false);
res = hash_map_insert(map, "key1", strdup("value2"), &updated);
cr_expect_eq(res, true);
cr_expect_eq(updated, true);
char *value = (char *)hash_map_get(map, "key1");
cr_expect_str_eq(value, "value2");
hash_map_free(&map);
}
Test(utils_hash_map, get_unknown_key)
{
struct hash_map *map = hash_map_init(10);
cr_expect_not_null(map);
cr_expect_eq(map->size, 10);
char *value = (char *)hash_map_get(map, "unknown_key");
cr_expect_null(value);
hash_map_free(&map);
}
Test(utils_hash_map, delete_key)
{
struct hash_map *map = hash_map_init(10);
cr_expect_not_null(map);
cr_expect_eq(map->size, 10);
bool updated = false;
bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), &updated);
cr_expect_eq(res, true);
cr_expect_eq(updated, false);
res = hash_map_remove(map, "key1");
cr_expect_eq(res, true);
char *value = (char *)hash_map_get(map, "key1");
cr_expect_null(value);
hash_map_free(&map);
}
Test(utils_hash_map, delete_unknown_key)
{
struct hash_map *map = hash_map_init(10);
cr_expect_not_null(map);
cr_expect_eq(map->size, 10);
bool res = hash_map_remove(map, "unknown_key");
cr_expect_eq(res, false);
hash_map_free(&map);
}
Test(utils_hash_map, free_nonnull_map)
{
struct hash_map *map = hash_map_init(10);
cr_expect_not_null(map);
cr_expect_eq(map->size, 10);
bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), NULL);
cr_expect_eq(res, true);
res = hash_map_insert(map, strdup("key2"), strdup("value2"), NULL);
cr_expect_eq(res, true);
hash_map_free(&map);
}
Test(utils_hash_map, free_null_map)
{
hash_map_free(NULL);
}
static size_t count = 0;
void foreach_fn(const char *key, const void *value)
{
printf("Key: %s, Value: %s\n", key, (const char *)value);
count++;
}
Test(utils_hash_map, foreach, .init = cr_redirect_stdout)
{
struct hash_map *map = hash_map_init(10);
cr_expect_not_null(map);
cr_expect_eq(map->size, 10);
bool res = hash_map_insert(map, strdup("key1"), strdup("value1"), NULL);
cr_expect_eq(res, true);
res = hash_map_insert(map, strdup("key2"), strdup("value2"), NULL);
cr_expect_eq(res, true);
count = 0;
hash_map_foreach(map, foreach_fn);
fflush(stdout);
cr_expect_eq(count, 2);
cr_expect_stdout_eq_str(
"Key: key2, Value: value2\nKey: key1, Value: value1\n");
hash_map_free(&map);
}

View file

@ -1,85 +0,0 @@
#define _POSIX_C_SOURCE 200809L
#include <criterion/criterion.h>
#include <criterion/new/assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../../../src/utils/string_utils/string_utils.h"
TestSuite(insert_into);
Test(insert_into, basic)
{
char *dest = strdup("The <WORD> is nice.");
const char *src = "weather";
size_t pos = 4;
char *result = insert_into(dest, src, pos, 6);
cr_expect(result != NULL);
cr_expect(eq(str, result, "The weather is nice."));
if (result)
free(result);
}
Test(insert_into, begin)
{
char *dest = strdup("Hello World!");
const char *src = "Hi";
size_t pos = 0;
char *result = insert_into(dest, src, pos, 5);
cr_expect(result != NULL);
cr_expect(eq(str, result, "Hi World!"));
if (result)
free(result);
}
Test(insert_into, end)
{
char *dest = strdup("The number is 1024");
const char *src = "2048";
size_t pos = 14;
char *result = insert_into(dest, src, pos, 4);
cr_expect(result != NULL);
cr_expect(eq(str, result, "The number is 2048"));
if (result)
free(result);
}
Test(insert_into, big)
{
char *dest = strdup("I could insert [VAR] here.");
const char *src = "a very very long string";
size_t pos = 15;
char *result = insert_into(dest, src, pos, 5);
cr_expect(result != NULL);
cr_expect(eq(str, result, "I could insert a very very long string here."));
if (result)
free(result);
}
Test(insert_into, small)
{
char *dest = strdup("I could insert [VARNAME_IS_SO_LONG] string here.");
const char *src = "a short";
size_t pos = 15;
char *result = insert_into(dest, src, pos, 20);
cr_expect(result != NULL);
cr_expect(eq(str, result, "I could insert a short string here."));
if (result)
free(result);
}

View file

@ -1,305 +0,0 @@
#define _POSIX_C_SOURCE 200809L
#include "../../../src/utils/lists/lists.h"
#include <criterion/criterion.h>
#include <criterion/new/assert.h>
#include <criterion/redirect.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
TestSuite(lists);
Test(lists, append_empty)
{
struct list *lst = NULL;
lst = list_append(lst, (void *)1);
cr_expect(lst != NULL);
cr_expect(lst->data == (void *)1);
cr_expect(lst->next == NULL);
cr_expect(list_length(lst) == 1);
list_destroy(&lst);
cr_expect(lst == NULL);
}
Test(lists, prepend_empty)
{
struct list *lst = NULL;
lst = list_prepend(lst, (void *)1);
cr_expect(lst != NULL);
cr_expect(lst->data == (void *)1);
cr_expect(lst->next == NULL);
cr_expect(list_length(lst) == 1);
list_destroy(&lst);
cr_expect(lst == NULL);
}
Test(lists, insert_empty)
{
struct list *lst = NULL;
lst = list_insert(lst, (void *)1, 0);
cr_expect(lst != NULL);
cr_expect(lst->data == (void *)1);
cr_expect(lst->next == NULL);
cr_expect(list_length(lst) == 1);
list_destroy(&lst);
cr_expect(lst == NULL);
}
Test(lists, insert_out_of_bounds)
{
struct list *lst = NULL;
lst = list_insert(lst, (void *)1, 5);
cr_expect(lst != NULL);
cr_expect(lst->data == (void *)1);
cr_expect(lst->next == NULL);
cr_expect(list_length(lst) == 1);
list_destroy(&lst);
cr_expect(lst == NULL);
}
Test(lists, remove_out_of_bounds)
{
struct list *lst = NULL;
lst = list_append(lst, (void *)1);
lst = list_remove(lst, 5);
cr_expect(lst != NULL);
cr_expect(lst->data == (void *)1);
cr_expect(lst->next == NULL);
cr_expect(list_length(lst) == 1);
list_destroy(&lst);
cr_expect(lst == NULL);
}
Test(lists, append_multiple)
{
struct list *lst = NULL;
lst = list_append(lst, (void *)1);
lst = list_append(lst, (void *)2);
lst = list_append(lst, (void *)3);
cr_expect(lst != NULL);
cr_expect(lst->data == (void *)1);
cr_expect(lst->next->data == (void *)2);
cr_expect(lst->next->next->data == (void *)3);
cr_expect(lst->next->next->next == NULL);
cr_expect(list_length(lst) == 3);
list_destroy(&lst);
cr_expect(lst == NULL);
}
Test(lists, prepend_multiple)
{
struct list *lst = NULL;
lst = list_prepend(lst, (void *)1);
lst = list_prepend(lst, (void *)2);
lst = list_prepend(lst, (void *)3);
cr_expect(lst != NULL);
cr_expect(lst->data == (void *)3);
cr_expect(lst->next->data == (void *)2);
cr_expect(lst->next->next->data == (void *)1);
cr_expect(lst->next->next->next == NULL);
cr_expect(list_length(lst) == 3);
list_destroy(&lst);
cr_expect(lst == NULL);
}
Test(lists, insert_multiple)
{
struct list *lst = NULL;
lst = list_insert(lst, (void *)1, 0);
lst = list_insert(lst, (void *)3, 1);
lst = list_insert(lst, (void *)2, 1);
cr_expect(lst != NULL);
cr_expect(lst->data == (void *)1);
cr_expect(lst->next->data == (void *)2);
cr_expect(lst->next->next->data == (void *)3);
cr_expect(lst->next->next->next == NULL);
cr_expect(list_length(lst) == 3);
list_destroy(&lst);
cr_expect(lst == NULL);
}
Test(lists, append)
{
struct list *lst = NULL;
lst = list_prepend(lst, (void *)2);
lst = list_prepend(lst, (void *)1);
lst = list_append(lst, (void *)3);
cr_expect(lst != NULL);
cr_expect(lst->data == (void *)1);
cr_expect(lst->next->data == (void *)2);
cr_expect(lst->next->next->data == (void *)3);
cr_expect(lst->next->next->next == NULL);
cr_expect(list_length(lst) == 3);
list_destroy(&lst);
cr_expect(lst == NULL);
}
Test(lists, prepend)
{
struct list *lst = NULL;
lst = list_append(lst, (void *)1);
lst = list_append(lst, (void *)2);
lst = list_prepend(lst, (void *)0);
cr_expect(lst != NULL);
cr_expect(lst->data == (void *)0);
cr_expect(lst->next->data == (void *)1);
cr_expect(lst->next->next->data == (void *)2);
cr_expect(lst->next->next->next == NULL);
cr_expect(list_length(lst) == 3);
list_destroy(&lst);
cr_expect(lst == NULL);
}
Test(lists, insert)
{
struct list *lst = NULL;
lst = list_append(lst, (void *)1);
lst = list_append(lst, (void *)3);
lst = list_insert(lst, (void *)2, 1);
lst = list_insert(lst, (void *)0, 0);
cr_expect(lst != NULL);
cr_expect(lst->data == (void *)0);
cr_expect(lst->next->data == (void *)1);
cr_expect(lst->next->next->data == (void *)2);
cr_expect(lst->next->next->next->data == (void *)3);
cr_expect(lst->next->next->next->next == NULL);
cr_expect(list_length(lst) == 4);
list_destroy(&lst);
cr_expect(lst == NULL);
}
Test(lists, remove)
{
struct list *lst = NULL;
lst = list_append(lst, (void *)1);
lst = list_append(lst, (void *)2);
lst = list_append(lst, (void *)3);
lst = list_append(lst, (void *)4);
lst = list_remove(lst, 1); // remove 2
lst = list_remove(lst, 2); // remove 4
lst = list_remove(lst, 0); // remove 1
cr_expect(lst != NULL);
cr_expect(lst->data == (void *)3);
cr_expect(lst->next == NULL);
cr_expect(list_length(lst) == 1);
list_destroy(&lst);
cr_expect(lst == NULL);
}
Test(lists, destroy_null)
{
struct list *lst = NULL;
list_destroy(&lst);
cr_expect(lst == NULL);
}
Test(lists, deep_destroy_null)
{
struct list *lst = NULL;
list_destroy(&lst);
cr_expect(lst == NULL);
}
Test(lists, deep_destroy)
{
struct list *lst = NULL;
lst = list_append(lst, strdup("string1"));
lst = list_append(lst, strdup("string2"));
lst = list_append(lst, strdup("string3"));
list_deep_destroy(lst);
}
Test(lists, length_empty)
{
struct list *lst = NULL;
cr_expect(list_length(lst) == 0);
}
Test(lists, print_empty, .init = cr_redirect_stdout)
{
struct list *lst = NULL;
list_print(lst);
cr_expect_stdout_eq_str("");
}
Test(lists, print_non_empty, .init = cr_redirect_stdout)
{
struct list *lst = NULL;
lst = list_append(lst, (void *)1);
lst = list_append(lst, (void *)2);
lst = list_append(lst, (void *)3);
list_print(lst);
fflush(stdout);
cr_expect_stdout_eq_str("0x1 0x2 0x3\n");
list_destroy(&lst);
cr_expect(lst == NULL);
}
Test(lists, find_empty)
{
struct list *lst = NULL;
cr_expect(list_find(lst, (void *)1) == -1);
}
Test(lists, find_non_empty)
{
struct list *lst = NULL;
lst = list_append(lst, (void *)1);
lst = list_append(lst, (void *)2);
lst = list_append(lst, (void *)3);
cr_expect(list_find(lst, (void *)1) == 0);
cr_expect(list_find(lst, (void *)2) == 1);
cr_expect(list_find(lst, (void *)3) == 2);
cr_expect(list_find(lst, (void *)4) == -1); // not found
list_destroy(&lst);
cr_expect(lst == NULL);
}
static void fold_func(void *acc, void *data)
{
*(int *)acc += *(int *)data;
}
Test(lists, fold)
{
struct list *lst = NULL;
int v1 = 10, v2 = 20, v3 = 30;
lst = list_append(lst, &v1);
lst = list_append(lst, &v2);
lst = list_append(lst, &v3);
int sum = 0;
list_fold(lst, &sum, fold_func);
cr_expect(sum == 60);
list_destroy(&lst);
cr_expect(lst == NULL);
}

View file

@ -1,105 +0,0 @@
#include <criterion/criterion.h>
#include <criterion/new/assert.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include "../../../src/utils/string_utils/string_utils.h"
TestSuite(string_utils);
Test(string_utils, skipblank_basic)
{
char input[] = " Hello World";
char expected_str[] = "Hello World";
char *trimmed = trim_blank_left(input);
ssize_t offset = trimmed - input;
ssize_t expected = 2;
cr_expect(eq(str, trimmed, expected_str));
cr_expect(offset == expected);
}
Test(string_utils, skipblank_noblank)
{
char input[] = "Hello World";
char expected_str[] = "Hello World";
char *trimmed = trim_blank_left(input);
ssize_t offset = trimmed - input;
ssize_t expected = 0;
cr_expect(eq(str, trimmed, expected_str));
cr_expect(offset == expected);
}
Test(string_utils, skipblank_tab)
{
char input[] = "\tHello World";
char expected_str[] = "Hello World";
char *trimmed = trim_blank_left(input);
ssize_t offset = trimmed - input;
ssize_t expected = 1;
cr_expect(eq(str, trimmed, expected_str));
cr_expect(offset == expected);
}
Test(string_utils, skipblank_space_tab)
{
char input[] = " \tHello World";
char expected_str[] = "Hello World";
char *trimmed = trim_blank_left(input);
ssize_t offset = trimmed - input;
ssize_t expected = 2;
cr_expect(eq(str, trimmed, expected_str));
cr_expect(offset == expected);
}
Test(string_utils, skipblank_2tab_1space)
{
char input[] = "\t \tHello World";
char expected_str[] = "Hello World";
char *trimmed = trim_blank_left(input);
ssize_t offset = trimmed - input;
ssize_t expected = 3;
cr_expect(eq(str, trimmed, expected_str));
cr_expect(offset == expected);
}
Test(string_utils, skipblank_a_lot)
{
char input[] = "\t \t \tHello World";
char expected_str[] = "Hello World";
char *trimmed = trim_blank_left(input);
ssize_t offset = trimmed - input;
ssize_t expected = 8;
cr_expect(eq(str, trimmed, expected_str));
cr_expect(offset == expected);
}
Test(string_utils, skipblank_newline)
{
char input[] = "\nHello World";
char expected_str[] = "\nHello World";
char *trimmed = trim_blank_left(input);
ssize_t offset = trimmed - input;
ssize_t expected = 0;
cr_expect(eq(str, trimmed, expected_str));
cr_expect(offset == expected);
}
Test(string_utils, skipblank_nul)
{
char *input = NULL;
char *trimmed = trim_blank_left(input);
ssize_t offset = trimmed - input;
ssize_t expected = 0;
cr_expect(input == NULL);
cr_expect(offset == expected);
}

View file

@ -1,85 +0,0 @@
#define _POSIX_C_SOURCE 200809L
#include "../../../src/utils/vars/vars.h"
#include <criterion/criterion.h>
#include <criterion/new/assert.h>
#include <criterion/redirect.h>
#include <stdio.h>
#include <unistd.h>
#include "../../../src/utils/hash_map/hash_map.h"
#include "../../../src/utils/string_utils/string_utils.h"
TestSuite(utils_vars);
Test(utils_vars, init_free)
{
struct hash_map *map = vars_init();
cr_expect_not_null(map);
hash_map_free(&map);
}
Test(utils_vars, get_defaults)
{
struct hash_map *map = vars_init();
cr_expect_not_null(map);
cr_assert_str_eq(get_var(map, "?"), "0");
char int_str[11];
int_to_str((int)getpid(), int_str);
cr_assert_str_eq(get_var(map, "$"), int_str);
int_to_str((int)getuid(), int_str);
cr_assert_str_eq(get_var(map, "UID"), int_str);
hash_map_free(&map);
}
Test(utils_vars, set_vars)
{
struct hash_map *map = vars_init();
cr_expect_not_null(map);
set_var_copy(map, "key1", "value1");
cr_assert_str_eq(get_var(map, "key1"), "value1");
set_var_copy(map, "key2", "value2");
cr_assert_str_eq(get_var(map, "key2"), "value2");
hash_map_free(&map);
}
Test(utils_vars, get_env_vars)
{
struct hash_map *map = vars_init();
cr_expect_not_null(map);
cr_assert_eq(get_var_or_env(map, "ENV_TEST"), NULL);
setenv("ENV_TEST", "value1", 0);
cr_assert_str_eq(get_var_or_env(map, "ENV_TEST"), "value1");
setenv("ENV_TEST", "value2", 1);
cr_assert_str_eq(get_var_or_env(map, "ENV_TEST"), "value2");
hash_map_free(&map);
}
Test(utils_vars, set_vars_update)
{
struct hash_map *map = vars_init();
cr_expect_not_null(map);
set_var_copy(map, "key", "value1");
cr_assert_str_eq(get_var(map, "key"), "value1");
set_var_copy(map, "key", "value2");
cr_assert_str_eq(get_var(map, "key"), "value2");
hash_map_free(&map);
}
Test(utils_vars, set_vars_int)
{
struct hash_map *map = vars_init();
cr_expect_not_null(map);
set_var_int(map, "key1", 100);
cr_assert_str_eq(get_var(map, "key1"), "100");
set_var_int(map, "key1", 200);
cr_assert_str_eq(get_var(map, "key1"), "200");
set_var_int(map, "key2", 10);
cr_assert_str_eq(get_var(map, "key2"), "10");
hash_map_free(&map);
}

View file

@ -1,12 +0,0 @@
#!/bin/sh
if [ "$BIN_PATH" = "" ];
then export BIN_PATH="$(pwd)/42sh"
fi
if [ "$COVERAGE" = "yes" ]; #coverage
then (./testsuite || true) && ../tests/functional/run-tests.sh
else ../tests/functional/run-tests.sh
fi
echo bin path: "$BIN_PATH"
echo output file: "$OUTPUT_FILE"