├── .clang-tidy ├── .gitignore ├── Makefile ├── README.md ├── UNLICENSE ├── compile_flags.txt └── src ├── ast.c ├── builtins.c ├── eval.c ├── exec.c ├── include ├── ast.h ├── builtins.h ├── eval.h ├── exec.h ├── lexer.h ├── parser.h ├── psh.h └── utils.h ├── lexer.c ├── parser.c ├── psh.c └── utils.c /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: " 2 | bugprone-*, 3 | -bugprone-easily-swappable-parameters, 4 | misc-*, 5 | -misc-no-recursion, 6 | performance-*, 7 | portability-*, 8 | readability-*, 9 | -readability-braces-around-statements, 10 | -readability-identifier-length, 11 | clang-analyzer-*, 12 | modernize-*, 13 | llvm-*," 14 | 15 | CheckOptions: 16 | - key: readability-identifier-naming.EnumCase 17 | value: lower_case 18 | - key: readability-identifier-naming.UnionCase 19 | value: lower_case 20 | 21 | - key: readability-identifier-naming.FunctionCase 22 | value: lower_case 23 | - key: readability-identifier-naming.ParameterCase 24 | value: lower_case 25 | - key: readability-identifier-naming.VariableCase 26 | value: lower_case 27 | 28 | - key: readability-identifier-naming.MacroDefinitionCase 29 | value: UPPER_CASE 30 | - key: readability-identifier-naming.GlobalVariableCase 31 | value: lower_case 32 | 33 | - key: readability-function-cognitive-complexity.IgnoreMacros 34 | value: true 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | build/ 54 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=clang 2 | TARGET_EXEC := psh 3 | BUILD_DIR := ./build 4 | SRC_DIRS := ./src 5 | SRCS := $(shell find $(SRC_DIRS) -name '*.cpp' -or -name '*.c' -or -name '*.s') 6 | OBJS := $(SRCS:%=$(BUILD_DIR)/%.o) 7 | DEPS := $(OBJS:.o=.d) 8 | INC_DIRS := $(shell find $(SRC_DIRS) -type d) 9 | INC_FLAGS := $(addprefix -I,$(INC_DIRS)) 10 | CFLAGS := $(INC_FLAGS) -MMD -MP -Wall -Werror -g -Wextra -pedantic -std=c99 11 | LDFLAGS := $(shell pkg-config --cflags --libs readline) 12 | 13 | $(BUILD_DIR)/$(TARGET_EXEC): $(OBJS) 14 | $(CC) $(OBJS) -o $@ $(LDFLAGS) 15 | 16 | $(BUILD_DIR)/%.c.o: %.c 17 | mkdir -p $(dir $@) 18 | $(CC) $(CFLAGS) $(CFLAGS) -c $< -o $@ 19 | 20 | .PHONY: clean 21 | clean: 22 | rm -r $(BUILD_DIR) 23 | 24 | -include $(DEPS) 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |
3 | logo 4 |
5 | psh 6 |
7 |

8 |

A small and minimal shell

9 | 10 | ### Preview🙈: 11 | [![asciicast](https://asciinema.org/a/ojkpeKFyhRJgohB4LNHK3sJ4w.svg)](https://asciinema.org/a/ojkpeKFyhRJgohB4LNHK3sJ4w) 12 | 13 | ### Features: 14 | 1. Basic commands 15 | 2. Wildcards 16 | 3. Redirections 17 | 4. Pipes 18 | 5. Background jobs 19 | 6. Seperate commands with ';' 20 | 7. Signal handling 21 | 8. Cursor movement, tab, history with libreadline 22 | 23 | ### Motivation: 24 | I did this mostly for the sake of learning and fun! I always wanted to have my own small shell that I can run everywhere and I can add custom features to. 25 | I am aiming to implement some other features in this later like aliasing, environmental variables and some basic if/else logics :). This project can also be seen as learning resource for people who want to learn how to make their own shell(I might write an article on this topic). 26 | 27 | 28 | 29 | ### How to install⬇️: 30 | 31 | #### Arch Linux 32 | [psh](https://aur.archlinux.org/packages/psh) is available on AUR. You can install it using an AUR helper (e.g. paru): 33 | ```shell 34 | paru -S psh 35 | ``` 36 | 37 | #### Manually 38 | 1. Clone this repo. 39 | ```shell 40 | git clone https://github.com/proh14/psh.git 41 | cd psh 42 | ``` 43 | 2. Run `make` inside this repo's main directory. 44 | ```shell 45 | make 46 | ``` 47 | 48 | 3. go to build directory 49 | ```shell 50 | cd build 51 | ``` 52 | 4. run psh! 53 | ```shell 54 | ./psh 55 | ``` 56 | 57 | OR to install you may run the command 58 | ```shell 59 | make install 60 | ``` 61 | then restart your shell and run 62 | ```shell 63 | psh 64 | ``` 65 | 66 | ### Contributions💖: 67 | If your willing to contribute, I must say thank you :) 68 | 69 | ### Thank you Stargazers⭐: 70 | [![Stargazers repo roster for @proh14/psh](http://reporoster.com/stars/proh14/psh)](https://github.com/proh14/psh/stargazers) 71 | 72 | ### Thank you Forkers🍴: 73 | [![Forkers repo roster for @proh14/psh](http://reporoster.com/forks/proh14/psh)](https://github.com/proh14/psh/network/members) 74 | 75 | ### Inspired by: 76 | 1. [mysh](https://github.com/Swoorup/mysh) 77 | 2. [ash(netbsd)](https://github.com/NetBSD/src/tree/trunk/bin/sh) 78 | 79 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /compile_flags.txt: -------------------------------------------------------------------------------- 1 | -Wall 2 | -g 3 | -Wextra 4 | -Werror 5 | -pedantic 6 | -std=c99 7 | -I./src/include 8 | -MMD 9 | -MP 10 | -------------------------------------------------------------------------------- /src/ast.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | ast_t *ast_create_node(void) { 6 | ast_t *node = malloc(sizeof(ast_t)); 7 | node->left = NULL; 8 | node->right = NULL; 9 | node->token = NULL; 10 | return node; 11 | } 12 | 13 | void ast_destroy_node(ast_t *node) { 14 | if (node == NULL) { 15 | return; 16 | } 17 | ast_destroy_node(node->left); 18 | ast_destroy_node(node->right); 19 | node->left = NULL; 20 | node->right = NULL; 21 | node->token = NULL; 22 | 23 | free(node); 24 | node = NULL; 25 | } 26 | 27 | static const char *ast_type_to_string(enum astype type) { 28 | switch (type) { 29 | case AST_COMMAND_TAIL: 30 | return "AST_COMMAND_TAIL"; 31 | break; 32 | case AST_BACKGROUND: 33 | return "AST_BACKGROUND"; 34 | break; 35 | case AST_REDIRECT_IN: 36 | return "AST_REDIRECT_IN"; 37 | break; 38 | case AST_REDIRECT_OUT: 39 | return "AST_REDIRECT_OUT"; 40 | break; 41 | case AST_PIPE: 42 | return "AST_PIPE"; 43 | break; 44 | default: 45 | return "UNKNOWN"; 46 | break; 47 | } 48 | } 49 | 50 | void ast_print(ast_t *node, int level) { 51 | if (node == NULL) { 52 | return; 53 | } 54 | 55 | for (int i = 0; i < level; i++) { 56 | printf(" "); 57 | } 58 | 59 | if (node->token == NULL) { 60 | printf("%s\n", ast_type_to_string(node->type)); 61 | } else { 62 | printf("%s(%s)\n", node->token->lexeme, ast_type_to_string(node->type)); 63 | } 64 | 65 | ast_print(node->right, level + 1); 66 | ast_print(node->left, level + 1); 67 | } 68 | -------------------------------------------------------------------------------- /src/builtins.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static void builtin_exit(int argc, char **argv) { 9 | (void)argc; 10 | (void)argv; 11 | exit(0); 12 | } 13 | 14 | static void builtin_cd(int argc, char **argv) { 15 | if (argc == 1) { 16 | if (chdir(getenv("HOME")) == -1) { 17 | perror("cd"); 18 | } 19 | } else if (argc == 2) { 20 | if (chdir(argv[1]) == -1) { 21 | perror("cd"); 22 | } 23 | } else { 24 | eprintf("cd: too many arguments\n"); 25 | } 26 | } 27 | 28 | static void builtin_pwd(int argc, char **argv) { 29 | (void)argv; 30 | if (argc > 1) { 31 | eprintf("pwd: too many arguments\n"); 32 | return; 33 | } 34 | char *cwd = getcwd(NULL, 0); 35 | printf("%s\n", cwd); 36 | free(cwd); 37 | } 38 | 39 | static builtin_t builtins[] = { 40 | {"exit", &builtin_exit}, 41 | {"cd", &builtin_cd}, 42 | {"pwd", &builtin_pwd}, 43 | }; 44 | 45 | builtin_t *builtin_find_by_name(char *name) { 46 | for (size_t i = 0; i < sizeof(builtins) / sizeof(builtin_t); i++) { 47 | if (strcmp(builtins[i].name, name) == 0) { 48 | return &builtins[i]; 49 | } 50 | } 51 | return NULL; 52 | } 53 | 54 | void builtin_execute(builtin_t *builtin, int argc, char **argv) { 55 | if (builtin != NULL) { 56 | builtin->func(argc, argv); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/eval.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | static void eval_full(ast_t *ast); 12 | 13 | static int input_fd = -1; 14 | static int output_fd = -1; 15 | static int bg = 0; 16 | 17 | static void eval_commandtail(ast_t *ast) { 18 | command_t *cmd = command_create(); 19 | command_argv_from_ast(cmd, ast); 20 | cmd->input_fd = input_fd; 21 | cmd->output_fd = output_fd; 22 | cmd->bg = bg; 23 | command_execute(cmd); 24 | command_destroy(cmd); 25 | } 26 | static void eval_sequence(ast_t *ast) { 27 | if (ast->type == AST_BACKGROUND) { 28 | bg = 1; 29 | } 30 | 31 | eval_full(ast->left); 32 | eval_full(ast->right); 33 | 34 | if (ast->type == AST_BACKGROUND) { 35 | bg = 0; 36 | } 37 | } 38 | static void eval_redirection(ast_t *ast) { 39 | int fd; 40 | if (ast->type == AST_REDIRECT_IN) { 41 | fd = open(ast->token->lexeme, O_RDONLY); 42 | if (fd == -1) { 43 | psherror("open"); 44 | return; 45 | } 46 | input_fd = fd; 47 | } else { 48 | fd = open(ast->token->lexeme, O_WRONLY | O_CREAT | O_TRUNC, FILE_MODE); 49 | if (fd == -1) { 50 | psherror("open"); 51 | return; 52 | } 53 | output_fd = fd; 54 | } 55 | 56 | eval_full(ast->right); 57 | close(fd); 58 | input_fd = -1; 59 | output_fd = -1; 60 | } 61 | static void eval_pipeline(ast_t *ast) { 62 | int pipefd[2]; 63 | if (pipe(pipefd) == -1) { 64 | psherror("pipe"); 65 | return; 66 | } 67 | 68 | input_fd = -1; 69 | output_fd = pipefd[1]; 70 | eval_full(ast->left); 71 | close(pipefd[1]); 72 | 73 | ast = ast->right; 74 | 75 | input_fd = pipefd[0]; 76 | 77 | while (ast->type == AST_PIPE) { 78 | if (pipe(pipefd) == -1) { 79 | psherror("pipe"); 80 | return; 81 | } 82 | output_fd = pipefd[1]; 83 | eval_full(ast->left); 84 | close(pipefd[1]); 85 | close(input_fd); 86 | input_fd = pipefd[0]; 87 | ast = ast->right; 88 | } 89 | output_fd = -1; 90 | input_fd = pipefd[0]; 91 | eval_full(ast); 92 | close(pipefd[0]); 93 | } 94 | 95 | static void eval_full(ast_t *ast) { 96 | if (ast == NULL) { 97 | return; 98 | } 99 | switch (ast->type) { 100 | case AST_PIPE: 101 | eval_pipeline(ast); 102 | break; 103 | case AST_REDIRECT_IN: 104 | case AST_REDIRECT_OUT: 105 | eval_redirection(ast); 106 | break; 107 | case AST_SEQUENCE: 108 | case AST_BACKGROUND: 109 | eval_sequence(ast); 110 | break; 111 | case AST_COMMAND_TAIL: 112 | eval_commandtail(ast); 113 | break; 114 | default: 115 | break; 116 | } 117 | } 118 | 119 | void evaluate(parser_t *parser) { eval_full(parser->ast); } 120 | -------------------------------------------------------------------------------- /src/exec.c: -------------------------------------------------------------------------------- 1 | #define _DEFAULT_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | static void command_reset(command_t *cmd) { 19 | cmd->argc = 0; 20 | cmd->argv = NULL; 21 | cmd->input_fd = -1; 22 | cmd->output_fd = -1; 23 | cmd->bg = 0; 24 | } 25 | 26 | command_t *command_create(void) { 27 | command_t *cmd = xmalloc(sizeof(command_t)); 28 | command_reset(cmd); 29 | return cmd; 30 | } 31 | 32 | void command_argv_from_ast(command_t *cmd, ast_t *ast) { 33 | 34 | // Prepare argv 35 | for (ast_t *n = ast; n != NULL; n = n->right) { 36 | cmd->argc++; 37 | } 38 | cmd->argv = xmalloc(sizeof(char *) * (cmd->argc + 1)); 39 | 40 | for (int i = 0; i < cmd->argc; i++) { 41 | cmd->argv[i] = xstrdup(ast->token->lexeme); 42 | ast = ast->right; 43 | } 44 | cmd->argv[cmd->argc] = NULL; 45 | } 46 | 47 | static int xopen(const char *path, int flags, mode_t mode) { 48 | int fd = open(path, flags, mode); 49 | if (fd == -1) { 50 | die("open"); 51 | } 52 | return fd; 53 | } 54 | 55 | static void untrap_signals(void) { 56 | signal(SIGINT, SIG_DFL); 57 | signal(SIGQUIT, SIG_DFL); 58 | signal(SIGTSTP, SIG_DFL); 59 | signal(SIGTTIN, SIG_DFL); 60 | signal(SIGTTOU, SIG_DFL); 61 | } 62 | 63 | static void background_signal_handler(int signo) { 64 | if (signo == SIGCHLD) { 65 | int status; 66 | pid_t pid; 67 | while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { 68 | if (WIFEXITED(status) || WIFSIGNALED(status)) { 69 | printf("[BACKGROUND] Process %d exited\n", pid); 70 | rl_on_new_line(); 71 | rl_refresh_line(0, 0); 72 | } 73 | } 74 | } 75 | } 76 | 77 | void command_execute(command_t *cmd) { 78 | if (cmd->argc == 0) { 79 | return; 80 | } 81 | 82 | // Builtins 83 | builtin_t *builtin = builtin_find_by_name(cmd->argv[0]); 84 | if (builtin) { 85 | builtin_execute(builtin, cmd->argc, cmd->argv); 86 | return; 87 | } 88 | 89 | // Fork 90 | pid_t pid = fork(); 91 | if (pid == -1) { 92 | psherror("fork"); 93 | return; 94 | } 95 | 96 | // Child 97 | if (pid == 0) { 98 | untrap_signals(); 99 | 100 | if (cmd->bg) { 101 | printf("\n[BACKGROUND] started backgroundjob: %s\n", cmd->argv[0]); 102 | setpgid(0, 0); 103 | 104 | int fd = xopen("/dev/null", O_RDONLY, 0); 105 | dup2(fd, STDIN_FILENO); 106 | close(fd); 107 | } 108 | 109 | // Pipe 110 | if (cmd->input_fd != -1) { 111 | dup2(cmd->input_fd, STDIN_FILENO); 112 | close(cmd->input_fd); 113 | } 114 | if (cmd->output_fd != -1) { 115 | dup2(cmd->output_fd, STDOUT_FILENO); 116 | close(cmd->output_fd); 117 | } 118 | 119 | // Execute 120 | if (execvp(cmd->argv[0], cmd->argv) == -1) { 121 | die("execvp"); 122 | } 123 | } 124 | // Wait for child 125 | if (!cmd->bg) { 126 | int status; 127 | do { 128 | waitpid(pid, &status, WUNTRACED); 129 | } while (!WIFEXITED(status) && !WIFSIGNALED(status)); 130 | return; 131 | } 132 | 133 | // Background process 134 | struct sigaction sa; 135 | sa.sa_handler = background_signal_handler; 136 | sigemptyset(&sa.sa_mask); 137 | sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; // Restart interrupted syscalls, 138 | // don't handle stopped children 139 | sigaction(SIGCHLD, &sa, NULL); 140 | } 141 | 142 | void command_destroy(command_t *cmd) { 143 | if (cmd->argv) { 144 | for (int i = 0; i < cmd->argc; i++) { 145 | free(cmd->argv[i]); 146 | } 147 | free(cmd->argv); 148 | } 149 | command_reset(cmd); 150 | free(cmd); 151 | cmd = NULL; 152 | } 153 | -------------------------------------------------------------------------------- /src/include/ast.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef _AST_H_ 3 | #define _AST_H_ 4 | 5 | #include 6 | 7 | enum astype { 8 | AST_COMMAND_TAIL, 9 | AST_BACKGROUND, 10 | AST_SEQUENCE, 11 | AST_REDIRECT_IN, 12 | AST_PIPE, 13 | AST_REDIRECT_OUT, 14 | }; 15 | 16 | typedef struct astree { 17 | enum astype type; 18 | token_t *token; 19 | struct astree *left; 20 | struct astree *right; 21 | } ast_t; 22 | 23 | ast_t *ast_create_node(void); 24 | void ast_destroy_node(ast_t *node); 25 | void ast_print(ast_t *node, int level); 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /src/include/builtins.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef _BUILTINS_H_ 3 | #define _BIULTINS_H_ 4 | 5 | typedef struct builtin { 6 | char *name; 7 | void (*func)(int argc, char **argv); 8 | } builtin_t; 9 | 10 | builtin_t *builtin_find_by_name(char *name); 11 | void builtin_execute(builtin_t *builtin, int argc, char **argv); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /src/include/eval.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef _EVAL_H_ 3 | #define _EVAL_H_ 4 | 5 | #include 6 | 7 | void evaluate(parser_t *parser); 8 | 9 | #endif // ! _EVAL_H_ 10 | -------------------------------------------------------------------------------- /src/include/exec.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef _EXEC_H_ 3 | #define _EXEC_H_ 4 | 5 | #include 6 | 7 | typedef struct command { 8 | int argc; 9 | char **argv; 10 | int input_fd; 11 | int output_fd; 12 | int bg; 13 | } command_t; 14 | 15 | command_t *command_create(void); 16 | void command_execute(command_t *cmd); 17 | void command_argv_from_ast(command_t *cmd, ast_t *ast); 18 | void command_destroy(command_t *cmd); 19 | 20 | #endif // ! _EXEC_H_ 21 | -------------------------------------------------------------------------------- /src/include/lexer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef _LEXER_H_ 3 | #define _LEXER_H_ 4 | 5 | enum token_type { 6 | TOKEN_ARGUMENT, 7 | TOKEN_PIPE, 8 | TOKEN_AMPERSAND, 9 | TOKEN_GREATER, 10 | TOKEN_LESSER, 11 | TOKEN_SEMICOLON, 12 | }; 13 | 14 | enum state { 15 | IN_GENERAL, 16 | IN_QUOTE, 17 | IN_DQUOTE, 18 | IN_ESCAPESEQUENCE, 19 | }; 20 | 21 | typedef struct token { 22 | char *lexeme; 23 | enum token_type type; 24 | struct token *next; 25 | } token_t; 26 | 27 | typedef struct lexical_error { 28 | char *msg; 29 | int character; 30 | } lexical_error_t; 31 | 32 | typedef struct lexer { 33 | token_t *root; 34 | lexical_error_t *error; 35 | } lexer_t; 36 | 37 | lexer_t *lexer_create(void); 38 | token_t *lex(lexer_t *l, char *input); 39 | void lexer_destroy(lexer_t *l); 40 | char *token_to_string(token_t *t); 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /src/include/parser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef _PARSER_H_ 3 | #define _PARSER_H_ 4 | 5 | #include 6 | #include 7 | 8 | typedef struct parsing_error { 9 | char *msg; 10 | char *bad_tok; 11 | } parsing_error_t; 12 | 13 | typedef struct parser { 14 | ast_t *ast; 15 | parsing_error_t *err; 16 | } parser_t; 17 | 18 | parser_t *parser_create(void); 19 | ast_t *parse(parser_t *parser, lexer_t *l); 20 | void parser_destroy(parser_t *p); 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /src/include/psh.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef _PSH_H_ 3 | #define _PSH_H_ 4 | 5 | #define PROMPT "psh> " 6 | 7 | #define PROGRAM_NAME "psh" 8 | #define PROGRAM_VERSION "1.0" 9 | #define AUTHOR "proh14" 10 | 11 | #endif // ! _PSH_H_ 12 | -------------------------------------------------------------------------------- /src/include/utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef _UTILS_H_ 3 | #define _UTILS_H_ 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) 10 | 11 | void *xmalloc(size_t size); 12 | void *xrealloc(void *ptr, size_t size); 13 | char *xstrdup(const char *s); 14 | 15 | void eprintf(const char *fmt, ...); 16 | void psherror(const char *msg); 17 | void die(const char *msg); 18 | 19 | #endif // !_UTILS_H_ 20 | -------------------------------------------------------------------------------- /src/lexer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | lexer_t *lexer_create(void) { 8 | lexer_t *l = xmalloc(sizeof(lexer_t)); 9 | l->root = NULL; 10 | l->error = NULL; 11 | return l; 12 | } 13 | 14 | static token_t *add_token(token_t *ptok, char *lexeme, enum token_type type) { 15 | token_t *t = xmalloc(sizeof(token_t)); 16 | if (lexeme) { 17 | t->lexeme = xstrdup(lexeme); 18 | } else { 19 | t->lexeme = NULL; 20 | } 21 | t->type = type; 22 | t->next = NULL; 23 | if (ptok) { 24 | ptok->next = t; 25 | } 26 | return t; 27 | } 28 | 29 | static enum token_type get_type(char ch) { 30 | switch (ch) { 31 | case '|': 32 | return TOKEN_PIPE; 33 | break; 34 | case '&': 35 | return TOKEN_AMPERSAND; 36 | break; 37 | case '<': 38 | return TOKEN_LESSER; 39 | break; 40 | case '>': 41 | return TOKEN_GREATER; 42 | break; 43 | case ';': 44 | return TOKEN_SEMICOLON; 45 | break; 46 | default: 47 | return TOKEN_ARGUMENT; 48 | break; 49 | } 50 | } 51 | 52 | char *token_to_string(token_t *t) { 53 | switch (t->type) { 54 | case TOKEN_PIPE: 55 | return "|"; 56 | break; 57 | case TOKEN_AMPERSAND: 58 | return "&"; 59 | break; 60 | case TOKEN_LESSER: 61 | return "<"; 62 | break; 63 | case TOKEN_GREATER: 64 | return ">"; 65 | break; 66 | case TOKEN_SEMICOLON: 67 | return ";"; 68 | break; 69 | case TOKEN_ARGUMENT: 70 | return t->lexeme; 71 | break; 72 | default: 73 | return NULL; 74 | break; 75 | } 76 | } 77 | 78 | static int advace_string(const char *from, char *to) { 79 | char type = *from; 80 | int i = 1; 81 | while (from[i] != '\0') { 82 | if (from[i] == type) { 83 | return i + 1; 84 | } 85 | to[i - 1] = from[i]; 86 | i++; 87 | } 88 | 89 | return 0; 90 | } 91 | 92 | static void add_error(lexer_t *l, char *message, int character) { 93 | if (l->error) { 94 | free(l->error); 95 | } 96 | l->error = xmalloc(sizeof(lexical_error_t)); 97 | l->error->msg = message; 98 | l->error->character = character; 99 | } 100 | 101 | static token_t *add_lexeme(lexer_t *l, token_t *prev, char *lexeme, 102 | int *lexeme_i) { 103 | if (*lexeme_i <= 0) { 104 | return prev; 105 | } 106 | 107 | lexeme[*lexeme_i] = '\0'; 108 | *lexeme_i = 0; 109 | 110 | glob_t globbuf; 111 | glob(lexeme, GLOB_TILDE | GLOB_NOCHECK | GLOB_NOSORT, NULL, &globbuf); 112 | 113 | for (size_t i = 0; i < globbuf.gl_pathc; i++) { 114 | prev = add_token(prev, globbuf.gl_pathv[i], TOKEN_ARGUMENT); 115 | } 116 | 117 | if (!l->root) { 118 | l->root = prev; 119 | } 120 | 121 | globfree(&globbuf); 122 | 123 | return prev; 124 | } 125 | 126 | token_t *lex(lexer_t *l, char *input) { 127 | int len = (int)strlen(input) + 1; 128 | char *lexeme = xmalloc(len); 129 | token_t *t = NULL; 130 | int add = 0; 131 | 132 | int i = 0; 133 | int lexeme_i = 0; 134 | while (i < len - 1) { 135 | switch (input[i]) { 136 | case ' ': 137 | case '\t': 138 | case '\n': 139 | t = add_lexeme(l, t, lexeme, &lexeme_i); 140 | break; 141 | case '&': 142 | case '>': 143 | case '<': 144 | case ';': 145 | case '|': 146 | t = add_lexeme(l, t, lexeme, &lexeme_i); 147 | t = add_token(t, NULL, get_type(input[i])); 148 | break; 149 | case '\'': 150 | case '\"': 151 | add = advace_string(input + i, lexeme + lexeme_i); 152 | if (add == 0) { 153 | add_error(l, "lexical error: unmatched quote", i); 154 | return NULL; 155 | } 156 | lexeme_i += add - 2; 157 | i += add; 158 | break; 159 | case '\\': 160 | if (input[i + 1] == '\0') { 161 | add_error(l, "lexical error: escape sequence at end of input", i); 162 | return NULL; 163 | } 164 | lexeme[lexeme_i++] = input[i + 1]; 165 | i++; 166 | break; 167 | default: 168 | lexeme[lexeme_i++] = input[i]; 169 | break; 170 | } 171 | i++; 172 | } 173 | 174 | t = add_lexeme(l, t, lexeme, &lexeme_i); 175 | free(lexeme); 176 | return l->root; 177 | } 178 | 179 | void lexer_destroy(lexer_t *l) { 180 | token_t *t = l->root; 181 | while (t) { 182 | token_t *next = t->next; 183 | free(t->lexeme); 184 | free(t); 185 | t = next; 186 | } 187 | if (l->error) { 188 | free(l->error); 189 | } 190 | l->error = NULL; 191 | 192 | free(l); 193 | l = NULL; 194 | } 195 | -------------------------------------------------------------------------------- /src/parser.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | /* Our tiny shell grammar :) 8 | * 9 | * ::= | 10 | * '&' | 11 | * '&' | 12 | * ';' | 13 | * ';' 14 | * 15 | * ::= | '|' 16 | * 17 | * ::= '>' | 18 | * '<' | 19 | * 20 | * 21 | * ::= | 22 | */ 23 | 24 | static token_t *curtok = NULL; 25 | 26 | static token_t *next_token(void) { 27 | if (curtok->next != NULL) { 28 | curtok = curtok->next; 29 | return curtok; 30 | } 31 | return NULL; 32 | } 33 | 34 | static token_t *peek(void) { return curtok->next; } 35 | 36 | static int match_token(enum token_type type) { return curtok->type == type; } 37 | 38 | static int expect_token(enum token_type type) { 39 | token_t *t = peek(); 40 | return t != NULL && t->type == type; 41 | } 42 | 43 | static ast_t *simple_command(void) { 44 | token_t *t = curtok; 45 | if (t->type != TOKEN_ARGUMENT) { 46 | return NULL; 47 | } 48 | ast_t *ast = ast_create_node(); 49 | ast->type = AST_COMMAND_TAIL; 50 | ast->token = t; 51 | 52 | if (next_token() != NULL) { 53 | ast->right = simple_command(); 54 | } 55 | 56 | return ast; 57 | } 58 | 59 | static inline ast_t *handle_redirection(enum astype type, ast_t *simple) { 60 | if (!expect_token(TOKEN_ARGUMENT)) { 61 | return NULL; 62 | } 63 | ast_t *ast = ast_create_node(); 64 | ast->type = type; 65 | ast->right = simple; 66 | ast->token = next_token(); 67 | next_token(); 68 | return ast; 69 | } 70 | 71 | static ast_t *command(void) { 72 | ast_t *simple = simple_command(); 73 | 74 | if (simple == NULL) { 75 | return NULL; 76 | } 77 | 78 | if (match_token(TOKEN_GREATER)) { 79 | // '>' 80 | ast_t *ast = handle_redirection(AST_REDIRECT_OUT, simple); 81 | if (ast == NULL) { 82 | ast_destroy_node(simple); 83 | } 84 | return ast; 85 | } 86 | if (match_token(TOKEN_LESSER)) { 87 | // '<' 88 | ast_t *ast = handle_redirection(AST_REDIRECT_IN, simple); 89 | if (ast == NULL) { 90 | ast_destroy_node(simple); 91 | } 92 | return ast; 93 | } 94 | 95 | // 96 | return simple; 97 | } 98 | 99 | static ast_t *pipe(void) { 100 | ast_t *left = command(); 101 | 102 | if (left == NULL) { 103 | return NULL; 104 | } 105 | 106 | // '|' 107 | if (match_token(TOKEN_PIPE)) { 108 | if (!expect_token(TOKEN_ARGUMENT)) { 109 | ast_destroy_node(left); 110 | return NULL; 111 | } 112 | ast_t *ast = ast_create_node(); 113 | ast->type = AST_PIPE; 114 | ast->left = left; 115 | next_token(); 116 | ast->right = pipe(); 117 | return ast; 118 | } 119 | 120 | // 121 | return left; 122 | } 123 | 124 | static ast_t *full_command(void) { 125 | ast_t *left = pipe(); 126 | 127 | if (left == NULL) { 128 | return NULL; 129 | } 130 | 131 | // '&' | '&' 132 | if (match_token(TOKEN_AMPERSAND)) { 133 | if (!expect_token(TOKEN_ARGUMENT) && peek() != NULL) { 134 | ast_destroy_node(left); 135 | return NULL; 136 | } 137 | ast_t *ast = ast_create_node(); 138 | ast->type = AST_BACKGROUND; 139 | ast->left = left; 140 | next_token(); 141 | ast->right = full_command(); 142 | return ast; 143 | } 144 | 145 | // ';' | ';' 146 | if (match_token(TOKEN_SEMICOLON)) { 147 | if (!expect_token(TOKEN_ARGUMENT) && peek() != NULL) { 148 | ast_destroy_node(left); 149 | return NULL; 150 | } 151 | ast_t *ast = ast_create_node(); 152 | ast->type = AST_SEQUENCE; 153 | ast->left = left; 154 | next_token(); 155 | ast->right = full_command(); 156 | return ast; 157 | } 158 | 159 | return left; 160 | } 161 | 162 | static void add_error(parser_t *p, token_t *t) { 163 | if (p->err != NULL) { 164 | free(p->err); 165 | } 166 | parsing_error_t *err = xmalloc(sizeof(parsing_error_t)); 167 | err->msg = "parsing error: bad token"; 168 | err->bad_tok = token_to_string(t); 169 | p->err = err; 170 | } 171 | 172 | parser_t *parser_create(void) { 173 | parser_t *p = xmalloc(sizeof(parser_t)); 174 | p->ast = NULL; 175 | p->err = NULL; 176 | return p; 177 | } 178 | 179 | ast_t *parse(parser_t *parser, lexer_t *l) { 180 | curtok = l->root; 181 | parser->ast = full_command(); 182 | if (peek() != NULL) { 183 | add_error(parser, peek()); 184 | curtok = NULL; 185 | return NULL; 186 | } 187 | curtok = NULL; 188 | return parser->ast; 189 | } 190 | 191 | void parser_destroy(parser_t *p) { 192 | ast_destroy_node(p->ast); 193 | if (p->err) { 194 | free(p->err); 195 | } 196 | p->err = NULL; 197 | free(p); 198 | p = NULL; 199 | } 200 | -------------------------------------------------------------------------------- /src/psh.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | static void lexical_error(lexical_error_t *e) { 14 | if (e == NULL) { 15 | return; 16 | } 17 | eprintf("%s: %s: at character %d\n", PROGRAM_NAME, e->msg, e->character); 18 | } 19 | 20 | static void parsing_error(parsing_error_t *e) { 21 | if (e == NULL) { 22 | return; 23 | } 24 | eprintf("%s: %s: '%s'\n", PROGRAM_NAME, e->msg, e->bad_tok); 25 | } 26 | 27 | static void trap_signals(void) { 28 | signal(SIGINT, SIG_IGN); 29 | signal(SIGQUIT, SIG_IGN); 30 | signal(SIGTSTP, SIG_IGN); 31 | } 32 | 33 | static void do_input(char *input) { 34 | lexer_t *lexer = lexer_create(); 35 | if (lex(lexer, input) == NULL) { 36 | lexical_error(lexer->error); 37 | lexer_destroy(lexer); 38 | return; 39 | } 40 | 41 | parser_t *parser = parser_create(); 42 | 43 | if (parse(parser, lexer) == NULL) { 44 | parsing_error(parser->err); 45 | 46 | parser_destroy(parser); 47 | lexer_destroy(lexer); 48 | return; 49 | } 50 | 51 | evaluate(parser); 52 | 53 | parser_destroy(parser); 54 | lexer_destroy(lexer); 55 | } 56 | 57 | int main(void) { 58 | trap_signals(); 59 | 60 | while (1) { 61 | char *input = readline(PROMPT); 62 | 63 | if (feof(stdin) || input == NULL) { 64 | exit(0); 65 | } 66 | 67 | add_history(input); 68 | 69 | do_input(input); 70 | 71 | free(input); 72 | } 73 | return 0; 74 | } 75 | -------------------------------------------------------------------------------- /src/utils.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | void eprintf(const char *fmt, ...) { 10 | va_list ap; 11 | va_start(ap, fmt); 12 | vfprintf(stderr, fmt, ap); 13 | va_end(ap); 14 | } 15 | 16 | void psherror(const char *msg) { 17 | eprintf("%s: %s: %s\n", PROGRAM_NAME, msg, strerror(errno)); 18 | } 19 | 20 | void die(const char *msg) { 21 | if (errno) { 22 | psherror(msg); 23 | } 24 | exit(EXIT_FAILURE); 25 | } 26 | 27 | void *xmalloc(size_t size) { 28 | void *p = malloc(size); 29 | if (!p) { 30 | die("malloc"); 31 | } 32 | return p; 33 | } 34 | 35 | void *xrealloc(void *ptr, size_t size) { 36 | void *p = realloc(ptr, size); 37 | if (!p) { 38 | die("realloc"); 39 | } 40 | return p; 41 | } 42 | 43 | char *xstrdup(const char *s) { 44 | char *p = malloc(strlen(s) + 1); 45 | if (!p) { 46 | die("strdup"); 47 | } 48 | strcpy(p, s); 49 | return p; 50 | } 51 | --------------------------------------------------------------------------------