Repository: emersion/mrsh Branch: master Commit: 4c81598721bc Files: 127 Total size: 367.3 KB Directory structure: gitextract_mub_c0wg/ ├── .builds/ │ ├── alpine.yml │ ├── archlinux.yml │ └── freebsd.yml ├── .editorconfig ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── arithm.c ├── array.c ├── ast.c ├── ast_print.c ├── buffer.c ├── builtin/ │ ├── alias.c │ ├── bg.c │ ├── break.c │ ├── builtin.c │ ├── cd.c │ ├── colon.c │ ├── command.c │ ├── dot.c │ ├── eval.c │ ├── exec.c │ ├── exit.c │ ├── export.c │ ├── false.c │ ├── fg.c │ ├── getopts.c │ ├── hash.c │ ├── jobs.c │ ├── pwd.c │ ├── read.c │ ├── return.c │ ├── set.c │ ├── shift.c │ ├── times.c │ ├── trap.c │ ├── true.c │ ├── type.c │ ├── ulimit.c │ ├── umask.c │ ├── unalias.c │ ├── unset.c │ ├── unspecified.c │ └── wait.c ├── configure ├── example/ │ ├── highlight.c │ └── meson.build ├── frontend/ │ ├── basic.c │ └── readline.c ├── getopt.c ├── hashtable.c ├── include/ │ ├── ast.h │ ├── builtin.h │ ├── frontend.h │ ├── mrsh/ │ │ ├── arithm.h │ │ ├── array.h │ │ ├── ast.h │ │ ├── buffer.h │ │ ├── builtin.h │ │ ├── entry.h │ │ ├── hashtable.h │ │ ├── parser.h │ │ └── shell.h │ ├── mrsh_getopt.h │ ├── parser.h │ └── shell/ │ ├── job.h │ ├── path.h │ ├── process.h │ ├── redir.h │ ├── shell.h │ ├── task.h │ ├── trap.h │ └── word.h ├── libmrsh.darwin.sym ├── libmrsh.gnu.sym ├── main.c ├── meson.build ├── meson_options.txt ├── mkpc ├── parser/ │ ├── arithm.c │ ├── parser.c │ ├── program.c │ └── word.c ├── shell/ │ ├── arithm.c │ ├── entry.c │ ├── job.c │ ├── path.c │ ├── process.c │ ├── redir.c │ ├── shell.c │ ├── task/ │ │ ├── pipeline.c │ │ ├── simple_command.c │ │ ├── task.c │ │ └── word.c │ ├── trap.c │ └── word.c └── test/ ├── args.sh ├── arithm.sh ├── async.sh ├── case.sh ├── command.sh ├── conformance/ │ ├── 2.2-quoted-characters.sh │ ├── 2.2-quoted-characters.stdout │ ├── 2.2.2-nested-single-quotes.fail.sh │ ├── 2.2.3-alias-expansion.fail.sh │ ├── 2.2.3-backquote-nonterminated-dquote.undefined.sh │ ├── 2.2.3-backquote-nonterminated-squote.undefined.sh │ ├── 2.2.3-dquote-nonterminated-backquote.undefined.sh │ ├── README │ ├── harness.sh │ └── meson.build ├── for.sh ├── function.sh ├── harness.sh ├── if.sh ├── loop.sh ├── meson.build ├── pipeline.sh ├── read.sh ├── readonly.sh ├── redir.sh ├── return.sh ├── subshell.sh ├── syntax.sh ├── ulimit.sh └── word.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .builds/alpine.yml ================================================ image: alpine/edge packages: - gcc - clang - meson - dash sources: - https://git.sr.ht/~emersion/mrsh tasks: - setup: | cd mrsh CC=gcc meson build-gcc -Dreference-shell=dash CC=clang meson build-clang -Dreference-shell=dash - build-gcc: | cd mrsh ninja -C build-gcc - build-clang: | cd mrsh ninja -C build-clang - test-gcc: | cd mrsh ninja -C build-gcc test - test-clang: | cd mrsh ninja -C build-clang test triggers: - action: email condition: failure to: "" ================================================ FILE: .builds/archlinux.yml ================================================ image: archlinux packages: - gcc - meson - readline sources: - https://git.sr.ht/~emersion/mrsh tasks: - setup: | cd mrsh meson build -Db_sanitize=address,undefined -Dauto_features=enabled - build: | cd mrsh ninja -C build - test: | cd mrsh ninja -C build test - build-release: | cd mrsh meson configure build --buildtype release ninja -C build - build-minimal: | cd mrsh meson configure build -Dauto_features=disabled ninja -C build triggers: - action: email condition: failure to: "" ================================================ FILE: .builds/freebsd.yml ================================================ image: freebsd/latest packages: - meson - libedit - pkgconf sources: - https://git.sr.ht/~emersion/mrsh tasks: - setup: | cd mrsh meson build -Dauto_features=enabled -Dreadline-provider=editline - build: | cd mrsh ninja -C build - test: | cd mrsh ninja -C build test triggers: - action: email condition: failure to: "" ================================================ FILE: .editorconfig ================================================ root = true [*] end_of_line = lf insert_final_newline = true charset = utf-8 indent_style = tab trim_trailing_whitespace = true indent_size = 4 [*.yml] indent_size = 2 indent_style = space ================================================ FILE: .gitignore ================================================ # Prerequisites *.d # Object files *.o *.ko *.obj *.elf # Linker output *.ilk *.map *.exp # Precompiled Headers *.gch *.pch # Libraries *.lib *.a *.la *.lo # Shared objects (inc. Windows DLLs) *.dll *.so *.so.* *.dylib # Executables *.exe *.out *.app *.i*86 *.x86_64 *.hex # Debug files *.dSYM/ *.su *.idb *.pdb # Kernel Module Compile Results *.mod* *.cmd .tmp_versions/ modules.order Module.symvers Mkfile.old dkms.conf /build /build-* /.build /highlight /mrsh ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2018 emersion Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ .POSIX: .SUFFIXES: OUTDIR=.build include $(OUTDIR)/config.mk INCLUDE=-Iinclude public_includes=\ include/mrsh/arithm.h \ include/mrsh/array.h \ include/mrsh/ast.h \ include/mrsh/buffer.h \ include/mrsh/builtin.h \ include/mrsh/entry.h \ include/mrsh/hashtable.h \ include/mrsh/parser.h \ include/mrsh/shell.h tests=\ test/args.sh \ test/arithm.sh \ test/async.sh \ test/case.sh \ test/command.sh \ test/for.sh \ test/function.sh \ test/if.sh \ test/loop.sh \ test/pipeline.sh \ test/read.sh \ test/readonly.sh \ test/redir.sh \ test/return.sh \ test/subshell.sh \ test/syntax.sh \ test/ulimit.sh \ test/word.sh include $(OUTDIR)/cppcache .SUFFIXES: .c .o .c.o: @mkdir -p $$(dirname "$@") @printf 'CC\t$@\n' @touch $(OUTDIR)/cppcache @grep $< $(OUTDIR)/cppcache >/dev/null || \ $(CPP) $(INCLUDE) -MM -MT $@ $< >> $(OUTDIR)/cppcache @$(CC) -c $(CFLAGS) $(INCLUDE) -o $@ $< $(OUTDIR)/libmrsh.a: $(libmrsh_objects) @printf 'AR\t$@\n' @$(AR) -csr $@ $(libmrsh_objects) libmrsh.so.$(SOVERSION): $(OUTDIR)/libmrsh.a @printf 'LD\t$@\n' @$(CC) -shared $(LDFLAGS) -o $@ $(OUTDIR)/libmrsh.a $(OUTDIR)/mrsh.pc: @printf 'MKPC\t$@\n' @PREFIX=$(PREFIX) ./mkpc $@ mrsh: $(OUTDIR)/libmrsh.a $(mrsh_objects) @printf 'CCLD\t$@\n' @$(CC) -o $@ $(LDFLAGS) $(mrsh_objects) -L$(OUTDIR) -lmrsh $(LIBS) highlight: $(OUTDIR)/libmrsh.a $(highlight_objects) @printf 'CCLD\t$@\n' @$(CC) -o $@ $(LDFLAGS) $(highlight_objects) -L$(OUTDIR) -lmrsh $(LIBS) check: mrsh $(tests) @for t in $(tests); do \ printf '%-30s... ' "$$t" && \ MRSH=./mrsh REF_SH=$${REF_SH:-sh} ./test/harness.sh $$t >/dev/null && \ echo OK || echo FAIL; \ done install: mrsh libmrsh.so.$(SOVERSION) $(OUTDIR)/mrsh.pc mkdir -p $(BINDIR) $(LIBDIR) $(INCDIR)/mrsh $(PCDIR) install -m755 mrsh $(BINDIR)/mrsh install -m755 libmrsh.so.$(SOVERSION) $(LIBDIR)/libmrsh.so.$(SOVERSION) for inc in $(public_includes); do \ install -m644 $$inc $(INCDIR)/mrsh/$$(basename $$inc); \ done install -m644 $(OUTDIR)/mrsh.pc $(PCDIR)/mrsh.pc uninstall: rm -f $(BINDIR)/mrsh rm -f $(LIBDIR)/libmrsh.so.$(SOVERSION) for inc in $(public_includes); do \ rm -f $(INCDIR)/mrsh/$$(basename $$inc); \ done rm -f $(PCDIR)/mrsh.pc clean: rm -rf \ $(libmrsh_objects) \ $(mrsh_objects) \ $(highlight_objects) \ mrsh highlight libmrsh.so.$(SOVERSION) $(OUTDIR)/mrsh.pc mrproper: clean rm -rf $(OUTDIR) .PHONY: all install clean check ================================================ FILE: README.md ================================================ # mrsh A minimal [POSIX] shell. [![builds.sr.ht status](https://builds.sr.ht/~emersion/mrsh/commits/master.svg)](https://builds.sr.ht/~emersion/mrsh/commits/master?) * POSIX compliant, no less, no more * Simple, readable code without magic * Library to build more elaborate shells This project is a [work in progress]. ## Build Both Meson and POSIX make are supported. To use Meson: meson build/ ninja -C build/ build/mrsh To use POSIX make: ./configure make ./mrsh ## Contributing Either [send GitHub pull requests][GitHub] or [send patches on the mailing list][ML]. ## License MIT [POSIX]: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html [work in progress]: https://github.com/emersion/mrsh/issues/8 [GitHub]: https://github.com/emersion/mrsh [ML]: https://lists.sr.ht/%7Eemersion/mrsh-dev ================================================ FILE: arithm.c ================================================ #include #include #include #include void mrsh_arithm_expr_destroy(struct mrsh_arithm_expr *expr) { if (expr == NULL) { return; } switch (expr->type) { case MRSH_ARITHM_LITERAL:; struct mrsh_arithm_literal *al = mrsh_arithm_expr_get_literal(expr); free(al); return; case MRSH_ARITHM_VARIABLE:; struct mrsh_arithm_variable *av = mrsh_arithm_expr_get_variable(expr); free(av->name); free(av); return; case MRSH_ARITHM_UNOP:; struct mrsh_arithm_unop *au = mrsh_arithm_expr_get_unop(expr); mrsh_arithm_expr_destroy(au->body); free(au); return; case MRSH_ARITHM_BINOP:; struct mrsh_arithm_binop *ab = mrsh_arithm_expr_get_binop(expr); mrsh_arithm_expr_destroy(ab->left); mrsh_arithm_expr_destroy(ab->right); free(ab); return; case MRSH_ARITHM_COND:; struct mrsh_arithm_cond *ac = mrsh_arithm_expr_get_cond(expr); mrsh_arithm_expr_destroy(ac->condition); mrsh_arithm_expr_destroy(ac->body); mrsh_arithm_expr_destroy(ac->else_part); free(ac); return; case MRSH_ARITHM_ASSIGN:; struct mrsh_arithm_assign *aa = mrsh_arithm_expr_get_assign(expr); free(aa->name); mrsh_arithm_expr_destroy(aa->value); free(aa); return; } abort(); } struct mrsh_arithm_literal *mrsh_arithm_literal_create(long value) { struct mrsh_arithm_literal *al = calloc(1, sizeof(struct mrsh_arithm_literal)); if (al == NULL) { return NULL; } al->expr.type = MRSH_ARITHM_LITERAL; al->value = value; return al; } struct mrsh_arithm_variable *mrsh_arithm_variable_create(char *name) { struct mrsh_arithm_variable *av = calloc(1, sizeof(struct mrsh_arithm_variable)); if (av == NULL) { return NULL; } av->expr.type = MRSH_ARITHM_VARIABLE; av->name = name; return av; } struct mrsh_arithm_unop *mrsh_arithm_unop_create( enum mrsh_arithm_unop_type type, struct mrsh_arithm_expr *body) { struct mrsh_arithm_unop *au = calloc(1, sizeof(struct mrsh_arithm_unop)); if (au == NULL) { return NULL; } au->expr.type = MRSH_ARITHM_UNOP; au->type = type; au->body = body; return au; } struct mrsh_arithm_binop *mrsh_arithm_binop_create( enum mrsh_arithm_binop_type type, struct mrsh_arithm_expr *left, struct mrsh_arithm_expr *right) { struct mrsh_arithm_binop *ab = calloc(1, sizeof(struct mrsh_arithm_binop)); if (ab == NULL) { return NULL; } ab->expr.type = MRSH_ARITHM_BINOP; ab->type = type; ab->left = left; ab->right = right; return ab; } struct mrsh_arithm_cond *mrsh_arithm_cond_create( struct mrsh_arithm_expr *condition, struct mrsh_arithm_expr *body, struct mrsh_arithm_expr *else_part) { struct mrsh_arithm_cond *ac = calloc(1, sizeof(struct mrsh_arithm_cond)); if (ac == NULL) { return NULL; } ac->expr.type = MRSH_ARITHM_COND; ac->condition = condition; ac->body = body; ac->else_part = else_part; return ac; } struct mrsh_arithm_assign *mrsh_arithm_assign_create( enum mrsh_arithm_assign_op op, char *name, struct mrsh_arithm_expr *value) { struct mrsh_arithm_assign *aa = calloc(1, sizeof(struct mrsh_arithm_assign)); if (aa == NULL) { return NULL; } aa->expr.type = MRSH_ARITHM_ASSIGN; aa->op = op; aa->name = name; aa->value = value; return aa; } struct mrsh_arithm_literal *mrsh_arithm_expr_get_literal( const struct mrsh_arithm_expr *expr) { assert(expr->type == MRSH_ARITHM_LITERAL); return (struct mrsh_arithm_literal *)expr; } struct mrsh_arithm_variable *mrsh_arithm_expr_get_variable( const struct mrsh_arithm_expr *expr) { assert(expr->type == MRSH_ARITHM_VARIABLE); return (struct mrsh_arithm_variable *)expr; } struct mrsh_arithm_unop *mrsh_arithm_expr_get_unop( const struct mrsh_arithm_expr *expr) { assert(expr->type == MRSH_ARITHM_UNOP); return (struct mrsh_arithm_unop *)expr; } struct mrsh_arithm_binop *mrsh_arithm_expr_get_binop( const struct mrsh_arithm_expr *expr) { assert(expr->type == MRSH_ARITHM_BINOP); return (struct mrsh_arithm_binop *)expr; } struct mrsh_arithm_cond *mrsh_arithm_expr_get_cond( const struct mrsh_arithm_expr *expr) { assert(expr->type == MRSH_ARITHM_COND); return (struct mrsh_arithm_cond *)expr; } struct mrsh_arithm_assign *mrsh_arithm_expr_get_assign( const struct mrsh_arithm_expr *expr) { assert(expr->type == MRSH_ARITHM_ASSIGN); return (struct mrsh_arithm_assign *)expr; } ================================================ FILE: array.c ================================================ #include #include #include #define INITIAL_SIZE 8 bool mrsh_array_reserve(struct mrsh_array *array, size_t new_cap) { if (array->cap >= new_cap) { return true; } void *new_data = realloc(array->data, new_cap * sizeof(void *)); if (new_data == NULL) { return false; } array->data = new_data; array->cap = new_cap; return true; } ssize_t mrsh_array_add(struct mrsh_array *array, void *value) { assert(array->len <= array->cap); if (array->len == array->cap) { size_t new_cap = 2 * array->cap; if (new_cap < INITIAL_SIZE) { new_cap = INITIAL_SIZE; } if (!mrsh_array_reserve(array, new_cap)) { return -1; } } size_t i = array->len; array->data[i] = value; array->len++; return i; } void mrsh_array_finish(struct mrsh_array *array) { free(array->data); array->cap = array->len = 0; } ================================================ FILE: ast.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include "ast.h" bool mrsh_position_valid(const struct mrsh_position *pos) { return pos->line > 0; } bool mrsh_range_valid(const struct mrsh_range *range) { return mrsh_position_valid(&range->begin) && mrsh_position_valid(&range->end); } void mrsh_node_destroy(struct mrsh_node *node) { switch (node->type) { case MRSH_NODE_PROGRAM:; struct mrsh_program *prog = mrsh_node_get_program(node); mrsh_program_destroy(prog); return; case MRSH_NODE_COMMAND_LIST:; struct mrsh_command_list *cl = mrsh_node_get_command_list(node); mrsh_command_list_destroy(cl); return; case MRSH_NODE_AND_OR_LIST:; struct mrsh_and_or_list *aol = mrsh_node_get_and_or_list(node); mrsh_and_or_list_destroy(aol); return; case MRSH_NODE_COMMAND:; struct mrsh_command *cmd = mrsh_node_get_command(node); mrsh_command_destroy(cmd); return; case MRSH_NODE_WORD:; struct mrsh_word *word = mrsh_node_get_word(node); mrsh_word_destroy(word); return; } abort(); } void mrsh_word_destroy(struct mrsh_word *word) { if (word == NULL) { return; } switch (word->type) { case MRSH_WORD_STRING:; struct mrsh_word_string *ws = mrsh_word_get_string(word); free(ws->str); free(ws); return; case MRSH_WORD_PARAMETER:; struct mrsh_word_parameter *wp = mrsh_word_get_parameter(word); free(wp->name); mrsh_word_destroy(wp->arg); free(wp); return; case MRSH_WORD_COMMAND:; struct mrsh_word_command *wc = mrsh_word_get_command(word); mrsh_program_destroy(wc->program); free(wc); return; case MRSH_WORD_ARITHMETIC:; struct mrsh_word_arithmetic *wa = mrsh_word_get_arithmetic(word); mrsh_word_destroy(wa->body); free(wa); return; case MRSH_WORD_LIST:; struct mrsh_word_list *wl = mrsh_word_get_list(word); for (size_t i = 0; i < wl->children.len; ++i) { struct mrsh_word *child = wl->children.data[i]; mrsh_word_destroy(child); } mrsh_array_finish(&wl->children); free(wl); return; } abort(); } void mrsh_io_redirect_destroy(struct mrsh_io_redirect *redir) { if (redir == NULL) { return; } mrsh_word_destroy(redir->name); for (size_t i = 0; i < redir->here_document.len; ++i) { struct mrsh_word *line = redir->here_document.data[i]; mrsh_word_destroy(line); } mrsh_array_finish(&redir->here_document); free(redir); } void mrsh_assignment_destroy(struct mrsh_assignment *assign) { if (assign == NULL) { return; } free(assign->name); mrsh_word_destroy(assign->value); free(assign); } void command_list_array_finish(struct mrsh_array *cmds) { for (size_t i = 0; i < cmds->len; ++i) { struct mrsh_command_list *l = cmds->data[i]; mrsh_command_list_destroy(l); } mrsh_array_finish(cmds); } void case_item_destroy(struct mrsh_case_item *item) { for (size_t j = 0; j < item->patterns.len; ++j) { struct mrsh_word *pattern = item->patterns.data[j]; mrsh_word_destroy(pattern); } mrsh_array_finish(&item->patterns); command_list_array_finish(&item->body); free(item); } void mrsh_command_destroy(struct mrsh_command *cmd) { if (cmd == NULL) { return; } switch (cmd->type) { case MRSH_SIMPLE_COMMAND:; struct mrsh_simple_command *sc = mrsh_command_get_simple_command(cmd); mrsh_word_destroy(sc->name); for (size_t i = 0; i < sc->arguments.len; ++i) { struct mrsh_word *arg = sc->arguments.data[i]; mrsh_word_destroy(arg); } mrsh_array_finish(&sc->arguments); for (size_t i = 0; i < sc->io_redirects.len; ++i) { struct mrsh_io_redirect *redir = sc->io_redirects.data[i]; mrsh_io_redirect_destroy(redir); } mrsh_array_finish(&sc->io_redirects); for (size_t i = 0; i < sc->assignments.len; ++i) { struct mrsh_assignment *assign = sc->assignments.data[i]; mrsh_assignment_destroy(assign); } mrsh_array_finish(&sc->assignments); free(sc); return; case MRSH_BRACE_GROUP:; struct mrsh_brace_group *bg = mrsh_command_get_brace_group(cmd); command_list_array_finish(&bg->body); free(bg); return; case MRSH_SUBSHELL:; struct mrsh_subshell *s = mrsh_command_get_subshell(cmd); command_list_array_finish(&s->body); free(s); return; case MRSH_IF_CLAUSE:; struct mrsh_if_clause *ic = mrsh_command_get_if_clause(cmd); command_list_array_finish(&ic->condition); command_list_array_finish(&ic->body); mrsh_command_destroy(ic->else_part); free(ic); return; case MRSH_FOR_CLAUSE:; struct mrsh_for_clause *fc = mrsh_command_get_for_clause(cmd); free(fc->name); for (size_t i = 0; i < fc->word_list.len; ++i) { struct mrsh_word *word = fc->word_list.data[i]; mrsh_word_destroy(word); } mrsh_array_finish(&fc->word_list); command_list_array_finish(&fc->body); free(fc); return; case MRSH_LOOP_CLAUSE:; struct mrsh_loop_clause *lc = mrsh_command_get_loop_clause(cmd); command_list_array_finish(&lc->condition); command_list_array_finish(&lc->body); free(lc); return; case MRSH_CASE_CLAUSE:; struct mrsh_case_clause *cc = mrsh_command_get_case_clause(cmd); mrsh_word_destroy(cc->word); for (size_t i = 0; i < cc->items.len; ++i) { struct mrsh_case_item *item = cc->items.data[i]; case_item_destroy(item); } mrsh_array_finish(&cc->items); free(cc); return; case MRSH_FUNCTION_DEFINITION:; struct mrsh_function_definition *fd = mrsh_command_get_function_definition(cmd); free(fd->name); mrsh_command_destroy(fd->body); for (size_t i = 0; i < fd->io_redirects.len; ++i) { struct mrsh_io_redirect *redir = fd->io_redirects.data[i]; mrsh_io_redirect_destroy(redir); } mrsh_array_finish(&fd->io_redirects); free(fd); return; } abort(); } void mrsh_and_or_list_destroy(struct mrsh_and_or_list *and_or_list) { if (and_or_list == NULL) { return; } switch (and_or_list->type) { case MRSH_AND_OR_LIST_PIPELINE:; struct mrsh_pipeline *p = mrsh_and_or_list_get_pipeline(and_or_list); for (size_t i = 0; i < p->commands.len; ++i) { struct mrsh_command *cmd = p->commands.data[i]; mrsh_command_destroy(cmd); } mrsh_array_finish(&p->commands); free(p); return; case MRSH_AND_OR_LIST_BINOP:; struct mrsh_binop *binop = mrsh_and_or_list_get_binop(and_or_list); mrsh_and_or_list_destroy(binop->left); mrsh_and_or_list_destroy(binop->right); free(binop); return; } abort(); } struct mrsh_command_list *mrsh_command_list_create(void) { struct mrsh_command_list *list = calloc(1, sizeof(struct mrsh_command_list)); list->node.type = MRSH_NODE_COMMAND_LIST; return list; } void mrsh_command_list_destroy(struct mrsh_command_list *l) { if (l == NULL) { return; } mrsh_and_or_list_destroy(l->and_or_list); free(l); } struct mrsh_program *mrsh_program_create(void) { struct mrsh_program *prog = calloc(1, sizeof(struct mrsh_program)); prog->node.type = MRSH_NODE_PROGRAM; return prog; } void mrsh_program_destroy(struct mrsh_program *prog) { if (prog == NULL) { return; } command_list_array_finish(&prog->body); free(prog); } struct mrsh_word *mrsh_node_get_word(const struct mrsh_node *node) { assert(node->type == MRSH_NODE_WORD); return (struct mrsh_word *)node; } struct mrsh_command *mrsh_node_get_command(const struct mrsh_node *node) { assert(node->type == MRSH_NODE_COMMAND); return (struct mrsh_command *)node; } struct mrsh_and_or_list *mrsh_node_get_and_or_list( const struct mrsh_node *node) { assert(node->type == MRSH_NODE_AND_OR_LIST); return (struct mrsh_and_or_list *)node; } struct mrsh_command_list *mrsh_node_get_command_list( const struct mrsh_node *node) { assert(node->type == MRSH_NODE_COMMAND_LIST); return (struct mrsh_command_list *)node; } struct mrsh_program *mrsh_node_get_program(const struct mrsh_node *node) { assert(node->type == MRSH_NODE_PROGRAM); return (struct mrsh_program *)node; } struct mrsh_word_string *mrsh_word_string_create(char *str, bool single_quoted) { struct mrsh_word_string *ws = calloc(1, sizeof(struct mrsh_word_string)); ws->word.node.type = MRSH_NODE_WORD; ws->word.type = MRSH_WORD_STRING; ws->str = str; ws->single_quoted = single_quoted; return ws; } struct mrsh_word_parameter *mrsh_word_parameter_create(char *name, enum mrsh_word_parameter_op op, bool colon, struct mrsh_word *arg) { struct mrsh_word_parameter *wp = calloc(1, sizeof(struct mrsh_word_parameter)); wp->word.node.type = MRSH_NODE_WORD; wp->word.type = MRSH_WORD_PARAMETER; wp->name = name; wp->op = op; wp->colon = colon; wp->arg = arg; return wp; } struct mrsh_word_command *mrsh_word_command_create(struct mrsh_program *prog, bool back_quoted) { struct mrsh_word_command *wc = calloc(1, sizeof(struct mrsh_word_command)); wc->word.node.type = MRSH_NODE_WORD; wc->word.type = MRSH_WORD_COMMAND; wc->program = prog; wc->back_quoted = back_quoted; return wc; } struct mrsh_word_arithmetic *mrsh_word_arithmetic_create( struct mrsh_word *body) { struct mrsh_word_arithmetic *wa = calloc(1, sizeof(struct mrsh_word_arithmetic)); wa->word.node.type = MRSH_NODE_WORD; wa->word.type = MRSH_WORD_ARITHMETIC; wa->body = body; return wa; } struct mrsh_word_list *mrsh_word_list_create(struct mrsh_array *children, bool double_quoted) { struct mrsh_word_list *wl = calloc(1, sizeof(struct mrsh_word_list)); wl->word.node.type = MRSH_NODE_WORD; wl->word.type = MRSH_WORD_LIST; if (children != NULL) { wl->children = *children; } wl->double_quoted = double_quoted; return wl; } struct mrsh_word_string *mrsh_word_get_string(const struct mrsh_word *word) { assert(word->type == MRSH_WORD_STRING); return (struct mrsh_word_string *)word; } struct mrsh_word_parameter *mrsh_word_get_parameter( const struct mrsh_word *word) { assert(word->type == MRSH_WORD_PARAMETER); return (struct mrsh_word_parameter *)word; } struct mrsh_word_command *mrsh_word_get_command(const struct mrsh_word *word) { assert(word->type == MRSH_WORD_COMMAND); return (struct mrsh_word_command *)word; } struct mrsh_word_arithmetic *mrsh_word_get_arithmetic( const struct mrsh_word *word) { assert(word->type == MRSH_WORD_ARITHMETIC); return (struct mrsh_word_arithmetic *)word; } struct mrsh_word_list *mrsh_word_get_list(const struct mrsh_word *word) { assert(word->type == MRSH_WORD_LIST); return (struct mrsh_word_list *)word; } struct mrsh_simple_command *mrsh_simple_command_create(struct mrsh_word *name, struct mrsh_array *arguments, struct mrsh_array *io_redirects, struct mrsh_array *assignments) { struct mrsh_simple_command *cmd = calloc(1, sizeof(struct mrsh_simple_command)); cmd->command.node.type = MRSH_NODE_COMMAND; cmd->command.type = MRSH_SIMPLE_COMMAND; cmd->name = name; cmd->arguments = *arguments; cmd->io_redirects = *io_redirects; cmd->assignments = *assignments; return cmd; } struct mrsh_brace_group *mrsh_brace_group_create(struct mrsh_array *body) { struct mrsh_brace_group *bg = calloc(1, sizeof(struct mrsh_brace_group)); bg->command.node.type = MRSH_NODE_COMMAND; bg->command.type = MRSH_BRACE_GROUP; bg->body = *body; return bg; } struct mrsh_subshell *mrsh_subshell_create(struct mrsh_array *body) { struct mrsh_subshell *s = calloc(1, sizeof(struct mrsh_subshell)); s->command.node.type = MRSH_NODE_COMMAND; s->command.type = MRSH_SUBSHELL; s->body = *body; return s; } struct mrsh_if_clause *mrsh_if_clause_create(struct mrsh_array *condition, struct mrsh_array *body, struct mrsh_command *else_part) { struct mrsh_if_clause *ic = calloc(1, sizeof(struct mrsh_if_clause)); ic->command.node.type = MRSH_NODE_COMMAND; ic->command.type = MRSH_IF_CLAUSE; ic->condition = *condition; ic->body = *body; ic->else_part = else_part; return ic; } struct mrsh_for_clause *mrsh_for_clause_create(char *name, bool in, struct mrsh_array *word_list, struct mrsh_array *body) { struct mrsh_for_clause *fc = calloc(1, sizeof(struct mrsh_for_clause)); fc->command.node.type = MRSH_NODE_COMMAND; fc->command.type = MRSH_FOR_CLAUSE; fc->name = name; fc->in = in; fc->word_list = *word_list; fc->body = *body; return fc; } struct mrsh_loop_clause *mrsh_loop_clause_create(enum mrsh_loop_type type, struct mrsh_array *condition, struct mrsh_array *body) { struct mrsh_loop_clause *lc = calloc(1, sizeof(struct mrsh_loop_clause)); lc->command.node.type = MRSH_NODE_COMMAND; lc->command.type = MRSH_LOOP_CLAUSE; lc->type = type; lc->condition = *condition; lc->body = *body; return lc; } struct mrsh_case_clause *mrsh_case_clause_create(struct mrsh_word *word, struct mrsh_array *items) { struct mrsh_case_clause *cc = calloc(1, sizeof(struct mrsh_case_clause)); cc->command.node.type = MRSH_NODE_COMMAND; cc->command.type = MRSH_CASE_CLAUSE; cc->word = word; cc->items = *items; return cc; } struct mrsh_function_definition *mrsh_function_definition_create(char *name, struct mrsh_command *body, struct mrsh_array *io_redirects) { struct mrsh_function_definition *fd = calloc(1, sizeof(struct mrsh_function_definition)); fd->command.node.type = MRSH_NODE_COMMAND; fd->command.type = MRSH_FUNCTION_DEFINITION; fd->name = name; fd->body = body; fd->io_redirects = *io_redirects; return fd; } struct mrsh_simple_command *mrsh_command_get_simple_command( const struct mrsh_command *cmd) { assert(cmd->type == MRSH_SIMPLE_COMMAND); return (struct mrsh_simple_command *)cmd; } struct mrsh_brace_group *mrsh_command_get_brace_group( const struct mrsh_command *cmd) { assert(cmd->type == MRSH_BRACE_GROUP); return (struct mrsh_brace_group *)cmd; } struct mrsh_subshell *mrsh_command_get_subshell( const struct mrsh_command *cmd) { assert(cmd->type == MRSH_SUBSHELL); return (struct mrsh_subshell *)cmd; } struct mrsh_if_clause *mrsh_command_get_if_clause( const struct mrsh_command *cmd) { assert(cmd->type == MRSH_IF_CLAUSE); return (struct mrsh_if_clause *)cmd; } struct mrsh_for_clause *mrsh_command_get_for_clause( const struct mrsh_command *cmd) { assert(cmd->type == MRSH_FOR_CLAUSE); return (struct mrsh_for_clause *)cmd; } struct mrsh_loop_clause *mrsh_command_get_loop_clause( const struct mrsh_command *cmd) { assert(cmd->type == MRSH_LOOP_CLAUSE); return (struct mrsh_loop_clause *)cmd; } struct mrsh_case_clause *mrsh_command_get_case_clause( const struct mrsh_command *cmd) { assert(cmd->type == MRSH_CASE_CLAUSE); return (struct mrsh_case_clause *)cmd; } struct mrsh_function_definition *mrsh_command_get_function_definition( const struct mrsh_command *cmd) { assert(cmd->type == MRSH_FUNCTION_DEFINITION); return (struct mrsh_function_definition *)cmd; } struct mrsh_pipeline *mrsh_pipeline_create(struct mrsh_array *commands, bool bang) { struct mrsh_pipeline *pl = calloc(1, sizeof(struct mrsh_pipeline)); pl->and_or_list.node.type = MRSH_NODE_AND_OR_LIST; pl->and_or_list.type = MRSH_AND_OR_LIST_PIPELINE; pl->commands = *commands; pl->bang = bang; return pl; } struct mrsh_binop *mrsh_binop_create(enum mrsh_binop_type type, struct mrsh_and_or_list *left, struct mrsh_and_or_list *right) { struct mrsh_binop *binop = calloc(1, sizeof(struct mrsh_binop)); binop->and_or_list.node.type = MRSH_NODE_AND_OR_LIST; binop->and_or_list.type = MRSH_AND_OR_LIST_BINOP; binop->type = type; binop->left = left; binop->right = right; return binop; } struct mrsh_pipeline *mrsh_and_or_list_get_pipeline( const struct mrsh_and_or_list *and_or_list) { assert(and_or_list->type == MRSH_AND_OR_LIST_PIPELINE); return (struct mrsh_pipeline *)and_or_list; } struct mrsh_binop *mrsh_and_or_list_get_binop( const struct mrsh_and_or_list *and_or_list) { assert(and_or_list->type == MRSH_AND_OR_LIST_BINOP); return (struct mrsh_binop *)and_or_list; } static void node_array_for_each(struct mrsh_array *nodes, mrsh_node_iterator_func iterator, void *user_data) { for (size_t i = 0; i < nodes->len; ++i) { struct mrsh_node *node = nodes->data[i]; mrsh_node_for_each(node, iterator, user_data); } } void mrsh_node_for_each(struct mrsh_node *node, mrsh_node_iterator_func iterator, void *user_data) { iterator(node, user_data); switch (node->type) { case MRSH_NODE_PROGRAM:; struct mrsh_program *program = mrsh_node_get_program(node); node_array_for_each(&program->body, iterator, user_data); return; case MRSH_NODE_COMMAND_LIST:; struct mrsh_command_list *list = mrsh_node_get_command_list(node); mrsh_node_for_each(&list->and_or_list->node, iterator, user_data); return; case MRSH_NODE_AND_OR_LIST:; struct mrsh_and_or_list *and_or_list = mrsh_node_get_and_or_list(node); switch (and_or_list->type) { case MRSH_AND_OR_LIST_BINOP:; struct mrsh_binop *binop = mrsh_and_or_list_get_binop(and_or_list); mrsh_node_for_each(&binop->left->node, iterator, user_data); mrsh_node_for_each(&binop->right->node, iterator, user_data); return; case MRSH_AND_OR_LIST_PIPELINE:; struct mrsh_pipeline *pipeline = mrsh_and_or_list_get_pipeline(and_or_list); node_array_for_each(&pipeline->commands, iterator, user_data); return; } abort(); case MRSH_NODE_COMMAND:; struct mrsh_command *cmd = mrsh_node_get_command(node); switch (cmd->type) { case MRSH_SIMPLE_COMMAND:; struct mrsh_simple_command *sc = mrsh_command_get_simple_command(cmd); if (sc->name != NULL) { mrsh_node_for_each(&sc->name->node, iterator, user_data); } node_array_for_each(&sc->arguments, iterator, user_data); // TODO: io_redirects, assignments return; case MRSH_BRACE_GROUP:; struct mrsh_brace_group *bg = mrsh_command_get_brace_group(cmd); node_array_for_each(&bg->body, iterator, user_data); return; case MRSH_SUBSHELL:; struct mrsh_subshell *ss = mrsh_command_get_subshell(cmd); node_array_for_each(&ss->body, iterator, user_data); return; case MRSH_IF_CLAUSE:; struct mrsh_if_clause *ic = mrsh_command_get_if_clause(cmd); node_array_for_each(&ic->condition, iterator, user_data); node_array_for_each(&ic->body, iterator, user_data); if (ic->else_part != NULL) { mrsh_node_for_each(&ic->else_part->node, iterator, user_data); } return; case MRSH_FOR_CLAUSE:; struct mrsh_for_clause *fc = mrsh_command_get_for_clause(cmd); node_array_for_each(&fc->word_list, iterator, user_data); node_array_for_each(&fc->body, iterator, user_data); return; case MRSH_LOOP_CLAUSE:; struct mrsh_loop_clause *lc = mrsh_command_get_loop_clause(cmd); node_array_for_each(&lc->condition, iterator, user_data); node_array_for_each(&lc->body, iterator, user_data); return; case MRSH_CASE_CLAUSE:; struct mrsh_case_clause *cc = mrsh_command_get_case_clause(cmd); mrsh_node_for_each(&cc->word->node, iterator, user_data); // TODO: items return; case MRSH_FUNCTION_DEFINITION:; struct mrsh_function_definition *fn = mrsh_command_get_function_definition(cmd); mrsh_node_for_each(&fn->body->node, iterator, user_data); return; } abort(); case MRSH_NODE_WORD:; struct mrsh_word *word = mrsh_node_get_word(node); switch (word->type) { case MRSH_WORD_STRING: return; case MRSH_WORD_PARAMETER:; struct mrsh_word_parameter *wp = mrsh_word_get_parameter(word); if (wp->arg != NULL) { mrsh_node_for_each(&wp->arg->node, iterator, user_data); } return; case MRSH_WORD_COMMAND:; struct mrsh_word_command *wc = mrsh_word_get_command(word); if (wc->program != NULL) { mrsh_node_for_each(&wc->program->node, iterator, user_data); } return; case MRSH_WORD_ARITHMETIC:; struct mrsh_word_arithmetic *wa = mrsh_word_get_arithmetic(word); mrsh_node_for_each(&wa->body->node, iterator, user_data); return; case MRSH_WORD_LIST:; struct mrsh_word_list *wl = mrsh_word_get_list(word); node_array_for_each(&wl->children, iterator, user_data); return; } abort(); } abort(); } static void position_next(struct mrsh_position *dst, const struct mrsh_position *src) { *dst = *src; ++dst->offset; ++dst->column; } void mrsh_word_range(struct mrsh_word *word, struct mrsh_position *begin, struct mrsh_position *end) { if (begin == NULL && end == NULL) { return; } struct mrsh_position _begin, _end; if (begin == NULL) { begin = &_begin; } if (end == NULL) { end = &_end; } switch (word->type) { case MRSH_WORD_STRING:; struct mrsh_word_string *ws = mrsh_word_get_string(word); *begin = ws->range.begin; *end = ws->range.end; return; case MRSH_WORD_PARAMETER:; struct mrsh_word_parameter *wp = mrsh_word_get_parameter(word); *begin = wp->dollar_pos; if (mrsh_position_valid(&wp->rbrace_pos)) { position_next(end, &wp->rbrace_pos); } else { *end = wp->name_range.end; } return; case MRSH_WORD_COMMAND:; struct mrsh_word_command *wc = mrsh_word_get_command(word); *begin = wc->range.begin; *end = wc->range.end; return; case MRSH_WORD_ARITHMETIC: abort(); // TODO case MRSH_WORD_LIST:; struct mrsh_word_list *wl = mrsh_word_get_list(word); if (wl->children.len == 0) { *begin = *end = (struct mrsh_position){0}; } else { struct mrsh_word *first = wl->children.data[0]; struct mrsh_word *last = wl->children.data[wl->children.len - 1]; mrsh_word_range(first, begin, NULL); mrsh_word_range(last, NULL, end); } return; } abort(); } void mrsh_command_range(struct mrsh_command *cmd, struct mrsh_position *begin, struct mrsh_position *end) { if (begin == NULL && end == NULL) { return; } struct mrsh_position _begin, _end; if (begin == NULL) { begin = &_begin; } if (end == NULL) { end = &_end; } switch (cmd->type) { case MRSH_SIMPLE_COMMAND:; struct mrsh_simple_command *sc = mrsh_command_get_simple_command(cmd); if (sc->name != NULL) { mrsh_word_range(sc->name, begin, end); } else { assert(sc->assignments.len > 0); struct mrsh_assignment *first = sc->assignments.data[0]; *begin = first->name_range.begin; *end = *begin; // That's a lie, but it'll be fixed by the code below } struct mrsh_position maybe_end; for (size_t i = 0; i < sc->arguments.len; ++i) { struct mrsh_word *arg = sc->arguments.data[i]; mrsh_word_range(arg, NULL, &maybe_end); if (maybe_end.offset > end->offset) { *end = maybe_end; } } for (size_t i = 0; i < sc->io_redirects.len; ++i) { struct mrsh_io_redirect *redir = sc->io_redirects.data[i]; mrsh_word_range(redir->name, NULL, &maybe_end); if (maybe_end.offset > end->offset) { *end = maybe_end; } } for (size_t i = 0; i < sc->assignments.len; ++i) { struct mrsh_assignment *assign = sc->assignments.data[i]; mrsh_word_range(assign->value, NULL, &maybe_end); if (maybe_end.offset > end->offset) { *end = maybe_end; } } return; case MRSH_BRACE_GROUP:; struct mrsh_brace_group *bg = mrsh_command_get_brace_group(cmd); *begin = bg->lbrace_pos; position_next(end, &bg->rbrace_pos); return; case MRSH_SUBSHELL:; struct mrsh_subshell *s = mrsh_command_get_subshell(cmd); *begin = s->lparen_pos; position_next(end, &s->rparen_pos); return; case MRSH_IF_CLAUSE:; struct mrsh_if_clause *ic = mrsh_command_get_if_clause(cmd); *begin = ic->if_range.begin; *end = ic->fi_range.end; return; case MRSH_FOR_CLAUSE:; struct mrsh_for_clause *fc = mrsh_command_get_for_clause(cmd); *begin = fc->for_range.begin; *end = fc->done_range.end; return; case MRSH_LOOP_CLAUSE:; struct mrsh_loop_clause *lc = mrsh_command_get_loop_clause(cmd); *begin = lc->while_until_range.begin; *end = lc->done_range.end; return; case MRSH_CASE_CLAUSE:; struct mrsh_case_clause *cc = mrsh_command_get_case_clause(cmd); *begin = cc->case_range.begin; *end = cc->esac_range.end; return; case MRSH_FUNCTION_DEFINITION:; struct mrsh_function_definition *fd = mrsh_command_get_function_definition(cmd); *begin = fd->name_range.begin; mrsh_command_range(fd->body, NULL, end); } abort(); } static void buffer_append_str(struct mrsh_buffer *buf, const char *str) { mrsh_buffer_append(buf, str, strlen(str)); } static void word_str(const struct mrsh_word *word, struct mrsh_buffer *buf) { switch (word->type) { case MRSH_WORD_STRING:; const struct mrsh_word_string *ws = mrsh_word_get_string(word); buffer_append_str(buf, ws->str); return; case MRSH_WORD_PARAMETER: case MRSH_WORD_COMMAND: case MRSH_WORD_ARITHMETIC: abort(); case MRSH_WORD_LIST:; const struct mrsh_word_list *wl = mrsh_word_get_list(word); for (size_t i = 0; i < wl->children.len; ++i) { const struct mrsh_word *child = wl->children.data[i]; word_str(child, buf); } return; } abort(); } char *mrsh_word_str(const struct mrsh_word *word) { struct mrsh_buffer buf = {0}; word_str(word, &buf); mrsh_buffer_append_char(&buf, '\0'); return mrsh_buffer_steal(&buf); } static const char *binop_type_str(enum mrsh_binop_type t) { switch (t) { case MRSH_BINOP_AND: return "&&"; case MRSH_BINOP_OR: return "||"; } abort(); } static void node_format(struct mrsh_node *node, struct mrsh_buffer *buf); static void node_array_format(struct mrsh_array *array, const char *sep, struct mrsh_buffer *buf) { for (size_t i = 0; i < array->len; i++) { struct mrsh_node *node = array->data[i]; if (i > 0) { buffer_append_str(buf, sep); } node_format(node, buf); } } static void node_format(struct mrsh_node *node, struct mrsh_buffer *buf) { switch (node->type) { case MRSH_NODE_PROGRAM:; struct mrsh_program *program = mrsh_node_get_program(node); node_array_format(&program->body, " ", buf); return; case MRSH_NODE_COMMAND_LIST:; struct mrsh_command_list *list = mrsh_node_get_command_list(node); node_format(&list->and_or_list->node, buf); buffer_append_str(buf, list->ampersand ? " &" : ";"); return; case MRSH_NODE_AND_OR_LIST:; struct mrsh_and_or_list *and_or_list = mrsh_node_get_and_or_list(node); switch (and_or_list->type) { case MRSH_AND_OR_LIST_BINOP:; struct mrsh_binop *binop = mrsh_and_or_list_get_binop(and_or_list); node_format(&binop->left->node, buf); mrsh_buffer_append_char(buf, ' '); buffer_append_str(buf, binop_type_str(binop->type)); mrsh_buffer_append_char(buf, ' '); node_format(&binop->right->node, buf); return; case MRSH_AND_OR_LIST_PIPELINE:; struct mrsh_pipeline *pipeline = mrsh_and_or_list_get_pipeline(and_or_list); if (pipeline->bang) { buffer_append_str(buf, "! "); } node_array_format(&pipeline->commands, " | ", buf); return; } abort(); case MRSH_NODE_COMMAND:; struct mrsh_command *cmd = mrsh_node_get_command(node); switch (cmd->type) { case MRSH_SIMPLE_COMMAND:; struct mrsh_simple_command *sc = mrsh_command_get_simple_command(cmd); if (sc->name != NULL) { node_format(&sc->name->node, buf); mrsh_buffer_append_char(buf, ' '); } node_array_format(&sc->arguments, " ", buf); // TODO: io_redirects, assignments return; case MRSH_BRACE_GROUP:; struct mrsh_brace_group *bg = mrsh_command_get_brace_group(cmd); buffer_append_str(buf, "{ "); node_array_format(&bg->body, " ", buf); buffer_append_str(buf, "; }"); return; case MRSH_SUBSHELL:; struct mrsh_subshell *ss = mrsh_command_get_subshell(cmd); mrsh_buffer_append_char(buf, '('); node_array_format(&ss->body, " ", buf); mrsh_buffer_append_char(buf, ')'); return; case MRSH_IF_CLAUSE:; struct mrsh_if_clause *ic = mrsh_command_get_if_clause(cmd); buffer_append_str(buf, "if "); node_array_format(&ic->condition, " ", buf); buffer_append_str(buf, "then "); node_array_format(&ic->body, " ", buf); if (ic->else_part != NULL) { // TODO: elif buffer_append_str(buf, "else "); node_format(&ic->else_part->node, buf); } buffer_append_str(buf, "fi"); return; case MRSH_FOR_CLAUSE:; //struct mrsh_for_clause *fc = mrsh_command_get_for_clause(cmd); // TODO return; case MRSH_LOOP_CLAUSE:; struct mrsh_loop_clause *lc = mrsh_command_get_loop_clause(cmd); buffer_append_str(buf, lc->type == MRSH_LOOP_WHILE ? "while " : "until "); node_array_format(&lc->condition, " ", buf); buffer_append_str(buf, "do "); node_array_format(&lc->body, " ", buf); buffer_append_str(buf, "done"); return; case MRSH_CASE_CLAUSE:; //struct mrsh_case_clause *cc = mrsh_command_get_case_clause(cmd); // TODO return; case MRSH_FUNCTION_DEFINITION:; struct mrsh_function_definition *fn = mrsh_command_get_function_definition(cmd); buffer_append_str(buf, fn->name); buffer_append_str(buf, "()"); node_format(&fn->body->node, buf); // TODO: io-redirect return; } abort(); case MRSH_NODE_WORD:; // TODO: quoting struct mrsh_word *word = mrsh_node_get_word(node); switch (word->type) { case MRSH_WORD_STRING:; struct mrsh_word_string *ws = mrsh_word_get_string(word); if (ws->single_quoted) { mrsh_buffer_append_char(buf, '\''); } buffer_append_str(buf, ws->str); if (ws->single_quoted) { mrsh_buffer_append_char(buf, '\''); } return; case MRSH_WORD_PARAMETER:; struct mrsh_word_parameter *wp = mrsh_word_get_parameter(word); buffer_append_str(buf, "${"); if (wp->arg != NULL) { node_format(&wp->arg->node, buf); } buffer_append_str(buf, "}"); return; case MRSH_WORD_COMMAND:; struct mrsh_word_command *wc = mrsh_word_get_command(word); buffer_append_str(buf, wc->back_quoted ? "`" : "$("); if (wc->program != NULL) { node_format(&wc->program->node, buf); } buffer_append_str(buf, wc->back_quoted ? "`" : ")"); return; case MRSH_WORD_ARITHMETIC:; struct mrsh_word_arithmetic *wa = mrsh_word_get_arithmetic(word); node_format(&wa->body->node, buf); return; case MRSH_WORD_LIST:; struct mrsh_word_list *wl = mrsh_word_get_list(word); if (wl->double_quoted) { mrsh_buffer_append_char(buf, '"'); } node_array_format(&wl->children, "", buf); if (wl->double_quoted) { mrsh_buffer_append_char(buf, '"'); } return; } abort(); } abort(); } char *mrsh_node_format(struct mrsh_node *node) { struct mrsh_buffer buf = {0}; node_format(node, &buf); mrsh_buffer_append_char(&buf, '\0'); return mrsh_buffer_steal(&buf); } struct mrsh_node *mrsh_node_copy(const struct mrsh_node *node) { switch (node->type) { case MRSH_NODE_PROGRAM:; struct mrsh_program *prog = mrsh_node_get_program(node); struct mrsh_program *prog_copy = mrsh_program_copy(prog); return &prog_copy->node; case MRSH_NODE_COMMAND_LIST:; struct mrsh_command_list *cl = mrsh_node_get_command_list(node); struct mrsh_command_list *cl_copy = mrsh_command_list_copy(cl); return &cl_copy->node; case MRSH_NODE_AND_OR_LIST:; struct mrsh_and_or_list *aol = mrsh_node_get_and_or_list(node); struct mrsh_and_or_list *aol_copy = mrsh_and_or_list_copy(aol); return &aol_copy->node; case MRSH_NODE_COMMAND:; struct mrsh_command *cmd = mrsh_node_get_command(node); struct mrsh_command *cmd_copy = mrsh_command_copy(cmd); return &cmd_copy->node; case MRSH_NODE_WORD:; struct mrsh_word *word = mrsh_node_get_word(node); struct mrsh_word *word_copy = mrsh_word_copy(word); return &word_copy->node; } abort(); } struct mrsh_word *mrsh_word_copy(const struct mrsh_word *word) { switch (word->type) { case MRSH_WORD_STRING:; struct mrsh_word_string *ws = mrsh_word_get_string(word); struct mrsh_word_string *ws_copy = mrsh_word_string_create(strdup(ws->str), ws->single_quoted); return &ws_copy->word; case MRSH_WORD_PARAMETER:; struct mrsh_word_parameter *wp = mrsh_word_get_parameter(word); struct mrsh_word *arg = NULL; if (wp->arg != NULL) { arg = mrsh_word_copy(wp->arg); } struct mrsh_word_parameter *wp_copy = mrsh_word_parameter_create( strdup(wp->name), wp->op, wp->colon, arg); return &wp_copy->word; case MRSH_WORD_COMMAND:; struct mrsh_word_command *wc = mrsh_word_get_command(word); struct mrsh_word_command *wc_copy = mrsh_word_command_create( mrsh_program_copy(wc->program), wc->back_quoted); return &wc_copy->word; case MRSH_WORD_ARITHMETIC:; struct mrsh_word_arithmetic *wa = mrsh_word_get_arithmetic(word); struct mrsh_word_arithmetic *wa_copy = mrsh_word_arithmetic_create( mrsh_word_copy(wa->body)); return &wa_copy->word; case MRSH_WORD_LIST:; struct mrsh_word_list *wl = mrsh_word_get_list(word); struct mrsh_array children = {0}; mrsh_array_reserve(&children, wl->children.len); for (size_t i = 0; i < wl->children.len; ++i) { struct mrsh_word *child = wl->children.data[i]; mrsh_array_add(&children, mrsh_word_copy(child)); } struct mrsh_word_list *wl_copy = mrsh_word_list_create(&children, wl->double_quoted); return &wl_copy->word; } abort(); } struct mrsh_io_redirect *mrsh_io_redirect_copy( const struct mrsh_io_redirect *redir) { struct mrsh_io_redirect *redir_copy = calloc(1, sizeof(struct mrsh_io_redirect)); redir_copy->io_number = redir->io_number; redir_copy->op = redir->op; redir_copy->name = mrsh_word_copy(redir->name); mrsh_array_reserve(&redir_copy->here_document, redir->here_document.len); for (size_t i = 0; i < redir->here_document.len; ++i) { struct mrsh_word *line = redir->here_document.data[i]; mrsh_array_add(&redir_copy->here_document, mrsh_word_copy(line)); } return redir_copy; } struct mrsh_assignment *mrsh_assignment_copy( const struct mrsh_assignment *assign) { struct mrsh_assignment *assign_copy = calloc(1, sizeof(struct mrsh_assignment)); assign_copy->name = strdup(assign->name); assign_copy->value = mrsh_word_copy(assign->value); return assign_copy; } static void command_list_array_copy(struct mrsh_array *dst, const struct mrsh_array *src) { mrsh_array_reserve(dst, src->len); for (size_t i = 0; i < src->len; ++i) { struct mrsh_command_list *l = src->data[i]; mrsh_array_add(dst, mrsh_command_list_copy(l)); } } static struct mrsh_case_item *case_item_copy(const struct mrsh_case_item *ci) { struct mrsh_case_item *ci_copy = calloc(1, sizeof(struct mrsh_case_item)); mrsh_array_reserve(&ci_copy->patterns, ci->patterns.len); for (size_t i = 0; i < ci->patterns.len; ++i) { struct mrsh_word *pattern = ci->patterns.data[i]; mrsh_array_add(&ci_copy->patterns, mrsh_word_copy(pattern)); } command_list_array_copy(&ci_copy->body, &ci->body); return ci_copy; } struct mrsh_command *mrsh_command_copy(const struct mrsh_command *cmd) { struct mrsh_array io_redirects = {0}; switch (cmd->type) { case MRSH_SIMPLE_COMMAND:; struct mrsh_simple_command *sc = mrsh_command_get_simple_command(cmd); struct mrsh_word *name = NULL; if (sc->name != NULL) { name = mrsh_word_copy(sc->name); } struct mrsh_array arguments = {0}; mrsh_array_reserve(&arguments, sc->arguments.len); for (size_t i = 0; i < sc->arguments.len; ++i) { struct mrsh_word *arg = sc->arguments.data[i]; mrsh_array_add(&arguments, mrsh_word_copy(arg)); } mrsh_array_reserve(&io_redirects, sc->io_redirects.len); for (size_t i = 0; i < sc->io_redirects.len; ++i) { struct mrsh_io_redirect *redir = sc->io_redirects.data[i]; mrsh_array_add(&io_redirects, mrsh_io_redirect_copy(redir)); } struct mrsh_array assignments = {0}; mrsh_array_reserve(&assignments, sc->assignments.len); for (size_t i = 0; i < sc->assignments.len; ++i) { struct mrsh_assignment *assign = sc->assignments.data[i]; mrsh_array_add(&assignments, mrsh_assignment_copy(assign)); } struct mrsh_simple_command *sc_copy = mrsh_simple_command_create( name, &arguments, &io_redirects, &assignments); return &sc_copy->command; case MRSH_BRACE_GROUP:; struct mrsh_brace_group *bg = mrsh_command_get_brace_group(cmd); struct mrsh_array bg_body = {0}; command_list_array_copy(&bg_body, &bg->body); struct mrsh_brace_group *bg_copy = mrsh_brace_group_create(&bg_body); return &bg_copy->command; case MRSH_SUBSHELL:; struct mrsh_subshell *ss = mrsh_command_get_subshell(cmd); struct mrsh_array ss_body = {0}; command_list_array_copy(&ss_body, &ss->body); struct mrsh_subshell *ss_copy = mrsh_subshell_create(&ss_body); return &ss_copy->command; case MRSH_IF_CLAUSE:; struct mrsh_if_clause *ic = mrsh_command_get_if_clause(cmd); struct mrsh_array ic_condition = {0}; command_list_array_copy(&ic_condition, &ic->condition); struct mrsh_array ic_body = {0}; command_list_array_copy(&ic_body, &ic->body); struct mrsh_command *else_part = NULL; if (ic->else_part != NULL) { else_part = mrsh_command_copy(ic->else_part); } struct mrsh_if_clause *ic_copy = mrsh_if_clause_create(&ic_condition, &ic_body, else_part); return &ic_copy->command; case MRSH_FOR_CLAUSE:; struct mrsh_for_clause *fc = mrsh_command_get_for_clause(cmd); struct mrsh_array word_list = {0}; mrsh_array_reserve(&word_list, fc->word_list.len); for (size_t i = 0; i < fc->word_list.len; ++i) { struct mrsh_word *word = fc->word_list.data[i]; mrsh_array_add(&word_list, mrsh_word_copy(word)); } struct mrsh_array fc_body = {0}; command_list_array_copy(&fc_body, &fc->body); struct mrsh_for_clause *fc_copy = mrsh_for_clause_create( strdup(fc->name), fc->in, &word_list, &fc_body); return &fc_copy->command; case MRSH_LOOP_CLAUSE:; struct mrsh_loop_clause *lc = mrsh_command_get_loop_clause(cmd); struct mrsh_array lc_condition = {0}; command_list_array_copy(&lc_condition, &lc->condition); struct mrsh_array lc_body = {0}; command_list_array_copy(&lc_body, &lc->body); struct mrsh_loop_clause *lc_copy = mrsh_loop_clause_create(lc->type, &lc_condition, &lc_body); return &lc_copy->command; case MRSH_CASE_CLAUSE:; struct mrsh_case_clause *cc = mrsh_command_get_case_clause(cmd); struct mrsh_array items = {0}; mrsh_array_reserve(&items, cc->items.len); for (size_t i = 0; i < cc->items.len; ++i) { struct mrsh_case_item *ci = cc->items.data[i]; mrsh_array_add(&items, case_item_copy(ci)); } struct mrsh_case_clause *cc_copy = mrsh_case_clause_create(mrsh_word_copy(cc->word), &items); return &cc_copy->command; case MRSH_FUNCTION_DEFINITION:; struct mrsh_function_definition *fd = mrsh_command_get_function_definition(cmd); mrsh_array_reserve(&io_redirects, fd->io_redirects.len); for (size_t i = 0; i < fd->io_redirects.len; ++i) { struct mrsh_io_redirect *redir = fd->io_redirects.data[i]; mrsh_array_add(&io_redirects, mrsh_io_redirect_copy(redir)); } struct mrsh_function_definition *fd_copy = mrsh_function_definition_create(strdup(fd->name), mrsh_command_copy(fd->body), &io_redirects); return &fd_copy->command; } abort(); } struct mrsh_and_or_list *mrsh_and_or_list_copy( const struct mrsh_and_or_list *and_or_list) { switch (and_or_list->type) { case MRSH_AND_OR_LIST_PIPELINE:; struct mrsh_pipeline *pl = mrsh_and_or_list_get_pipeline(and_or_list); struct mrsh_array commands = {0}; mrsh_array_reserve(&commands, pl->commands.len); for (size_t i = 0; i < pl->commands.len; ++i) { struct mrsh_command *cmd = pl->commands.data[i]; mrsh_array_add(&commands, mrsh_command_copy(cmd)); } struct mrsh_pipeline *p_copy = mrsh_pipeline_create(&commands, pl->bang); return &p_copy->and_or_list; case MRSH_AND_OR_LIST_BINOP:; struct mrsh_binop *binop = mrsh_and_or_list_get_binop(and_or_list); struct mrsh_binop *binop_copy = mrsh_binop_create(binop->type, mrsh_and_or_list_copy(binop->left), mrsh_and_or_list_copy(binop->right)); return &binop_copy->and_or_list; } abort(); } struct mrsh_command_list *mrsh_command_list_copy( const struct mrsh_command_list *l) { struct mrsh_command_list *l_copy = mrsh_command_list_create(); l_copy->and_or_list = mrsh_and_or_list_copy(l->and_or_list); l_copy->ampersand = l->ampersand; return l_copy; } struct mrsh_program *mrsh_program_copy(const struct mrsh_program *prog) { struct mrsh_program *prog_copy = mrsh_program_create(); command_list_array_copy(&prog_copy->body, &prog->body); return prog_copy; } ================================================ FILE: ast_print.c ================================================ #include #include #include #include #include #include #define L_LINE "│ " #define L_VAL "├─" #define L_LAST "└─" #define L_GAP " " static size_t make_sub_prefix(const char *prefix, bool last, char *buf) { if (buf != NULL) { memcpy(buf, prefix, strlen(prefix) + 1); strcat(buf, last ? L_GAP : L_LINE); } return strlen(prefix) + strlen(L_LINE) + 1; } static void print_prefix(const char *prefix, bool last) { printf("%s%s", prefix, last ? L_LAST : L_VAL); } static void print_range(const struct mrsh_range *range) { printf("[%d:%d → %d:%d]", range->begin.line, range->begin.column, range->end.line, range->end.column); } static const char *word_parameter_op_str(enum mrsh_word_parameter_op op) { switch (op) { case MRSH_PARAM_NONE: return NULL; case MRSH_PARAM_MINUS: return "-"; case MRSH_PARAM_EQUAL: return "="; case MRSH_PARAM_QMARK: return "?"; case MRSH_PARAM_PLUS: return "+"; case MRSH_PARAM_LEADING_HASH: return "# (leading)"; case MRSH_PARAM_PERCENT: return "%"; case MRSH_PARAM_DPERCENT: return "%%"; case MRSH_PARAM_HASH: return "#"; case MRSH_PARAM_DHASH: return "##"; } abort(); } static void print_program(struct mrsh_program *prog, const char *prefix); static void print_word(struct mrsh_word *word, const char *prefix) { char sub_prefix[make_sub_prefix(prefix, true, NULL)]; switch (word->type) { case MRSH_WORD_STRING:; struct mrsh_word_string *ws = mrsh_word_get_string(word); printf("word_string%s ", ws->single_quoted ? " (quoted)" : ""); print_range(&ws->range); printf(" %s\n", ws->str); break; case MRSH_WORD_PARAMETER:; struct mrsh_word_parameter *wp = mrsh_word_get_parameter(word); printf("word_parameter\n"); print_prefix(prefix, wp->op == MRSH_PARAM_NONE && wp->arg == NULL); printf("name %s\n", wp->name); if (wp->op != MRSH_PARAM_NONE) { print_prefix(prefix, wp->arg == NULL); printf("op %s%s\n", wp->colon ? ":" : "", word_parameter_op_str(wp->op)); } if (wp->arg != NULL) { make_sub_prefix(prefix, true, sub_prefix); print_prefix(prefix, true); printf("arg ─ "); print_word(wp->arg, sub_prefix); } break; case MRSH_WORD_COMMAND:; struct mrsh_word_command *wc = mrsh_word_get_command(word); printf("word_command%s ─ ", wc->back_quoted ? " (quoted)" : ""); print_program(wc->program, prefix); break; case MRSH_WORD_ARITHMETIC:; struct mrsh_word_arithmetic *wa = mrsh_word_get_arithmetic(word); printf("word_arithmetic ─ "); print_word(wa->body, prefix); break; case MRSH_WORD_LIST:; struct mrsh_word_list *wl = mrsh_word_get_list(word); printf("word_list%s\n", wl->double_quoted ? " (quoted)" : ""); for (size_t i = 0; i < wl->children.len; ++i) { struct mrsh_word *child = wl->children.data[i]; bool last = i == wl->children.len - 1; make_sub_prefix(prefix, last, sub_prefix); print_prefix(prefix, last); print_word(child, sub_prefix); } break; } } static const char *io_redirect_op_str(enum mrsh_io_redirect_op op) { switch (op) { case MRSH_IO_LESS: return "<"; case MRSH_IO_GREAT: return ">"; case MRSH_IO_CLOBBER: return ">|"; case MRSH_IO_DGREAT: return ">>"; case MRSH_IO_LESSAND: return "<&"; case MRSH_IO_GREATAND: return ">&"; case MRSH_IO_LESSGREAT: return "<>"; case MRSH_IO_DLESS: return "<<"; case MRSH_IO_DLESSDASH: return "<<-"; } abort(); } static void print_word_array(struct mrsh_array *words, const char *prefix); static void print_io_redirect(struct mrsh_io_redirect *redir, const char *prefix) { printf("io_redirect\n"); print_prefix(prefix, false); printf("io_number %d\n", redir->io_number); print_prefix(prefix, false); printf("op %s\n", io_redirect_op_str(redir->op)); bool name_is_last = redir->here_document.len == 0; char sub_prefix[make_sub_prefix(prefix, name_is_last, NULL)]; make_sub_prefix(prefix, name_is_last, sub_prefix); print_prefix(prefix, name_is_last); printf("name ─ "); print_word(redir->name, sub_prefix); if (redir->here_document.len > 0) { make_sub_prefix(prefix, true, sub_prefix); print_prefix(prefix, true); printf("here_document\n"); print_word_array(&redir->here_document, sub_prefix); } } static void print_assignment(struct mrsh_assignment *assign, const char *prefix) { printf("assignment\n"); print_prefix(prefix, false); printf("name %s\n", assign->name); char sub_prefix[make_sub_prefix(prefix, true, NULL)]; make_sub_prefix(prefix, true, sub_prefix); print_prefix(prefix, true); printf("value ─ "); print_word(assign->value, sub_prefix); } static void print_simple_command(struct mrsh_simple_command *cmd, const char *prefix) { printf("simple_command\n"); char sub_prefix[make_sub_prefix(prefix, false, NULL)]; if (cmd->name != NULL) { bool last = cmd->arguments.len == 0 && cmd->io_redirects.len == 0 && cmd->assignments.len == 0; make_sub_prefix(prefix, last, sub_prefix); print_prefix(prefix, last); printf("name ─ "); print_word(cmd->name, sub_prefix); } for (size_t i = 0; i < cmd->arguments.len; ++i) { struct mrsh_word *arg = cmd->arguments.data[i]; bool last = i == cmd->arguments.len - 1 && cmd->io_redirects.len == 0 && cmd->assignments.len == 0; make_sub_prefix(prefix, last, sub_prefix); print_prefix(prefix, last); printf("argument %zu ─ ", i + 1); print_word(arg, sub_prefix); } for (size_t i = 0; i < cmd->io_redirects.len; ++i) { struct mrsh_io_redirect *redir = cmd->io_redirects.data[i]; bool last = i == cmd->io_redirects.len - 1 && cmd->assignments.len == 0; make_sub_prefix(prefix, last, sub_prefix); print_prefix(prefix, last); print_io_redirect(redir, sub_prefix); } for (size_t i = 0; i < cmd->assignments.len; ++i) { struct mrsh_assignment *assign = cmd->assignments.data[i]; bool last = i == cmd->assignments.len - 1; make_sub_prefix(prefix, last, sub_prefix); print_prefix(prefix, last); print_assignment(assign, sub_prefix); } } static void print_command_list(struct mrsh_command_list *l, const char *prefix); static void print_command_list_array(struct mrsh_array *array, const char *prefix) { for (size_t i = 0; i < array->len; ++i) { struct mrsh_command_list *l = array->data[i]; bool last = i == array->len - 1; char sub_prefix[make_sub_prefix(prefix, last, NULL)]; make_sub_prefix(prefix, last, sub_prefix); print_prefix(prefix, last); print_command_list(l, sub_prefix); } } static void print_command(struct mrsh_command *cmd, const char *prefix); static void print_if_clause(struct mrsh_if_clause *ic, const char *prefix) { printf("if_clause\n"); char sub_prefix[make_sub_prefix(prefix, false, NULL)]; make_sub_prefix(prefix, false, sub_prefix); print_prefix(prefix, false); printf("condition\n"); print_command_list_array(&ic->condition, sub_prefix); bool last = ic->else_part == NULL; make_sub_prefix(prefix, last, sub_prefix); print_prefix(prefix, last); printf("body\n"); print_command_list_array(&ic->body, sub_prefix); if (ic->else_part != NULL) { make_sub_prefix(prefix, true, sub_prefix); print_prefix(prefix, true); printf("else_part ─ "); print_command(ic->else_part, sub_prefix); } } static void print_word_array(struct mrsh_array *words, const char *prefix) { for (size_t i = 0; i < words->len; ++i) { struct mrsh_word *word = words->data[i]; bool last = i == words->len - 1; char sub_prefix[make_sub_prefix(prefix, last, NULL)]; make_sub_prefix(prefix, last, sub_prefix); print_prefix(prefix, last); print_word(word, sub_prefix); } } static void print_for_clause(struct mrsh_for_clause *fc, const char *prefix) { printf("for_clause\n"); char sub_prefix[make_sub_prefix(prefix, false, NULL)]; make_sub_prefix(prefix, false, sub_prefix); print_prefix(prefix, false); printf("name %s\n", fc->name); if (fc->in) { print_prefix(prefix, false); printf("in\n"); print_word_array(&fc->word_list, sub_prefix); } make_sub_prefix(prefix, true, sub_prefix); print_prefix(prefix, true); printf("body\n"); print_command_list_array(&fc->body, sub_prefix); } static const char *loop_type_str(enum mrsh_loop_type type) { switch (type) { case MRSH_LOOP_UNTIL: return "until"; case MRSH_LOOP_WHILE: return "while"; } abort(); } static void print_loop_clause(struct mrsh_loop_clause *lc, const char *prefix) { printf("loop_clause %s\n", loop_type_str(lc->type)); char sub_prefix[make_sub_prefix(prefix, false, NULL)]; make_sub_prefix(prefix, false, sub_prefix); print_prefix(prefix, false); printf("condition\n"); print_command_list_array(&lc->condition, sub_prefix); make_sub_prefix(prefix, true, sub_prefix); print_prefix(prefix, true); printf("body\n"); print_command_list_array(&lc->body, sub_prefix); } static void print_case_item(struct mrsh_case_item *item, const char *prefix) { printf("case_item\n"); char sub_prefix[make_sub_prefix(prefix, false, NULL)]; make_sub_prefix(prefix, false, sub_prefix); print_prefix(prefix, false); printf("patterns\n"); print_word_array(&item->patterns, sub_prefix); make_sub_prefix(prefix, true, sub_prefix); print_prefix(prefix, true); printf("body\n"); print_command_list_array(&item->body, sub_prefix); } static void print_case_item_array(struct mrsh_array *items, const char *prefix) { for (size_t i = 0; i < items->len; ++i) { struct mrsh_case_item *item = items->data[i]; bool last = i == items->len - 1; char sub_prefix[make_sub_prefix(prefix, last, NULL)]; make_sub_prefix(prefix, last, sub_prefix); print_prefix(prefix, last); print_case_item(item, sub_prefix); } } static void print_case_clause(struct mrsh_case_clause *cc, const char *prefix) { printf("case_clause\n"); char sub_prefix[make_sub_prefix(prefix, false, NULL)]; make_sub_prefix(prefix, false, sub_prefix); print_prefix(prefix, false); printf("word ─ "); print_word(cc->word, sub_prefix); make_sub_prefix(prefix, true, sub_prefix); print_prefix(prefix, true); printf("items\n"); print_case_item_array(&cc->items, sub_prefix); } static void print_function_definition(struct mrsh_function_definition *fd, const char *prefix) { printf("function_definition %s ─ ", fd->name); print_command(fd->body, prefix); // TODO: print io_redirects } static void print_command(struct mrsh_command *cmd, const char *prefix) { switch (cmd->type) { case MRSH_SIMPLE_COMMAND:; struct mrsh_simple_command *sc = mrsh_command_get_simple_command(cmd); print_simple_command(sc, prefix); break; case MRSH_BRACE_GROUP:; struct mrsh_brace_group *bg = mrsh_command_get_brace_group(cmd); printf("brace_group\n"); print_command_list_array(&bg->body, prefix); break; case MRSH_SUBSHELL:; struct mrsh_subshell *s = mrsh_command_get_subshell(cmd); printf("subshell\n"); print_command_list_array(&s->body, prefix); break; case MRSH_IF_CLAUSE:; struct mrsh_if_clause *ic = mrsh_command_get_if_clause(cmd); print_if_clause(ic, prefix); break; case MRSH_FOR_CLAUSE:; struct mrsh_for_clause *fc = mrsh_command_get_for_clause(cmd); print_for_clause(fc, prefix); break; case MRSH_LOOP_CLAUSE:; struct mrsh_loop_clause *lc = mrsh_command_get_loop_clause(cmd); print_loop_clause(lc, prefix); break; case MRSH_CASE_CLAUSE:; struct mrsh_case_clause *cc = mrsh_command_get_case_clause(cmd); print_case_clause(cc, prefix); break; case MRSH_FUNCTION_DEFINITION:; struct mrsh_function_definition *fd = mrsh_command_get_function_definition(cmd); print_function_definition(fd, prefix); break; } } static void print_pipeline(struct mrsh_pipeline *pl, const char *prefix) { printf("pipeline%s\n", pl->bang ? " !" : ""); for (size_t i = 0; i < pl->commands.len; ++i) { struct mrsh_command *cmd = pl->commands.data[i]; bool last = i == pl->commands.len - 1; char sub_prefix[make_sub_prefix(prefix, last, NULL)]; make_sub_prefix(prefix, last, sub_prefix); print_prefix(prefix, last); print_command(cmd, sub_prefix); } } static const char *binop_type_str(enum mrsh_binop_type type) { switch (type) { case MRSH_BINOP_AND: return "&&"; case MRSH_BINOP_OR: return "||"; } return NULL; } static void print_and_or_list(struct mrsh_and_or_list *and_or_list, const char *prefix); static void print_binop(struct mrsh_binop *binop, const char *prefix) { printf("binop %s\n", binop_type_str(binop->type)); char sub_prefix[make_sub_prefix(prefix, false, NULL)]; make_sub_prefix(prefix, false, sub_prefix); print_prefix(prefix, false); print_and_or_list(binop->left, sub_prefix); make_sub_prefix(prefix, true, sub_prefix); print_prefix(prefix, true); print_and_or_list(binop->right, sub_prefix); } static void print_and_or_list(struct mrsh_and_or_list *and_or_list, const char *prefix) { switch (and_or_list->type) { case MRSH_AND_OR_LIST_PIPELINE:; struct mrsh_pipeline *pl = mrsh_and_or_list_get_pipeline(and_or_list); print_pipeline(pl, prefix); break; case MRSH_AND_OR_LIST_BINOP:; struct mrsh_binop *binop = mrsh_and_or_list_get_binop(and_or_list); print_binop(binop, prefix); break; } } static void print_command_list(struct mrsh_command_list *list, const char *prefix) { printf("command_list%s ─ ", list->ampersand ? " &" : ""); print_and_or_list(list->and_or_list, prefix); } static void print_program(struct mrsh_program *prog, const char *prefix) { printf("program\n"); print_command_list_array(&prog->body, prefix); } void mrsh_program_print(struct mrsh_program *prog) { print_program(prog, ""); } ================================================ FILE: buffer.c ================================================ #include #include #include char *mrsh_buffer_reserve(struct mrsh_buffer *buf, size_t size) { size_t new_len = buf->len + size; if (new_len > buf->cap) { size_t new_cap = 2 * buf->cap; if (new_cap == 0) { new_cap = 32; } if (new_cap < new_len) { new_cap = new_len; } char *new_buf = realloc(buf->data, new_cap); if (new_buf == NULL) { return NULL; } buf->data = new_buf; buf->cap = new_cap; } return &buf->data[buf->len]; } char *mrsh_buffer_add(struct mrsh_buffer *buf, size_t size) { char *data = mrsh_buffer_reserve(buf, size); if (data == NULL) { return NULL; } buf->len += size; return data; } bool mrsh_buffer_append(struct mrsh_buffer *buf, const char *data, size_t size) { char *dst = mrsh_buffer_add(buf, size); if (dst == NULL) { return false; } memcpy(dst, data, size); return true; } bool mrsh_buffer_append_char(struct mrsh_buffer *buf, char c) { char *dst = mrsh_buffer_add(buf, sizeof(char)); if (dst == NULL) { return false; } *dst = c; return true; } char *mrsh_buffer_steal(struct mrsh_buffer *buf) { char *data = buf->data; buf->data = NULL; buf->cap = buf->len = 0; return data; } void mrsh_buffer_finish(struct mrsh_buffer *buf) { free(buf->data); buf->data = NULL; buf->cap = buf->len = 0; } ================================================ FILE: builtin/alias.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include "builtin.h" #include "mrsh_getopt.h" #include "shell/shell.h" static const char alias_usage[] = "usage: alias [alias-name[=string]...]\n"; static void print_alias_iterator(const char *key, void *_value, void *user_data) { const char *value = _value; printf("%s=", key); print_escaped(value); printf("\n"); } int builtin_alias(struct mrsh_state *state, int argc, char *argv[]) { struct mrsh_state_priv *priv = state_get_priv(state); _mrsh_optind = 0; if (_mrsh_getopt(argc, argv, ":") != -1) { fprintf(stderr, "alias: unknown option -- %c\n", _mrsh_optopt); fprintf(stderr, "%s", alias_usage); return 1; } if (_mrsh_optind == argc) { mrsh_hashtable_for_each(&priv->aliases, print_alias_iterator, NULL); return 0; } for (int i = _mrsh_optind; i < argc; ++i) { char *alias = argv[i]; char *equal = strchr(alias, '='); if (equal != NULL) { char *value = strdup(equal + 1); *equal = '\0'; char *old_value = mrsh_hashtable_set(&priv->aliases, alias, value); free(old_value); } else { const char *value = mrsh_hashtable_get(&priv->aliases, alias); if (value == NULL) { fprintf(stderr, "%s: %s not found\n", argv[0], alias); return 1; } printf("%s=", alias); print_escaped(value); printf("\n"); } } return 0; } ================================================ FILE: builtin/bg.c ================================================ #include #include #include "builtin.h" #include "mrsh_getopt.h" #include "shell/job.h" #include "shell/shell.h" #include "shell/task.h" static const char bg_usage[] = "usage: bg [job_id...]\n"; int builtin_bg(struct mrsh_state *state, int argc, char *argv[]) { _mrsh_optind = 0; int opt; while ((opt = _mrsh_getopt(argc, argv, ":")) != -1) { switch (opt) { default: fprintf(stderr, "bg: unknown option -- %c\n", _mrsh_optopt); fprintf(stderr, bg_usage); return EXIT_FAILURE; } } if (_mrsh_optind == argc) { struct mrsh_job *job = job_by_id(state, "%%", true); if (!job) { return EXIT_FAILURE; } if (!job_set_foreground(job, false, true)) { return EXIT_FAILURE; } return EXIT_SUCCESS; } for (int i = _mrsh_optind; i < argc; ++i) { struct mrsh_job *job = job_by_id(state, argv[i], true); if (!job) { return EXIT_FAILURE; } if (!job_set_foreground(job, false, true)) { return EXIT_FAILURE; } } return EXIT_SUCCESS; } ================================================ FILE: builtin/break.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include "builtin.h" #include "shell/shell.h" #include "shell/task.h" static const char break_usage[] = "usage: %s [n]\n"; int builtin_break(struct mrsh_state *state, int argc, char *argv[]) { if (argc > 2) { fprintf(stderr, break_usage, argv[0]); return 1; } int n = 1; if (argc == 2) { char *end; n = strtol(argv[1], &end, 10); if (end[0] != '\0' || argv[0][0] == '\0' || n < 0) { fprintf(stderr, "%s: invalid loop number '%s'\n", argv[0], argv[1]); return 1; } } struct mrsh_call_frame_priv *frame_priv = call_frame_get_priv(state->frame); if (n > frame_priv->nloops) { n = frame_priv->nloops; } frame_priv->nloops -= n - 1; frame_priv->branch_control = strcmp(argv[0], "break") == 0 ? MRSH_BRANCH_BREAK : MRSH_BRANCH_CONTINUE; return TASK_STATUS_INTERRUPTED; } ================================================ FILE: builtin/builtin.c ================================================ #include #include #include #include #include #include #include "builtin.h" #include "shell/shell.h" struct builtin { const char *name; mrsh_builtin_func func; bool special; }; static const struct builtin builtins[] = { // Keep alpha sorted { ".", builtin_dot, true }, { ":", builtin_colon, true }, { "alias", builtin_alias, false }, { "bg", builtin_bg, false }, { "break", builtin_break, true }, { "cd", builtin_cd, false }, { "command", builtin_command, false }, { "continue", builtin_break, true }, { "eval", builtin_eval, true }, { "exec", builtin_exec, true }, { "exit", builtin_exit, true }, { "export", builtin_export, true }, { "false", builtin_false, false }, // { "fc", builtin_fc, false }, { "fg", builtin_fg, false }, { "getopts", builtin_getopts, false }, { "hash", builtin_hash, false }, { "jobs", builtin_jobs, false }, // { "kill", builtin_kill, false }, // { "newgrp", builtin_newgrp, false }, { "pwd", builtin_pwd, false }, { "read", builtin_read, false }, { "readonly", builtin_export, true }, { "return", builtin_return, true }, { "set", builtin_set, true }, { "shift", builtin_shift, true }, { "times", builtin_times, true }, { "trap", builtin_trap, true }, { "true", builtin_true, false }, { "type", builtin_type, false }, { "ulimit", builtin_ulimit, false }, { "umask", builtin_umask, false }, { "unalias", builtin_unalias, false }, { "unset", builtin_unset, true }, { "wait", builtin_wait, false }, }; // The following commands are explicitly unspecified by POSIX static const char *unspecified_names[] = { "alloc", "autoload", "bind", "bindkey", "builtin", "bye", "caller", "cap", "chdir", "clone", "comparguments", "compcall", "compctl", "compdescribe", "compfiles", "compgen", "compgroups", "complete", "compquote", "comptags", "comptry", "compvalues", "declare", "dirs", "disable", "disown", "dosh", "echotc", "echoti", "help", "history", "hist", "let", "local", "login", "logout", "map", "mapfile", "popd", "print", "pushd", "readarray", "repeat", "savehistory", "source", "shopt", "stop", "suspend", "typeset", "whence" }; static const struct builtin unspecified = { .name = "unspecified", .func = builtin_unspecified, .special = false, }; static int builtin_compare(const void *_a, const void *_b) { const struct builtin *a = _a, *b = _b; return strcmp(a->name, b->name); } static int unspecified_compare(const void *_a, const void *_b) { const char *a = _a; const char *const *b = _b; return strcmp(a, *b); } static const struct builtin *get_builtin(const char *name) { if (bsearch(name, unspecified_names, sizeof(unspecified_names) / sizeof(unspecified_names[0]), sizeof(unspecified_names[0]), unspecified_compare)) { return &unspecified; } struct builtin key = { .name = name }; return bsearch(&key, builtins, sizeof(builtins) / sizeof(builtins[0]), sizeof(builtins[0]), builtin_compare); } bool mrsh_has_builtin(const char *name) { return get_builtin(name) != NULL; } bool mrsh_has_special_builtin(const char *name) { const struct builtin *builtin = get_builtin(name); return builtin != NULL && builtin->special; } int mrsh_run_builtin(struct mrsh_state *state, int argc, char *argv[]) { assert(argc > 0); const char *name = argv[0]; const struct builtin *builtin = get_builtin(name); if (builtin == NULL) { return -1; } return builtin->func(state, argc, argv); } void print_escaped(const char *value) { const char safe[] = "@%+=:,./-"; size_t i; for (i = 0; value[i] != '\0'; ++i) { if (!isalnum(value[i]) && !strchr(safe, value[i])) { break; } } if (value[i] == '\0' && i > 0) { printf("%s", value); } else { printf("'"); for (size_t i = 0; value[i] != '\0'; ++i) { if (value[i] == '\'') { printf("'\"'\"'"); } else { printf("%c", value[i]); } } printf("'"); } } struct collect_iter { size_t cap, len; uint32_t attribs; struct mrsh_collect_var *values; }; static void collect_vars_iterator(const char *key, void *_var, void *data) { const struct mrsh_variable *var = _var; struct collect_iter *iter = data; if (iter->attribs != MRSH_VAR_ATTRIB_NONE && !(var->attribs & iter->attribs)) { return; } if ((iter->len + 1) * sizeof(struct mrsh_collect_var) >= iter->cap) { iter->cap *= 2; iter->values = realloc(iter->values, iter->cap * sizeof(struct mrsh_collect_var)); } iter->values[iter->len].key = key; iter->values[iter->len++].value = var->value; } static int varcmp(const void *p1, const void *p2) { const struct mrsh_collect_var *v1 = p1; const struct mrsh_collect_var *v2 = p2; return strcmp(v1->key, v2->key); } struct mrsh_collect_var *collect_vars(struct mrsh_state *state, uint32_t attribs, size_t *count) { struct mrsh_state_priv *priv = state_get_priv(state); struct collect_iter iter = { .cap = 64, .len = 0, .values = malloc(64 * sizeof(struct mrsh_collect_var)), .attribs = attribs, }; mrsh_hashtable_for_each(&priv->variables, collect_vars_iterator, &iter); qsort(iter.values, iter.len, sizeof(struct mrsh_collect_var), varcmp); *count = iter.len; return iter.values; } ================================================ FILE: builtin/cd.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include "builtin.h" #include "mrsh_getopt.h" #include "shell/path.h" static const char cd_usage[] = "usage: cd [-L|-P] [-|directory]\n"; static int cd(struct mrsh_state *state, const char *path) { const char *oldPWD = mrsh_env_get(state, "PWD", NULL); if (chdir(path) != 0) { // TODO make better error messages fprintf(stderr, "cd: %s\n", strerror(errno)); return 1; } char *cwd = current_working_dir(); if (cwd == NULL) { perror("current_working_dir failed"); return 1; } mrsh_env_set(state, "OLDPWD", oldPWD, MRSH_VAR_ATTRIB_NONE); mrsh_env_set(state, "PWD", cwd, MRSH_VAR_ATTRIB_EXPORT); free(cwd); return 0; } static int isdir(char *path) { struct stat s; stat(path, &s); return S_ISDIR(s.st_mode); } int builtin_cd(struct mrsh_state *state, int argc, char *argv[]) { _mrsh_optind = 0; int opt; while ((opt = _mrsh_getopt(argc, argv, ":LP")) != -1) { switch (opt) { case 'L': case 'P': // TODO implement `-L` and `-P` fprintf(stderr, "cd: `-L` and `-P` not yet implemented\n"); return 1; default: fprintf(stderr, "cd: unknown option -- %c\n", _mrsh_optopt); fprintf(stderr, cd_usage); return 1; } } if (_mrsh_optind == argc) { const char *home = mrsh_env_get(state, "HOME", NULL); if (home && home[0] != '\0') { return cd(state, home); } fprintf(stderr, "cd: No arguments were given and $HOME " "is not defined.\n"); return 1; } char *curpath = argv[_mrsh_optind]; // `cd -` if (strcmp(curpath, "-") == 0) { // This case is special as we print `pwd` at the end const char *oldpwd = mrsh_env_get(state, "OLDPWD", NULL); const char *pwd = mrsh_env_get(state, "PWD", NULL); if (!pwd) { fprintf(stderr, "cd: PWD is not set\n"); return 1; } if (!oldpwd) { fprintf(stderr, "cd: OLDPWD is not set\n"); return 1; } if (chdir(oldpwd) != 0) { fprintf(stderr, "cd: %s\n", strerror(errno)); return 1; } char *_pwd = strdup(pwd); puts(oldpwd); mrsh_env_set(state, "PWD", oldpwd, MRSH_VAR_ATTRIB_EXPORT); mrsh_env_set(state, "OLDPWD", _pwd, MRSH_VAR_ATTRIB_NONE); free(_pwd); return 0; } // $CDPATH if (curpath[0] != '/' && strncmp(curpath, "./", 2) != 0 && strncmp(curpath, "../", 3) != 0) { const char *_cdpath = mrsh_env_get(state, "CDPATH", NULL); char *cdpath = NULL; if (_cdpath) { cdpath = strdup(_cdpath); } char *c = cdpath; while (c != NULL) { char *next = strchr(c, ':'); char *slash = strrchr(c, '/'); if (next != NULL) { *next = '\0'; ++next; } if (c[0] == '\0') { // path is empty c = "."; slash = NULL; } const char *sep = (slash == NULL || slash[1] != '\0') ? "/" : ""; int len = snprintf(NULL, 0, "%s%s%s", c, sep, curpath); if (len < 0) { perror("snprintf failed"); continue; } char *path = malloc(len + 1); if (path == NULL) { perror("malloc failed"); continue; } snprintf(path, len + 1, "%s%s%s", c, sep, curpath); if (isdir(path)) { free(cdpath); int ret = cd(state, path); free(path); return ret; } free(path); c = next; } free(cdpath); } return cd(state, curpath); } ================================================ FILE: builtin/colon.c ================================================ #include #include #include "builtin.h" int builtin_colon(struct mrsh_state *state, int argc, char *argv[]) { return 0; // This builtin does not do anything } ================================================ FILE: builtin/command.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include "builtin.h" #include "mrsh_getopt.h" #include "parser.h" #include "shell/job.h" #include "shell/path.h" #include "shell/process.h" #include "shell/shell.h" static const char command_usage[] = "usage: command [-v|-V|-p] " "command_name [argument...]\n"; static int verify_command(struct mrsh_state *state, const char *command_name, bool default_path) { struct mrsh_state_priv *priv = state_get_priv(state); size_t len_command_name = strlen(command_name); const char *look_alias = mrsh_hashtable_get(&priv->aliases, command_name); if (look_alias != NULL) { printf("alias %s='%s'\n", command_name, look_alias); return 0; } const char *look_fn = mrsh_hashtable_get(&priv->functions, command_name); if (look_fn != NULL) { printf("%s\n", command_name); return 0; } if (mrsh_has_builtin(command_name)) { printf("%s\n", command_name); return 0; } for (size_t i = 0; i < keywords_len; ++i) { if (strlen(keywords[i]) == len_command_name && strcmp(command_name, keywords[i]) == 0) { printf("%s\n", command_name); return 0; } } char *expanded = expand_path(state, command_name, true, default_path); if (expanded != NULL) { printf("%s\n", expanded); free(expanded); return 0; } return 1; } static int run_command(struct mrsh_state *state, int argc, char *argv[], bool default_path) { if (mrsh_has_builtin(argv[0])) { return mrsh_run_builtin(state, argc - _mrsh_optind, &argv[_mrsh_optind]); } char *path = expand_path(state, argv[0], true, default_path); if (path == NULL) { fprintf(stderr, "%s: not found\n", argv[0]); return 127; } // TODO: job control support pid_t pid = fork(); if (pid < 0) { perror("fork"); return 126; } else if (pid == 0) { execv(path, argv); // Something went wrong perror(argv[0]); exit(126); } free(path); struct mrsh_process *proc = process_create(state, pid); return job_wait_process(proc); } int builtin_command(struct mrsh_state *state, int argc, char *argv[]) { _mrsh_optind = 0; int opt; bool verify = false, default_path = false; while ((opt = _mrsh_getopt(argc, argv, ":vVp")) != -1) { switch (opt) { case 'v': verify = true; break; case 'V': fprintf(stderr, "command: `-V` has an unspecified output format, " "use `-v` instead\n"); return 0; case 'p': default_path = true; break; default: fprintf(stderr, "command: unknown option -- %c\n", _mrsh_optopt); fprintf(stderr, command_usage); return 1; } } if (_mrsh_optind >= argc) { fprintf(stderr, command_usage); return 1; } if (verify) { if (_mrsh_optind != argc - 1) { fprintf(stderr, command_usage); return 1; } return verify_command(state, argv[_mrsh_optind], default_path); } return run_command(state, argc - _mrsh_optind, &argv[_mrsh_optind], default_path); } ================================================ FILE: builtin/dot.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include "builtin.h" #include "shell/path.h" static const char source_usage[] = "usage: . \n"; int builtin_dot(struct mrsh_state *state, int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, source_usage); return 1; } char *path = expand_path(state, argv[1], false, false); if (!path) { fprintf(stderr, "%s: not found\n", argv[1]); if (!state->interactive) { state->exit = 1; } return 1; } int fd = open(path, O_RDONLY | O_CLOEXEC); if (fd < 0) { fprintf(stderr, "unable to open %s for reading: %s\n", argv[1], strerror(errno)); goto error; } free(path); struct mrsh_parser *parser = mrsh_parser_with_fd(fd); struct mrsh_program *program = mrsh_parse_program(parser); int ret; struct mrsh_position err_pos; const char *err_msg = mrsh_parser_error(parser, &err_pos); if (err_msg != NULL) { fprintf(stderr, "%s %d:%d: %s\n", argv[1], err_pos.line, err_pos.column, err_msg); ret = 1; } else if (program != NULL) { ret = mrsh_run_program(state, program); } else { ret = 0; } mrsh_program_destroy(program); mrsh_parser_destroy(parser); close(fd); return ret; error: if (!state->interactive) { state->exit = 1; } return 1; } ================================================ FILE: builtin/eval.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include "builtin.h" static const char eval_usage[] = "usage: eval [cmds...]\n"; int builtin_eval(struct mrsh_state *state, int argc, char *argv[]) { if (argc == 1) { fprintf(stderr, eval_usage); return 1; } struct mrsh_buffer buf = {0}; for (int i = 1; i < argc; ++i) { mrsh_buffer_append(&buf, argv[i], strlen(argv[i])); if (i != argc - 1) { mrsh_buffer_append_char(&buf, ' '); } } mrsh_buffer_append_char(&buf, '\n'); struct mrsh_parser *parser = mrsh_parser_with_data(buf.data, buf.len); struct mrsh_program *program = mrsh_parse_program(parser); int ret; struct mrsh_position err_pos; const char *err_msg = mrsh_parser_error(parser, &err_pos); if (err_msg != NULL) { fprintf(stderr, "%s %d:%d: %s\n", argv[1], err_pos.line, err_pos.column, err_msg); ret = 1; } else if (program != NULL) { ret = mrsh_run_program(state, program); } else { ret = 0; } mrsh_program_destroy(program); mrsh_parser_destroy(parser); mrsh_buffer_finish(&buf); return ret; } ================================================ FILE: builtin/exec.c ================================================ #include #include #include "builtin.h" #include "mrsh_getopt.h" #include "shell/path.h" static const char exec_usage[] = "usage: exec [command [argument...]]\n"; int builtin_exec(struct mrsh_state *state, int argc, char *argv[]) { _mrsh_optind = 0; if (_mrsh_getopt(argc, argv, ":") != -1) { fprintf(stderr, "exec: unknown option -- %c\n", _mrsh_optopt); fprintf(stderr, exec_usage); return 1; } if (_mrsh_optind == argc) { return 0; } char *path = expand_path(state, argv[_mrsh_optind], false, false); if (path == NULL) { fprintf(stderr, "exec: %s: command not found\n", argv[_mrsh_optind]); return 127; } if (access(path, X_OK) != 0) { fprintf(stderr, "exec: %s: not executable\n", path); return 126; } execv(path, &argv[_mrsh_optind]); perror("exec"); return 1; } ================================================ FILE: builtin/exit.c ================================================ #include #include #include #include #include "builtin.h" #include "shell/task.h" static const char exit_usage[] = "usage: exit [n]\n"; int builtin_exit(struct mrsh_state *state, int argc, char *argv[]) { if (argc > 2) { fprintf(stderr, exit_usage); return 1; } int status = 0; if (argc > 1) { char *endptr; errno = 0; long status_long = strtol(argv[1], &endptr, 10); if (endptr[0] != '\0' || errno != 0 || status_long < 0 || status_long > 255) { fprintf(stderr, exit_usage); return 1; } status = (int)status_long; } struct mrsh_call_frame_priv *frame_priv = call_frame_get_priv(state->frame); state->exit = status; frame_priv->branch_control = MRSH_BRANCH_EXIT; return TASK_STATUS_INTERRUPTED; } ================================================ FILE: builtin/export.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include "builtin.h" #include "shell/word.h" static const char export_usage[] = "usage: %s -p|name[=word]...\n"; int builtin_export(struct mrsh_state *state, int argc, char *argv[]) { uint32_t attrib = MRSH_VAR_ATTRIB_EXPORT; if (strcmp(argv[0], "readonly") == 0) { attrib = MRSH_VAR_ATTRIB_READONLY; } if (argc < 2) { fprintf(stderr, export_usage, argv[0]); return 1; } else if (argc == 2 && strcmp(argv[1], "-p") == 0) { size_t count; struct mrsh_collect_var *vars = collect_vars( state, attrib, &count); for (size_t i = 0; i < count; ++i) { printf("%s %s=", argv[0], vars[i].key); print_escaped(vars[i].value); printf("\n"); } free(vars); return 0; } for (int i = 1; i < argc; ++i) { char *eql, *key; const char *val; uint32_t prev_attribs = 0; eql = strchr(argv[i], '='); if (eql) { size_t klen = eql - argv[i]; key = strndup(argv[i], klen); val = &eql[1]; mrsh_env_get(state, key, &prev_attribs); } else { key = strdup(argv[i]); val = mrsh_env_get(state, key, &prev_attribs); if (!val) { val = ""; } } if ((prev_attribs & MRSH_VAR_ATTRIB_READONLY)) { fprintf(stderr, "%s: cannot modify readonly variable %s\n", argv[0], key); free(key); return 1; } struct mrsh_word_string *ws = mrsh_word_string_create(strdup(val), false); struct mrsh_word *word = &ws->word; expand_tilde(state, &word, true); char *new_val = mrsh_word_str(word); mrsh_word_destroy(word); mrsh_env_set(state, key, new_val, attrib | prev_attribs); free(key); free(new_val); } return 0; } ================================================ FILE: builtin/false.c ================================================ #include #include #include "builtin.h" int builtin_false(struct mrsh_state *state, int argc, char *argv[]) { return 1; } ================================================ FILE: builtin/fg.c ================================================ #include #include #include "builtin.h" #include "mrsh_getopt.h" #include "shell/job.h" #include "shell/shell.h" static const char fg_usage[] = "usage: fg [job_id]\n"; int builtin_fg(struct mrsh_state *state, int argc, char *argv[]) { _mrsh_optind = 0; int opt; while ((opt = _mrsh_getopt(argc, argv, ":")) != -1) { switch (opt) { default: fprintf(stderr, "fg: unknown option -- %c\n", _mrsh_optopt); fprintf(stderr, fg_usage); return EXIT_FAILURE; } } struct mrsh_job *job; if (_mrsh_optind == argc) { job = job_by_id(state, "%%", true); } else if (_mrsh_optind == argc - 1) { job = job_by_id(state, argv[_mrsh_optind], true); } else { fprintf(stderr, fg_usage); return EXIT_FAILURE; } if (!job) { return EXIT_FAILURE; } if (!job_set_foreground(job, true, true)) { return EXIT_FAILURE; } return job_wait(job); } ================================================ FILE: builtin/getopts.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include "builtin.h" #include "mrsh_getopt.h" static const char getopts_usage[] = "usage: getopts optstring name [arg...]\n"; int builtin_getopts(struct mrsh_state *state, int argc, char *argv[]) { _mrsh_optind = 0; if (_mrsh_getopt(argc, argv, ":") != -1) { fprintf(stderr, "getopts: unknown option -- %c\n", _mrsh_optopt); fprintf(stderr, getopts_usage); return 1; } if (_mrsh_optind + 2 < argc) { fprintf(stderr, getopts_usage); return 1; } int optc; char **optv; if (_mrsh_optind + 2 > argc) { optc = argc - _mrsh_optind - 2; optv = &argv[_mrsh_optind + 2]; } else { optc = state->frame->argc; optv = state->frame->argv; } char *optstring = argv[_mrsh_optind]; char *name = argv[_mrsh_optind + 1]; const char *optind_str = mrsh_env_get(state, "OPTIND", NULL); if (optind_str == NULL) { fprintf(stderr, "getopts: OPTIND is not defined\n"); return 1; } char *endptr; long optind_long = strtol(optind_str, &endptr, 10); if (endptr[0] != '\0' || optind_long <= 0 || optind_long > INT_MAX) { fprintf(stderr, "getopts: OPTIND is not a positive integer\n"); return 1; } _mrsh_optind = (int)optind_long; _mrsh_optopt = 0; int opt = _mrsh_getopt(optc, optv, optstring); char optind_fmt[16]; snprintf(optind_fmt, sizeof(optind_fmt), "%d", _mrsh_optind); mrsh_env_set(state, "OPTIND", optind_fmt, MRSH_VAR_ATTRIB_NONE); if (_mrsh_optopt != 0) { if (opt == ':') { char value[] = {(char)_mrsh_optopt, '\0'}; mrsh_env_set(state, "OPTARG", value, MRSH_VAR_ATTRIB_NONE); } else if (optstring[0] != ':') { mrsh_env_unset(state, "OPTARG"); } else { // either missing option-argument or unknown option character // in the former case, unset OPTARG // in the latter case, set OPTARG to _mrsh_optopt bool opt_exists = false; size_t len = strlen(optstring); for (size_t i = 0; i < len; ++i) { if (optstring[i] == _mrsh_optopt) { opt_exists = true; break; } } if (opt_exists) { mrsh_env_unset(state, "OPTARG"); } else { char value[] = {(char)_mrsh_optopt, '\0'}; mrsh_env_set(state, "OPTARG", value, MRSH_VAR_ATTRIB_NONE); } } } else if (_mrsh_optarg != NULL) { mrsh_env_set(state, "OPTARG", _mrsh_optarg, MRSH_VAR_ATTRIB_NONE); } else { mrsh_env_unset(state, "OPTARG"); } char value[] = {opt == -1 ? (char)'?' : (char)opt, '\0'}; mrsh_env_set(state, name, value, MRSH_VAR_ATTRIB_NONE); if (opt == -1) { return 1; } return 0; } ================================================ FILE: builtin/hash.c ================================================ #include #include #include #include #include #include "builtin.h" #include "mrsh_getopt.h" static const char hash_usage[] = "usage: hash -r|utility...\n"; int builtin_hash(struct mrsh_state *state, int argc, char *argv[]) { /* Hashing and remembering executable location isn't implemented. Thus most * of this builtin just does nothing. */ _mrsh_optind = 0; int opt; while ((opt = _mrsh_getopt(argc, argv, ":r")) != -1) { switch (opt) { case 'r': /* no-op: reset list of cached utilities */ return 0; default: fprintf(stderr, "hash: unknown option -- %c\n", _mrsh_optopt); fprintf(stderr, hash_usage); return 1; } } if (argc == 1) { /* no-op: print list of cached utilities */ return 0; } for (int i = 1; i < argc; i++) { const char *utility = argv[i]; if (strchr(utility, '/') != NULL) { fprintf(stderr, "hash: undefined behaviour: utility contains a slash\n"); return 1; } if (mrsh_has_builtin(utility)) { continue; } char *path = expand_path(state, utility, true, false); if (path == NULL) { fprintf(stderr, "hash: command not found: %s\n", utility); return 1; } free(path); } return 0; } ================================================ FILE: builtin/jobs.c ================================================ #include #include #include #include #include #include "builtin.h" #include "mrsh_getopt.h" #include "shell/job.h" #include "shell/process.h" #include "shell/shell.h" #include "shell/task.h" static const char jobs_usage[] = "usage: jobs [-l|-p] [job_id...]\n"; struct jobs_context { struct mrsh_job *current, *previous; bool pids; bool pgids; bool r; }; static void show_job(struct mrsh_job *job, const struct jobs_context *ctx) { if (job_poll(job) >= 0) { return; } char curprev = ' '; if (job == ctx->current) { curprev = '+'; } else if (job == ctx->previous) { curprev = '-'; } if (ctx->pids) { for (size_t i = 0; i < job->processes.len; ++i) { struct mrsh_process *proc = job->processes.data[i]; printf("%d\n", proc->pid); } } else if (ctx->pgids) { char *cmd = mrsh_node_format(job->node); printf("[%d] %c %d %s %s\n", job->job_id, curprev, job->pgid, job_state_str(job, ctx->r), cmd); free(cmd); } else { char *cmd = mrsh_node_format(job->node); printf("[%d] %c %s %s\n", job->job_id, curprev, job_state_str(job, ctx->r), cmd); free(cmd); } } int builtin_jobs(struct mrsh_state *state, int argc, char *argv[]) { struct mrsh_state_priv *priv = state_get_priv(state); struct mrsh_job *current = job_by_id(state, "%+", false), *previous = job_by_id(state, "%-", false); struct jobs_context ctx = { .current = current, .previous = previous, .pids = false, .pgids = false, .r = rand() % 2 == 0, }; _mrsh_optind = 0; int opt; while ((opt = _mrsh_getopt(argc, argv, ":lp")) != -1) { switch (opt) { case 'l': if (ctx.pids) { fprintf(stderr, "jobs: the -p and -l options are " "mutually exclusive\n"); return EXIT_FAILURE; } ctx.pgids = true; break; case 'p': if (ctx.pgids) { fprintf(stderr, "jobs: the -p and -l options are " "mutually exclusive\n"); return EXIT_FAILURE; } ctx.pids = true; break; default: fprintf(stderr, "jobs: unknown option -- %c\n", _mrsh_optopt); fprintf(stderr, jobs_usage); return EXIT_FAILURE; } } if (_mrsh_optind == argc) { for (size_t i = 0; i < priv->jobs.len; i++) { struct mrsh_job *job = priv->jobs.data[i]; show_job(job, &ctx); } } else { for (int i = _mrsh_optind; i < argc; i++) { struct mrsh_job *job = job_by_id(state, argv[i], true); if (!job) { return 1; } show_job(job, &ctx); } } return EXIT_SUCCESS; } ================================================ FILE: builtin/pwd.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include "builtin.h" #include "mrsh_getopt.h" static const char pwd_usage[] = "usage: pwd [-L|-P]\n"; int builtin_pwd(struct mrsh_state *state, int argc, char *argv[]) { _mrsh_optind = 0; int opt; while ((opt = _mrsh_getopt(argc, argv, ":LP")) != -1) { switch (opt) { case 'L': case 'P': /* This space deliberately left blank */ break; default: fprintf(stderr, "pwd: unknown option -- %c\n", _mrsh_optopt); fprintf(stderr, pwd_usage); return 1; } } if (_mrsh_optind < argc) { fprintf(stderr, pwd_usage); return 1; } const char *pwd = mrsh_env_get(state, "PWD", NULL); assert(pwd != NULL); puts(pwd); return 0; } ================================================ FILE: builtin/read.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include "builtin.h" #include "mrsh_getopt.h" static const char read_usage[] = "usage: read [-r] var...\n"; int builtin_read(struct mrsh_state *state, int argc, char *argv[]) { bool raw = false; _mrsh_optind = 0; int opt; while ((opt = _mrsh_getopt(argc, argv, ":r")) != -1) { switch (opt) { case 'r': raw = true; break; default: fprintf(stderr, "read: unknown option -- %c\n", _mrsh_optopt); fprintf(stderr, read_usage); return 1; } } if (_mrsh_optind == argc) { fprintf(stderr, read_usage); return 1; } struct mrsh_buffer buf = {0}; bool escaped = false; int c; while ((c = fgetc(stdin)) != EOF) { if (!raw && !escaped && c == '\\') { escaped = true; continue; } if (c == '\n') { if (escaped) { escaped = false; const char *ps2 = mrsh_env_get(state, "PS2", NULL); fprintf(stderr, "%s", ps2 != NULL ? ps2 : "> "); continue; } break; } escaped = false; mrsh_buffer_append_char(&buf, (char)c); } mrsh_buffer_append_char(&buf, '\0'); struct mrsh_array fields = {0}; struct mrsh_word_string *ws = mrsh_word_string_create(mrsh_buffer_steal(&buf), false); split_fields(&fields, &ws->word, mrsh_env_get(state, "IFS", NULL)); mrsh_word_destroy(&ws->word); struct mrsh_array strs = {0}; get_fields_str(&strs, &fields); for (size_t i = 0; i < fields.len; ++i) { mrsh_word_destroy(fields.data[i]); } mrsh_array_finish(&fields); fields = strs; if (fields.len <= (size_t)(argc - _mrsh_optind)) { for (size_t i = 0; i < fields.len; ++i) { mrsh_env_set(state, argv[_mrsh_optind + i], (char *)fields.data[i], MRSH_VAR_ATTRIB_NONE); } for (size_t i = fields.len; i < (size_t)(argc - _mrsh_optind); ++i) { mrsh_env_set(state, argv[_mrsh_optind + i], "", MRSH_VAR_ATTRIB_NONE); } } else { for (int i = 0; i < argc - _mrsh_optind - 1; ++i) { mrsh_env_set(state, argv[_mrsh_optind + i], (char *)fields.data[i], MRSH_VAR_ATTRIB_NONE); } struct mrsh_buffer buf_last = {0}; for (size_t i = (size_t)(argc - _mrsh_optind - 1); i < fields.len; ++i) { char *field = (char *)fields.data[i]; mrsh_buffer_append(&buf_last, field, strlen(field)); if (i != fields.len - 1) { // TODO add the field delimiter rather than space (bash and dash always use spaces) mrsh_buffer_append_char(&buf_last, ' '); } } mrsh_buffer_append_char(&buf_last, '\0'); mrsh_env_set(state, argv[argc - 1], buf_last.data, MRSH_VAR_ATTRIB_NONE); mrsh_buffer_finish(&buf_last); } for (size_t i = 0; i < fields.len; ++i) { free(fields.data[i]); } mrsh_array_finish(&fields); if (c == EOF) { return 1; } else { return 0; } } ================================================ FILE: builtin/return.c ================================================ #include #include #include #include #include "builtin.h" #include "shell/task.h" static const char return_usage[] = "usage: %s [n]\n"; int builtin_return(struct mrsh_state *state, int argc, char *argv[]) { if (argc > 2) { fprintf(stderr, return_usage, argv[0]); return 1; } int n = 0; if (argc == 2) { char *end; n = strtol(argv[1], &end, 10); if (end[0] != '\0' || argv[0][0] == '\0' || n < 0 || n > 255) { fprintf(stderr, "%s: invalid return number '%s'\n", argv[0], argv[1]); return 1; } } struct mrsh_call_frame_priv *frame_priv = call_frame_get_priv(state->frame); frame_priv->nloops = 0; frame_priv->branch_control = MRSH_BRANCH_RETURN; state->last_status = n; return TASK_STATUS_INTERRUPTED; } ================================================ FILE: builtin/set.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include "builtin.h" #include "shell/shell.h" static const char set_usage[] = "usage: set [(-|+)abCefhmnuvx] [-o option] [args...]\n" " set [(-|+)abCefhmnuvx] [+o option] [args...]\n" " set -- [args...]\n" " set -o\n" " set +o\n"; struct option_map { const char *name; char short_name; enum mrsh_option value; }; static const struct option_map options[] = { { "allexport", 'a', MRSH_OPT_ALLEXPORT }, { "notify", 'b', MRSH_OPT_NOTIFY }, { "noclobber", 'C', MRSH_OPT_NOCLOBBER }, { "errexit", 'e', MRSH_OPT_ERREXIT }, { "noglob", 'f', MRSH_OPT_NOGLOB }, { NULL, 'h', MRSH_OPT_PRELOOKUP }, { "monitor", 'm', MRSH_OPT_MONITOR }, { "noexec", 'n', MRSH_OPT_NOEXEC }, { "ignoreeof", 0, MRSH_OPT_IGNOREEOF }, { "nolog", 0, MRSH_OPT_NOLOG }, { "vi", 0, MRSH_OPT_VI }, { "nounset", 'u', MRSH_OPT_NOUNSET }, { "verbose", 'v', MRSH_OPT_VERBOSE }, { "xtrace", 'x', MRSH_OPT_XTRACE }, }; const char *state_get_options(struct mrsh_state *state) { static char opts[sizeof(options) / sizeof(options[0]) + 1]; int i = 0; for (size_t j = 0; j < sizeof(options) / sizeof(options[0]); ++j) { if (options[j].short_name != '\0' && (state->options & options[j].value)) { opts[i++] = options[j].short_name; } } opts[i] = '\0'; return opts; } static void print_options(struct mrsh_state *state) { for (size_t j = 0; j < sizeof(options) / sizeof(options[0]); ++j) { if (options[j].name != NULL) { printf("set %co %s\n", (state->options & options[j].value) ? '-' : '+', options[j].name); } } } static const struct option_map *find_option(char opt) { for (size_t i = 0; i < sizeof(options) / sizeof(options[0]); ++i) { if (options[i].short_name && options[i].short_name == opt) { return &options[i]; } } return NULL; } static const struct option_map *find_long_option(const char *opt) { for (size_t i = 0; i < sizeof(options) / sizeof(options[0]); ++i) { if (options[i].name && strcmp(options[i].name, opt) == 0) { return &options[i]; } } return NULL; } static char **argv_dup(char *argv_0, int argc, char *argv[]) { char **_argv = calloc(argc + 1, sizeof(char *)); _argv[0] = argv_0; for (int i = 1; i < argc; ++i) { _argv[i] = strdup(argv[i - 1]); } return _argv; } static void argv_free(int argc, char **argv) { if (!argv) { return; } for (int i = 0; i < argc; ++i) { free(argv[i]); } free(argv); } static int set(struct mrsh_state *state, int argc, char *argv[], struct mrsh_init_args *init_args, uint32_t *populated_opts) { if (argc == 1 && init_args == NULL) { size_t count; struct mrsh_collect_var *vars = collect_vars( state, MRSH_VAR_ATTRIB_NONE, &count); for (size_t i = 0; i < count; ++i) { printf("%s=", vars[i].key); print_escaped(vars[i].value); printf("\n"); } free(vars); return 0; } bool force_positional = false; int i; for (i = 1; i < argc; ++i) { if (strcmp(argv[i], "--") == 0) { force_positional = true; ++i; break; } if (argv[i][0] != '-' && argv[i][0] != '+') { break; } if (argv[i][1] == '\0') { fprintf(stderr, set_usage); return 1; } const struct option_map *option; switch (argv[i][1]) { case 'o': if (i + 1 == argc) { print_options(state); goto out; // we must populate state->argv } option = find_long_option(argv[i + 1]); if (!option) { fprintf(stderr, set_usage); return 1; } if (argv[i][0] == '-') { state->options |= option->value; } else { state->options &= ~option->value; } if (populated_opts != NULL) { *populated_opts |= option->value; } ++i; continue; case 'c': if (init_args == NULL) { fprintf(stderr, set_usage); return 1; } init_args->command_str = argv[i + 1]; ++i; break; case 's': if (init_args == NULL) { fprintf(stderr, set_usage); return 1; } init_args->command_str = NULL; init_args->command_file = NULL; break; default: for (int j = 1; argv[i][j]; ++j) { option = find_option(argv[i][j]); if (!option) { fprintf(stderr, set_usage); return 1; } if (argv[i][0] == '-') { state->options |= option->value; } else { state->options &= ~option->value; } if (populated_opts != NULL) { *populated_opts |= option->value; } } break; } } if (i != argc || force_positional) { char *argv_0; if (init_args != NULL) { argv_0 = strdup(argv[i++]); init_args->command_file = argv_0; } else if (state->frame->argv) { argv_0 = strdup(state->frame->argv[0]); } else { fprintf(stderr, set_usage); return 1; } argv_free(state->frame->argc, state->frame->argv); state->frame->argc = argc - i + 1; state->frame->argv = argv_dup(argv_0, state->frame->argc, &argv[i]); } else out: if (init_args != NULL) { // No args given, but we need to initialize state->argv state->frame->argc = 1; state->frame->argv = argv_dup(strdup(argv[0]), 1, argv); } return 0; } int builtin_set(struct mrsh_state *state, int argc, char *argv[]) { uint32_t populated_opts = 0; int ret = set(state, argc, argv, NULL, &populated_opts); if (ret != 0) { return ret; } if (populated_opts & MRSH_OPT_MONITOR) { if (!mrsh_set_job_control(state, state->options & MRSH_OPT_MONITOR)) { return 1; } } return 0; } int mrsh_process_args(struct mrsh_state *state, struct mrsh_init_args *init_args, int argc, char *argv[]) { struct mrsh_state_priv *priv = state_get_priv(state); uint32_t populated_opts = 0; int ret = set(state, argc, argv, init_args, &populated_opts); if (ret != 0) { return ret; } state->interactive = isatty(priv->term_fd) && init_args->command_str == NULL && init_args->command_file == NULL; if (state->interactive && !(populated_opts & MRSH_OPT_MONITOR)) { state->options |= MRSH_OPT_MONITOR; } return 0; } ================================================ FILE: builtin/shift.c ================================================ #include #include #include #include #include "builtin.h" static const char shift_usage[] = "usage: shift [n]\n"; int builtin_shift(struct mrsh_state *state, int argc, char *argv[]) { if (argc > 2) { fprintf(stderr, shift_usage); return 1; } int n = 1; if (argc == 2) { char *endptr; errno = 0; long n_long = strtol(argv[1], &endptr, 10); if (*endptr != '\0' || errno != 0) { fprintf(stderr, shift_usage); if (!state->interactive) { state->exit = 1; } return 1; } n = (int)n_long; } if (n == 0) { return 0; } else if (n < 1) { fprintf(stderr, "shift: [n] must be positive\n"); if (!state->interactive) { state->exit = 1; } return 1; } else if (n > state->frame->argc - 1) { fprintf(stderr, "shift: [n] must be less than $#\n"); if (!state->interactive) { state->exit = 1; } return 1; } for (int i = 1, j = n + 1; j < state->frame->argc; ++i, ++j) { if (j <= state->frame->argc - n) { state->frame->argv[i] = state->frame->argv[j]; } else { free(state->frame->argv[i]); } } state->frame->argc -= n; return 0; } ================================================ FILE: builtin/times.c ================================================ #include #include #include #include #include #include #include #include "builtin.h" static const char times_usage[] = "usage: times\n"; int builtin_times(struct mrsh_state *state, int argc, char *argv[]) { if (argc > 1) { fprintf(stderr, times_usage); return 1; } struct tms buf; long clk_tck = sysconf(_SC_CLK_TCK); if (clk_tck == -1) { perror("sysconf"); return 1; } if (times(&buf) == (clock_t)-1) { perror("times"); return 1; } printf("%dm%fs %dm%fs\n%dm%fs %dm%fs\n", (int)(buf.tms_utime / clk_tck / 60), ((double) buf.tms_utime) / clk_tck, (int)(buf.tms_stime / clk_tck / 60), ((double) buf.tms_stime) / clk_tck, (int)(buf.tms_cutime / clk_tck / 60), ((double) buf.tms_cutime) / clk_tck, (int)(buf.tms_cstime / clk_tck / 60), ((double)buf.tms_cstime) / clk_tck); return 0; } ================================================ FILE: builtin/trap.c ================================================ #define _XOPEN_SOURCE 1 // for SIGPOLL and SIGVTALRM #include #include #include #include #include #include #include #include "builtin.h" #include "mrsh_getopt.h" #include "shell/shell.h" #include "shell/trap.h" static const char trap_usage[] = "usage: trap [condition...]\n" " trap [action condition...]\n"; static const char *sig_names[] = { [SIGABRT] = "ABRT", [SIGALRM] = "ALRM", [SIGBUS] = "BUS", [SIGCHLD] = "CHLD", [SIGCONT] = "CONT", [SIGFPE] = "FPE", [SIGHUP] = "HUP", [SIGILL] = "ILL", [SIGINT] = "INT", [SIGKILL] = "KILL", [SIGPIPE] = "PIPE", [SIGQUIT] = "QUIT", [SIGSEGV] = "SEGV", [SIGSTOP] = "STOP", [SIGTERM] = "TERM", [SIGTSTP] = "TSTP", [SIGTTIN] = "TTIN", [SIGTTOU] = "TTOU", [SIGUSR1] = "USR1", [SIGUSR2] = "USR2", // Some BSDs have decided against implementing SIGPOLL for functional and // security reasons #ifdef SIGPOLL [SIGPOLL] = "POLL", #endif [SIGPROF] = "PROF", [SIGSYS] = "SYS", [SIGTRAP] = "TRAP", [SIGURG] = "URG", [SIGVTALRM] = "VTALRM", [SIGXCPU] = "XCPU", [SIGXFSZ] = "XFSZ", }; static bool is_decimal_str(const char *str) { for (size_t i = 0; str[i] != '\0'; i++) { if (str[i] < '0' || str[i] > '9') { return false; } } return true; } static int parse_sig(const char *str) { if (strcmp(str, "0") == 0 || strcmp(str, "EXIT") == 0) { return 0; } // XSI-conformant systems need to recognize a few more numeric signal // numbers if (strcmp(str, "1") == 0) { return SIGHUP; } else if (strcmp(str, "2") == 0) { return SIGINT; } else if (strcmp(str, "3") == 0) { return SIGQUIT; } else if (strcmp(str, "6") == 0) { return SIGABRT; } else if (strcmp(str, "9") == 0) { return SIGKILL; } else if (strcmp(str, "14") == 0) { return SIGALRM; } else if (strcmp(str, "15") == 0) { return SIGTERM; } for (size_t i = 0; i < sizeof(sig_names) / sizeof(sig_names[0]); i++) { if (sig_names[i] == NULL) { continue; } if (strcmp(str, sig_names[i]) == 0) { return (int)i; } } fprintf(stderr, "trap: failed to parse condition: %s\n", str); return -1; } static const char *sig_str(int sig) { if (sig == 0) { return "EXIT"; } assert(sig > 0); assert((size_t)sig < sizeof(sig_names) / sizeof(sig_names[0])); assert(sig_names[sig] != NULL); return sig_names[sig]; } static void print_traps(struct mrsh_state *state) { struct mrsh_state_priv *priv = state_get_priv(state); for (size_t i = 0; i < sizeof(priv->traps) / sizeof(priv->traps[0]); i++) { struct mrsh_trap *trap = &priv->traps[i]; if (!trap->set) { continue; } printf("trap -- "); switch (trap->action) { case MRSH_TRAP_DEFAULT: printf("-"); break; case MRSH_TRAP_IGNORE: printf("''"); break; case MRSH_TRAP_CATCH:; char *cmd = mrsh_node_format(&trap->program->node); print_escaped(cmd); free(cmd); break; } printf(" %s\n", sig_str(i)); } } int builtin_trap(struct mrsh_state *state, int argc, char *argv[]) { _mrsh_optind = 0; if (_mrsh_getopt(argc, argv, ":") != -1) { fprintf(stderr, "trap: unknown option -- %c\n", _mrsh_optopt); fprintf(stderr, trap_usage); return 1; } if (_mrsh_optind == argc) { print_traps(state); return 0; } const char *action_str; if (is_decimal_str(argv[_mrsh_optind])) { action_str = "-"; } else { action_str = argv[_mrsh_optind]; _mrsh_optind++; } enum mrsh_trap_action action; struct mrsh_program *program = NULL; if (action_str[0] == '\0') { action = MRSH_TRAP_IGNORE; } else if (strcmp(action_str, "-") == 0) { action = MRSH_TRAP_DEFAULT; } else { action = MRSH_TRAP_CATCH; struct mrsh_parser *parser = mrsh_parser_with_data(action_str, strlen(action_str)); program = mrsh_parse_program(parser); struct mrsh_position err_pos; const char *err_msg = mrsh_parser_error(parser, &err_pos); if (err_msg != NULL) { fprintf(stderr, "trap: %d:%d: %s\n", err_pos.line, err_pos.column, err_msg); mrsh_parser_destroy(parser); mrsh_program_destroy(program); return 1; } mrsh_parser_destroy(parser); } for (int i = _mrsh_optind; i < argc; i++) { int sig = parse_sig(argv[i]); if (sig < 0) { return 1; } if (sig == SIGKILL || sig == SIGSTOP) { fprintf(stderr, "trap: setting a trap for SIGKILL or SIGSTOP " "produces undefined results\n"); return 1; } if (!set_trap(state, sig, action, program)) { return 1; } } return 0; } ================================================ FILE: builtin/true.c ================================================ #include #include #include "builtin.h" int builtin_true(struct mrsh_state *state, int argc, char *argv[]) { return 0; } ================================================ FILE: builtin/type.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include #include "builtin.h" #include "mrsh_getopt.h" #include "shell/shell.h" static const char type_usage[] = "usage: type name...\n"; int builtin_type(struct mrsh_state *state, int argc, char *argv[]) { struct mrsh_state_priv *priv = state_get_priv(state); _mrsh_optind = 0; if (_mrsh_getopt(argc, argv, ":") != -1) { fprintf(stderr, "type: unknown option -- %c\n", _mrsh_optopt); fprintf(stderr, type_usage); return 1; } if (_mrsh_optind == argc) { fprintf(stderr, type_usage); return 1; } bool error = false; for (int i = _mrsh_optind; i < argc; ++i) { char *name = argv[i]; char *alias = mrsh_hashtable_get(&priv->aliases, name); if (alias != NULL) { fprintf(stdout, "%s is an alias for %s\n", name, alias); continue; } if (mrsh_has_special_builtin(name)) { fprintf(stdout, "%s is a special shell builtin\n", name); continue; } if (mrsh_has_builtin(name)) { fprintf(stdout, "%s is a shell builtin\n", name); continue; } char *path = expand_path(state, name, true, false); if (path != NULL) { fprintf(stdout, "%s is %s\n", name, path); free(path); continue; } fprintf(stdout, "%s: not found\n", name); error = true; } return error ? 1 : 0; } ================================================ FILE: builtin/ulimit.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include "builtin.h" #include "mrsh_getopt.h" static const char ulimit_usage[] = "usage: ulimit [-f] [blocks]\n"; int builtin_ulimit(struct mrsh_state *state, int argc, char *argv[]) { _mrsh_optind = 0; int opt; while ((opt = _mrsh_getopt(argc, argv, ":f")) != -1) { if (opt == 'f') { // Nothing here } else { fprintf(stderr, "%s", ulimit_usage); return 1; } } if (_mrsh_optind == argc - 1) { char *arg = argv[_mrsh_optind]; char *end; long int new_limit = strtol(arg, &end, 10); if (end == arg || end[0] != '\0') { fprintf(stderr, "ulimit: invalid argument: %s\n", arg); return 1; } struct rlimit new = { .rlim_cur = new_limit * 512, .rlim_max = new_limit * 512 }; if (setrlimit(RLIMIT_FSIZE, &new) != 0) { perror("setrlimit"); return 1; } } else if (_mrsh_optind == argc) { struct rlimit old = { 0 }; if (getrlimit(RLIMIT_FSIZE, &old) != 0) { perror("getrlimit"); return 1; } if (old.rlim_max == RLIM_INFINITY) { printf("unlimited\n"); } else { printf("%" PRIuMAX "\n", (uintmax_t)(old.rlim_max / 512)); } } else { fprintf(stderr, "%s", ulimit_usage); return 1; } return 0; } ================================================ FILE: builtin/umask.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include "builtin.h" #include "mrsh_getopt.h" static const char umask_usage[] = "usage: umask [-S] [mode]\n"; enum umask_symbolic_state { UMASK_WHO, UMASK_PERM, }; static mode_t umask_current_mask(void) { const mode_t default_mode = 0022; mode_t mask = umask(default_mode); umask(mask); return mask; } static bool umask_update_mode(mode_t *mode, char action, mode_t *perm_mask, mode_t *who_mask) { switch (action) { case '+': *mode |= (*who_mask & *perm_mask); break; case '=': *mode = (*mode & ~(*who_mask)) | (*perm_mask & *who_mask); break; case '-': *mode &= (0777 & ~(*who_mask & *perm_mask)); break; default: fprintf(stderr, "unknown action -- '%c'\n", action); return false; } *perm_mask = *who_mask = 0; return true; } static bool umask_mode(mode_t *mode, char *symbolic) { mode_t tmp_mode = 0777 & ~(umask_current_mask()); enum umask_symbolic_state state = UMASK_WHO; mode_t who_mask = 0; mode_t perm_mask = 0; char action = '\0'; for (char *c = symbolic; *c != '\0'; c++) { switch (state) { case UMASK_WHO: switch (*c) { case 'u': who_mask |= S_IRWXU; break; case 'g': who_mask |= S_IRWXG; break; case 'o': who_mask |= S_IRWXO; break; case 'a': who_mask |= (S_IRWXU | S_IRWXG | S_IRWXO); break; case '+': case '-': case '=': if (who_mask == 0) { who_mask |= (S_IRWXU | S_IRWXG | S_IRWXO); } action = *c; state = UMASK_PERM; break; default: fprintf(stderr, "Unknown who -- '%c'\n", *c); return false; } break; case UMASK_PERM: switch (*c) { case 'u': perm_mask = (tmp_mode & S_IRWXU) | ((tmp_mode & S_IRWXU) >> 3) | ((tmp_mode & S_IRWXU) >> 6); break; case 'g': perm_mask = ((tmp_mode & S_IRWXG) << 3) | (tmp_mode & S_IRWXG) | ((tmp_mode & S_IRWXG) >> 3); break; case 'o': perm_mask = ((tmp_mode & S_IRWXO) << 6) | ((tmp_mode & S_IRWXO) << 3) | (tmp_mode & S_IRWXO); break; case 'r': perm_mask |= (S_IRUSR | S_IRGRP | S_IROTH); break; case 'w': perm_mask |= (S_IWUSR | S_IWGRP | S_IWOTH); break; case 'x': perm_mask |= (S_IXUSR | S_IXGRP | S_IXOTH); break; case ',': state = UMASK_WHO; if (!umask_update_mode(&tmp_mode, action, &perm_mask, &who_mask)) { return false; } break; default: fprintf(stderr, "Invalid permission -- '%c'\n", *c); return false; } break; } } if (state == UMASK_PERM) { if (!umask_update_mode(&tmp_mode, action, &perm_mask, &who_mask)) { return false; } } else { fprintf(stderr, "Missing permission from symbolic mode\n"); return false; } *mode = 0777 & ~tmp_mode; return true; } static void umask_modestring(char string[static 4], mode_t mode) { size_t i = 0; if (S_IROTH & mode) { string[i++] = 'r'; } if (S_IWOTH & mode) { string[i++] = 'w'; } if (S_IXOTH & mode) { string[i++] = 'x'; } } static void umask_print_symbolic(mode_t mask) { char user[4] = {0}; char group[4] = {0}; char other[4] = {0}; mode_t mode = 0777 & ~mask; umask_modestring(user, (mode & 0700) >> 6); umask_modestring(group, (mode & 0070) >> 3); umask_modestring(other, (mode & 0007)); printf("u=%s,g=%s,o=%s\n", user, group, other); } int builtin_umask(struct mrsh_state *state, int argc, char *argv[]) { mode_t mode; bool umask_symbolic = false; _mrsh_optind = 0; int opt; while ((opt = _mrsh_getopt(argc, argv, ":S")) != -1) { switch (opt) { case 'S': umask_symbolic = true; break; default: fprintf(stderr, "Unknown option -- '%c'\n", _mrsh_optopt); fprintf(stderr, umask_usage); return 1; } } if (_mrsh_optind == argc) { mode = umask_current_mask(); if (umask_symbolic) { umask_print_symbolic(mode); } else { printf("%04o\n", mode); } return 0; } char *endptr; mode = strtol(argv[_mrsh_optind], &endptr, 8); if (*endptr != '\0') { if (!umask_mode(&mode, argv[_mrsh_optind])) { fprintf(stderr, umask_usage); return 1; } } umask(mode); return 0; } ================================================ FILE: builtin/unalias.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include "builtin.h" #include "mrsh_getopt.h" #include "shell/shell.h" static const char unalias_usage[] = "usage: unalias -a|alias-name...\n"; static void delete_alias_iterator(const char *key, void *_value, void *user_data) { free(mrsh_hashtable_del((struct mrsh_hashtable*)user_data, key)); } int builtin_unalias(struct mrsh_state *state, int argc, char *argv[]) { struct mrsh_state_priv *priv = state_get_priv(state); bool all = false; _mrsh_optind = 0; int opt; while ((opt = _mrsh_getopt(argc, argv, ":a")) != -1) { switch (opt) { case 'a': all = true; break; default: fprintf(stderr, "unalias: unknown option -- %c\n", _mrsh_optopt); fprintf(stderr, unalias_usage); return 1; } } if (all) { if (_mrsh_optind < argc) { fprintf(stderr, unalias_usage); return 1; } mrsh_hashtable_for_each(&priv->aliases, delete_alias_iterator, &priv->aliases); return 0; } if (_mrsh_optind == argc) { fprintf(stderr, unalias_usage); return 1; } for (int i = _mrsh_optind; i < argc; ++i) { free(mrsh_hashtable_del(&priv->aliases, argv[i])); } return 0; } ================================================ FILE: builtin/unset.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include "builtin.h" #include "mrsh_getopt.h" #include "shell/shell.h" static const char unset_usage[] = "usage: unset [-fv] name...\n"; int builtin_unset(struct mrsh_state *state, int argc, char *argv[]) { struct mrsh_state_priv *priv = state_get_priv(state); bool funcs = false; _mrsh_optind = 0; int opt; while ((opt = _mrsh_getopt(argc, argv, ":fv")) != -1) { switch (opt) { case 'f': funcs = true; break; case 'v': funcs = false; break; default: fprintf(stderr, "unset: unknown option -- %c\n", _mrsh_optopt); fprintf(stderr, unset_usage); return 1; } } if (_mrsh_optind >= argc) { fprintf(stderr, unset_usage); return 1; } for (int i = _mrsh_optind; i < argc; ++i) { if (!funcs) { uint32_t prev_attribs = 0; if (mrsh_env_get(state, argv[i], &prev_attribs)) { if ((prev_attribs & MRSH_VAR_ATTRIB_READONLY)) { fprintf(stderr, "unset: cannot modify readonly variable %s\n", argv[i]); return 1; } mrsh_env_unset(state, argv[i]); } } else { struct mrsh_function *oldfn = mrsh_hashtable_del(&priv->functions, argv[i]); function_destroy(oldfn); } } return 0; } ================================================ FILE: builtin/unspecified.c ================================================ #include #include #include #include "builtin.h" int builtin_unspecified(struct mrsh_state *state, int argc, char *argv[]) { // Ref: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_09_01_01 if (state->interactive) { fprintf(stderr, "%s: The behavior of this command is undefined.\n", argv[0]); } else { fprintf(stderr, "%s: The behavior of this command is undefined. " "This is an error in your script. Aborting.\n", argv[0]); state->exit = 1; } return 1; } ================================================ FILE: builtin/wait.c ================================================ #define _POSIX_C_SOURCE 200112L #include #include #include #include #include #include #include "builtin.h" #include "shell/process.h" #include "shell/shell.h" struct wait_handle { pid_t pid; int status; }; int builtin_wait(struct mrsh_state *state, int argc, char *argv[]) { struct mrsh_state_priv *priv = state_get_priv(state); int npids = argc - 1; if (npids == 0) { npids = priv->processes.len; } struct wait_handle *pids = malloc(npids * sizeof(struct wait_handle)); if (pids == NULL) { fprintf(stderr, "wait: unable to allocate pid list"); return EXIT_FAILURE; } if (argc == 1) { /* All known processes */ int _npids = 0; for (size_t j = 0; j < priv->processes.len; ++j) { struct mrsh_process *process = priv->processes.data[j]; if (process->terminated) { continue; } pids[_npids].pid = process->pid; pids[_npids].status = -1; ++_npids; } npids = _npids; } else { for (int i = 1; i < argc; ++i) { if (argv[i][0] == '%') { struct mrsh_job *job = job_by_id(state, argv[i], true); if (!job) { goto failure; } pids[i - 1].pid = job->pgid; pids[i - 1].status = -1; } else { char *endptr; pid_t pid = (pid_t)strtol(argv[i], &endptr, 10); if (*endptr != '\0' || argv[i][0] == '\0') { fprintf(stderr, "wait: error parsing pid '%s'", argv[i]); goto failure; } if (pid <= 0) { fprintf(stderr, "wait: invalid process ID\n"); goto failure; } pids[i - 1].pid = pid; pids[i - 1].status = -1; /* Check if this pid is known */ bool found = false; for (size_t j = 0; j < priv->processes.len; ++j) { struct mrsh_process *process = priv->processes.data[j]; if (process->pid == pid) { if (process->terminated) { pids[i - 1].status = process->stat; } found = true; break; } } if (!found) { /* Unknown pids are assumed to have exited 127 */ pids[i - 1].status = 127; } } } } for (int i = 0; i < npids; ++i) { int stat; pid_t waited = waitpid(pids[i].pid, &stat, 0); // TODO: update jobs internal state? if (waited == -1) { if (errno == ECHILD) { continue; } perror("wait"); goto failure; } update_process(state, waited, stat); if (WIFEXITED(stat)) { pids[i].status = WEXITSTATUS(stat); } else { pids[i].status = 129; } } int status; if (argc == 1) { status = EXIT_SUCCESS; } else { status = pids[npids - 1].status; } free(pids); return status; failure: free(pids); return EXIT_FAILURE; } ================================================ FILE: configure ================================================ #!/bin/sh -e SOVERSION=0.0.0 pkg_config=${PKG_CONFIG:-pkg-config} outdir=${OUTDIR:-.build} srcdir=${SRCDIR:-$(dirname "$0")} CC=${CC:-cc} LIBS= use_readline=-1 readline=readline static= for arg do case "$arg" in --prefix=*) PREFIX=${arg#*=} ;; --without-readline) use_readline=0 ;; --with-readline=*) use_readline=1 readline=${arg#*=} ;; --static) static=$arg ;; --dynamic) static= ;; esac done libmrsh() { genrules libmrsh \ 'arithm.c' \ 'array.c' \ 'ast_print.c' \ 'ast.c' \ 'buffer.c' \ 'builtin/alias.c' \ 'builtin/bg.c' \ 'builtin/break.c' \ 'builtin/builtin.c' \ 'builtin/cd.c' \ 'builtin/colon.c' \ 'builtin/command.c' \ 'builtin/dot.c' \ 'builtin/eval.c' \ 'builtin/exec.c' \ 'builtin/exit.c' \ 'builtin/export.c' \ 'builtin/false.c' \ 'builtin/fg.c' \ 'builtin/getopts.c' \ 'builtin/hash.c' \ 'builtin/jobs.c' \ 'builtin/pwd.c' \ 'builtin/read.c' \ 'builtin/return.c' \ 'builtin/set.c' \ 'builtin/shift.c' \ 'builtin/times.c' \ 'builtin/trap.c' \ 'builtin/true.c' \ 'builtin/type.c' \ 'builtin/ulimit.c' \ 'builtin/umask.c' \ 'builtin/unalias.c' \ 'builtin/unset.c' \ 'builtin/unspecified.c' \ 'builtin/wait.c' \ 'getopt.c' \ 'hashtable.c' \ 'parser/arithm.c' \ 'parser/parser.c' \ 'parser/program.c' \ 'parser/word.c' \ 'shell/arithm.c' \ 'shell/entry.c' \ 'shell/job.c' \ 'shell/path.c' \ 'shell/process.c' \ 'shell/redir.c' \ 'shell/shell.c' \ 'shell/task/pipeline.c' \ 'shell/task/simple_command.c' \ 'shell/task/task.c' \ 'shell/task/word.c' \ 'shell/trap.c' \ 'shell/word.c' } mrsh() { if [ $use_readline -eq 1 ] then genrules mrsh \ 'main.c' \ 'frontend/readline.c' else genrules mrsh \ 'main.c' \ 'frontend/basic.c' fi } highlight() { genrules highlight example/highlight.c } genrules() { target="$1" shift printf '# Begin generated rules for %s\n' "$target" for file in "$@" do file="${file%.*}" printf '%s.o: %s.c\n' "$file" "$file" done printf '%s_objects=\\\n' "$target" n=0 for file in "$@" do file="${file%.*}" n=$((n+1)) if [ $n -eq $# ] then printf '\t%s.o\n' "$file" else printf '\t%s.o \\\n' "$file" fi done printf '# End generated rules for %s\n' "$target" } append_cflags() { for flag do CFLAGS="$(printf '%s \\\n\t%s' "$CFLAGS" "$flag")" done } append_ldflags() { for flag do LDFLAGS="$(printf '%s \\\n\t%s' "$LDFLAGS" "$flag")" done } append_libs() { for flag do LIBS="$(printf '%s \\\n\t%s' "$LIBS" "$flag")" done } test_cflags() { [ ! -e "$outdir"/check.c ] && cat <<-EOF > "$outdir"/check.c int main(void) { return 0; } EOF werror="" case "$CFLAGS" in *-Werror*) werror="-Werror" ;; esac if $CC $werror "$@" -o /dev/null "$outdir"/check.c >/dev/null 2>&1 then append_cflags "$@" else return 1 fi } test_ldflags() { [ ! -e "$outdir"/check.c ] && cat <<-EOF > "$outdir"/check.c int main(void) { return 0; } EOF if $CC "$@" -o /dev/null "$outdir"/check.c >/dev/null 2>&1 then append_ldflags "$@" else return 1 fi } mkdir -p "$outdir" if [ -n "$static" ] then test_ldflags $static fi for flag in \ -g -std=c99 -pedantic -Werror -Wundef -Wlogical-op \ -Wmissing-include-dirs -Wold-style-definition -Wpointer-arith -Winit-self \ -Wfloat-equal -Wstrict-prototypes -Wredundant-decls \ -Wimplicit-fallthrough=2 -Wendif-labels -Wstrict-aliasing=2 -Woverflow \ -Wformat=2 -Wno-missing-braces -Wno-missing-field-initializers \ -Wno-unused-parameter -Wno-unused-result do printf "Checking for $flag... " if test_cflags "$flag" then echo yes else echo no fi done for flag in -fPIC -Wl,--no-undefined -Wl,--as-needed do test_ldflags "$flag" done soname=libmrsh.so.$(echo "$SOVERSION" | cut -d. -f1) printf "Checking for specifying soname for shared lib... " if ! \ test_ldflags -Wl,-soname,$soname || \ test_ldflags -Wl,-install_name,$soname then echo no echo "Unable to specify soname (is $(uname) supported?)" >&2 exit 1 else echo yes fi printf "Checking for exported symbol restrictions... " if ! \ test_ldflags -Wl,--version-script="libmrsh.gnu.sym" || \ test_ldflags -Wl,-exported_symbols_list,"libmrsh.darwin.sym" then echo no echo "Unable to specify exported symbols (is $(uname) supported?)" >&2 exit 1 else echo yes fi if [ $use_readline -eq -1 ] then printf "Checking for readline... " if $pkg_config readline then readline=readline use_readline=1 append_cflags -DHAVE_READLINE # TODO: check for rl_replace_line append_cflags -DHAVE_READLINE_REPLACE_LINE echo yes else echo no fi fi if [ $use_readline -eq -1 ] then printf "Checking for libedit... " if $pkg_config libedit then echo yes readline=libedit use_readline=1 append_cflags -DHAVE_EDITLINE else echo no fi fi if [ $use_readline -eq 1 ] then append_cflags $($pkg_config $static --cflags-only-I $readline) append_libs $($pkg_config $static --libs $readline) fi printf "Creating %s/config.mk... " "$outdir" cat < "$outdir"/config.mk SOVERSION=$SOVERSION CC=$CC PREFIX=${PREFIX:-/usr/local} _INSTDIR=\$(DESTDIR)\$(PREFIX) BINDIR?=${BINDIR:-\$(_INSTDIR)/bin} LIBDIR?=${LIBDIR:-\$(_INSTDIR)/lib} INCDIR?=${INCDIR:-\$(_INSTDIR)/include} MANDIR?=${MANDIR:-\$(_INSTDIR)/share/man} PCDIR?=${PCDIR:-\$(_INSTDIR)/lib/pkgconfig} CFLAGS=${CFLAGS} LDFLAGS=${LDFLAGS} LIBS=${LIBS} SRCDIR=${srcdir} all: mrsh highlight libmrsh.so.\$(SOVERSION) \$(OUTDIR)/mrsh.pc EOF libmrsh >>"$outdir"/config.mk mrsh >>"$outdir"/config.mk highlight >>"$outdir"/config.mk echo done touch "$outdir"/cppcache ================================================ FILE: example/highlight.c ================================================ #include #include #include #include #include #include #include #include #define READ_SIZE 4096 #define FORMAT_STACK_SIZE 64 enum format { FORMAT_RESET = 0, FORMAT_GREEN = 32, FORMAT_YELLOW = 33, FORMAT_BLUE = 34, FORMAT_CYAN = 36, FORMAT_DEFAULT = 39, FORMAT_LIGHT_BLUE = 94, }; struct highlight_state { const char *buf; size_t offset; enum format fmt_stack[FORMAT_STACK_SIZE]; size_t fmt_stack_len; }; static void highlight(struct highlight_state *state, struct mrsh_position *pos, enum format fmt) { assert(pos->offset >= state->offset); fwrite(&state->buf[state->offset], sizeof(char), pos->offset - state->offset, stdout); state->offset = pos->offset; if (fmt == FORMAT_RESET) { assert(state->fmt_stack_len > 0); --state->fmt_stack_len; if (state->fmt_stack_len > 0) { fmt = state->fmt_stack[state->fmt_stack_len - 1]; } } else { if (state->fmt_stack_len >= FORMAT_STACK_SIZE) { fprintf(stderr, "format stack overflow\n"); exit(1); } state->fmt_stack[state->fmt_stack_len] = fmt; ++state->fmt_stack_len; } fprintf(stdout, "%c[%dm", 0x1B, fmt); } static void highlight_str(struct highlight_state *state, struct mrsh_range *range, enum format fmt) { highlight(state, &range->begin, fmt); highlight(state, &range->end, FORMAT_RESET); } static void highlight_char(struct highlight_state *state, struct mrsh_position *pos, enum format fmt) { struct mrsh_position next = { .offset = pos->offset + 1 }; highlight(state, pos, fmt); highlight(state, &next, FORMAT_RESET); } static void highlight_word(struct highlight_state *state, struct mrsh_word *word, bool cmd_name, bool quoted) { switch (word->type) { case MRSH_WORD_STRING:; struct mrsh_word_string *ws = mrsh_word_get_string(word); if (!quoted) { enum format fmt = FORMAT_CYAN; if (ws->single_quoted) { fmt = FORMAT_YELLOW; } else if (cmd_name) { fmt = FORMAT_BLUE; } highlight_str(state, &ws->range, fmt); } break; case MRSH_WORD_PARAMETER:; struct mrsh_word_parameter *wp = mrsh_word_get_parameter(word); highlight(state, &wp->dollar_pos, FORMAT_CYAN); highlight_char(state, &wp->dollar_pos, FORMAT_GREEN); if (mrsh_position_valid(&wp->lbrace_pos)) { highlight_char(state, &wp->lbrace_pos, FORMAT_GREEN); } if (mrsh_range_valid(&wp->op_range)) { highlight_str(state, &wp->op_range, FORMAT_GREEN); } if (wp->arg != NULL) { highlight_word(state, wp->arg, false, false); } struct mrsh_position end = {0}; if (mrsh_position_valid(&wp->rbrace_pos)) { highlight_char(state, &wp->rbrace_pos, FORMAT_GREEN); end.offset = wp->rbrace_pos.offset + 1; } else { end = wp->name_range.end; } highlight(state, &end, FORMAT_RESET); break; case MRSH_WORD_COMMAND:; struct mrsh_word_command *wc = mrsh_word_get_command(word); if (wc->back_quoted) { highlight_char(state, &wc->range.begin, FORMAT_GREEN); } // TODO: highlight inside if (wc->back_quoted) { struct mrsh_position rquote = { .offset = wc->range.end.offset - 1, }; highlight_char(state, &rquote, FORMAT_GREEN); } break; case MRSH_WORD_ARITHMETIC: abort(); // TODO case MRSH_WORD_LIST:; struct mrsh_word_list *wl = mrsh_word_get_list(word); if (wl->children.len == 0) { break; } if (wl->double_quoted) { highlight(state, &wl->lquote_pos, FORMAT_YELLOW); } for (size_t i = 0; i < wl->children.len; ++i) { struct mrsh_word *child = wl->children.data[i]; highlight_word(state, child, cmd_name, wl->double_quoted); } if (wl->double_quoted) { struct mrsh_position end = { .offset = wl->rquote_pos.offset + 1 }; highlight(state, &end, FORMAT_RESET); } break; } } static void highlight_simple_command(struct highlight_state *state, struct mrsh_simple_command *cmd) { if (cmd->name != NULL) { highlight_word(state, cmd->name, true, false); } for (size_t i = 0; i < cmd->arguments.len; ++i) { struct mrsh_word *arg = cmd->arguments.data[i]; highlight_word(state, arg, false, false); } // TODO: cmd->io_redirects, cmd->assignments } static void highlight_command_list_array(struct highlight_state *state, struct mrsh_array *array); static void highlight_command(struct highlight_state *state, struct mrsh_command *cmd) { switch (cmd->type) { case MRSH_SIMPLE_COMMAND:; struct mrsh_simple_command *sc = mrsh_command_get_simple_command(cmd); highlight_simple_command(state, sc); break; case MRSH_BRACE_GROUP:; struct mrsh_brace_group *bg = mrsh_command_get_brace_group(cmd); highlight_char(state, &bg->lbrace_pos, FORMAT_GREEN); highlight_command_list_array(state, &bg->body); highlight_char(state, &bg->rbrace_pos, FORMAT_GREEN); break; case MRSH_SUBSHELL:; struct mrsh_subshell *s = mrsh_command_get_subshell(cmd); highlight_char(state, &s->lparen_pos, FORMAT_GREEN); highlight_command_list_array(state, &s->body); highlight_char(state, &s->rparen_pos, FORMAT_GREEN); break; case MRSH_IF_CLAUSE:; struct mrsh_if_clause *ic = mrsh_command_get_if_clause(cmd); highlight_str(state, &ic->if_range, FORMAT_BLUE); highlight_command_list_array(state, &ic->condition); highlight_str(state, &ic->then_range, FORMAT_BLUE); highlight_command_list_array(state, &ic->body); if (ic->else_part != NULL) { if (mrsh_range_valid(&ic->else_range)) { highlight_str(state, &ic->else_range, FORMAT_BLUE); } highlight_command(state, ic->else_part); } if (mrsh_range_valid(&ic->fi_range)) { highlight_str(state, &ic->fi_range, FORMAT_BLUE); } break; case MRSH_FOR_CLAUSE:; struct mrsh_for_clause *fc = mrsh_command_get_for_clause(cmd); highlight_str(state, &fc->for_range, FORMAT_BLUE); highlight_str(state, &fc->name_range, FORMAT_CYAN); if (mrsh_range_valid(&fc->in_range)) { highlight_str(state, &fc->in_range, FORMAT_BLUE); } for (size_t i = 0; i < fc->word_list.len; ++i) { struct mrsh_word *word = fc->word_list.data[i]; highlight_word(state, word, false, false); } highlight_str(state, &fc->do_range, FORMAT_BLUE); highlight_command_list_array(state, &fc->body); highlight_str(state, &fc->done_range, FORMAT_BLUE); break; case MRSH_LOOP_CLAUSE:; struct mrsh_loop_clause *lc = mrsh_command_get_loop_clause(cmd); highlight_str(state, &lc->while_until_range, FORMAT_BLUE); highlight_command_list_array(state, &lc->condition); highlight_str(state, &lc->do_range, FORMAT_BLUE); highlight_command_list_array(state, &lc->body); highlight_str(state, &lc->done_range, FORMAT_BLUE); break; case MRSH_CASE_CLAUSE:; struct mrsh_case_clause *cc = mrsh_command_get_case_clause(cmd); highlight_str(state, &cc->case_range, FORMAT_BLUE); highlight_word(state, cc->word, false, false); highlight_str(state, &cc->in_range, FORMAT_BLUE); for (size_t i = 0; i < cc->items.len; ++i) { struct mrsh_case_item *item = cc->items.data[i]; if (mrsh_position_valid(&item->lparen_pos)) { highlight_char(state, &item->lparen_pos, FORMAT_GREEN); } for (size_t j = 0; j < item->patterns.len; ++j) { struct mrsh_word *pattern = item->patterns.data[j]; highlight_word(state, pattern, false, false); } highlight_char(state, &item->rparen_pos, FORMAT_GREEN); highlight_command_list_array(state, &item->body); if (mrsh_range_valid(&item->dsemi_range)) { highlight_str(state, &item->dsemi_range, FORMAT_GREEN); } } highlight_str(state, &cc->esac_range, FORMAT_BLUE); break; case MRSH_FUNCTION_DEFINITION:; struct mrsh_function_definition *fd = mrsh_command_get_function_definition(cmd); highlight_str(state, &fd->name_range, FORMAT_BLUE); highlight_char(state, &fd->lparen_pos, FORMAT_GREEN); highlight_char(state, &fd->rparen_pos, FORMAT_GREEN); highlight_command(state, fd->body); break; } } static void highlight_and_or_list(struct highlight_state *state, struct mrsh_and_or_list *and_or_list) { switch (and_or_list->type) { case MRSH_AND_OR_LIST_PIPELINE:; struct mrsh_pipeline *pl = mrsh_and_or_list_get_pipeline(and_or_list); if (mrsh_position_valid(&pl->bang_pos)) { highlight_char(state, &pl->bang_pos, FORMAT_GREEN); } for (size_t i = 0; i < pl->commands.len; ++i) { struct mrsh_command *cmd = pl->commands.data[i]; highlight_command(state, cmd); } break; case MRSH_AND_OR_LIST_BINOP:; struct mrsh_binop *binop = mrsh_and_or_list_get_binop(and_or_list); highlight_and_or_list(state, binop->left); highlight_str(state, &binop->op_range, FORMAT_GREEN); highlight_and_or_list(state, binop->right); break; } } static void highlight_command_list(struct highlight_state *state, struct mrsh_command_list *list) { highlight_and_or_list(state, list->and_or_list); if (mrsh_position_valid(&list->separator_pos)) { highlight_char(state, &list->separator_pos, FORMAT_GREEN); } } static void highlight_command_list_array(struct highlight_state *state, struct mrsh_array *array) { for (size_t i = 0; i < array->len; ++i) { struct mrsh_command_list *l = array->data[i]; highlight_command_list(state, l); } } static void highlight_program(struct highlight_state *state, struct mrsh_program *prog) { highlight_command_list_array(state, &prog->body); } int main(int argc, char *argv[]) { struct mrsh_buffer buf = {0}; while (true) { char *dst = mrsh_buffer_reserve(&buf, READ_SIZE); ssize_t n_read = read(STDIN_FILENO, dst, READ_SIZE); if (n_read < 0) { perror("read"); return 1; } else if (n_read == 0) { break; } buf.len += n_read; } struct mrsh_parser *parser = mrsh_parser_with_data(buf.data, buf.len); struct mrsh_program *prog = mrsh_parse_program(parser); const char *err_msg = mrsh_parser_error(parser, NULL); if (err_msg != NULL) { fprintf(stderr, "failed to parse script: %s\n", err_msg); return 1; } if (prog == NULL) { return 0; } mrsh_parser_destroy(parser); struct highlight_state state = { .buf = buf.data, }; struct mrsh_position begin = { .offset = 0 }; highlight(&state, &begin, FORMAT_DEFAULT); highlight_program(&state, prog); struct mrsh_position end = { .offset = buf.len }; highlight(&state, &end, FORMAT_RESET); assert(state.fmt_stack_len == 0); mrsh_buffer_finish(&buf); return 0; } ================================================ FILE: example/meson.build ================================================ executable( 'highlight', files('highlight.c'), dependencies: [mrsh], build_by_default: get_option('examples'), ) ================================================ FILE: frontend/basic.c ================================================ /* Basic, strictly POSIX interactive line interface */ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include "frontend.h" void interactive_init(struct mrsh_state *state) { // no-op } size_t interactive_next(struct mrsh_state *state, char **line, const char *prompt) { fprintf(stderr, "%s", prompt); size_t len = 0; char *_line = NULL; errno = 0; ssize_t n_read = getline(&_line, &len, stdin); if (n_read < 0) { free(_line); if (errno != 0) { perror("getline"); } return 0; } *line = _line; return n_read; } ================================================ FILE: frontend/readline.c ================================================ // readline/editline interactive line interface #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #if defined(HAVE_READLINE) #include #include #elif defined(HAVE_EDITLINE) #include #include #endif #include "frontend.h" #if defined(HAVE_READLINE) #if !defined(HAVE_READLINE_REPLACE_LINE) static void rl_replace_line(const char *text, int clear_undo) { return; } #endif static void sigint_handler(int n) { /* Signal safety is done here on a best-effort basis. rl_redisplay is not * signal safe, but under these circumstances it's very likely that the * interrupted function will not be affected. */ char newline = '\n'; (void)write(STDOUT_FILENO, &newline, 1); rl_on_new_line(); rl_replace_line("", 0); rl_redisplay(); } #endif static char *get_history_path(void) { const char *home = getenv("HOME"); int len = snprintf(NULL, 0, "%s/.mrsh_history", home); char *path = malloc(len + 1); if (path == NULL) { return NULL; } snprintf(path, len + 1, "%s/.mrsh_history", home); return path; } void interactive_init(struct mrsh_state *state) { rl_initialize(); char *history_path = get_history_path(); read_history(history_path); free(history_path); } size_t interactive_next(struct mrsh_state *state, char **line, const char *prompt) { /* TODO: make SIGINT handling work with editline */ #if defined(HAVE_READLINE) struct sigaction sa = { .sa_handler = sigint_handler }, old; sigaction(SIGINT, &sa, &old); #endif char *rline = readline(prompt); #if defined(HAVE_READLINE) sigaction(SIGINT, &old, NULL); #endif if (!rline) { return 0; } size_t len = strlen(rline); if (!(state->options & MRSH_OPT_NOLOG)) { add_history(rline); char *history_path = get_history_path(); write_history(history_path); free(history_path); } *line = malloc(len + 2); strcpy(*line, rline); strcat(*line, "\n"); free(rline); return len + 1; } ================================================ FILE: getopt.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include "mrsh_getopt.h" char *_mrsh_optarg = NULL; int _mrsh_optind = 1; int _mrsh_opterr = 1; int _mrsh_optopt = 0; int _mrsh_optpos = 1; int _mrsh_getopt(int argc, char *const argv[], const char *optstring) { assert(argv[argc] == NULL); _mrsh_optarg = NULL; if (_mrsh_optind == 0) { _mrsh_optind = 1; _mrsh_optpos = 1; } if (_mrsh_optind >= argc) { return -1; } if (argv[_mrsh_optind][0] != '-') { return -1; } if (argv[_mrsh_optind][1] == '\0') { return -1; } if (argv[_mrsh_optind][1] == '-') { _mrsh_optind++; return -1; } const char *c = optstring; if (*c == ':') { c++; } _mrsh_optopt = 0; int opt = argv[_mrsh_optind][_mrsh_optpos]; for (; *c != '\0'; c++) { if (*c != opt) { continue; } if (c[1] != ':') { if (argv[_mrsh_optind][_mrsh_optpos + 1] == '\0') { _mrsh_optind++; _mrsh_optpos = 1; } else { _mrsh_optpos++; } return opt; } if (argv[_mrsh_optind][_mrsh_optpos + 1] != '\0') { _mrsh_optarg = &argv[_mrsh_optind][_mrsh_optpos + 1]; } else { if (_mrsh_optind + 2 > argc) { _mrsh_optopt = opt; if (_mrsh_opterr != 0 && optstring[0] != ':') { fprintf(stderr, "%s: Option '%c' requires an argument.\n", argv[0], _mrsh_optopt); } return optstring[0] == ':' ? ':' : '?'; } _mrsh_optarg = argv[++_mrsh_optind]; } _mrsh_optind++; return opt; } if (_mrsh_opterr != 0 && optstring[0] != ':') { fprintf(stderr, "%s: Option '%c' not found.\n", argv[0], opt); } return '?'; } ================================================ FILE: hashtable.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include static unsigned int djb2(const char *str) { unsigned int hash = 5381; char c; while ((c = *str++)) { hash = ((hash << 5) + hash) + c; } return hash; } void *mrsh_hashtable_get(struct mrsh_hashtable *table, const char *key) { unsigned int hash = djb2(key); unsigned int bucket = hash % MRSH_HASHTABLE_BUCKETS; struct mrsh_hashtable_entry *entry = table->buckets[bucket]; while (entry != NULL) { if (entry->hash == hash && strcmp(entry->key, key) == 0) { return entry->value; } entry = entry->next; } return NULL; } void *mrsh_hashtable_set(struct mrsh_hashtable *table, const char *key, void *value) { unsigned int hash = djb2(key); unsigned int bucket = hash % MRSH_HASHTABLE_BUCKETS; struct mrsh_hashtable_entry *entry = table->buckets[bucket]; struct mrsh_hashtable_entry *previous = NULL; while (entry != NULL) { if (entry->hash == hash && strcmp(entry->key, key) == 0) { break; } previous = entry; entry = entry->next; } if (entry == NULL) { entry = calloc(1, sizeof(struct mrsh_hashtable_entry)); entry->hash = hash; entry->key = strdup(key); if (previous != NULL) { previous->next = entry; } else { table->buckets[bucket] = entry; } } void *old_value = entry->value; entry->value = value; return old_value; } static void hashtable_entry_destroy(struct mrsh_hashtable_entry *entry) { if (entry == NULL) { return; } free(entry->key); free(entry); } void *mrsh_hashtable_del(struct mrsh_hashtable *table, const char *key) { unsigned int hash = djb2(key); unsigned int bucket = hash % MRSH_HASHTABLE_BUCKETS; struct mrsh_hashtable_entry *entry = table->buckets[bucket]; struct mrsh_hashtable_entry *previous = NULL; while (entry != NULL) { if (entry->hash == hash && strcmp(entry->key, key) == 0) { break; } previous = entry; entry = entry->next; } if (entry == NULL) { return NULL; } if (previous != NULL) { previous->next = entry->next; } else { table->buckets[bucket] = entry->next; } void *old_value = entry->value; hashtable_entry_destroy(entry); return old_value; } void mrsh_hashtable_finish(struct mrsh_hashtable *table) { for (size_t i = 0; i < MRSH_HASHTABLE_BUCKETS; ++i) { struct mrsh_hashtable_entry *entry = table->buckets[i]; while (entry != NULL) { struct mrsh_hashtable_entry *next = entry->next; hashtable_entry_destroy(entry); entry = next; } } } void mrsh_hashtable_for_each(struct mrsh_hashtable *table, mrsh_hashtable_iterator_func iterator, void *user_data) { for (size_t i = 0; i < MRSH_HASHTABLE_BUCKETS; ++i) { struct mrsh_hashtable_entry *entry = table->buckets[i]; while (entry != NULL) { struct mrsh_hashtable_entry *next = entry->next; iterator(entry->key, entry->value, user_data); entry = next; } } } ================================================ FILE: include/ast.h ================================================ #ifndef AST_H #define AST_H #include void command_list_array_finish(struct mrsh_array *cmds); void case_item_destroy(struct mrsh_case_item *item); #endif ================================================ FILE: include/builtin.h ================================================ #ifndef BUILTIN_H #define BUILTIN_H #include struct mrsh_state; typedef int (*mrsh_builtin_func)(struct mrsh_state *state, int argc, char *argv[]); void print_escaped(const char *value); int builtin_alias(struct mrsh_state *state, int argc, char *argv[]); int builtin_bg(struct mrsh_state *state, int argc, char *argv[]); int builtin_break(struct mrsh_state *state, int argc, char *argv[]); int builtin_cd(struct mrsh_state *state, int argc, char *argv[]); int builtin_command(struct mrsh_state *state, int argc, char *argv[]); int builtin_colon(struct mrsh_state *state, int argc, char *argv[]); int builtin_dot(struct mrsh_state *state, int argc, char *argv[]); int builtin_eval(struct mrsh_state *state, int argc, char *argv[]); int builtin_exec(struct mrsh_state *state, int argc, char *argv[]); int builtin_exit(struct mrsh_state *state, int argc, char *argv[]); int builtin_export(struct mrsh_state *state, int argc, char *argv[]); int builtin_false(struct mrsh_state *state, int argc, char *argv[]); int builtin_fg(struct mrsh_state *state, int argc, char *argv[]); int builtin_getopts(struct mrsh_state *state, int argc, char *argv[]); int builtin_hash(struct mrsh_state *state, int argc, char *argv[]); int builtin_jobs(struct mrsh_state *state, int argc, char *argv[]); int builtin_pwd(struct mrsh_state *state, int argc, char *argv[]); int builtin_read(struct mrsh_state *state, int argc, char *argv[]); int builtin_return(struct mrsh_state *state, int argc, char *argv[]); int builtin_set(struct mrsh_state *state, int argc, char *argv[]); int builtin_shift(struct mrsh_state *state, int argc, char *argv[]); int builtin_times(struct mrsh_state *state, int argc, char *argv[]); int builtin_trap(struct mrsh_state *state, int argc, char *argv[]); int builtin_true(struct mrsh_state *state, int argc, char *argv[]); int builtin_type(struct mrsh_state *state, int argc, char *argv[]); int builtin_ulimit(struct mrsh_state *state, int argc, char *argv[]); int builtin_umask(struct mrsh_state *state, int argc, char *argv[]); int builtin_unalias(struct mrsh_state *state, int argc, char *argv[]); int builtin_unset(struct mrsh_state *state, int argc, char *argv[]); int builtin_wait(struct mrsh_state *state, int argc, char *argv[]); int builtin_unspecified(struct mrsh_state *state, int argc, char *argv[]); const char *state_get_options(struct mrsh_state *state); struct mrsh_collect_var { const char *key, *value; }; /** Collects and alpha-sorts variables matching attribs. Count will be set to * the number of matching variables. You are responsible for freeing the return * value when you're done.*/ struct mrsh_collect_var *collect_vars(struct mrsh_state *state, uint32_t attribs, size_t *count); #endif ================================================ FILE: include/frontend.h ================================================ #ifndef FRONTEND_H #define FRONTEND_H #include #include void interactive_init(struct mrsh_state *state); size_t interactive_next(struct mrsh_state *state, char **restrict line, const char *prompt); #endif ================================================ FILE: include/mrsh/arithm.h ================================================ #ifndef MRSH_AST_ARITHM_H #define MRSH_AST_ARITHM_H enum mrsh_arithm_expr_type { MRSH_ARITHM_LITERAL, MRSH_ARITHM_VARIABLE, MRSH_ARITHM_UNOP, MRSH_ARITHM_BINOP, MRSH_ARITHM_COND, MRSH_ARITHM_ASSIGN, }; /** * An aritmetic expression. One of: * - A literal * - A variable * - An unary operation * - A binary operation * - A condition * - An assignment */ struct mrsh_arithm_expr { enum mrsh_arithm_expr_type type; }; struct mrsh_arithm_literal { struct mrsh_arithm_expr expr; long value; }; struct mrsh_arithm_variable { struct mrsh_arithm_expr expr; char *name; }; enum mrsh_arithm_unop_type { MRSH_ARITHM_UNOP_PLUS, MRSH_ARITHM_UNOP_MINUS, MRSH_ARITHM_UNOP_TILDE, MRSH_ARITHM_UNOP_BANG, }; struct mrsh_arithm_unop { struct mrsh_arithm_expr expr; enum mrsh_arithm_unop_type type; struct mrsh_arithm_expr *body; }; enum mrsh_arithm_binop_type { MRSH_ARITHM_BINOP_ASTERISK, MRSH_ARITHM_BINOP_SLASH, MRSH_ARITHM_BINOP_PERCENT, MRSH_ARITHM_BINOP_PLUS, MRSH_ARITHM_BINOP_MINUS, MRSH_ARITHM_BINOP_DLESS, MRSH_ARITHM_BINOP_DGREAT, MRSH_ARITHM_BINOP_LESS, MRSH_ARITHM_BINOP_LESSEQ, MRSH_ARITHM_BINOP_GREAT, MRSH_ARITHM_BINOP_GREATEQ, MRSH_ARITHM_BINOP_DEQ, MRSH_ARITHM_BINOP_BANGEQ, MRSH_ARITHM_BINOP_AND, MRSH_ARITHM_BINOP_CIRC, MRSH_ARITHM_BINOP_OR, MRSH_ARITHM_BINOP_DAND, MRSH_ARITHM_BINOP_DOR, }; struct mrsh_arithm_binop { struct mrsh_arithm_expr expr; enum mrsh_arithm_binop_type type; struct mrsh_arithm_expr *left, *right; }; struct mrsh_arithm_cond { struct mrsh_arithm_expr expr; struct mrsh_arithm_expr *condition, *body, *else_part; }; enum mrsh_arithm_assign_op { MRSH_ARITHM_ASSIGN_NONE, MRSH_ARITHM_ASSIGN_ASTERISK, MRSH_ARITHM_ASSIGN_SLASH, MRSH_ARITHM_ASSIGN_PERCENT, MRSH_ARITHM_ASSIGN_PLUS, MRSH_ARITHM_ASSIGN_MINUS, MRSH_ARITHM_ASSIGN_DLESS, MRSH_ARITHM_ASSIGN_DGREAT, MRSH_ARITHM_ASSIGN_AND, MRSH_ARITHM_ASSIGN_CIRC, MRSH_ARITHM_ASSIGN_OR, }; struct mrsh_arithm_assign { struct mrsh_arithm_expr expr; enum mrsh_arithm_assign_op op; char *name; struct mrsh_arithm_expr *value; }; void mrsh_arithm_expr_destroy(struct mrsh_arithm_expr *expr); struct mrsh_arithm_literal *mrsh_arithm_literal_create(long value); struct mrsh_arithm_variable *mrsh_arithm_variable_create(char *name); struct mrsh_arithm_unop *mrsh_arithm_unop_create( enum mrsh_arithm_unop_type type, struct mrsh_arithm_expr *body); struct mrsh_arithm_binop *mrsh_arithm_binop_create( enum mrsh_arithm_binop_type type, struct mrsh_arithm_expr *left, struct mrsh_arithm_expr *right); struct mrsh_arithm_cond *mrsh_arithm_cond_create( struct mrsh_arithm_expr *condition, struct mrsh_arithm_expr *body, struct mrsh_arithm_expr *else_part); struct mrsh_arithm_assign *mrsh_arithm_assign_create( enum mrsh_arithm_assign_op op, char *name, struct mrsh_arithm_expr *value); struct mrsh_arithm_literal *mrsh_arithm_expr_get_literal( const struct mrsh_arithm_expr *expr); struct mrsh_arithm_variable *mrsh_arithm_expr_get_variable( const struct mrsh_arithm_expr *expr); struct mrsh_arithm_unop *mrsh_arithm_expr_get_unop( const struct mrsh_arithm_expr *expr); struct mrsh_arithm_binop *mrsh_arithm_expr_get_binop( const struct mrsh_arithm_expr *expr); struct mrsh_arithm_cond *mrsh_arithm_expr_get_cond( const struct mrsh_arithm_expr *expr); struct mrsh_arithm_assign *mrsh_arithm_expr_get_assign( const struct mrsh_arithm_expr *expr); #endif ================================================ FILE: include/mrsh/array.h ================================================ #ifndef MRSH_ARRAY_H #define MRSH_ARRAY_H #include #include #include struct mrsh_array { void **data; size_t len, cap; }; bool mrsh_array_reserve(struct mrsh_array *array, size_t size); ssize_t mrsh_array_add(struct mrsh_array *array, void *value); void mrsh_array_finish(struct mrsh_array *array); #endif ================================================ FILE: include/mrsh/ast.h ================================================ #ifndef MRSH_AST_H #define MRSH_AST_H #include #include /** * Position describes an arbitrary source position including line and column * location. */ struct mrsh_position { size_t offset; // starting at 0 int line; // starting at 1 int column; // starting at 1 }; /** * Range describes a continuous source region. It has a beginning position and * a non-included ending position. */ struct mrsh_range { struct mrsh_position begin, end; }; enum mrsh_node_type { MRSH_NODE_PROGRAM, MRSH_NODE_COMMAND_LIST, MRSH_NODE_AND_OR_LIST, MRSH_NODE_COMMAND, MRSH_NODE_WORD, }; struct mrsh_node { enum mrsh_node_type type; }; enum mrsh_word_type { MRSH_WORD_STRING, MRSH_WORD_PARAMETER, MRSH_WORD_COMMAND, MRSH_WORD_ARITHMETIC, MRSH_WORD_LIST, }; /** * A word can be: * - An unquoted or a single-quoted string * - A candidate for parameter expansion * - A candidate for command substitution * - A candidate for arithmetic expansion * - An unquoted or a double-quoted list of words */ struct mrsh_word { struct mrsh_node node; enum mrsh_word_type type; }; /** * A string word is a type of word. It can be unquoted or single-quoted. */ struct mrsh_word_string { struct mrsh_word word; char *str; bool single_quoted; // true if candidate for field splitting (ie. result of parameter // expansion, command substitution or arithmetic expansion) bool split_fields; struct mrsh_range range; }; enum mrsh_word_parameter_op { MRSH_PARAM_NONE, // `$name` or `${parameter}`, no-op MRSH_PARAM_MINUS, // `${parameter:-[word]}`, Use Default Values MRSH_PARAM_EQUAL, // `${parameter:=[word]}`, Assign Default Values MRSH_PARAM_QMARK, // `${parameter:?[word]}`, Indicate Error if Null or Unset MRSH_PARAM_PLUS, // `${parameter:+[word]}`, Use Alternative Value MRSH_PARAM_LEADING_HASH, // `${#parameter}`, String Length MRSH_PARAM_PERCENT, // `${parameter%[word]}`, Remove Smallest Suffix Pattern MRSH_PARAM_DPERCENT, // `${parameter%%[word]}`, Remove Largest Suffix Pattern MRSH_PARAM_HASH, // `${parameter#[word]}`, Remove Smallest Prefix Pattern MRSH_PARAM_DHASH, // `${parameter##[word]}`, Remove Largest Prefix Pattern }; /** * A word parameter is a type of word candidate for parameter expansion. The * format is either `$name` or `${expression}`. */ struct mrsh_word_parameter { struct mrsh_word word; char *name; enum mrsh_word_parameter_op op; bool colon; // only for -, =, ?, + struct mrsh_word *arg; // can be NULL struct mrsh_position dollar_pos; struct mrsh_range name_range; struct mrsh_range op_range; // can be invalid struct mrsh_position lbrace_pos, rbrace_pos; // can be invalid }; /** * A word command is a type of word candidate for command substitution. The * format is either `` `command` `` or `$(command)`. */ struct mrsh_word_command { struct mrsh_word word; struct mrsh_program *program; // can be NULL bool back_quoted; struct mrsh_range range; }; /** * An arithmetic word is a type of word containing an arithmetic expression. The * format is `$((expression))`. */ struct mrsh_word_arithmetic { struct mrsh_word word; struct mrsh_word *body; }; /** * A word list is a type of word. It can be unquoted or double-quoted. Its * children are _not_ separated by blanks. Here's an example: * * abc"d ef"g'h i' */ struct mrsh_word_list { struct mrsh_word word; struct mrsh_array children; // struct mrsh_word * bool double_quoted; struct mrsh_position lquote_pos, rquote_pos; // can be invalid }; enum mrsh_io_redirect_op { MRSH_IO_LESS, // < MRSH_IO_GREAT, // > MRSH_IO_CLOBBER, // >| MRSH_IO_DGREAT, // >> MRSH_IO_LESSAND, // <& MRSH_IO_GREATAND, // >& MRSH_IO_LESSGREAT, // <> MRSH_IO_DLESS, // << MRSH_IO_DLESSDASH, // <<- }; /** * An IO redirection operator. The format is: `[io_number]op name`. */ struct mrsh_io_redirect { int io_number; // -1 if unspecified enum mrsh_io_redirect_op op; struct mrsh_word *name; // filename or here-document delimiter struct mrsh_array here_document; // struct mrsh_word *, only for << and <<- struct mrsh_position io_number_pos; // can be invalid struct mrsh_range op_range; }; /** * A variable assignment. The format is: `name=value`. */ struct mrsh_assignment { char *name; struct mrsh_word *value; struct mrsh_range name_range; struct mrsh_position equal_pos; }; enum mrsh_command_type { MRSH_SIMPLE_COMMAND, MRSH_BRACE_GROUP, MRSH_SUBSHELL, MRSH_IF_CLAUSE, MRSH_FOR_CLAUSE, MRSH_LOOP_CLAUSE, // `while` or `until` MRSH_CASE_CLAUSE, MRSH_FUNCTION_DEFINITION, }; /** * A command. It is either a simple command, a brace group or an if clause. */ struct mrsh_command { struct mrsh_node node; enum mrsh_command_type type; }; /** * A simple command is a type of command. It contains a command name, followed * by command arguments. It can also contain IO redirections and variable * assignments. */ struct mrsh_simple_command { struct mrsh_command command; struct mrsh_word *name; // can be NULL if it contains only assignments struct mrsh_array arguments; // struct mrsh_word * struct mrsh_array io_redirects; // struct mrsh_io_redirect * struct mrsh_array assignments; // struct mrsh_assignment * }; /** * A brace group is a type of command. It contains command lists and executes * them in the current process environment. The format is: * `{ compound-list ; }`. */ struct mrsh_brace_group { struct mrsh_command command; struct mrsh_array body; // struct mrsh_command_list * struct mrsh_position lbrace_pos, rbrace_pos; }; /** * A subshell is a type of command. It contains command lists and executes * them in a subshell environment. The format is: `( compound-list )`. */ struct mrsh_subshell { struct mrsh_command command; struct mrsh_array body; // struct mrsh_command_list * struct mrsh_position lparen_pos, rparen_pos; }; /** * An if clause is a type of command. The format is: * * if compound-list * then * compound-list * [elif compound-list * then * compound-list] ... * [else * compound-list] * fi */ struct mrsh_if_clause { struct mrsh_command command; struct mrsh_array condition; // struct mrsh_command_list * struct mrsh_array body; // struct mrsh_command_list * struct mrsh_command *else_part; // can be NULL struct mrsh_range if_range; // for `if` or `elif` struct mrsh_range then_range, fi_range; struct mrsh_range else_range; // can be invalid }; /** * A for clause is a type of command. The format is: * * for name [ in [word ... ]] * do * compound-list * done */ struct mrsh_for_clause { struct mrsh_command command; char *name; bool in; struct mrsh_array word_list; // struct mrsh_word * struct mrsh_array body; // struct mrsh_command_list * struct mrsh_range for_range, name_range, do_range, done_range; struct mrsh_range in_range; // can be invalid }; enum mrsh_loop_type { MRSH_LOOP_WHILE, MRSH_LOOP_UNTIL, }; /** * A loop clause is a type of command. The format is: * * while/until compound-list-1 * do * compound-list-2 * done */ struct mrsh_loop_clause { struct mrsh_command command; enum mrsh_loop_type type; struct mrsh_array condition; // struct mrsh_command_list * struct mrsh_array body; // struct mrsh_command_list * struct mrsh_range while_until_range; // for `while` or `until` struct mrsh_range do_range, done_range; }; /** * A case item contains one or more patterns with a body. The format is: * * [(] pattern[ | pattern] ... ) compound-list ;; * * The double-semicolumn is optional if it's the last item. */ struct mrsh_case_item { struct mrsh_array patterns; // struct mrsh_word * struct mrsh_array body; // struct mrsh_command_list * struct mrsh_position lparen_pos; // can be invalid // TODO: pipe positions between each pattern struct mrsh_position rparen_pos; struct mrsh_range dsemi_range; // can be invalid }; /** * A case clause is a type of command. The format is: * * case word in * [(] pattern1 ) compound-list ;; * [[(] pattern[ | pattern] ... ) compound-list ;;] ... * [[(] pattern[ | pattern] ... ) compound-list] * esac */ struct mrsh_case_clause { struct mrsh_command command; struct mrsh_word *word; struct mrsh_array items; // struct mrsh_case_item * struct mrsh_range case_range, in_range, esac_range; }; /** * A function definition is a type of command. The format is: * * fname ( ) compound-command [io-redirect ...] */ struct mrsh_function_definition { struct mrsh_command command; char *name; struct mrsh_command *body; struct mrsh_array io_redirects; // struct mrsh_io_redirect * struct mrsh_range name_range; struct mrsh_position lparen_pos, rparen_pos; }; enum mrsh_and_or_list_type { MRSH_AND_OR_LIST_PIPELINE, MRSH_AND_OR_LIST_BINOP, }; /** * An AND-OR list is a tree of pipelines and binary operations. */ struct mrsh_and_or_list { struct mrsh_node node; enum mrsh_and_or_list_type type; }; /** * A pipeline is a type of AND-OR list which consists of multiple commands * separated by `|`. The format is: `[!] command1 [ | command2 ...]`. */ struct mrsh_pipeline { struct mrsh_and_or_list and_or_list; struct mrsh_array commands; // struct mrsh_command * bool bang; // whether the pipeline begins with `!` struct mrsh_position bang_pos; // can be invalid // TODO: pipe positions between each command }; enum mrsh_binop_type { MRSH_BINOP_AND, // `&&` MRSH_BINOP_OR, // `||` }; /** * A binary operation is a type of AND-OR list which consists of multiple * pipelines separated by `&&` or `||`. */ struct mrsh_binop { struct mrsh_and_or_list and_or_list; enum mrsh_binop_type type; struct mrsh_and_or_list *left, *right; struct mrsh_range op_range; }; /** * A command list contains AND-OR lists separated by `;` (for sequential * execution) or `&` (for asynchronous execution). */ struct mrsh_command_list { struct mrsh_node node; struct mrsh_and_or_list *and_or_list; bool ampersand; // whether the command list ends with `&` struct mrsh_position separator_pos; // can be invalid }; /** * A shell program. It contains command lists. */ struct mrsh_program { struct mrsh_node node; struct mrsh_array body; // struct mrsh_command_list * }; typedef void (*mrsh_node_iterator_func)(struct mrsh_node *node, void *user_data); bool mrsh_position_valid(const struct mrsh_position *pos); bool mrsh_range_valid(const struct mrsh_range *range); struct mrsh_word_string *mrsh_word_string_create(char *str, bool single_quoted); struct mrsh_word_parameter *mrsh_word_parameter_create(char *name, enum mrsh_word_parameter_op op, bool colon, struct mrsh_word *arg); struct mrsh_word_command *mrsh_word_command_create(struct mrsh_program *prog, bool back_quoted); struct mrsh_word_arithmetic *mrsh_word_arithmetic_create( struct mrsh_word *body); struct mrsh_word_list *mrsh_word_list_create(struct mrsh_array *children, bool double_quoted); struct mrsh_simple_command *mrsh_simple_command_create(struct mrsh_word *name, struct mrsh_array *arguments, struct mrsh_array *io_redirects, struct mrsh_array *assignments); struct mrsh_brace_group *mrsh_brace_group_create(struct mrsh_array *body); struct mrsh_subshell *mrsh_subshell_create(struct mrsh_array *body); struct mrsh_if_clause *mrsh_if_clause_create(struct mrsh_array *condition, struct mrsh_array *body, struct mrsh_command *else_part); struct mrsh_for_clause *mrsh_for_clause_create(char *name, bool in, struct mrsh_array *word_list, struct mrsh_array *body); struct mrsh_loop_clause *mrsh_loop_clause_create(enum mrsh_loop_type type, struct mrsh_array *condition, struct mrsh_array *body); struct mrsh_case_clause *mrsh_case_clause_create(struct mrsh_word *word, struct mrsh_array *items); struct mrsh_function_definition *mrsh_function_definition_create(char *name, struct mrsh_command *body, struct mrsh_array *io_redirects); struct mrsh_pipeline *mrsh_pipeline_create(struct mrsh_array *commands, bool bang); struct mrsh_binop *mrsh_binop_create(enum mrsh_binop_type type, struct mrsh_and_or_list *left, struct mrsh_and_or_list *right); struct mrsh_command_list *mrsh_command_list_create(void); struct mrsh_program *mrsh_program_create(void); void mrsh_node_destroy(struct mrsh_node *node); void mrsh_word_destroy(struct mrsh_word *word); void mrsh_io_redirect_destroy(struct mrsh_io_redirect *redir); void mrsh_assignment_destroy(struct mrsh_assignment *assign); void mrsh_command_destroy(struct mrsh_command *cmd); void mrsh_and_or_list_destroy(struct mrsh_and_or_list *and_or_list); void mrsh_command_list_destroy(struct mrsh_command_list *l); void mrsh_program_destroy(struct mrsh_program *prog); struct mrsh_word *mrsh_node_get_word(const struct mrsh_node *node); struct mrsh_command *mrsh_node_get_command(const struct mrsh_node *node); struct mrsh_and_or_list *mrsh_node_get_and_or_list( const struct mrsh_node *node); struct mrsh_command_list *mrsh_node_get_command_list( const struct mrsh_node *node); struct mrsh_program *mrsh_node_get_program(const struct mrsh_node *node); struct mrsh_word_string *mrsh_word_get_string(const struct mrsh_word *word); struct mrsh_word_parameter *mrsh_word_get_parameter( const struct mrsh_word *word); struct mrsh_word_command *mrsh_word_get_command(const struct mrsh_word *word); struct mrsh_word_arithmetic *mrsh_word_get_arithmetic( const struct mrsh_word *word); struct mrsh_word_list *mrsh_word_get_list(const struct mrsh_word *word); struct mrsh_simple_command *mrsh_command_get_simple_command( const struct mrsh_command *cmd); struct mrsh_brace_group *mrsh_command_get_brace_group( const struct mrsh_command *cmd); struct mrsh_subshell *mrsh_command_get_subshell( const struct mrsh_command *cmd); struct mrsh_if_clause *mrsh_command_get_if_clause( const struct mrsh_command *cmd); struct mrsh_for_clause *mrsh_command_get_for_clause( const struct mrsh_command *cmd); struct mrsh_loop_clause *mrsh_command_get_loop_clause( const struct mrsh_command *cmd); struct mrsh_case_clause *mrsh_command_get_case_clause( const struct mrsh_command *cmd); struct mrsh_function_definition *mrsh_command_get_function_definition( const struct mrsh_command *cmd); struct mrsh_pipeline *mrsh_and_or_list_get_pipeline( const struct mrsh_and_or_list *and_or_list); struct mrsh_binop *mrsh_and_or_list_get_binop( const struct mrsh_and_or_list *and_or_list); void mrsh_node_for_each(struct mrsh_node *node, mrsh_node_iterator_func iterator, void *user_data); void mrsh_word_range(struct mrsh_word *word, struct mrsh_position *begin, struct mrsh_position *end); void mrsh_command_range(struct mrsh_command *cmd, struct mrsh_position *begin, struct mrsh_position *end); char *mrsh_word_str(const struct mrsh_word *word); char *mrsh_node_format(struct mrsh_node *node); void mrsh_program_print(struct mrsh_program *prog); struct mrsh_node *mrsh_node_copy(const struct mrsh_node *node); struct mrsh_word *mrsh_word_copy(const struct mrsh_word *word); struct mrsh_io_redirect *mrsh_io_redirect_copy( const struct mrsh_io_redirect *redir); struct mrsh_assignment *mrsh_assignment_copy( const struct mrsh_assignment *assign); struct mrsh_command *mrsh_command_copy(const struct mrsh_command *cmd); struct mrsh_and_or_list *mrsh_and_or_list_copy( const struct mrsh_and_or_list *and_or_list); struct mrsh_command_list *mrsh_command_list_copy( const struct mrsh_command_list *l); struct mrsh_program *mrsh_program_copy(const struct mrsh_program *prog); #endif ================================================ FILE: include/mrsh/buffer.h ================================================ #ifndef MRSH_BUFFER_H #define MRSH_BUFFER_H #include #include struct mrsh_buffer { char *data; size_t len, cap; }; /** * Makes sure at least `size` bytes can be written to the buffer, without * increasing its length. Returns a pointer to the end of the buffer, or NULL if * resizing fails. Callers are responsible for manually increasing `buf->len`. * * This function is useful when e.g. reading from a file. Example with error * handling left out: * * char *dst = mrsh_buffer_reserve(buf, READ_SIZE); * ssize_t n = read(fd, dst, READ_SIZE); * buf->len += n; */ char *mrsh_buffer_reserve(struct mrsh_buffer *buf, size_t size); /** * Increases the length of the buffer by `size` bytes. Returns a pointer to the * beginning of the newly appended space, or NULL if resizing fails. */ char *mrsh_buffer_add(struct mrsh_buffer *buf, size_t size); bool mrsh_buffer_append(struct mrsh_buffer *buf, const char *data, size_t size); bool mrsh_buffer_append_char(struct mrsh_buffer *buf, char c); /** * Get the buffer's current data and reset it. */ char *mrsh_buffer_steal(struct mrsh_buffer *buf); void mrsh_buffer_finish(struct mrsh_buffer *buf); #endif ================================================ FILE: include/mrsh/builtin.h ================================================ #ifndef MRSH_BUILTIN_H #define MRSH_BUILTIN_H #include bool mrsh_has_builtin(const char *name); bool mrsh_has_special_builtin(const char *name); int mrsh_run_builtin(struct mrsh_state *state, int argc, char *argv[]); struct mrsh_init_args { const char *command_file; const char *command_str; }; int mrsh_process_args(struct mrsh_state *state, struct mrsh_init_args *args, int argc, char *argv[]); #endif ================================================ FILE: include/mrsh/entry.h ================================================ #ifndef MRSH_ENTRY_H #define MRSH_ENTRY_H #include #include /** * Expands $PS1 or returns the POSIX-specified default of "$" or "#". The caller * must free the return value. */ char *mrsh_get_ps1(struct mrsh_state *state, int next_history_id); /** * Expands $PS2 or returns the POSIX-specified default of ">". The caller must * free the return value. */ char *mrsh_get_ps2(struct mrsh_state *state); /** * Expands $PS4 or returns the POSIX-specified default of "+ ". The caller must * free the return value. */ char *mrsh_get_ps4(struct mrsh_state *state); /** * Copies variables from the environment and sets up internal variables like * IFS, PPID, PWD, etc. */ bool mrsh_populate_env(struct mrsh_state *state, char **environ); /** * Sources /etc/profile and $HOME/.profile. Note that this behavior is not * specified by POSIX. It is recommended to call this file in login shells * (for which argv[0][0] == '-' by convention). */ void mrsh_source_profile(struct mrsh_state *state); /** Sources $ENV. It is recommended to source this in interactive shells. */ void mrsh_source_env(struct mrsh_state *state); /** * Run the trap registered on EXIT. It is recommended to call this function * right before exiting the shell. */ bool mrsh_run_exit_trap(struct mrsh_state *state); #endif ================================================ FILE: include/mrsh/hashtable.h ================================================ #ifndef MRSH_HASHTABLE_H #define MRSH_HASHTABLE_H #define MRSH_HASHTABLE_BUCKETS 256 struct mrsh_hashtable_entry { struct mrsh_hashtable_entry *next; unsigned int hash; char *key; void *value; }; struct mrsh_hashtable { struct mrsh_hashtable_entry *buckets[MRSH_HASHTABLE_BUCKETS]; }; typedef void (*mrsh_hashtable_iterator_func)(const char *key, void *value, void *user_data); void mrsh_hashtable_finish(struct mrsh_hashtable *table); void *mrsh_hashtable_get(struct mrsh_hashtable *table, const char *key); void *mrsh_hashtable_set(struct mrsh_hashtable *table, const char *key, void *value); void *mrsh_hashtable_del(struct mrsh_hashtable *table, const char *key); /** * Calls `iterator` for each (key, value) pair in the hash table. It is safe to * call `mrsh_hashtable_del` on the current element, however it is not safe to * do so an any other element. */ void mrsh_hashtable_for_each(struct mrsh_hashtable *table, mrsh_hashtable_iterator_func iterator, void *user_data); #endif ================================================ FILE: include/mrsh/parser.h ================================================ #ifndef MRSH_PARSER_H #define MRSH_PARSER_H #include #include struct mrsh_parser; struct mrsh_buffer; /** * An alias callback. The alias named is given as a parameter and the alias * value should be returned. NULL should be returned if the alias doesn't exist. */ typedef const char *(*mrsh_parser_alias_func)(const char *name, void *user_data); /** * Create a parser from a file descriptor. */ struct mrsh_parser *mrsh_parser_with_fd(int fd); /** * Create a parser from a static buffer. */ struct mrsh_parser *mrsh_parser_with_data(const char *buf, size_t len); /** * Create a parser with a shared buffer. Data will be read from `buf` each time * the parser needs input data. */ struct mrsh_parser *mrsh_parser_with_buffer(struct mrsh_buffer *buf); void mrsh_parser_destroy(struct mrsh_parser *parser); /** * Parse a complete multi-line program. */ struct mrsh_program *mrsh_parse_program(struct mrsh_parser *parser); /** * Parse a program line. Continuation lines are consumed. */ struct mrsh_program *mrsh_parse_line(struct mrsh_parser *parser); /** * Parse an arithmetic expression. */ struct mrsh_arithm_expr *mrsh_parse_arithm_expr(struct mrsh_parser *parser); /** * Check if the input has been completely consumed. */ bool mrsh_parser_eof(struct mrsh_parser *parser); /** * Set the alias callback. */ void mrsh_parser_set_alias_func(struct mrsh_parser *parser, mrsh_parser_alias_func alias, void *user_data); /** * Check if the parser ended with a syntax error. The error message is returned. * The error position can optionally be obtained. */ const char *mrsh_parser_error(struct mrsh_parser *parser, struct mrsh_position *pos); /** * Check if the input ends on a continuation line. */ bool mrsh_parser_continuation_line(struct mrsh_parser *parser); /** * Reset the parser state. */ void mrsh_parser_reset(struct mrsh_parser *parser); #endif ================================================ FILE: include/mrsh/shell.h ================================================ #ifndef MRSH_SHELL_H #define MRSH_SHELL_H #include #include #include #include #include enum mrsh_option { // -a: When this option is on, the export attribute shall be set for each // variable to which an assignment is performed. MRSH_OPT_ALLEXPORT = 1 << 0, // -b: Shall cause the shell to notify the user asynchronously of background // job completions. MRSH_OPT_NOTIFY = 1 << 1, // -C: Prevent existing files from being overwritten by the shell's '>' // redirection operator; the ">|" redirection operator shall override this // noclobber option for an individual file. MRSH_OPT_NOCLOBBER = 1 << 2, // -e: When this option is on, when any command fails (for any of the // reasons listed in Consequences of Shell Errors or by returning an exit // status greater than zero), the shell immediately shall exit MRSH_OPT_ERREXIT = 1 << 3, // -f: The shell shall disable pathname expansion. MRSH_OPT_NOGLOB = 1 << 4, // -h: Locate and remember utilities invoked by functions as those functions // are defined (the utilities are normally located when the function is // executed). MRSH_OPT_PRELOOKUP = 1 << 5, // -m: All jobs shall be run in their own process groups. Immediately before // the shell issues a prompt after completion of the background job, a // message reporting the exit status of the background job shall be written // to standard error. If a foreground job stops, the shell shall write a // message to standard error to that effect, formatted as described by the // jobs utility. MRSH_OPT_MONITOR = 1 << 6, // -n: The shell shall read commands but does not execute them; this can be // used to check for shell script syntax errors. An interactive shell may // ignore this option. MRSH_OPT_NOEXEC = 1 << 7, // -o ignoreeof: Prevent an interactive shell from exiting on end-of-file. MRSH_OPT_IGNOREEOF = 1 << 8, // -o nolog: Prevent the entry of function definitions into the command // history MRSH_OPT_NOLOG = 1 << 9, // -o vi: Allow shell command line editing using the built-in vi editor. MRSH_OPT_VI = 1 << 10, // -u: When the shell tries to expand an unset parameter other than the '@' // and '*' special parameters, it shall write a message to standard error // and the expansion shall fail. MRSH_OPT_NOUNSET = 1 << 11, // -v: The shell shall write its input to standard error as it is read. MRSH_OPT_VERBOSE = 1 << 12, // -x: The shell shall write to standard error a trace for each command // after it expands the command and before it executes it. MRSH_OPT_XTRACE = 1 << 13, }; enum mrsh_variable_attrib { MRSH_VAR_ATTRIB_NONE = 0, MRSH_VAR_ATTRIB_EXPORT = 1 << 0, MRSH_VAR_ATTRIB_READONLY = 1 << 1, }; struct mrsh_call_frame { char **argv; int argc; struct mrsh_call_frame *prev; }; struct mrsh_state { int exit; uint32_t options; // enum mrsh_option struct mrsh_call_frame *frame; // last call frame bool interactive; int last_status; }; struct mrsh_parser; struct mrsh_state *mrsh_state_create(void); void mrsh_state_destroy(struct mrsh_state *state); void mrsh_state_set_parser_alias_func( struct mrsh_state *state, struct mrsh_parser *parser); void mrsh_env_set(struct mrsh_state *state, const char *key, const char *value, uint32_t attribs); void mrsh_env_unset(struct mrsh_state *state, const char *key); const char *mrsh_env_get(struct mrsh_state *state, const char *key, uint32_t *attribs); int mrsh_run_program(struct mrsh_state *state, struct mrsh_program *prog); int mrsh_run_word(struct mrsh_state *state, struct mrsh_word **word); bool mrsh_run_arithm_expr(struct mrsh_state *state, struct mrsh_arithm_expr *expr, long *result); /** * Enable or disable job control. This will setup signal handlers, process * groups and the terminal accordingly. */ bool mrsh_set_job_control(struct mrsh_state *state, bool enabled); /** * Destroy terminated jobs and print job notifications. This function should be * called after mrsh_run_program. */ void mrsh_destroy_terminated_jobs(struct mrsh_state *state); #endif ================================================ FILE: include/mrsh_getopt.h ================================================ #ifndef MRSH_GETOPT_H #define MRSH_GETOPT_H extern char *_mrsh_optarg; extern int _mrsh_opterr, _mrsh_optind, _mrsh_optopt; int _mrsh_getopt(int argc, char * const argv[], const char *optstring); #endif ================================================ FILE: include/parser.h ================================================ #ifndef PARSER_H #define PARSER_H #include #include #include enum symbol_name { EOF_TOKEN, TOKEN, NEWLINE, // The following are the operators (see XBD Operator) containing more than // one character. AND_IF, OR_IF, DSEMI, DLESS, DGREAT, LESSAND, GREATAND, LESSGREAT, DLESSDASH, CLOBBER, }; struct symbol { enum symbol_name name; char *str; }; extern const struct symbol operators[]; extern const size_t operators_len; extern const size_t operators_max_str_len; extern const char *keywords[]; extern const size_t keywords_len; struct mrsh_parser { int fd; // can be -1 struct mrsh_buffer *in_buf; // can be NULL bool eof; struct mrsh_buffer buf; // internal read buffer struct mrsh_position pos; struct { char *msg; struct mrsh_position pos; } error; bool has_sym; enum symbol_name sym; struct mrsh_array here_documents; bool continuation_line; mrsh_parser_alias_func alias; void *alias_user_data; int arith_nested_parens; }; typedef struct mrsh_word *(*word_func)(struct mrsh_parser *parser, char end); size_t parser_peek(struct mrsh_parser *parser, char *buf, size_t size); char parser_peek_char(struct mrsh_parser *parser); size_t parser_read(struct mrsh_parser *parser, char *buf, size_t size); char parser_read_char(struct mrsh_parser *parser); bool token(struct mrsh_parser *parser, const char *str, struct mrsh_range *range); bool expect_token(struct mrsh_parser *parser, const char *str, struct mrsh_range *range); char *read_token(struct mrsh_parser *parser, size_t len, struct mrsh_range *range); void read_continuation_line(struct mrsh_parser *parser); void parser_set_error(struct mrsh_parser *parser, const char *msg); void parser_begin(struct mrsh_parser *parser); bool is_operator_start(char c); enum symbol_name get_symbol(struct mrsh_parser *parser); /** * Invalidates the current symbol. Should be used each time manual * parser_read calls are performed. */ void consume_symbol(struct mrsh_parser *parser); bool symbol(struct mrsh_parser *parser, enum symbol_name sym); bool eof(struct mrsh_parser *parser); bool newline(struct mrsh_parser *parser); void linebreak(struct mrsh_parser *parser); bool newline_list(struct mrsh_parser *parser); size_t peek_name(struct mrsh_parser *parser, bool in_braces); size_t peek_word(struct mrsh_parser *parser, char end); struct mrsh_word *expect_dollar(struct mrsh_parser *parser); struct mrsh_word *back_quotes(struct mrsh_parser *parser); struct mrsh_word *word(struct mrsh_parser *parser, char end); struct mrsh_word *arithmetic_word(struct mrsh_parser *parser, char end); struct mrsh_word *parameter_expansion_word(struct mrsh_parser *parser); #endif ================================================ FILE: include/shell/job.h ================================================ #ifndef SHELL_JOB_H #define SHELL_JOB_H #include #include #include #include struct mrsh_node; struct mrsh_state; struct mrsh_process; /** * A job is a set of processes, comprising a shell pipeline, and any processes * descended from it, that are all in the same process group. * * In practice, a single job is also created when executing an asynchronous * command list. * * This object is guaranteed to be valid until either: * - The job terminates * - The shell is destroyed */ struct mrsh_job { struct mrsh_node *node; pid_t pgid; int job_id; struct termios term_modes; // only valid if stopped struct mrsh_state *state; struct mrsh_array processes; // struct mrsh_process * bool pending_notification; // need to print a job status notification int last_status; }; /** * Create a new job. It will start in the background by default. */ struct mrsh_job *job_create(struct mrsh_state *state, const struct mrsh_node *node); void job_destroy(struct mrsh_job *job); /** * Add a process to the job. This puts the process into the job's process * group. This has to be done both in the parent and in the child to prevent * race conditions. * * If the job doesn't have a process group (because it's empty), then a new * process group is created. */ void job_add_process(struct mrsh_job *job, struct mrsh_process *proc); /** * Polls the job's current status without blocking. Returns: * - TASK_STATUS_WAIT if the job is running (ie. one or more processes are * running) * - TASK_STATUS_STOPPED if the job is stopped (ie. one or more processes are * stopped, all the others are terminated) * - An integer >= 0 if the job has terminated (ie. all processes have * terminated) */ int job_poll(struct mrsh_job *job); /** * Wait for the completion of the job. */ int job_wait(struct mrsh_job *job); /** * Wait for the completion of the process. */ int job_wait_process(struct mrsh_process *proc); /** * Put the job in the foreground or in the background. If the job is stopped and * cont is set to true, it will be continued. * * It is illegal to put a job in the foreground if another job is already in the * foreground. */ bool job_set_foreground(struct mrsh_job *job, bool foreground, bool cont); /** * Initialize a child process state. */ bool init_job_child_process(struct mrsh_state *state); /** * Refreshes status for all jobs. */ bool refresh_jobs_status(struct mrsh_state *state); /** * Look up a job by its XBD Job Control Job ID. * * When using this to look up jobs internally, set interactive to false. This * suppresses error reporting. */ struct mrsh_job *job_by_id(struct mrsh_state *state, const char *id, bool interactive); /** * Return a string describing the process' state. `r` is a random boolean. */ const char *job_state_str(struct mrsh_job *job, bool r); /** * Send SIGHUP to all running jobs. */ void broadcast_sighup_to_jobs(struct mrsh_state *state); #endif ================================================ FILE: include/shell/path.h ================================================ #ifndef SHELL_PATH_H #define SHELL_PATH_H #include #include /* Searches $PATH for the requested file and returns it if found. If exec is * true, it will require the file to be executable in order to be considered a * match. If default_path is true, the system's default search path will be * used instead of the $PATH variable. Fully qualified paths are returned * as-is. */ char *expand_path(struct mrsh_state *state, const char *file, bool exec, bool default_path); /* Like getcwd, but returns allocated memory */ char *current_working_dir(void); #endif ================================================ FILE: include/shell/process.h ================================================ #ifndef SHELL_PROCESS_H #define SHELL_PROCESS_H #include #include #include /** * This struct is used to track child processes. * * This object is guaranteed to be valid until either: * - The process terminates * - The shell is destroyed */ struct mrsh_process { pid_t pid; struct mrsh_state *state; bool stopped; bool terminated; int stat; // only valid if terminated int signal; // only valid if stopped is true }; /** * Register a new process. */ struct mrsh_process *process_create(struct mrsh_state *state, pid_t pid); void process_destroy(struct mrsh_process *process); /** * Polls the process' current status without blocking. Returns: * - An integer >= 0 if the process has terminated * - TASK_STATUS_STOPPED if the process is stopped * - TASK_STATUS_WAIT if the process is running */ int process_poll(struct mrsh_process *process); /** * Update the shell's state with a child process status. */ void update_process(struct mrsh_state *state, pid_t pid, int stat); #endif ================================================ FILE: include/shell/redir.h ================================================ #ifndef SHELL_REDIR_H #define SHELL_REDIR_H #include int process_redir(const struct mrsh_io_redirect *redir, int *redir_fd); #endif ================================================ FILE: include/shell/shell.h ================================================ #ifndef SHELL_SHELL_H #define SHELL_SHELL_H #include #include #include "job.h" #include "process.h" #include "shell/trap.h" struct mrsh_variable { char *value; uint32_t attribs; // enum mrsh_variable_attrib }; struct mrsh_function { struct mrsh_command *body; }; enum mrsh_branch_control { MRSH_BRANCH_BREAK, MRSH_BRANCH_CONTINUE, MRSH_BRANCH_RETURN, MRSH_BRANCH_EXIT, }; struct mrsh_call_frame_priv { struct mrsh_call_frame pub; enum mrsh_branch_control branch_control; int nloops; }; struct mrsh_trap { bool set; enum mrsh_trap_action action; struct mrsh_program *program; }; struct mrsh_state_priv { struct mrsh_state pub; int term_fd; struct mrsh_array processes; struct mrsh_hashtable aliases; // char * struct mrsh_hashtable variables; // struct mrsh_variable * struct mrsh_hashtable functions; // struct mrsh_function * bool job_control; pid_t pgid; struct termios term_modes; struct mrsh_array jobs; // struct mrsh_job * struct mrsh_job *foreground_job; struct mrsh_trap traps[MRSH_NSIG]; // TODO: move this to context bool child; // true if we're not the main shell process }; /** * A context holds state information and per-job information. A context is * guaranteed to be shared between all members of a job. */ struct mrsh_context { struct mrsh_state *state; // When executing a pipeline, this is set to the job created for the // pipeline struct mrsh_job *job; // When executing an asynchronous list, this is set to true bool background; }; void function_destroy(struct mrsh_function *fn); struct mrsh_call_frame_priv *call_frame_get_priv(struct mrsh_call_frame *frame); struct mrsh_state_priv *state_get_priv(struct mrsh_state *state); void push_frame(struct mrsh_state *state, int argc, const char *argv[]); void pop_frame(struct mrsh_state *state); #endif ================================================ FILE: include/shell/task.h ================================================ #ifndef SHELL_TASK_H #define SHELL_TASK_H #include "shell/shell.h" #include "shell/word.h" /** * The task is waiting for child processes to finish. */ #define TASK_STATUS_WAIT -1 /** * A fatal error occured, the task should be destroyed. */ #define TASK_STATUS_ERROR -2 /** * The task has been stopped and the job has been put in the background. */ #define TASK_STATUS_STOPPED -3 /** * The task has been interrupted for some reason. */ #define TASK_STATUS_INTERRUPTED -4 struct mrsh_context; /* Perform parameter expansion, command substitution and arithmetic expansion. */ int run_word(struct mrsh_context *ctx, struct mrsh_word **word_ptr); /* Perform all word expansions, as specified in section 2.6. Fills `fields` * with `char *` elements. Not suitable for assignments. */ int expand_word(struct mrsh_context *ctx, const struct mrsh_word *word, struct mrsh_array *fields); int run_simple_command(struct mrsh_context *ctx, struct mrsh_simple_command *sc); int run_command(struct mrsh_context *ctx, struct mrsh_command *cmd); int run_and_or_list(struct mrsh_context *ctx, struct mrsh_and_or_list *and_or_list); int run_pipeline(struct mrsh_context *ctx, struct mrsh_pipeline *pipeline); int run_command_list_array(struct mrsh_context *ctx, struct mrsh_array *array); #endif ================================================ FILE: include/shell/trap.h ================================================ #ifndef SHELL_TRAP_H #define SHELL_TRAP_H #include struct mrsh_state; struct mrsh_program; // TODO: find a POSIX-compatible way to get the max signal value #define MRSH_NSIG 64 enum mrsh_trap_action { MRSH_TRAP_DEFAULT, // SIG_DFL MRSH_TRAP_IGNORE, // SIG_IGN MRSH_TRAP_CATCH, }; bool set_trap(struct mrsh_state *state, int sig, enum mrsh_trap_action action, struct mrsh_program *program); bool set_job_control_traps(struct mrsh_state *state, bool enabled); bool reset_caught_traps(struct mrsh_state *state); bool run_pending_traps(struct mrsh_state *state); bool run_exit_trap(struct mrsh_state *state); #endif ================================================ FILE: include/shell/word.h ================================================ #ifndef SHELL_WORD_H #define SHELL_WORD_H #include /** * Performs tilde expansion. It leaves the word as-is in case of error. */ void expand_tilde(struct mrsh_state *state, struct mrsh_word **word_ptr, bool assignment); /** * Performs field splitting on `word`, writing fields to `fields`. This should * be done after expansions/substitutions. */ void split_fields(struct mrsh_array *fields, const struct mrsh_word *word, const char *ifs); void get_fields_str(struct mrsh_array *strs, const struct mrsh_array *fields); /** * Convert a word to a pattern. Returns NULL if word doesn't contain any * special pattern character (ie. requires an exact match). */ char *word_to_pattern(const struct mrsh_word *word); /** * Performs pathname expansion on each item in `fields`. */ bool expand_pathnames(struct mrsh_array *expanded, const struct mrsh_array *fields); #endif ================================================ FILE: libmrsh.darwin.sym ================================================ # On Darwin, symbols are prefixed with an underscore _mrsh_* ================================================ FILE: libmrsh.gnu.sym ================================================ { global: mrsh_*; local: *; }; ================================================ FILE: main.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include #include #include #include "frontend.h" extern char **environ; int main(int argc, char *argv[]) { struct mrsh_state *state = mrsh_state_create(); struct mrsh_init_args init_args = {0}; if (mrsh_process_args(state, &init_args, argc, argv) != 0) { mrsh_state_destroy(state); return 1; } if (!mrsh_populate_env(state, environ)) { return 1; } struct mrsh_buffer parser_buffer = {0}; struct mrsh_parser *parser; int fd = -1; if (state->interactive) { interactive_init(state); parser = mrsh_parser_with_buffer(&parser_buffer); } else { if (init_args.command_str) { parser = mrsh_parser_with_data(init_args.command_str, strlen(init_args.command_str)); } else { if (init_args.command_file) { fd = open(init_args.command_file, O_RDONLY | O_CLOEXEC); if (fd < 0) { fprintf(stderr, "failed to open %s for reading: %s\n", init_args.command_file, strerror(errno)); return 1; } } else { fd = STDIN_FILENO; } parser = mrsh_parser_with_fd(fd); } } mrsh_state_set_parser_alias_func(state, parser); if (state->options & MRSH_OPT_MONITOR) { if (!mrsh_set_job_control(state, true)) { fprintf(stderr, "failed to enable job control\n"); } } if (!(state->options & MRSH_OPT_NOEXEC)) { // If argv[0] begins with `-`, it's a login shell if (state->frame->argv[0][0] == '-') { mrsh_source_profile(state); } if (state->interactive) { mrsh_source_env(state); } } struct mrsh_buffer read_buffer = {0}; while (state->exit == -1) { if (state->interactive) { char *prompt; if (read_buffer.len > 0) { prompt = mrsh_get_ps2(state); } else { // TODO: next_history_id prompt = mrsh_get_ps1(state, 0); } char *line = NULL; size_t n = interactive_next(state, &line, prompt); free(prompt); if (!line) { state->exit = state->last_status; continue; } mrsh_buffer_append(&read_buffer, line, n); free(line); parser_buffer.len = 0; mrsh_buffer_append(&parser_buffer, read_buffer.data, read_buffer.len); mrsh_parser_reset(parser); } struct mrsh_program *prog = mrsh_parse_line(parser); if (state->interactive && mrsh_parser_continuation_line(parser)) { // Nothing to see here } else if (prog == NULL) { struct mrsh_position err_pos; const char *err_msg = mrsh_parser_error(parser, &err_pos); if (err_msg != NULL) { mrsh_buffer_finish(&read_buffer); fprintf(stderr, "%s:%d:%d: syntax error: %s\n", state->frame->argv[0], err_pos.line, err_pos.column, err_msg); if (state->interactive) { continue; } else { state->exit = 1; break; } } else if (mrsh_parser_eof(parser)) { state->exit = state->last_status; break; } else { fprintf(stderr, "unknown error\n"); state->exit = 1; break; } } else { if ((state->options & MRSH_OPT_NOEXEC)) { mrsh_program_print(prog); } else { mrsh_run_program(state, prog); mrsh_destroy_terminated_jobs(state); } mrsh_buffer_finish(&read_buffer); } mrsh_program_destroy(prog); } if (state->interactive) { printf("\n"); } int exit = state->exit; mrsh_run_exit_trap(state); mrsh_buffer_finish(&read_buffer); mrsh_parser_destroy(parser); mrsh_buffer_finish(&parser_buffer); mrsh_state_destroy(state); if (fd >= 0) { close(fd); } return exit; } ================================================ FILE: meson.build ================================================ project( 'mrsh', 'c', version: '0.0.0', license: 'MIT', meson_version: '>=0.47.0', default_options: [ 'c_std=c99', 'warning_level=3', 'werror=true', ], ) cc = meson.get_compiler('c') add_project_arguments(cc.get_supported_arguments([ '-Wundef', '-Wlogical-op', '-Wmissing-include-dirs', '-Wold-style-definition', '-Wpointer-arith', '-Winit-self', '-Wfloat-equal', '-Wstrict-prototypes', '-Wredundant-decls', '-Wimplicit-fallthrough=2', '-Wendif-labels', '-Wstrict-aliasing=2', '-Woverflow', '-Wformat=2', '-Wmissing-prototypes', '-Wno-missing-braces', '-Wno-missing-field-initializers', '-Wno-unused-parameter', '-Wno-unused-result', # fuck you glibc, and gcc, and the little dog, too '-Wno-format-overflow', # causes false positives with gcc ]), language: 'c') if get_option('readline-provider') == 'readline' readline = cc.find_library('readline', required: get_option('readline')) if readline.found() add_project_arguments('-DHAVE_READLINE', language: 'c') endif if cc.has_function('rl_replace_line', prefix: '#include \n #include ', dependencies: [readline]) add_project_arguments('-DHAVE_READLINE_REPLACE_LINE', language: 'c') endif else # editline readline = dependency('libedit', required: get_option('readline')) if readline.found() add_project_arguments('-DHAVE_EDITLINE', language: 'c') endif endif mrsh_inc = include_directories('include') install_subdir('include/mrsh', install_dir: get_option('includedir')) libmrsh_gnu_sym_path = join_paths(meson.current_source_dir(), 'libmrsh.gnu.sym') libmrsh_gnu_sym_ldflag = '-Wl,--version-script=' + libmrsh_gnu_sym_path libmrsh_darwin_sym_path = join_paths(meson.current_source_dir(), 'libmrsh.darwin.sym') # On FreeBSD, -Wl,--version-script only works with -shared if cc.links('', name: '-Wl,--version-script', args: ['-shared', libmrsh_gnu_sym_ldflag]) # GNU ld link_args = [libmrsh_gnu_sym_ldflag] elif host_machine.system() == 'darwin' and cc.has_multi_link_arguments('-Wl,-exported_symbols_list', libmrsh_darwin_sym_path) # Clang on Darwin link_args = ['-Wl,-exported_symbols_list', libmrsh_darwin_sym_path] else error('Linker doesn\'t support --version-script or -exported_symbols_list') endif lib_mrsh = library( meson.project_name(), files( 'arithm.c', 'array.c', 'ast_print.c', 'ast.c', 'buffer.c', 'builtin/alias.c', 'builtin/bg.c', 'builtin/break.c', 'builtin/builtin.c', 'builtin/cd.c', 'builtin/colon.c', 'builtin/command.c', 'builtin/dot.c', 'builtin/eval.c', 'builtin/exec.c', 'builtin/exit.c', 'builtin/export.c', 'builtin/false.c', 'builtin/fg.c', 'builtin/getopts.c', 'builtin/hash.c', 'builtin/jobs.c', 'builtin/pwd.c', 'builtin/read.c', 'builtin/return.c', 'builtin/set.c', 'builtin/shift.c', 'builtin/times.c', 'builtin/trap.c', 'builtin/true.c', 'builtin/type.c', 'builtin/ulimit.c', 'builtin/umask.c', 'builtin/unalias.c', 'builtin/unset.c', 'builtin/unspecified.c', 'builtin/wait.c', 'getopt.c', 'hashtable.c', 'parser/arithm.c', 'parser/parser.c', 'parser/program.c', 'parser/word.c', 'shell/arithm.c', 'shell/entry.c', 'shell/job.c', 'shell/path.c', 'shell/process.c', 'shell/redir.c', 'shell/shell.c', 'shell/task/pipeline.c', 'shell/task/simple_command.c', 'shell/task/task.c', 'shell/task/word.c', 'shell/trap.c', 'shell/word.c', ), include_directories: mrsh_inc, version: meson.project_version(), link_args: link_args, install: true, ) mrsh = declare_dependency( link_with: lib_mrsh, include_directories: mrsh_inc, ) shell_deps = [mrsh] shell_files = [ 'main.c' ] if readline.found() shell_deps += [readline] shell_files += ['frontend/readline.c'] else shell_files += ['frontend/basic.c'] endif mrsh_exe = executable( 'mrsh', files(shell_files), dependencies: shell_deps, install: true, ) subdir('example') subdir('test') pkgconfig = import('pkgconfig') pkgconfig.generate( libraries: lib_mrsh, version: meson.project_version(), filebase: meson.project_name(), name: meson.project_name(), description: 'POSIX shell library', ) status = [ '', 'Features:', ' readline: @0@'.format(readline.found()), ' examples: @0@'.format(get_option('examples')), ] message('\n'.join(status)) ================================================ FILE: meson_options.txt ================================================ option( 'readline', type: 'feature', value: 'auto', description: 'Enable improved interactive interface via readline', ) option( 'readline-provider', type: 'combo', choices: ['readline', 'editline'], value: 'readline', description: 'Provider of the readline library', ) option( 'examples', type: 'boolean', value: true, description: 'Build example programs', ) option( 'reference-shell', type: 'string', value: 'sh', description: 'Reference shell used in tests', ) option( 'test-undefined-behavior', type: 'boolean', value: true, description: 'Run tests that assert undefined behavior fails', ) ================================================ FILE: mkpc ================================================ #!/bin/sh cat < $1 prefix=$PREFIX libdir=\${prefix}/lib includedir=\${prefix}/include Name: mrsh Description: POSIX shell library Version: 0.0.0 Libs: -L\${libdir} -lmrsh Cflags: -I\${includedir} EOF ================================================ FILE: parser/arithm.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include "parser.h" static bool parse_char(struct mrsh_parser *parser, char c) { if (parser_peek_char(parser) != c) { return false; } parser_read_char(parser); return true; } static bool parse_whitespace(struct mrsh_parser *parser) { if (!isspace(parser_peek_char(parser))) { return false; } parser_read_char(parser); return true; } static inline void consume_whitespace(struct mrsh_parser *parser) { while (parse_whitespace(parser)) { // This space is intentionally left blank } } static bool expect_char(struct mrsh_parser *parser, char c) { if (parse_char(parser, c)) { return true; } char msg[128]; snprintf(msg, sizeof(msg), "expected '%c'", c); parser_set_error(parser, msg); return false; } static bool parse_str(struct mrsh_parser *parser, const char *str) { size_t len = strlen(str); for (size_t i = 0; i < len; ++i) { parser_peek(parser, NULL, i + 1); if (parser->buf.data[i] != str[i]) { return false; } } parser_read(parser, NULL, len); return true; } static size_t peek_literal(struct mrsh_parser *parser) { size_t i = 0; while (true) { parser_peek(parser, NULL, i + 1); char c = parser->buf.data[i]; // TODO: 0x, 0b prefixes if (!isdigit(c)) { break; } ++i; } return i; } static struct mrsh_arithm_literal *literal(struct mrsh_parser *parser) { size_t len = peek_literal(parser); if (len == 0) { return NULL; } char *str = strndup(parser->buf.data, len); parser_read(parser, NULL, len); char *end; errno = 0; long value = strtol(str, &end, 0); if (end[0] != '\0' || errno != 0) { free(str); parser_set_error(parser, "failed to parse literal"); return NULL; } free(str); return mrsh_arithm_literal_create(value); } static struct mrsh_arithm_variable *variable(struct mrsh_parser *parser) { size_t name_len = peek_name(parser, false); if (name_len == 0) { return NULL; } char *name = malloc(name_len + 1); parser_read(parser, name, name_len); name[name_len] = '\0'; return mrsh_arithm_variable_create(name); } static struct mrsh_arithm_expr *arithm_expr(struct mrsh_parser *parser); static struct mrsh_arithm_unop *unop(struct mrsh_parser *parser) { enum mrsh_arithm_unop_type type; switch (parser_peek_char(parser)) { case '+': type = MRSH_ARITHM_UNOP_PLUS; break; case '-': type = MRSH_ARITHM_UNOP_MINUS; break; case '~': type = MRSH_ARITHM_UNOP_TILDE; break; case '!': type = MRSH_ARITHM_UNOP_BANG; break; default: return NULL; } parser_read_char(parser); struct mrsh_arithm_expr *body = arithm_expr(parser); if (body == NULL) { parser_set_error(parser, "expected an arithmetic expression after unary operator"); return NULL; } return mrsh_arithm_unop_create(type, body); } static struct mrsh_arithm_expr *paren(struct mrsh_parser *parser) { if (!parse_char(parser, '(')) { return NULL; } consume_whitespace(parser); struct mrsh_arithm_expr *expr = arithm_expr(parser); // consume_whitespace() is not needed here, since the call to arithm_expr() // consumes the trailing whitespace. if (!expect_char(parser, ')')) { mrsh_arithm_expr_destroy(expr); return NULL; } return expr; } static struct mrsh_arithm_expr *term(struct mrsh_parser *parser) { struct mrsh_arithm_expr *expr = paren(parser); if (expr != NULL) { return expr; } struct mrsh_arithm_unop *au = unop(parser); if (au != NULL) { return &au->expr; } struct mrsh_arithm_literal *al = literal(parser); if (al != NULL) { return &al->expr; } struct mrsh_arithm_variable *av = variable(parser); if (av != NULL) { return &av->expr; } return NULL; } static struct mrsh_arithm_expr *factor(struct mrsh_parser *parser) { struct mrsh_arithm_expr *expr = term(parser); if (expr == NULL) { return NULL; } /* This loop ensures we parse factors as left-assossiative */ while (true) { consume_whitespace(parser); enum mrsh_arithm_binop_type type; if (parse_char(parser, '*')) { type = MRSH_ARITHM_BINOP_ASTERISK; } else if (parse_char(parser, '/')) { type = MRSH_ARITHM_BINOP_SLASH; } else if (parse_char(parser, '%')) { type = MRSH_ARITHM_BINOP_PERCENT; } else { return expr; } consume_whitespace(parser); /* Instead of calling ourselves recursively, we call term for * left-associativity */ struct mrsh_arithm_expr *right = term(parser); if (right == NULL) { parser_set_error(parser, "expected a term after *, / or % operator"); return NULL; } struct mrsh_arithm_binop *bo = mrsh_arithm_binop_create(type, expr, right); expr = &bo->expr; } } static struct mrsh_arithm_expr *addend(struct mrsh_parser *parser) { struct mrsh_arithm_expr *expr = factor(parser); if (expr == NULL) { return NULL; } /* This loop ensures we parse addends as left-assossiative */ while (true) { // consume_whitespace() is not needed here, since the call to factor() // consumes trailing whitespace. enum mrsh_arithm_binop_type type; if (parse_char(parser, '+')) { type = MRSH_ARITHM_BINOP_PLUS; } else if (parse_char(parser, '-')) { type = MRSH_ARITHM_BINOP_MINUS; } else { return expr; } consume_whitespace(parser); /* Instead of calling ourselves recursively, we call factor for * left-associativity */ struct mrsh_arithm_expr *right = factor(parser); if (right == NULL) { parser_set_error(parser, "expected a factor after + or - operator"); return NULL; } struct mrsh_arithm_binop *bo = mrsh_arithm_binop_create(type, expr, right); expr = &bo->expr; } } static struct mrsh_arithm_expr *shift(struct mrsh_parser *parser) { struct mrsh_arithm_expr *left = addend(parser); if (left == NULL) { return NULL; } // consume_whitespace() is not needed here, since the call to addend() // consumes the trailing whitespace. enum mrsh_arithm_binop_type type; if (parse_str(parser, "<<")) { type = MRSH_ARITHM_BINOP_DLESS; } else if (parse_str(parser, ">>")) { type = MRSH_ARITHM_BINOP_DGREAT; } else { return left; } consume_whitespace(parser); struct mrsh_arithm_expr *right = shift(parser); if (right == NULL) { mrsh_arithm_expr_destroy(left); parser_set_error(parser, "expected a term"); return NULL; } struct mrsh_arithm_binop *bo = mrsh_arithm_binop_create(type, left, right); return &bo->expr; } static struct mrsh_arithm_expr *comp(struct mrsh_parser *parser) { struct mrsh_arithm_expr *left = shift(parser); if (left == NULL) { return NULL; } enum mrsh_arithm_binop_type type; if (parse_str(parser, "<=")) { type = MRSH_ARITHM_BINOP_LESSEQ; } else if (parse_char(parser, '<')) { type = MRSH_ARITHM_BINOP_LESS; } else if (parse_str(parser, ">=")) { type = MRSH_ARITHM_BINOP_GREATEQ; } else if (parse_char(parser, '>')) { type = MRSH_ARITHM_BINOP_GREAT; } else { return left; } consume_whitespace(parser); struct mrsh_arithm_expr *right = comp(parser); if (right == NULL) { mrsh_arithm_expr_destroy(left); parser_set_error(parser, "expected a term"); return NULL; } struct mrsh_arithm_binop *bo = mrsh_arithm_binop_create(type, left, right); return &bo->expr; } static struct mrsh_arithm_expr *equal(struct mrsh_parser *parser) { struct mrsh_arithm_expr *left = comp(parser); if (left == NULL) { return NULL; } enum mrsh_arithm_binop_type type; if (parse_str(parser, "==")) { type = MRSH_ARITHM_BINOP_DEQ; } else if (parse_str(parser, "!=")) { type = MRSH_ARITHM_BINOP_BANGEQ; } else { return left; } struct mrsh_arithm_expr *right = equal(parser); if (right == NULL) { mrsh_arithm_expr_destroy(left); parser_set_error(parser, "expected a term"); return NULL; } struct mrsh_arithm_binop *bo = mrsh_arithm_binop_create(type, left, right); return &bo->expr; } static bool parse_binop(struct mrsh_parser *parser, const char *str) { size_t len = strlen(str); for (size_t i = 0; i < len; ++i) { parser_peek(parser, NULL, i + 1); if (parser->buf.data[i] != str[i]) { return false; } } // Make sure we don't parse "&&" as "&" parser_peek(parser, NULL, len + 1); switch (parser->buf.data[len]) { case '|': case '&': return false; } parser_read(parser, NULL, len); return true; } static struct mrsh_arithm_expr *binop(struct mrsh_parser *parser, enum mrsh_arithm_binop_type type, const char *str, struct mrsh_arithm_expr *(*term)(struct mrsh_parser *parser)) { struct mrsh_arithm_expr *left = term(parser); if (left == NULL) { return NULL; } if (!parse_binop(parser, str)) { return left; } consume_whitespace(parser); struct mrsh_arithm_expr *right = binop(parser, type, str, term); if (right == NULL) { mrsh_arithm_expr_destroy(left); parser_set_error(parser, "expected a term"); return NULL; } struct mrsh_arithm_binop *bo = mrsh_arithm_binop_create(type, left, right); return &bo->expr; } static struct mrsh_arithm_expr *bitwise_and(struct mrsh_parser *parser) { return binop(parser, MRSH_ARITHM_BINOP_AND, "&", equal); } static struct mrsh_arithm_expr *bitwise_xor(struct mrsh_parser *parser) { return binop(parser, MRSH_ARITHM_BINOP_CIRC, "^", bitwise_and); } static struct mrsh_arithm_expr *bitwise_or(struct mrsh_parser *parser) { return binop(parser, MRSH_ARITHM_BINOP_OR, "|", bitwise_xor); } static struct mrsh_arithm_expr *logical_and(struct mrsh_parser *parser) { return binop(parser, MRSH_ARITHM_BINOP_DAND, "&&", bitwise_or); } static struct mrsh_arithm_expr *logical_or(struct mrsh_parser *parser) { return binop(parser, MRSH_ARITHM_BINOP_DOR, "||", logical_and); } static struct mrsh_arithm_expr *ternary(struct mrsh_parser *parser) { struct mrsh_arithm_expr *expr = logical_or(parser); if (expr == NULL) { return NULL; } if (!parse_char(parser, '?')) { return expr; } struct mrsh_arithm_expr *condition = expr; struct mrsh_arithm_expr *body = ternary(parser); if (body == NULL) { parser_set_error(parser, "expected a logical or term"); goto error_body; } if (!expect_char(parser, ':')) { goto error_semi; } struct mrsh_arithm_expr *else_part = ternary(parser); if (else_part == NULL) { parser_set_error(parser, "expected an or term"); goto error_else_part; } struct mrsh_arithm_cond *c = mrsh_arithm_cond_create(condition, body, else_part); return &c->expr; error_else_part: error_semi: mrsh_arithm_expr_destroy(body); error_body: mrsh_arithm_expr_destroy(condition); return NULL; } static bool peek_assign_op(struct mrsh_parser *parser, size_t *offset, const char *str) { size_t len = strlen(str); for (size_t i = 0; i < len; ++i) { parser_peek(parser, NULL, *offset + i + 1); if (parser->buf.data[*offset + i] != str[i]) { return false; } } *offset += len; return true; } static struct mrsh_arithm_expr *assignment(struct mrsh_parser *parser) { size_t name_len = peek_name(parser, false); if (name_len == 0) { return NULL; } enum mrsh_arithm_assign_op op; size_t offset = name_len; if (peek_assign_op(parser, &offset, "=")) { op = MRSH_ARITHM_ASSIGN_NONE; } else if (peek_assign_op(parser, &offset, "*=")) { op = MRSH_ARITHM_ASSIGN_ASTERISK; } else if (peek_assign_op(parser, &offset, "/=")) { op = MRSH_ARITHM_ASSIGN_SLASH; } else if (peek_assign_op(parser, &offset, "%=")) { op = MRSH_ARITHM_ASSIGN_PERCENT; } else if (peek_assign_op(parser, &offset, "+=")) { op = MRSH_ARITHM_ASSIGN_PLUS; } else if (peek_assign_op(parser, &offset, "-=")) { op = MRSH_ARITHM_ASSIGN_MINUS; } else if (peek_assign_op(parser, &offset, "<<=")) { op = MRSH_ARITHM_ASSIGN_DLESS; } else if (peek_assign_op(parser, &offset, ">>=")) { op = MRSH_ARITHM_ASSIGN_DGREAT; } else if (peek_assign_op(parser, &offset, "&=")) { op = MRSH_ARITHM_ASSIGN_AND; } else if (peek_assign_op(parser, &offset, "^=")) { op = MRSH_ARITHM_ASSIGN_CIRC; } else if (peek_assign_op(parser, &offset, "|=")) { op = MRSH_ARITHM_ASSIGN_OR; } else { return NULL; } // offset is now the offset till the end of the operator char *name = malloc(name_len + 1); parser_read(parser, name, name_len); name[name_len] = '\0'; parser_read(parser, NULL, offset - name_len); // operator struct mrsh_arithm_expr *value = arithm_expr(parser); if (value == NULL) { free(name); parser_set_error(parser, "expected an assignment value"); return NULL; } struct mrsh_arithm_assign *a = mrsh_arithm_assign_create(op, name, value); return &a->expr; } static struct mrsh_arithm_expr *arithm_expr(struct mrsh_parser *parser) { struct mrsh_arithm_expr *expr = assignment(parser); if (expr != NULL) { return expr; } return ternary(parser); } struct mrsh_arithm_expr *mrsh_parse_arithm_expr(struct mrsh_parser *parser) { consume_whitespace(parser); struct mrsh_arithm_expr *expr = arithm_expr(parser); if (expr == NULL) { return NULL; } if (parser_peek_char(parser) != '\0') { parser_set_error(parser, "garbage at the end of the arithmetic expression"); mrsh_arithm_expr_destroy(expr); return NULL; } return expr; } ================================================ FILE: parser/parser.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include "ast.h" #include "parser.h" #define READ_SIZE 4096 // Keep sorted from the longest to the shortest const struct symbol operators[] = { { DLESSDASH, "<<-" }, { AND_IF, "&&" }, { OR_IF, "||" }, { DSEMI, ";;" }, { DLESS, "<<" }, { DGREAT, ">>" }, { LESSAND, "<&" }, { GREATAND, ">&" }, { LESSGREAT, "<>" }, { CLOBBER, ">|" }, }; const size_t operators_len = sizeof(operators) / sizeof(operators[0]); const size_t operators_max_str_len = 3; const char *keywords[] = { "if", "then", "else", "elif", "fi", "do", "done", "case", "esac", "while", "until", "for", "{", "}", "!", "in", }; const size_t keywords_len = sizeof(keywords) / sizeof(keywords[0]); static struct mrsh_parser *parser_create(void) { struct mrsh_parser *parser = calloc(1, sizeof(struct mrsh_parser)); parser->fd = -1; parser->pos.line = parser->pos.column = 1; return parser; } struct mrsh_parser *mrsh_parser_with_fd(int fd) { struct mrsh_parser *parser = parser_create(); parser->fd = fd; return parser; } struct mrsh_parser *mrsh_parser_with_data(const char *buf, size_t len) { struct mrsh_parser *parser = parser_create(); mrsh_buffer_append(&parser->buf, buf, len); mrsh_buffer_append_char(&parser->buf, '\0'); return parser; } struct mrsh_parser *mrsh_parser_with_buffer(struct mrsh_buffer *buf) { struct mrsh_parser *parser = parser_create(); parser->in_buf = buf; return parser; } void mrsh_parser_destroy(struct mrsh_parser *parser) { if (parser == NULL) { return; } mrsh_buffer_finish(&parser->buf); mrsh_array_finish(&parser->here_documents); free(parser->error.msg); free(parser); } static ssize_t parser_peek_fd(struct mrsh_parser *parser, size_t size) { assert(parser->fd >= 0); size_t n_read = 0; while (n_read < size) { char *dst = mrsh_buffer_reserve(&parser->buf, READ_SIZE); errno = 0; ssize_t n = read(parser->fd, dst, READ_SIZE); if (n < 0 && errno == EINTR) { continue; } else if (n < 0) { return -1; } else if (n == 0) { break; } parser->buf.len += n; n_read += n; } return n_read; } static ssize_t parser_peek_buffer(struct mrsh_parser *parser, size_t size) { assert(parser->in_buf != NULL); size_t n_read = parser->in_buf->len; if (n_read == 0) { return 0; } if (parser->buf.len == 0) { // Move data from one buffer to the other mrsh_buffer_finish(&parser->buf); memcpy(&parser->buf, parser->in_buf, sizeof(struct mrsh_buffer)); memset(parser->in_buf, 0, sizeof(struct mrsh_buffer)); } else { mrsh_buffer_append(&parser->buf, parser->in_buf->data, n_read); parser->in_buf->len = 0; } return n_read; } size_t parser_peek(struct mrsh_parser *parser, char *buf, size_t size) { if (size > parser->buf.len) { size_t n_more = size - parser->buf.len; ssize_t n_read; if (parser->fd >= 0) { n_read = parser_peek_fd(parser, n_more); } else if (parser->in_buf != NULL) { n_read = parser_peek_buffer(parser, n_more); } else { n_read = 0; } if (n_read < 0) { parser_set_error(parser, "failed to read"); return 0; // TODO: better error handling } if ((size_t)n_read < n_more) { if (!parser->eof) { mrsh_buffer_append_char(&parser->buf, '\0'); parser->eof = true; } size = parser->buf.len; } } if (buf != NULL) { memcpy(buf, parser->buf.data, size); } return size; } char parser_peek_char(struct mrsh_parser *parser) { char c = '\0'; parser_peek(parser, &c, sizeof(char)); return c; } size_t parser_read(struct mrsh_parser *parser, char *buf, size_t size) { size_t n = parser_peek(parser, buf, size); if (n > 0) { for (size_t i = 0; i < n; ++i) { assert(parser->buf.data[i] != '\0'); ++parser->pos.offset; if (parser->buf.data[i] == '\n') { ++parser->pos.line; parser->pos.column = 1; } else { ++parser->pos.column; } } memmove(parser->buf.data, parser->buf.data + n, parser->buf.len - n); parser->buf.len -= n; parser->continuation_line = false; } return n; } char parser_read_char(struct mrsh_parser *parser) { char c = '\0'; parser_read(parser, &c, sizeof(char)); return c; } void read_continuation_line(struct mrsh_parser *parser) { char c = parser_read_char(parser); assert(c == '\n'); parser->continuation_line = true; } bool is_operator_start(char c) { switch (c) { case '&': case '|': case ';': case '<': case '>': return true; default: return false; } } void parser_set_error(struct mrsh_parser *parser, const char *msg) { if (msg != NULL) { if (parser->error.msg != NULL) { return; } parser->here_documents.len = 0; parser->error.pos = parser->pos; parser->error.msg = strdup(msg); } else { free(parser->error.msg); parser->error.pos = (struct mrsh_position){0}; parser->error.msg = NULL; } } const char *mrsh_parser_error(struct mrsh_parser *parser, struct mrsh_position *pos) { if (pos != NULL) { *pos = parser->error.pos; } return parser->error.msg; } void parser_begin(struct mrsh_parser *parser) { parser_set_error(parser, NULL); parser->continuation_line = false; } // See section 2.3 Token Recognition static void next_symbol(struct mrsh_parser *parser) { parser->has_sym = true; char c = parser_peek_char(parser); if (c == '\0') { parser->sym = EOF_TOKEN; return; } if (c == '\n') { parser->sym = NEWLINE; return; } if (is_operator_start(c)) { for (size_t i = 0; i < operators_len; ++i) { const char *str = operators[i].str; size_t j; for (j = 0; str[j] != '\0'; ++j) { size_t n = j + 1; size_t n_read = parser_peek(parser, NULL, n); if (n != n_read || parser->buf.data[j] != str[j]) { break; } } if (str[j] == '\0') { parser->sym = operators[i].name; return; } } } if (isblank(c)) { parser_read_char(parser); next_symbol(parser); return; } if (c == '#') { while (true) { char c = parser_peek_char(parser); if (c == '\0' || c == '\n') { break; } parser_read_char(parser); } next_symbol(parser); return; } parser->sym = TOKEN; } enum symbol_name get_symbol(struct mrsh_parser *parser) { if (!parser->has_sym) { next_symbol(parser); } return parser->sym; } void consume_symbol(struct mrsh_parser *parser) { parser->has_sym = false; } bool symbol(struct mrsh_parser *parser, enum symbol_name sym) { return get_symbol(parser) == sym; } bool eof(struct mrsh_parser *parser) { return symbol(parser, EOF_TOKEN); } bool newline(struct mrsh_parser *parser) { if (!symbol(parser, NEWLINE)) { return false; } char c = parser_read_char(parser); assert(c == '\n'); consume_symbol(parser); return true; } void linebreak(struct mrsh_parser *parser) { while (newline(parser)) { // This space is intentionally left blank } } bool newline_list(struct mrsh_parser *parser) { if (!newline(parser)) { return false; } linebreak(parser); return true; } bool mrsh_parser_eof(struct mrsh_parser *parser) { return parser->has_sym && parser->sym == EOF_TOKEN; } void mrsh_parser_set_alias_func(struct mrsh_parser *parser, mrsh_parser_alias_func alias, void *user_data) { parser->alias = alias; parser->alias_user_data = user_data; } bool mrsh_parser_continuation_line(struct mrsh_parser *parser) { return parser->continuation_line; } void mrsh_parser_reset(struct mrsh_parser *parser) { parser->buf.len = 0; parser->has_sym = false; parser->pos = (struct mrsh_position){0}; } ================================================ FILE: parser/program.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include "ast.h" #include "parser.h" static const char *operator_str(enum symbol_name sym) { for (size_t i = 0; i < operators_len; ++i) { if (operators[i].name == sym) { return operators[i].str; } } return NULL; } static bool operator(struct mrsh_parser *parser, enum symbol_name sym, struct mrsh_range *range) { if (!symbol(parser, sym)) { return false; } struct mrsh_position begin = parser->pos; const char *str = operator_str(sym); assert(str != NULL); char buf[operators_max_str_len]; parser_read(parser, buf, strlen(str)); assert(strncmp(str, buf, strlen(str)) == 0); if (range != NULL) { range->begin = begin; range->end = parser->pos; } consume_symbol(parser); return true; } static int separator_op(struct mrsh_parser *parser) { if (token(parser, "&", NULL)) { return '&'; } if (token(parser, ";", NULL)) { return ';'; } return -1; } static size_t peek_alias(struct mrsh_parser *parser) { size_t n = peek_word(parser, 0); for (size_t i = 0; i < n; ++i) { char c = parser->buf.data[i]; switch (c) { case '_': case '!': case '%': case ',': case '@': break; default: if (!isalnum(c)) { return 0; } } } return n; } static void apply_aliases(struct mrsh_parser *parser) { if (parser->alias == NULL) { return; } const char *last_repl = NULL; while (true) { if (!symbol(parser, TOKEN)) { return; } size_t alias_len = peek_alias(parser); if (alias_len == 0) { return; } char *name = strndup(parser->buf.data, alias_len); const char *repl = parser->alias(name, parser->alias_user_data); free(name); if (repl == NULL || last_repl == repl) { break; } size_t trailing_len = parser->buf.len - alias_len; size_t repl_len = strlen(repl); if (repl_len > alias_len) { mrsh_buffer_reserve(&parser->buf, repl_len - alias_len); } memmove(&parser->buf.data[repl_len], &parser->buf.data[alias_len], parser->buf.len - alias_len); memcpy(parser->buf.data, repl, repl_len); parser->buf.len = repl_len + trailing_len; // TODO: fixup parser->pos // TODO: if repl's last char is blank, replace next alias too consume_symbol(parser); last_repl = repl; } } static bool io_here(struct mrsh_parser *parser, struct mrsh_io_redirect *redir) { if (operator(parser, DLESS, &redir->op_range)) { redir->op = MRSH_IO_DLESS; } else if (operator(parser, DLESSDASH, &redir->op_range)) { redir->op = MRSH_IO_DLESSDASH; } else { return false; } redir->name = word(parser, 0); if (redir->name == NULL) { parser_set_error(parser, "expected a name after IO here-document redirection operator"); return false; } // TODO: check redir->name only contains word strings and lists return true; } static struct mrsh_word *filename(struct mrsh_parser *parser) { // TODO: Apply rule 2 return word(parser, 0); } static int io_redirect_op(struct mrsh_parser *parser, struct mrsh_range *range) { if (token(parser, "<", range)) { return MRSH_IO_LESS; } else if (token(parser, ">", range)) { return MRSH_IO_GREAT; } else if (operator(parser, LESSAND, range)) { return MRSH_IO_LESSAND; } else if (operator(parser, GREATAND, range)) { return MRSH_IO_GREATAND; } else if (operator(parser, DGREAT, range)) { return MRSH_IO_DGREAT; } else if (operator(parser, CLOBBER, range)) { return MRSH_IO_CLOBBER; } else if (operator(parser, LESSGREAT, range)) { return MRSH_IO_LESSGREAT; } else { return -1; } } static bool io_file(struct mrsh_parser *parser, struct mrsh_io_redirect *redir) { int op = io_redirect_op(parser, &redir->op_range); if (op < 0) { return false; } redir->op = op; redir->name = filename(parser); if (redir->name == NULL) { parser_set_error(parser, "expected a filename after IO file redirection operator"); return false; } return true; } static int io_number(struct mrsh_parser *parser) { if (!symbol(parser, TOKEN)) { return -1; } char c = parser_peek_char(parser); if (!isdigit(c)) { return -1; } char buf[2]; parser_peek(parser, buf, sizeof(buf)); if (buf[1] != '<' && buf[1] != '>') { return -1; } parser_read_char(parser); consume_symbol(parser); return strtol(buf, NULL, 10); } static struct mrsh_io_redirect *io_redirect(struct mrsh_parser *parser) { struct mrsh_io_redirect redir = {0}; struct mrsh_position io_number_pos = parser->pos; redir.io_number = io_number(parser); if (redir.io_number >= 0) { redir.io_number_pos = io_number_pos; } redir.op_range.begin = parser->pos; if (io_file(parser, &redir)) { struct mrsh_io_redirect *redir_ptr = calloc(1, sizeof(struct mrsh_io_redirect)); memcpy(redir_ptr, &redir, sizeof(struct mrsh_io_redirect)); redir.op_range.end = parser->pos; return redir_ptr; } if (io_here(parser, &redir)) { struct mrsh_io_redirect *redir_ptr = calloc(1, sizeof(struct mrsh_io_redirect)); memcpy(redir_ptr, &redir, sizeof(struct mrsh_io_redirect)); redir.op_range.end = parser->pos; mrsh_array_add(&parser->here_documents, redir_ptr); return redir_ptr; } if (redir.io_number >= 0) { parser_set_error(parser, "expected an IO redirect after IO number"); } return NULL; } static struct mrsh_assignment *assignment_word(struct mrsh_parser *parser) { if (!symbol(parser, TOKEN)) { return NULL; } size_t name_len = peek_name(parser, false); if (name_len == 0) { return NULL; } parser_peek(parser, NULL, name_len + 1); if (parser->buf.data[name_len] != '=') { return NULL; } struct mrsh_range name_range; char *name = read_token(parser, name_len, &name_range); struct mrsh_position equal_pos = parser->pos; parser_read(parser, NULL, 1); struct mrsh_word *value = word(parser, 0); if (value == NULL) { value = &mrsh_word_string_create(strdup(""), false)->word; } struct mrsh_assignment *assign = calloc(1, sizeof(struct mrsh_assignment)); assign->name = name; assign->value = value; assign->name_range = name_range; assign->equal_pos = equal_pos; return assign; } static bool cmd_prefix(struct mrsh_parser *parser, struct mrsh_simple_command *cmd) { struct mrsh_io_redirect *redir = io_redirect(parser); if (redir != NULL) { mrsh_array_add(&cmd->io_redirects, redir); return true; } struct mrsh_assignment *assign = assignment_word(parser); if (assign != NULL) { mrsh_array_add(&cmd->assignments, assign); return true; } return false; } static struct mrsh_word *cmd_name(struct mrsh_parser *parser) { apply_aliases(parser); size_t word_len = peek_word(parser, 0); if (word_len == 0) { return word(parser, 0); } // TODO: optimize this for (size_t i = 0; i < keywords_len; ++i) { if (strlen(keywords[i]) == word_len && strncmp(parser->buf.data, keywords[i], word_len) == 0) { return NULL; } } struct mrsh_range range; char *str = read_token(parser, word_len, &range); struct mrsh_word_string *ws = mrsh_word_string_create(str, false); ws->range = range; return &ws->word; } static bool cmd_suffix(struct mrsh_parser *parser, struct mrsh_simple_command *cmd) { struct mrsh_io_redirect *redir = io_redirect(parser); if (redir != NULL) { mrsh_array_add(&cmd->io_redirects, redir); return true; } struct mrsh_word *arg = word(parser, 0); if (arg != NULL) { mrsh_array_add(&cmd->arguments, arg); return true; } return false; } static struct mrsh_simple_command *simple_command(struct mrsh_parser *parser) { struct mrsh_simple_command cmd = {0}; bool has_prefix = false; while (cmd_prefix(parser, &cmd)) { has_prefix = true; } cmd.name = cmd_name(parser); if (cmd.name == NULL && !has_prefix) { return NULL; } else if (cmd.name != NULL) { while (cmd_suffix(parser, &cmd)) { // This space is intentionally left blank } } return mrsh_simple_command_create(cmd.name, &cmd.arguments, &cmd.io_redirects, &cmd.assignments); } static int separator(struct mrsh_parser *parser) { int sep = separator_op(parser); if (sep != -1) { linebreak(parser); return sep; } if (newline_list(parser)) { return '\n'; } return -1; } static struct mrsh_and_or_list *and_or(struct mrsh_parser *parser); static bool expect_here_document(struct mrsh_parser *parser, struct mrsh_io_redirect *redir, const char *delim); static struct mrsh_command_list *term(struct mrsh_parser *parser) { struct mrsh_and_or_list *and_or_list = and_or(parser); if (and_or_list == NULL) { return NULL; } struct mrsh_command_list *cmd = mrsh_command_list_create(); cmd->and_or_list = and_or_list; struct mrsh_position separator_pos = parser->pos; int sep = separator(parser); if (sep == '&') { cmd->ampersand = true; } if (sep >= 0) { cmd->separator_pos = separator_pos; } if (sep == '\n' && parser->here_documents.len > 0) { for (size_t i = 0; i < parser->here_documents.len; ++i) { struct mrsh_io_redirect *redir = parser->here_documents.data[i]; char *delim = mrsh_word_str(redir->name); bool ok = expect_here_document(parser, redir, delim); free(delim); if (!ok) { return false; } } parser->here_documents.len = 0; } return cmd; } static bool compound_list(struct mrsh_parser *parser, struct mrsh_array *cmds) { linebreak(parser); struct mrsh_command_list *l = term(parser); if (l == NULL) { return false; } mrsh_array_add(cmds, l); while (true) { l = term(parser); if (l == NULL) { break; } mrsh_array_add(cmds, l); } return true; } static bool expect_compound_list(struct mrsh_parser *parser, struct mrsh_array *cmds) { if (!compound_list(parser, cmds)) { parser_set_error(parser, "expected a compound list"); return false; } return true; } static struct mrsh_brace_group *brace_group(struct mrsh_parser *parser) { struct mrsh_position lbrace_pos = parser->pos; if (!token(parser, "{", NULL)) { return NULL; } struct mrsh_array body = {0}; if (!expect_compound_list(parser, &body)) { return NULL; } struct mrsh_position rbrace_pos = parser->pos; if (!expect_token(parser, "}", NULL)) { command_list_array_finish(&body); return NULL; } struct mrsh_brace_group *bg = mrsh_brace_group_create(&body); bg->lbrace_pos = lbrace_pos; bg->rbrace_pos = rbrace_pos; return bg; } static struct mrsh_subshell *subshell(struct mrsh_parser *parser) { struct mrsh_position lparen_pos = parser->pos; if (!token(parser, "(", NULL)) { return NULL; } struct mrsh_array body = {0}; if (!expect_compound_list(parser, &body)) { return NULL; } struct mrsh_position rparen_pos = parser->pos; if (!expect_token(parser, ")", NULL)) { command_list_array_finish(&body); return NULL; } struct mrsh_subshell *s = mrsh_subshell_create(&body); s->lparen_pos = lparen_pos; s->rparen_pos = rparen_pos; return s; } static struct mrsh_command *else_part(struct mrsh_parser *parser) { struct mrsh_range if_range = {0}; if (token(parser, "elif", &if_range)) { struct mrsh_array cond = {0}; if (!expect_compound_list(parser, &cond)) { return NULL; } struct mrsh_range then_range; if (!expect_token(parser, "then", &then_range)) { command_list_array_finish(&cond); return NULL; } struct mrsh_array body = {0}; if (!expect_compound_list(parser, &body)) { command_list_array_finish(&cond); return NULL; } struct mrsh_command *ep = else_part(parser); struct mrsh_if_clause *ic = mrsh_if_clause_create(&cond, &body, ep); ic->if_range = if_range; ic->then_range = then_range; return &ic->command; } if (token(parser, "else", NULL)) { struct mrsh_array body = {0}; if (!expect_compound_list(parser, &body)) { return NULL; } // TODO: position information is missing struct mrsh_brace_group *bg = mrsh_brace_group_create(&body); return &bg->command; } return NULL; } static struct mrsh_if_clause *if_clause(struct mrsh_parser *parser) { struct mrsh_range if_range; if (!token(parser, "if", &if_range)) { return NULL; } struct mrsh_array cond = {0}; if (!expect_compound_list(parser, &cond)) { goto error_cond; } struct mrsh_range then_range; if (!expect_token(parser, "then", &then_range)) { goto error_cond; } struct mrsh_array body = {0}; if (!expect_compound_list(parser, &body)) { goto error_body; } struct mrsh_command *ep = else_part(parser); struct mrsh_range fi_range; if (!expect_token(parser, "fi", &fi_range)) { goto error_else_part; } struct mrsh_if_clause *ic = mrsh_if_clause_create(&cond, &body, ep); ic->if_range = if_range; ic->then_range = then_range; ic->fi_range = fi_range; return ic; error_else_part: mrsh_command_destroy(ep); error_body: command_list_array_finish(&body); error_cond: command_list_array_finish(&cond); return NULL; } static bool sequential_sep(struct mrsh_parser *parser) { if (token(parser, ";", NULL)) { linebreak(parser); return true; } return newline_list(parser); } static void wordlist(struct mrsh_parser *parser, struct mrsh_array *words) { while (true) { struct mrsh_word *w = word(parser, 0); if (w == NULL) { break; } mrsh_array_add(words, w); } } static bool expect_do_group(struct mrsh_parser *parser, struct mrsh_array *body, struct mrsh_range *do_range, struct mrsh_range *done_range) { if (!token(parser, "do", do_range)) { parser_set_error(parser, "expected 'do'"); return false; } if (!expect_compound_list(parser, body)) { return false; } if (!token(parser, "done", done_range)) { parser_set_error(parser, "expected 'done'"); command_list_array_finish(body); return false; } return true; } static struct mrsh_for_clause *for_clause(struct mrsh_parser *parser) { struct mrsh_range for_range; if (!token(parser, "for", &for_range)) { return NULL; } size_t name_len = peek_name(parser, false); if (name_len == 0) { parser_set_error(parser, "expected name"); return NULL; } struct mrsh_range name_range; char *name = read_token(parser, name_len, &name_range); linebreak(parser); struct mrsh_range in_range = {0}; bool in = token(parser, "in", &in_range); // TODO: save sequential_sep position, if any struct mrsh_array words = {0}; if (in) { wordlist(parser, &words); if (!sequential_sep(parser)) { parser_set_error(parser, "expected sequential separator"); goto error_words; } } else { sequential_sep(parser); } struct mrsh_array body = {0}; struct mrsh_range do_range, done_range; if (!expect_do_group(parser, &body, &do_range, &done_range)) { goto error_words; } struct mrsh_for_clause *fc = mrsh_for_clause_create(name, in, &words, &body); fc->for_range = for_range; fc->name_range = name_range; fc->in_range = in_range; fc->do_range = do_range; fc->done_range = done_range; return fc; error_words: for (size_t i = 0; i < words.len; ++i) { struct mrsh_word *word = words.data[i]; mrsh_word_destroy(word); } mrsh_array_finish(&words); free(name); return NULL; } static struct mrsh_loop_clause *loop_clause(struct mrsh_parser *parser) { enum mrsh_loop_type type; struct mrsh_range while_until_range; if (token(parser, "while", &while_until_range)) { type = MRSH_LOOP_WHILE; } else if (token(parser, "until", &while_until_range)) { type = MRSH_LOOP_UNTIL; } else { return NULL; } struct mrsh_array condition = {0}; if (!expect_compound_list(parser, &condition)) { return NULL; } struct mrsh_array body = {0}; struct mrsh_range do_range, done_range; if (!expect_do_group(parser, &body, &do_range, &done_range)) { command_list_array_finish(&condition); return NULL; } struct mrsh_loop_clause *fc = mrsh_loop_clause_create(type, &condition, &body); fc->while_until_range = while_until_range; fc->do_range = do_range; fc->done_range = done_range; return fc; } static struct mrsh_case_item *expect_case_item(struct mrsh_parser *parser, bool *dsemi) { struct mrsh_position lparen_pos = parser->pos; if (!token(parser, "(", NULL)) { lparen_pos = (struct mrsh_position){0}; } struct mrsh_word *w = word(parser, 0); if (w == NULL) { parser_set_error(parser, "expected a word"); return NULL; } struct mrsh_array patterns = {0}; mrsh_array_add(&patterns, w); while (token(parser, "|", NULL)) { struct mrsh_word *w = word(parser, 0); if (w == NULL) { parser_set_error(parser, "expected a word"); return NULL; } mrsh_array_add(&patterns, w); } struct mrsh_position rparen_pos = parser->pos; if (!expect_token(parser, ")", NULL)) { goto error_patterns; } // It's okay if there's no body struct mrsh_array body = {0}; compound_list(parser, &body); if (mrsh_parser_error(parser, NULL)) { goto error_patterns; } struct mrsh_range dsemi_range = {0}; *dsemi = operator(parser, DSEMI, &dsemi_range); if (*dsemi) { linebreak(parser); } struct mrsh_case_item *item = calloc(1, sizeof(struct mrsh_case_item)); if (item == NULL) { goto error_body; } item->patterns = patterns; item->body = body; item->lparen_pos = lparen_pos; item->rparen_pos = rparen_pos; item->dsemi_range = dsemi_range; return item; error_body: command_list_array_finish(&body); error_patterns: for (size_t i = 0; i < patterns.len; ++i) { struct mrsh_word *w = patterns.data[i]; mrsh_word_destroy(w); } mrsh_array_finish(&patterns); return NULL; } static struct mrsh_case_clause *case_clause(struct mrsh_parser *parser) { struct mrsh_range case_range; if (!token(parser, "case", &case_range)) { return NULL; } struct mrsh_word *w = word(parser, 0); if (w == NULL) { parser_set_error(parser, "expected a word"); return NULL; } linebreak(parser); struct mrsh_range in_range; if (!expect_token(parser, "in", &in_range)) { goto error_word; } linebreak(parser); bool dsemi = false; struct mrsh_array items = {0}; struct mrsh_range esac_range; while (!token(parser, "esac", &esac_range)) { struct mrsh_case_item *item = expect_case_item(parser, &dsemi); if (item == NULL) { goto error_items; } mrsh_array_add(&items, item); if (!dsemi) { // Only the last case can omit `;;` if (!expect_token(parser, "esac", &esac_range)) { goto error_items; } break; } } struct mrsh_case_clause *cc = mrsh_case_clause_create(w, &items); cc->case_range = case_range; cc->in_range = in_range; cc->esac_range = esac_range; return cc; error_items: for (size_t i = 0; i < items.len; ++i) { struct mrsh_case_item *item = items.data[i]; case_item_destroy(item); } mrsh_array_finish(&items); error_word: mrsh_word_destroy(w); return NULL; } static struct mrsh_command *compound_command(struct mrsh_parser *parser); static struct mrsh_function_definition *function_definition( struct mrsh_parser *parser) { size_t name_len = peek_name(parser, false); if (name_len == 0) { return NULL; } size_t i = name_len; while (true) { parser_peek(parser, NULL, i + 1); char c = parser->buf.data[i]; if (c == '(') { break; } else if (!isblank(c)) { return NULL; } ++i; } struct mrsh_range name_range; char *name = read_token(parser, name_len, &name_range); struct mrsh_position lparen_pos = parser->pos; if (!expect_token(parser, "(", NULL)) { return NULL; } struct mrsh_position rparen_pos = parser->pos; if (!expect_token(parser, ")", NULL)) { return NULL; } linebreak(parser); struct mrsh_command *cmd = compound_command(parser); if (cmd == NULL) { parser_set_error(parser, "expected a compound command"); return NULL; } struct mrsh_array io_redirects = {0}; while (true) { struct mrsh_io_redirect *redir = io_redirect(parser); if (redir == NULL) { break; } mrsh_array_add(&io_redirects, redir); } struct mrsh_function_definition *fd = mrsh_function_definition_create(name, cmd, &io_redirects); fd->name_range = name_range; fd->lparen_pos = lparen_pos; fd->rparen_pos = rparen_pos; return fd; } static bool unspecified_word(struct mrsh_parser *parser) { const char *const reserved[] = { "[[", "]]", "function", "select", }; size_t word_len = peek_word(parser, 0); if (word_len == 0) { return false; } for (size_t i = 0; i < sizeof(reserved) / sizeof(reserved[0]); i++) { if (strncmp(parser->buf.data, reserved[i], word_len) == 0 && word_len == strlen(reserved[i])) { char err_msg[256]; snprintf(err_msg, sizeof(err_msg), "keyword is reserved and causes unspecified results: %s", reserved[i]); parser_set_error(parser, err_msg); return true; } } size_t name_len = peek_name(parser, false); if (name_len == 0) { return false; } parser_peek(parser, NULL, name_len + 1); if (parser->buf.data[name_len] == ':') { parser_set_error(parser, "words that are the concatenation of a name " "and a colon produce unspecified results"); return true; } return false; } static struct mrsh_command *compound_command(struct mrsh_parser *parser) { struct mrsh_brace_group *bg = brace_group(parser); if (bg != NULL) { return &bg->command; } else if (mrsh_parser_error(parser, NULL)) { return NULL; } struct mrsh_subshell *s = subshell(parser); if (s != NULL) { return &s->command; } else if (mrsh_parser_error(parser, NULL)) { return NULL; } struct mrsh_if_clause *ic = if_clause(parser); if (ic != NULL) { return &ic->command; } else if (mrsh_parser_error(parser, NULL)) { return NULL; } struct mrsh_for_clause *fc = for_clause(parser); if (fc != NULL) { return &fc->command; } else if (mrsh_parser_error(parser, NULL)) { return NULL; } struct mrsh_loop_clause *lc = loop_clause(parser); if (lc != NULL) { return &lc->command; } else if (mrsh_parser_error(parser, NULL)) { return NULL; } struct mrsh_case_clause *cc = case_clause(parser); if (cc != NULL) { return &cc->command; } else if (mrsh_parser_error(parser, NULL)) { return NULL; } if (unspecified_word(parser)) { return NULL; } struct mrsh_function_definition *fd = function_definition(parser); if (fd != NULL) { return &fd->command; } else if (mrsh_parser_error(parser, NULL)) { return NULL; } return NULL; } static struct mrsh_command *command(struct mrsh_parser *parser) { apply_aliases(parser); struct mrsh_command *cmd = compound_command(parser); if (cmd != NULL || mrsh_parser_error(parser, NULL)) { return cmd; } // TODO: compound_command redirect_list struct mrsh_simple_command *sc = simple_command(parser); if (sc != NULL) { return &sc->command; } return NULL; } static struct mrsh_pipeline *pipeline(struct mrsh_parser *parser) { struct mrsh_range bang_range = {0}; bool bang = token(parser, "!", &bang_range); struct mrsh_position bang_pos = bang_range.begin; // can be invalid struct mrsh_command *cmd = command(parser); if (cmd == NULL) { return NULL; } struct mrsh_array commands = {0}; mrsh_array_add(&commands, cmd); while (token(parser, "|", NULL)) { linebreak(parser); struct mrsh_command *cmd = command(parser); if (cmd == NULL) { parser_set_error(parser, "expected a command"); goto error_commands; } mrsh_array_add(&commands, cmd); } struct mrsh_pipeline *p = mrsh_pipeline_create(&commands, bang); p->bang_pos = bang_pos; return p; error_commands: for (size_t i = 0; i < commands.len; ++i) { mrsh_command_destroy((struct mrsh_command *)commands.data[i]); } mrsh_array_finish(&commands); return NULL; } static struct mrsh_and_or_list *and_or(struct mrsh_parser *parser) { struct mrsh_pipeline *pl = pipeline(parser); if (pl == NULL) { return NULL; } enum mrsh_binop_type binop_type; struct mrsh_range op_range; if (operator(parser, AND_IF, &op_range)) { binop_type = MRSH_BINOP_AND; } else if (operator(parser, OR_IF, &op_range)) { binop_type = MRSH_BINOP_OR; } else { return &pl->and_or_list; } linebreak(parser); struct mrsh_and_or_list *and_or_list = and_or(parser); if (and_or_list == NULL) { mrsh_and_or_list_destroy(&pl->and_or_list); parser_set_error(parser, "expected an AND-OR list"); return NULL; } struct mrsh_binop *binop = mrsh_binop_create(binop_type, &pl->and_or_list, and_or_list); binop->op_range = op_range; return &binop->and_or_list; } static struct mrsh_command_list *list(struct mrsh_parser *parser) { struct mrsh_and_or_list *and_or_list = and_or(parser); if (and_or_list == NULL) { return NULL; } struct mrsh_command_list *cmd = mrsh_command_list_create(); cmd->and_or_list = and_or_list; struct mrsh_position separator_pos = parser->pos; int sep = separator_op(parser); if (sep == '&') { cmd->ampersand = true; } if (sep >= 0) { cmd->separator_pos = separator_pos; } return cmd; } /** * Append a new string word to `children` with the contents of `buf`, and reset * `buf`. */ static void push_buffer_word_string(struct mrsh_array *children, struct mrsh_buffer *buf) { if (buf->len == 0) { return; } mrsh_buffer_append_char(buf, '\0'); char *data = mrsh_buffer_steal(buf); struct mrsh_word_string *ws = mrsh_word_string_create(data, false); mrsh_array_add(children, &ws->word); } static struct mrsh_word *here_document_line(struct mrsh_parser *parser) { struct mrsh_array children = {0}; struct mrsh_buffer buf = {0}; while (true) { char c = parser_peek_char(parser); if (c == '\0') { break; } if (c == '$') { push_buffer_word_string(&children, &buf); struct mrsh_word *t = expect_dollar(parser); if (t == NULL) { return NULL; } mrsh_array_add(&children, t); continue; } if (c == '`') { push_buffer_word_string(&children, &buf); struct mrsh_word *t = back_quotes(parser); mrsh_array_add(&children, t); continue; } if (c == '\\') { // Here-document backslash, same semantics as quoted backslash // except double-quotes are not special char next[2]; parser_peek(parser, next, sizeof(next)); switch (next[1]) { case '$': case '`': case '\\': parser_read_char(parser); c = next[1]; break; } } parser_read_char(parser); mrsh_buffer_append_char(&buf, c); } push_buffer_word_string(&children, &buf); mrsh_buffer_finish(&buf); if (children.len == 1) { struct mrsh_word *word = children.data[0]; mrsh_array_finish(&children); // TODO: don't allocate this array return word; } else { struct mrsh_word_list *wl = mrsh_word_list_create(&children, false); return &wl->word; } } static bool is_word_quoted(struct mrsh_word *word) { switch (word->type) { case MRSH_WORD_STRING:; struct mrsh_word_string *ws = mrsh_word_get_string(word); return ws->single_quoted; case MRSH_WORD_LIST:; struct mrsh_word_list *wl = mrsh_word_get_list(word); if (wl->double_quoted) { return true; } for (size_t i = 0; i < wl->children.len; ++i) { struct mrsh_word *child = wl->children.data[i]; if (is_word_quoted(child)) { return true; } } return false; default: abort(); } } static bool expect_here_document(struct mrsh_parser *parser, struct mrsh_io_redirect *redir, const char *delim) { bool trim_tabs = redir->op == MRSH_IO_DLESSDASH; bool expand_lines = !is_word_quoted(redir->name); parser->continuation_line = true; struct mrsh_buffer buf = {0}; while (true) { buf.len = 0; while (true) { char c = parser_peek_char(parser); if (c == '\0' || c == '\n') { break; } mrsh_buffer_append_char(&buf, parser_read_char(parser)); } mrsh_buffer_append_char(&buf, '\0'); const char *line = buf.data; if (trim_tabs) { while (line[0] == '\t') { ++line; } } if (strcmp(line, delim) == 0) { if (parser_peek_char(parser) == '\n') { parser_read_char(parser); } break; } if (parser_peek_char(parser) == '\0') { parser_set_error(parser, "unterminated here-document"); return false; } read_continuation_line(parser); struct mrsh_word *word; if (expand_lines) { struct mrsh_parser *subparser = mrsh_parser_with_data(line, strlen(line)); word = here_document_line(subparser); mrsh_parser_destroy(subparser); } else { struct mrsh_word_string *ws = mrsh_word_string_create(strdup(line), true); word = &ws->word; } mrsh_array_add(&redir->here_document, word); } mrsh_buffer_finish(&buf); consume_symbol(parser); return true; } static bool complete_command(struct mrsh_parser *parser, struct mrsh_array *cmds) { struct mrsh_command_list *l = list(parser); if (l == NULL) { return false; } mrsh_array_add(cmds, l); while (true) { l = list(parser); if (l == NULL) { break; } mrsh_array_add(cmds, l); } if (parser->here_documents.len > 0) { for (size_t i = 0; i < parser->here_documents.len; ++i) { struct mrsh_io_redirect *redir = parser->here_documents.data[i]; if (!newline(parser)) { parser_set_error(parser, "expected a newline followed by a here-document"); return false; } char *delim = mrsh_word_str(redir->name); bool ok = expect_here_document(parser, redir, delim); free(delim); if (!ok) { return false; } } parser->here_documents.len = 0; } return true; } static bool expect_complete_command(struct mrsh_parser *parser, struct mrsh_array *cmds) { if (!complete_command(parser, cmds)) { parser_set_error(parser, "expected a complete command"); return false; } return true; } static struct mrsh_program *program(struct mrsh_parser *parser) { struct mrsh_program *prog = mrsh_program_create(); if (prog == NULL) { return NULL; } linebreak(parser); if (eof(parser)) { return prog; } if (!expect_complete_command(parser, &prog->body)) { mrsh_program_destroy(prog); return NULL; } while (newline_list(parser)) { if (eof(parser)) { return prog; } if (!complete_command(parser, &prog->body)) { break; } } linebreak(parser); return prog; } struct mrsh_program *mrsh_parse_line(struct mrsh_parser *parser) { parser_begin(parser); if (eof(parser)) { return NULL; } struct mrsh_program *prog = mrsh_program_create(); if (prog == NULL) { return NULL; } if (newline(parser)) { return prog; } if (!expect_complete_command(parser, &prog->body)) { goto error; } if (!eof(parser) && !newline(parser)) { parser_set_error(parser, "expected a newline"); goto error; } return prog; error: mrsh_program_destroy(prog); // Consume the whole line while (true) { char c = parser_peek_char(parser); if (c == '\0') { break; } parser_read_char(parser); if (c == '\n') { break; } } parser->has_sym = false; return NULL; } struct mrsh_program *mrsh_parse_program(struct mrsh_parser *parser) { parser_begin(parser); return program(parser); } ================================================ FILE: parser/word.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include "ast.h" #include "parser.h" static struct mrsh_word *single_quotes(struct mrsh_parser *parser) { struct mrsh_position begin = parser->pos; char c = parser_read_char(parser); assert(c == '\''); struct mrsh_buffer buf = {0}; while (true) { char c = parser_peek_char(parser); if (c == '\0') { parser_set_error(parser, "single quotes not terminated"); return NULL; } if (c == '\'') { parser_read_char(parser); break; } if (c == '\n') { read_continuation_line(parser); } else { parser_read_char(parser); } mrsh_buffer_append_char(&buf, c); } mrsh_buffer_append_char(&buf, '\0'); char *data = mrsh_buffer_steal(&buf); struct mrsh_word_string *ws = mrsh_word_string_create(data, true); ws->range.begin = begin; ws->range.end = parser->pos; return &ws->word; } size_t peek_name(struct mrsh_parser *parser, bool in_braces) { // In the shell command language, a word consisting solely of underscores, // digits, and alphabetics from the portable character set. The first // character of a name is not a digit. if (!symbol(parser, TOKEN)) { return false; } size_t i = 0; while (true) { parser_peek(parser, NULL, i + 1); char c = parser->buf.data[i]; if (c != '_' && !isalnum(c)) { break; } else if (i == 0 && isdigit(c) && !in_braces) { break; } ++i; } return i; } size_t peek_word(struct mrsh_parser *parser, char end) { if (!symbol(parser, TOKEN)) { return false; } size_t i = 0; while (true) { parser_peek(parser, NULL, i + 1); char c = parser->buf.data[i]; switch (c) { case '\0': case '\n': case ')': return i; case '$': case '`': case '\'': case '"': case '\\': // TODO: allow backslash in words return 0; } if (is_operator_start(c) || isblank(c) || c == end) { return i; } ++i; } } bool token(struct mrsh_parser *parser, const char *str, struct mrsh_range *range) { if (!symbol(parser, TOKEN)) { return false; } struct mrsh_position begin = parser->pos; size_t len = strlen(str); assert(len > 0); if (len == 1 && !isalpha(str[0])) { if (parser_peek_char(parser) != str[0]) { return false; } parser_read_char(parser); } else { size_t word_len = peek_word(parser, 0); if (len != word_len || strncmp(parser->buf.data, str, word_len) != 0) { return false; } // assert(isalpha(str[i])); parser_read(parser, NULL, len); } if (range != NULL) { range->begin = begin; range->end = parser->pos; } consume_symbol(parser); return true; } bool expect_token(struct mrsh_parser *parser, const char *str, struct mrsh_range *range) { if (token(parser, str, range)) { return true; } char msg[128]; snprintf(msg, sizeof(msg), "expected '%s'", str); parser_set_error(parser, msg); return false; } char *read_token(struct mrsh_parser *parser, size_t len, struct mrsh_range *range) { if (!symbol(parser, TOKEN)) { return NULL; } struct mrsh_position begin = parser->pos; char *tok = malloc(len + 1); parser_read(parser, tok, len); tok[len] = '\0'; if (range != NULL) { range->begin = begin; range->end = parser->pos; } consume_symbol(parser); return tok; } static struct mrsh_word *word_list(struct mrsh_parser *parser, char end, word_func f) { struct mrsh_array children = {0}; while (true) { if (parser_peek_char(parser) == end) { break; } struct mrsh_word *child = f(parser, end); if (child == NULL) { break; } mrsh_array_add(&children, child); struct mrsh_position begin = parser->pos; struct mrsh_buffer buf = {0}; while (true) { char c = parser_peek_char(parser); if (!isblank(c)) { break; } mrsh_buffer_append_char(&buf, parser_read_char(parser)); } if (buf.len == 0) { break; // word() ended on a non-blank char, stop here } mrsh_buffer_append_char(&buf, '\0'); struct mrsh_word_string *ws = mrsh_word_string_create(mrsh_buffer_steal(&buf), false); ws->range.begin = begin; ws->range.end = parser->pos; mrsh_array_add(&children, &ws->word); mrsh_buffer_finish(&buf); } if (children.len == 0) { return NULL; } else if (children.len == 1) { struct mrsh_word *child = children.data[0]; mrsh_array_finish(&children); return child; } else { struct mrsh_word_list *wl = mrsh_word_list_create(&children, false); return &wl->word; } } static enum mrsh_word_parameter_op char_to_parameter_op_val(char c) { switch (c) { case '-': return MRSH_PARAM_MINUS; case '=': return MRSH_PARAM_EQUAL; case '?': return MRSH_PARAM_QMARK; case '+': return MRSH_PARAM_PLUS; default: return MRSH_PARAM_NONE; } } static bool expect_parameter_op(struct mrsh_parser *parser, enum mrsh_word_parameter_op *op, bool *colon) { char c = parser_read_char(parser); *colon = c == ':'; if (*colon) { c = parser_read_char(parser); } *op = char_to_parameter_op_val(c); if (*op != MRSH_PARAM_NONE) { return true; } // Colon can only be used with value operations if (*colon) { parser_set_error(parser, "expected a parameter operation"); return false; } // Substring processing operations char c_next = parser_peek_char(parser); bool is_double = c == c_next; switch (c) { case '%': *op = is_double ? MRSH_PARAM_DPERCENT : MRSH_PARAM_PERCENT; break; case '#': *op = is_double ? MRSH_PARAM_DHASH : MRSH_PARAM_HASH; break; default: parser_set_error(parser, "expected a parameter operation"); return false; } if (is_double) { parser_read_char(parser); } return true; } static struct mrsh_word_parameter *expect_parameter_expression( struct mrsh_parser *parser) { struct mrsh_position lbrace_pos = parser->pos; char c = parser_read_char(parser); assert(c == '{'); enum mrsh_word_parameter_op op = MRSH_PARAM_NONE; struct mrsh_range op_range = {0}; if (parser_peek_char(parser) == '#') { op_range.begin = parser->pos; parser_read_char(parser); op_range.end = parser->pos; op = MRSH_PARAM_LEADING_HASH; } size_t name_len = peek_name(parser, true); if (name_len == 0) { parser_set_error(parser, "expected a parameter"); return NULL; } struct mrsh_range name_range; char *name = read_token(parser, name_len, &name_range); if (name == NULL) { return NULL; } bool colon = false; struct mrsh_word *arg = NULL; if (op == MRSH_PARAM_NONE && parser_peek_char(parser) != '}') { op_range.begin = parser->pos; if (!expect_parameter_op(parser, &op, &colon)) { return NULL; } op_range.end = parser->pos; arg = word_list(parser, '}', word); } struct mrsh_position rbrace_pos = parser->pos; if (parser_read_char(parser) != '}') { parser_set_error(parser, "expected end of parameter"); return NULL; } struct mrsh_word_parameter *wp = mrsh_word_parameter_create(name, op, colon, arg); wp->name_range = name_range; wp->op_range = op_range; wp->lbrace_pos = lbrace_pos; wp->rbrace_pos = rbrace_pos; return wp; } static struct mrsh_word_command *expect_word_command( struct mrsh_parser *parser) { char c = parser_read_char(parser); assert(c == '('); assert(symbol(parser, TOKEN)); consume_symbol(parser); // Alias substitution is not allowed inside command substitution, see // section 2.2.3 mrsh_parser_alias_func alias = parser->alias; parser->alias = NULL; struct mrsh_program *prog = mrsh_parse_program(parser); parser->alias = alias; if (mrsh_parser_error(parser, NULL) != NULL) { mrsh_program_destroy(prog); return NULL; } else if (prog == NULL) { parser_set_error(parser, "expected a program"); return NULL; } if (!expect_token(parser, ")", NULL)) { mrsh_program_destroy(prog); return NULL; } return mrsh_word_command_create(prog, false); } static struct mrsh_word_arithmetic *expect_word_arithmetic( struct mrsh_parser *parser) { char c = parser_read_char(parser); assert(c == '('); c = parser_read_char(parser); assert(c == '('); parser->arith_nested_parens = 0; struct mrsh_word *body = word_list(parser, 0, arithmetic_word); if (body == NULL) { if (!mrsh_parser_error(parser, NULL)) { parser_set_error(parser, "expected an arithmetic expression"); } return NULL; } if (!expect_token(parser, ")", NULL)) { mrsh_word_destroy(body); return NULL; } if (!expect_token(parser, ")", NULL)) { mrsh_word_destroy(body); return NULL; } return mrsh_word_arithmetic_create(body); } // Expect parameter expansion or command substitution struct mrsh_word *expect_dollar(struct mrsh_parser *parser) { struct mrsh_position dollar_pos = parser->pos; char c = parser_read_char(parser); assert(c == '$'); struct mrsh_word_parameter *wp; c = parser_peek_char(parser); switch (c) { case '{': // Parameter expansion in the form `${expression}` wp = expect_parameter_expression(parser); if (wp == NULL) { return NULL; } wp->dollar_pos = dollar_pos; return &wp->word; // Command substitution in the form `$(command)` or arithmetic expansion in // the form `$((expression))` case '(':; char next[2]; parser_peek(parser, next, sizeof(next)); if (next[1] == '(') { struct mrsh_word_arithmetic *wa = expect_word_arithmetic(parser); if (wa == NULL) { return NULL; } // TODO: store dollar_pos in wa return &wa->word; } else { struct mrsh_word_command *wc = expect_word_command(parser); if (wc == NULL) { return NULL; } // TODO: store dollar_pos in wc return &wc->word; } default:; // Parameter expansion in the form `$parameter` size_t name_len = peek_name(parser, false); if (name_len == 0) { bool ok = false; switch (c) { case '@': case '*': case '#': case '?': case '-': case '$': case '!': ok = true; break; default: ok = isdigit(c); } if (ok) { name_len = 1; } else { // 2.6. If an unquoted '$' is followed by a character that is // not one of the following […] the result is unspecified. parser_set_error(parser, "invalid parameter name"); return NULL; } } struct mrsh_range name_range; char *name = read_token(parser, name_len, &name_range); if (name == NULL) { return NULL; } wp = mrsh_word_parameter_create(name, MRSH_PARAM_NONE, false, NULL); wp->dollar_pos = dollar_pos; wp->name_range = name_range; return &wp->word; } } struct mrsh_word *back_quotes(struct mrsh_parser *parser) { struct mrsh_position begin = parser->pos; char c = parser_read_char(parser); assert(c == '`'); struct mrsh_buffer buf = {0}; while (true) { char c = parser_peek_char(parser); if (c == '\0') { parser_set_error(parser, "back quotes not terminated"); return NULL; } if (c == '`') { parser_read_char(parser); break; } if (c == '\\') { // Quoted backslash char next[2]; parser_peek(parser, next, sizeof(next)); switch (next[1]) { case '$': case '`': case '\\': parser_read_char(parser); c = next[1]; break; } } if (c == '\n') { read_continuation_line(parser); } else { parser_read_char(parser); } mrsh_buffer_append_char(&buf, c); } struct mrsh_parser *subparser = mrsh_parser_with_data(buf.data, buf.len); if (subparser == NULL) { goto error; } struct mrsh_program *prog = mrsh_parse_program(subparser); const char *err_msg = mrsh_parser_error(subparser, NULL); if (err_msg != NULL) { // TODO: how should we handle subparser error position? parser_set_error(parser, err_msg); mrsh_program_destroy(prog); goto error; } mrsh_parser_destroy(subparser); mrsh_buffer_finish(&buf); struct mrsh_word_command *wc = mrsh_word_command_create(prog, true); wc->range.begin = begin; wc->range.end = parser->pos; return &wc->word; error: mrsh_parser_destroy(subparser); mrsh_buffer_finish(&buf); return NULL; } /** * Append a new string word to `children` with the contents of `buf`, and reset * `buf`. */ static void push_buffer_word_string(struct mrsh_parser *parser, struct mrsh_array *children, struct mrsh_buffer *buf, struct mrsh_position *child_begin) { if (buf->len == 0) { *child_begin = (struct mrsh_position){0}; return; } mrsh_buffer_append_char(buf, '\0'); char *data = mrsh_buffer_steal(buf); struct mrsh_word_string *ws = mrsh_word_string_create(data, false); ws->range.begin = *child_begin; ws->range.end = parser->pos; mrsh_array_add(children, &ws->word); *child_begin = (struct mrsh_position){0}; } static struct mrsh_word *double_quotes(struct mrsh_parser *parser) { struct mrsh_position lquote_pos = parser->pos; char c = parser_read_char(parser); assert(c == '"'); struct mrsh_array children = {0}; struct mrsh_buffer buf = {0}; struct mrsh_position child_begin = {0}; struct mrsh_position rquote_pos = {0}; while (true) { if (!mrsh_position_valid(&child_begin)) { child_begin = parser->pos; } char c = parser_peek_char(parser); if (c == '\0') { parser_set_error(parser, "double quotes not terminated"); return NULL; } if (c == '"') { push_buffer_word_string(parser, &children, &buf, &child_begin); rquote_pos = parser->pos; parser_read_char(parser); break; } if (c == '$') { push_buffer_word_string(parser, &children, &buf, &child_begin); struct mrsh_word *t = expect_dollar(parser); if (t == NULL) { return NULL; } mrsh_array_add(&children, t); continue; } if (c == '`') { push_buffer_word_string(parser, &children, &buf, &child_begin); struct mrsh_word *t = back_quotes(parser); if (t == NULL) { return NULL; } mrsh_array_add(&children, t); continue; } if (c == '\\') { // Quoted backslash char next[2]; parser_peek(parser, next, sizeof(next)); switch (next[1]) { case '$': case '`': case '"': case '\\': parser_read_char(parser); c = next[1]; break; } if (next[1] == '\n') { parser_read_char(parser); // read backslash read_continuation_line(parser); continue; } } parser_read_char(parser); mrsh_buffer_append_char(&buf, c); } mrsh_buffer_finish(&buf); struct mrsh_word_list *wl = mrsh_word_list_create(&children, true); wl->lquote_pos = lquote_pos; wl->rquote_pos = rquote_pos; return &wl->word; } struct mrsh_word *word(struct mrsh_parser *parser, char end) { if (!symbol(parser, TOKEN)) { return NULL; } if (is_operator_start(parser_peek_char(parser)) || parser_peek_char(parser) == ')' || parser_peek_char(parser) == end) { return NULL; } struct mrsh_array children = {0}; struct mrsh_buffer buf = {0}; struct mrsh_position child_begin = {0}; while (true) { if (!mrsh_position_valid(&child_begin)) { child_begin = parser->pos; } char c = parser_peek_char(parser); if (c == '\0' || c == '\n' || c == ')' || c == end) { break; } if (c == '$') { push_buffer_word_string(parser, &children, &buf, &child_begin); struct mrsh_word *t = expect_dollar(parser); if (t == NULL) { return NULL; } mrsh_array_add(&children, t); continue; } if (c == '`') { push_buffer_word_string(parser, &children, &buf, &child_begin); struct mrsh_word *t = back_quotes(parser); if (t == NULL) { return NULL; } mrsh_array_add(&children, t); continue; } // Quoting if (c == '\'') { push_buffer_word_string(parser, &children, &buf, &child_begin); struct mrsh_word *t = single_quotes(parser); if (t == NULL) { return NULL; } mrsh_array_add(&children, t); continue; } if (c == '"') { push_buffer_word_string(parser, &children, &buf, &child_begin); struct mrsh_word *t = double_quotes(parser); if (t == NULL) { return NULL; } mrsh_array_add(&children, t); continue; } if (c == '\\') { // Unquoted backslash parser_read_char(parser); c = parser_peek_char(parser); if (c == '\n') { // Continuation line read_continuation_line(parser); continue; } } else if (is_operator_start(c) || isblank(c)) { break; } parser_read_char(parser); mrsh_buffer_append_char(&buf, c); } push_buffer_word_string(parser, &children, &buf, &child_begin); mrsh_buffer_finish(&buf); consume_symbol(parser); if (children.len == 1) { struct mrsh_word *word = children.data[0]; mrsh_array_finish(&children); // TODO: don't allocate this array return word; } else { struct mrsh_word_list *wl = mrsh_word_list_create(&children, false); return &wl->word; } } /* TODO remove end parameter when no *_word function takes it */ struct mrsh_word *arithmetic_word(struct mrsh_parser *parser, char end) { char next[3] = {0}; char c = parser_peek_char(parser); if (c == ')') { parser_peek(parser, next, sizeof(*next) * 2); // If arith_nested_parens != 0, we might be closing an expr. // E.g. $(((1+1 ))) // ^ if (!strcmp(next, "))") && parser->arith_nested_parens == 0) { return NULL; } } struct mrsh_array children = {0}; struct mrsh_buffer buf = {0}; struct mrsh_position child_begin = {0}; while (true) { if (!mrsh_position_valid(&child_begin)) { child_begin = parser->pos; } parser_peek(parser, next, sizeof(*next) * 2); c = next[0]; if (c == '\0' || c == '\n' || c == ';' || isblank(c) || (strcmp(next, "))") == 0 && parser->arith_nested_parens == 0)) { break; } if (c == '$') { push_buffer_word_string(parser, &children, &buf, &child_begin); struct mrsh_word *t = expect_dollar(parser); if (t == NULL) { return NULL; } mrsh_array_add(&children, t); continue; } if (c == '`') { push_buffer_word_string(parser, &children, &buf, &child_begin); struct mrsh_word *t = back_quotes(parser); if (t == NULL) { return NULL; } mrsh_array_add(&children, t); continue; } // Quoting if (c == '\'') { push_buffer_word_string(parser, &children, &buf, &child_begin); struct mrsh_word *t = single_quotes(parser); if (t == NULL) { return NULL; } mrsh_array_add(&children, t); continue; } if (c == '"') { push_buffer_word_string(parser, &children, &buf, &child_begin); struct mrsh_word *t = double_quotes(parser); if (t == NULL) { return NULL; } mrsh_array_add(&children, t); continue; } if (c == '\\') { // Unquoted backslash parser_read_char(parser); c = parser_peek_char(parser); if (c == '\n') { // Continuation line read_continuation_line(parser); continue; } } if (!strcmp(next, "<<") || !strcmp(next, ">>")) { parser_read_char(parser); mrsh_buffer_append_char(&buf, c); } if (c == '(') { parser->arith_nested_parens++; } else if (c == ')') { if (parser->arith_nested_parens == 0) { parser_set_error(parser, "unmatched closing parenthesis " "in arithmetic expression"); return NULL; } parser->arith_nested_parens--; } parser_read_char(parser); mrsh_buffer_append_char(&buf, c); } push_buffer_word_string(parser, &children, &buf, &child_begin); mrsh_buffer_finish(&buf); consume_symbol(parser); if (children.len == 1) { struct mrsh_word *word = children.data[0]; mrsh_array_finish(&children); // TODO: don't allocate this array return word; } else { struct mrsh_word_list *wl = mrsh_word_list_create(&children, false); return &wl->word; } } /** * Parses a word, only recognizing parameter expansion. Quoting and operators * are ignored. */ struct mrsh_word *parameter_expansion_word(struct mrsh_parser *parser) { struct mrsh_array children = {0}; struct mrsh_buffer buf = {0}; struct mrsh_position child_begin = {0}; while (true) { if (!mrsh_position_valid(&child_begin)) { child_begin = parser->pos; } char c = parser_peek_char(parser); if (c == '\0') { break; } if (c == '$') { push_buffer_word_string(parser, &children, &buf, &child_begin); struct mrsh_word *t = expect_dollar(parser); if (t == NULL) { return NULL; } mrsh_array_add(&children, t); continue; } if (c == '`') { push_buffer_word_string(parser, &children, &buf, &child_begin); struct mrsh_word *t = back_quotes(parser); if (t == NULL) { return NULL; } mrsh_array_add(&children, t); continue; } if (c == '\\') { // Unquoted backslash parser_read_char(parser); c = parser_peek_char(parser); if (c == '\n') { // Continuation line read_continuation_line(parser); continue; } } parser_read_char(parser); mrsh_buffer_append_char(&buf, c); } push_buffer_word_string(parser, &children, &buf, &child_begin); mrsh_buffer_finish(&buf); consume_symbol(parser); if (children.len == 1) { struct mrsh_word *word = children.data[0]; mrsh_array_finish(&children); // TODO: don't allocate this array return word; } else { struct mrsh_word_list *wl = mrsh_word_list_create(&children, false); return &wl->word; } } ================================================ FILE: shell/arithm.c ================================================ #include #include #include static bool run_variable(struct mrsh_state *state, const char *name, long *val, uint32_t *attribs) { const char *str = mrsh_env_get(state, name, attribs); if (str == NULL) { if ((state->options & MRSH_OPT_NOUNSET)) { fprintf(stderr, "%s: %s: unbound variable\n", state->frame->argv[0], name); return false; } *val = 0; // POSIX is not clear what to do in this case } else { char *end; *val = strtod(str, &end); if (end == str || end[0] != '\0') { fprintf(stderr, "%s: %s: not a number: %s\n", state->frame->argv[0], name, str); return false; } } return true; } static bool run_arithm_binop(struct mrsh_state *state, struct mrsh_arithm_binop *binop, long *result) { long left, right; if (!mrsh_run_arithm_expr(state, binop->left, &left)) { return false; } if (!mrsh_run_arithm_expr(state, binop->right, &right)) { return false; } switch (binop->type) { case MRSH_ARITHM_BINOP_ASTERISK: *result = left * right; return true; case MRSH_ARITHM_BINOP_SLASH: if (right == 0) { fprintf(stderr, "%s: division by zero: %ld/%ld\n", state->frame->argv[0], left, right); return false; } *result = left / right; return true; case MRSH_ARITHM_BINOP_PERCENT: if (right == 0) { fprintf(stderr, "%s: division by zero: %ld%%%ld\n", state->frame->argv[0], left, right); return false; } *result = left % right; return true; case MRSH_ARITHM_BINOP_PLUS: *result = left + right; return true; case MRSH_ARITHM_BINOP_MINUS: *result = left - right; return true; case MRSH_ARITHM_BINOP_DLESS: *result = left << right; return true; case MRSH_ARITHM_BINOP_DGREAT: *result = left >> right; return true; case MRSH_ARITHM_BINOP_LESS: *result = left < right; return true; case MRSH_ARITHM_BINOP_LESSEQ: *result = left <= right; return true; case MRSH_ARITHM_BINOP_GREAT: *result = left > right; return true; case MRSH_ARITHM_BINOP_GREATEQ: *result = left >= right; return true; case MRSH_ARITHM_BINOP_DEQ: *result = left == right; return true; case MRSH_ARITHM_BINOP_BANGEQ: *result = left != right; return true; case MRSH_ARITHM_BINOP_AND: *result = left & right; return true; case MRSH_ARITHM_BINOP_CIRC: *result = left ^ right; return true; case MRSH_ARITHM_BINOP_OR: *result = left | right; return true; case MRSH_ARITHM_BINOP_DAND: *result = left && right; return true; case MRSH_ARITHM_BINOP_DOR: *result = left || right; return true; } abort(); // Unknown binary arithmetic operation } static bool run_arithm_unop(struct mrsh_state *state, struct mrsh_arithm_unop *unop, long *result) { long val; if (!mrsh_run_arithm_expr(state, unop->body, &val)) { return false; } switch (unop->type) { case MRSH_ARITHM_UNOP_PLUS:; /* no-op */ return true; case MRSH_ARITHM_UNOP_MINUS:; *result = -val; return true; case MRSH_ARITHM_UNOP_TILDE:; *result = ~val; return true; case MRSH_ARITHM_UNOP_BANG:; *result = !val; return true; } abort(); // Unknown unary arithmetic operation } static bool run_arithm_cond(struct mrsh_state *state, struct mrsh_arithm_cond *cond, long *result) { long condition; if (!mrsh_run_arithm_expr(state, cond->condition, &condition)) { return false; } if (condition) { if (!mrsh_run_arithm_expr(state, cond->body, result)) { return false; } } else { if (!mrsh_run_arithm_expr(state, cond->else_part, result)) { return false; } } return true; } static long run_arithm_assign_op(enum mrsh_arithm_assign_op op, long cur, long val, long *result) { switch (op) { case MRSH_ARITHM_ASSIGN_NONE: *result = val; return true; case MRSH_ARITHM_ASSIGN_ASTERISK: *result = cur * val; return true; case MRSH_ARITHM_ASSIGN_SLASH: if (val == 0) { fprintf(stderr, "division by zero: %ld/%ld\n", cur, val); return false; } *result = cur / val; return true; case MRSH_ARITHM_ASSIGN_PERCENT: if (val == 0) { fprintf(stderr, "division by zero: %ld%%%ld\n", cur, val); return false; } *result = cur % val; return true; case MRSH_ARITHM_ASSIGN_PLUS: *result = cur + val; return true; case MRSH_ARITHM_ASSIGN_MINUS: *result = cur - val; return true; case MRSH_ARITHM_ASSIGN_DLESS: *result = cur << val; return true; case MRSH_ARITHM_ASSIGN_DGREAT: *result = cur >> val; return true; case MRSH_ARITHM_ASSIGN_AND: *result = cur & val; return true; case MRSH_ARITHM_ASSIGN_CIRC: *result = cur ^ val; return true; case MRSH_ARITHM_ASSIGN_OR: *result = cur | val; return true; } abort(); } static bool run_arithm_assign(struct mrsh_state *state, struct mrsh_arithm_assign *assign, long *result) { long val; if (!mrsh_run_arithm_expr(state, assign->value, &val)) { return false; } long cur = 0; uint32_t attribs = MRSH_VAR_ATTRIB_NONE; if (assign->op != MRSH_ARITHM_ASSIGN_NONE) { if (!run_variable(state, assign->name, &cur, &attribs)) { return false; } } if (!run_arithm_assign_op(assign->op, cur, val, result)) { return false; } char buf[32]; snprintf(buf, sizeof(buf), "%ld", *result); mrsh_env_set(state, assign->name, buf, attribs); return true; } bool mrsh_run_arithm_expr(struct mrsh_state *state, struct mrsh_arithm_expr *expr, long *result) { switch (expr->type) { case MRSH_ARITHM_LITERAL:; struct mrsh_arithm_literal *literal = (struct mrsh_arithm_literal *)expr; *result = literal->value; return true; case MRSH_ARITHM_VARIABLE:; struct mrsh_arithm_variable *variable = (struct mrsh_arithm_variable *)expr; return run_variable(state, variable->name, result, NULL); case MRSH_ARITHM_BINOP:; struct mrsh_arithm_binop *binop = (struct mrsh_arithm_binop *)expr; return run_arithm_binop(state, binop, result); case MRSH_ARITHM_UNOP:; struct mrsh_arithm_unop *unop = (struct mrsh_arithm_unop *)expr; return run_arithm_unop(state, unop, result); case MRSH_ARITHM_COND:; struct mrsh_arithm_cond *cond = (struct mrsh_arithm_cond *)expr; return run_arithm_cond(state, cond, result); case MRSH_ARITHM_ASSIGN:; struct mrsh_arithm_assign *assign = (struct mrsh_arithm_assign *)expr; return run_arithm_assign(state, assign, result); } abort(); } ================================================ FILE: shell/entry.c ================================================ #define _XOPEN_SOURCE 700 #include #include #include #include #include #include #include #include #include "builtin.h" #include "parser.h" #include "shell/path.h" #include "shell/trap.h" static char *expand_str(struct mrsh_state *state, const char *src) { struct mrsh_parser *parser = mrsh_parser_with_data(src, strlen(src)); if (parser == NULL) { return NULL; } struct mrsh_word *word = parameter_expansion_word(parser); if (word == NULL) { struct mrsh_position err_pos; const char *err_msg = mrsh_parser_error(parser, &err_pos); if (err_msg != NULL) { fprintf(stderr, "%d:%d: syntax error: %s\n", err_pos.line, err_pos.column, err_msg); } else { fprintf(stderr, "expand_str: unknown error\n"); } mrsh_parser_destroy(parser); return NULL; } mrsh_parser_destroy(parser); mrsh_run_word(state, &word); char *str = mrsh_word_str(word); mrsh_word_destroy(word); return str; } static char *expand_ps(struct mrsh_state *state, const char *name) { const char *ps = mrsh_env_get(state, name, NULL); if (ps == NULL) { return NULL; } char *str = expand_str(state, ps); if (str == NULL) { fprintf(stderr, "failed to expand '%s'\n", name); // On error, fallback to the default PSn value } return str; } char *mrsh_get_ps1(struct mrsh_state *state, int next_history_id) { // TODO: Replace ! with next history ID char *str = expand_ps(state, "PS1"); if (str != NULL) { return str; } char *p = malloc(3); sprintf(p, "%s", getuid() ? "$ " : "# "); return p; } char *mrsh_get_ps2(struct mrsh_state *state) { // TODO: Replace ! with next history ID char *str = expand_ps(state, "PS2"); if (str != NULL) { return str; } return strdup("> "); } char *mrsh_get_ps4(struct mrsh_state *state) { char *str = expand_ps(state, "PS4"); if (str != NULL) { return str; } return strdup("+ "); } bool mrsh_populate_env(struct mrsh_state *state, char **environ) { for (size_t i = 0; environ[i] != NULL; ++i) { char *eql = strchr(environ[i], '='); size_t klen = eql - environ[i]; char *key = strndup(environ[i], klen); char *val = &eql[1]; mrsh_env_set(state, key, val, MRSH_VAR_ATTRIB_EXPORT); free(key); } mrsh_env_set(state, "IFS", " \t\n", MRSH_VAR_ATTRIB_NONE); pid_t ppid = getppid(); char ppid_str[24]; snprintf(ppid_str, sizeof(ppid_str), "%d", ppid); mrsh_env_set(state, "PPID", ppid_str, MRSH_VAR_ATTRIB_NONE); // TODO check if path is well-formed, has . or .., and handle symbolic links const char *pwd = mrsh_env_get(state, "PWD", NULL); if (pwd == NULL) { char *cwd = current_working_dir(); if (cwd == NULL) { perror("current_working_dir failed"); return false; } mrsh_env_set(state, "PWD", cwd, MRSH_VAR_ATTRIB_EXPORT | MRSH_VAR_ATTRIB_READONLY); free(cwd); } else { mrsh_env_set(state, "PWD", pwd, MRSH_VAR_ATTRIB_EXPORT | MRSH_VAR_ATTRIB_READONLY); } mrsh_env_set(state, "OPTIND", "1", MRSH_VAR_ATTRIB_NONE); return true; } static void source_file(struct mrsh_state *state, char *path) { if (access(path, F_OK) == -1) { return; } char *env_argv[] = { ".", path }; mrsh_run_builtin(state, sizeof(env_argv) / sizeof(env_argv[0]), env_argv); } void mrsh_source_profile(struct mrsh_state *state) { source_file(state, "/etc/profile"); const char *home = getenv("HOME"); int n = snprintf(NULL, 0, "%s/.profile", home); if (n < 0) { perror("snprintf failed"); return; } char *path = malloc(n + 1); if (path == NULL) { perror("malloc failed"); return; } snprintf(path, n + 1, "%s/.profile", home); source_file(state, path); free(path); } void mrsh_source_env(struct mrsh_state *state) { char *path = getenv("ENV"); if (path == NULL) { return; } if (getuid() != geteuid() || getgid() != getegid()) { return; } path = expand_str(state, path); if (path[0] != '/') { fprintf(stderr, "Error: $ENV is not an absolute path; " "this is undefined behavior.\n"); fprintf(stderr, "Continuing without sourcing it.\n"); } else { source_file(state, path); } free(path); } bool mrsh_run_exit_trap(struct mrsh_state *state) { return run_exit_trap(state); } ================================================ FILE: shell/job.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include "shell/job.h" #include "shell/process.h" #include "shell/shell.h" #include "shell/task.h" bool mrsh_set_job_control(struct mrsh_state *state, bool enabled) { struct mrsh_state_priv *priv = state_get_priv(state); assert(priv->term_fd >= 0); if (priv->job_control == enabled) { return true; } if (enabled) { // Loop until we are in the foreground while (true) { pid_t pgid = getpgrp(); if (tcgetpgrp(priv->term_fd) == pgid) { break; } kill(-pgid, SIGTTIN); } // Ignore interactive and job-control signals set_job_control_traps(state, true); // Put ourselves in our own process group, if we aren't the session // leader priv->pgid = getpid(); if (getsid(0) != priv->pgid) { if (setpgid(priv->pgid, priv->pgid) != 0) { perror("setpgid"); return false; } } // Grab control of the terminal if (tcsetpgrp(priv->term_fd, priv->pgid) != 0) { perror("tcsetpgrp"); return false; } // Save default terminal attributes for the shell if (tcgetattr(priv->term_fd, &priv->term_modes) != 0) { perror("tcgetattr"); return false; } } else { set_job_control_traps(state, false); } priv->job_control = enabled; return true; } static void array_remove(struct mrsh_array *array, size_t i) { memmove(&array->data[i], &array->data[i + 1], (array->len - i - 1) * sizeof(void *)); --array->len; } struct mrsh_job *job_create(struct mrsh_state *state, const struct mrsh_node *node) { struct mrsh_state_priv *priv = state_get_priv(state); int id = 1; for (size_t i = 0; i < priv->jobs.len; ++i) { struct mrsh_job *job = priv->jobs.data[i]; if (id < job->job_id + 1) { id = job->job_id + 1; } } struct mrsh_job *job = calloc(1, sizeof(struct mrsh_job)); job->state = state; job->node = mrsh_node_copy(node); job->pgid = -1; job->job_id = id; job->last_status = TASK_STATUS_WAIT; mrsh_array_add(&priv->jobs, job); return job; } void job_destroy(struct mrsh_job *job) { if (job == NULL) { return; } struct mrsh_state_priv *priv = state_get_priv(job->state); if (priv->foreground_job == job) { job_set_foreground(job, false, false); } for (size_t i = 0; i < priv->jobs.len; ++i) { if (priv->jobs.data[i] == job) { array_remove(&priv->jobs, i); break; } } for (size_t j = 0; j < job->processes.len; ++j) { process_destroy(job->processes.data[j]); } mrsh_array_finish(&job->processes); mrsh_node_destroy(job->node); free(job); } void job_add_process(struct mrsh_job *job, struct mrsh_process *proc) { if (job->pgid <= 0) { job->pgid = proc->pid; } // This can fail because we do it both in the parent and the child if (setpgid(proc->pid, job->pgid) != 0 && errno != EPERM) { perror("setpgid"); return; } mrsh_array_add(&job->processes, proc); } static void job_queue_notification(struct mrsh_job *job) { struct mrsh_state_priv *priv = state_get_priv(job->state); int status = job_poll(job); if (status != job->last_status && job->pgid > 0 && priv->foreground_job != job) { job->pending_notification = true; } job->last_status = status; } bool job_set_foreground(struct mrsh_job *job, bool foreground, bool cont) { struct mrsh_state *state = job->state; struct mrsh_state_priv *priv = state_get_priv(state); assert(job->pgid > 0); if (!priv->job_control) { return false; } // Don't try to continue the job if it's not stopped if (job_poll(job) != TASK_STATUS_STOPPED) { cont = false; } if (foreground && priv->foreground_job != job) { assert(priv->foreground_job == NULL); // Put the job in the foreground tcsetpgrp(priv->term_fd, job->pgid); if (cont) { // Restore the job's terminal modes tcsetattr(priv->term_fd, TCSADRAIN, &job->term_modes); } priv->foreground_job = job; } if (!foreground && priv->foreground_job == job) { // Put the shell back in the foreground tcsetpgrp(priv->term_fd, priv->pgid); // Save the job's terminal modes, to restore them if it's put in the // foreground again tcgetattr(priv->term_fd, &job->term_modes); // Restore the shell’s terminal modes tcsetattr(priv->term_fd, TCSADRAIN, &priv->term_modes); priv->foreground_job = NULL; } if (cont) { if (kill(-job->pgid, SIGCONT) != 0) { perror("kill"); return false; } for (size_t j = 0; j < job->processes.len; ++j) { struct mrsh_process *proc = job->processes.data[j]; proc->stopped = false; } } job_queue_notification(job); return true; } int job_poll(struct mrsh_job *job) { int proc_status = 0; bool stopped = false; for (size_t j = 0; j < job->processes.len; ++j) { struct mrsh_process *proc = job->processes.data[j]; proc_status = process_poll(proc); if (proc_status == TASK_STATUS_WAIT) { return TASK_STATUS_WAIT; } if (proc_status == TASK_STATUS_STOPPED) { stopped = true; } } if (stopped) { return TASK_STATUS_STOPPED; } // All processes have terminated, return the last one's status return proc_status; } static void update_job(struct mrsh_state *state, pid_t pid, int stat); static bool _job_wait(struct mrsh_state *state, pid_t pid, int options) { struct mrsh_state_priv *priv = state_get_priv(state); assert(pid > 0 && pid != getpid()); // We only want to be notified about stopped processes in the main // shell. Child processes want to block until their own children have // terminated. if (!priv->child) { options |= WUNTRACED; } while (true) { // Here it's important to wait for a specific process: we don't want to // steal one of our grandchildren's status for one of our children. int stat; pid_t ret = waitpid(pid, &stat, options); if (ret == 0) { // no status available assert(options & WNOHANG); return true; } else if (ret < 0) { if (errno == EINTR) { continue; } fprintf(stderr, "waitpid(%d): %s\n", pid, strerror(errno)); return false; } assert(ret == pid); update_job(state, ret, stat); return true; } } static struct mrsh_process *job_get_running_process(struct mrsh_job *job) { for (size_t j = 0; j < job->processes.len; ++j) { struct mrsh_process *proc = job->processes.data[j]; if (process_poll(proc) == TASK_STATUS_WAIT) { return proc; } } return NULL; } int job_wait(struct mrsh_job *job) { while (true) { int status = job_poll(job); if (status != TASK_STATUS_WAIT) { return status; } struct mrsh_process *wait_proc = job_get_running_process(job); assert(wait_proc != NULL); if (!_job_wait(job->state, wait_proc->pid, 0)) { return TASK_STATUS_ERROR; } } } int job_wait_process(struct mrsh_process *proc) { while (true) { int status = process_poll(proc); if (status != TASK_STATUS_WAIT) { return status; } if (!_job_wait(proc->state, proc->pid, 0)) { return TASK_STATUS_ERROR; } } } bool refresh_jobs_status(struct mrsh_state *state) { struct mrsh_state_priv *priv = state_get_priv(state); for (size_t i = 0; i < priv->jobs.len; ++i) { struct mrsh_job *job = priv->jobs.data[i]; struct mrsh_process *proc = job_get_running_process(job); if (proc == NULL) { continue; } if (!_job_wait(job->state, proc->pid, WNOHANG)) { return false; } } return true; } bool init_job_child_process(struct mrsh_state *state) { return mrsh_set_job_control(state, false); } static void update_job(struct mrsh_state *state, pid_t pid, int stat) { struct mrsh_state_priv *priv = state_get_priv(state); update_process(state, pid, stat); if (!priv->job_control) { return; } // Put stopped and terminated jobs in the background. We don't want to do so // if we're not the main shell, because we only have a partial view of the // jobs (we only know about our own child processes). for (size_t i = 0; i < priv->jobs.len; ++i) { struct mrsh_job *job = priv->jobs.data[i]; int status = job_poll(job); if (status >= 0) { job_queue_notification(job); } if (status != TASK_STATUS_WAIT && job->pgid > 0) { job_set_foreground(job, false, false); } } } // https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_204 struct mrsh_job *job_by_id(struct mrsh_state *state, const char *id, bool interactive) { struct mrsh_state_priv *priv = state_get_priv(state); if (id[0] != '%' || id[1] == '\0') { if (interactive) { fprintf(stderr, "Invalid job ID specifier\n"); } return NULL; } if (id[2] == '\0') { switch (id[1]) { case '%': case '+': // Current job for (ssize_t i = priv->jobs.len - 1; i >= 0; --i) { struct mrsh_job *job = priv->jobs.data[i]; if (job_poll(job) == TASK_STATUS_STOPPED) { return job; } } for (ssize_t i = priv->jobs.len - 1; i >= 0; --i) { struct mrsh_job *job = priv->jobs.data[i]; if (job_poll(job) == TASK_STATUS_WAIT) { return job; } } if (interactive) { fprintf(stderr, "No current job\n"); } return NULL; case '-': // Previous job for (ssize_t i = priv->jobs.len - 1, n = 0; i >= 0; --i) { struct mrsh_job *job = priv->jobs.data[i]; if (job_poll(job) == TASK_STATUS_STOPPED) { if (++n == 2) { return job; } } } bool first = true; for (ssize_t i = priv->jobs.len - 1; i >= 0; --i) { struct mrsh_job *job = priv->jobs.data[i]; if (job_poll(job) == TASK_STATUS_WAIT) { if (first) { first = false; continue; } return job; } } if (interactive) { fprintf(stderr, "No previous job\n"); } return NULL; } } if (id[1] >= '0' && id[1] <= '9') { char *endptr; int n = strtol(&id[1], &endptr, 10); if (endptr[0] != '\0') { if (interactive) { fprintf(stderr, "Invalid job number '%s'\n", id); } return NULL; } for (size_t i = 0; i < priv->jobs.len; ++i) { struct mrsh_job *job = priv->jobs.data[i]; if (job->job_id == n) { return job; } } if (interactive) { fprintf(stderr, "No such job '%s' (%d)\n", id, n); } return NULL; } for (size_t i = 0; i < priv->jobs.len; i++) { struct mrsh_job *job = priv->jobs.data[i]; char *cmd = mrsh_node_format(job->node); bool match = false; switch (id[1]) { case '?': match = strstr(cmd, &id[2]) != NULL; break; default: match = strstr(cmd, &id[1]) == cmd; break; } free(cmd); if (match) { return job; } } if (interactive) { fprintf(stderr, "No such job '%s'\n", id); } return NULL; } const char *job_state_str(struct mrsh_job *job, bool r) { int status = job_poll(job); switch (status) { case TASK_STATUS_WAIT: return "Running"; case TASK_STATUS_ERROR: return "Error"; case TASK_STATUS_STOPPED: if (job->processes.len > 0) { struct mrsh_process *proc = job->processes.data[0]; switch (proc->signal) { case SIGSTOP: return r ? "Stopped (SIGSTOP)" : "Suspended (SIGSTOP)"; case SIGTTIN: return r ? "Stopped (SIGTTIN)" : "Suspended (SIGTTIN)"; case SIGTTOU: return r ? "Stopped (SIGTTOU)" : "Suspended (SIGTTOU)"; } } return r ? "Stopped" : "Suspended"; default: if (job->processes.len > 0) { struct mrsh_process *proc = job->processes.data[0]; if (proc->stat != 0) { static char stat[128]; snprintf(stat, sizeof(stat), "Done(%d)", proc->stat); return stat; } } assert(status >= 0); return "Done"; } } void broadcast_sighup_to_jobs(struct mrsh_state *state) { struct mrsh_state_priv *priv = state_get_priv(state); assert(priv->job_control); for (size_t i = 0; i < priv->jobs.len; ++i) { struct mrsh_job *job = priv->jobs.data[i]; if (job_poll(job) >= 0) { continue; } if (kill(-job->pgid, SIGHUP) != 0) { perror("kill"); } } } ================================================ FILE: shell/path.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include "shell/path.h" char *expand_path(struct mrsh_state *state, const char *file, bool exec, bool default_path) { if (strchr(file, '/')) { return strdup(file); } char *pathe; if (!default_path) { const char *_pathe = mrsh_env_get(state, "PATH", NULL); if (!_pathe) { return NULL; } pathe = strdup(_pathe); if (!pathe) { return NULL; } } else { size_t pathe_size = confstr(_CS_PATH, NULL, 0); if (pathe_size == 0) { return NULL; } pathe = malloc(pathe_size); if (pathe == NULL) { return NULL; } if (confstr(_CS_PATH, pathe, pathe_size) != pathe_size) { free(pathe); return NULL; } } char *path = NULL; char *basedir = strtok(pathe, ":"); while (basedir != NULL) { size_t blen = strlen(basedir); if (blen == 0) { goto next; } bool slash = basedir[blen - 1] == '/'; size_t n = snprintf(NULL, 0, "%s%s%s", basedir, slash ? "" : "/", file); path = realloc(path, n + 1); if (path == NULL) { goto next; } snprintf(path, n + 1, "%s%s%s", basedir, slash ? "" : "/", file); if (access(path, exec ? X_OK : R_OK) != -1) { free(pathe); return path; } next: basedir = strtok(NULL, ":"); } free(path); free(pathe); return NULL; } char *current_working_dir(void) { // POSIX doesn't provide a way to query the CWD size struct mrsh_buffer buf = {0}; if (mrsh_buffer_reserve(&buf, 256) == NULL) { return NULL; } while (getcwd(buf.data, buf.cap) == NULL) { if (errno != ERANGE) { return NULL; } if (mrsh_buffer_reserve(&buf, buf.cap * 2) == NULL) { return NULL; } } return mrsh_buffer_steal(&buf); } ================================================ FILE: shell/process.c ================================================ #define _POSIX_C_SOURCE 1 #include #include #include #include #include #include #include #include "shell/process.h" #include "shell/task.h" struct mrsh_process *process_create(struct mrsh_state *state, pid_t pid) { struct mrsh_state_priv *priv = state_get_priv(state); struct mrsh_process *proc = calloc(1, sizeof(struct mrsh_process)); proc->pid = pid; proc->state = state; mrsh_array_add(&priv->processes, proc); return proc; } static void array_remove(struct mrsh_array *array, size_t i) { memmove(&array->data[i], &array->data[i + 1], (array->len - i - 1) * sizeof(void *)); --array->len; } void process_destroy(struct mrsh_process *proc) { struct mrsh_state_priv *priv = state_get_priv(proc->state); for (size_t i = 0; i < priv->processes.len; ++i) { if (priv->processes.data[i] == proc) { array_remove(&priv->processes, i); break; } } free(proc); } int process_poll(struct mrsh_process *proc) { if (proc->stopped) { return TASK_STATUS_STOPPED; } else if (!proc->terminated) { return TASK_STATUS_WAIT; } if (WIFEXITED(proc->stat)) { return WEXITSTATUS(proc->stat); } else if (WIFSIGNALED(proc->stat)) { return 129; // POSIX requires >128 } else { abort(); } } void update_process(struct mrsh_state *state, pid_t pid, int stat) { struct mrsh_state_priv *priv = state_get_priv(state); struct mrsh_process *proc = NULL; bool found = false; for (size_t i = 0; i < priv->processes.len; ++i) { proc = priv->processes.data[i]; if (proc->pid == pid) { found = true; break; } } if (!found) { return; } if (WIFEXITED(stat) || WIFSIGNALED(stat)) { proc->terminated = true; proc->stat = stat; } else if (WIFSTOPPED(stat)) { proc->stopped = true; proc->signal = WSTOPSIG(stat); } else { abort(); } } ================================================ FILE: shell/redir.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include "shell/redir.h" static ssize_t write_here_document_line(int fd, struct mrsh_word *line, ssize_t max_size) { char *line_str = mrsh_word_str(line); size_t line_len = strlen(line_str); size_t write_len = line_len + 1; // line + terminating \n if (max_size >= 0 && write_len > (size_t)max_size) { free(line_str); return 0; } errno = 0; ssize_t n = write(fd, line_str, line_len); free(line_str); if (n < 0 || (size_t)n != line_len) { goto err_write; } if (write(fd, "\n", sizeof(char)) != 1) { goto err_write; } return write_len; err_write: fprintf(stderr, "write() failed: %s\n", errno ? strerror(errno) : "short write"); return -1; } static int create_here_document_fd(const struct mrsh_array *lines) { int fds[2]; if (pipe(fds) != 0) { perror("pipe"); return -1; } // We can write at most PIPE_BUF bytes without blocking. If we want to write // more, we need to fork and continue writing in another process. size_t remaining = PIPE_BUF; bool more = false; size_t i; for (i = 0; i < lines->len; ++i) { struct mrsh_word *line = lines->data[i]; ssize_t n = write_here_document_line(fds[1], line, remaining); if (n < 0) { close(fds[0]); close(fds[1]); return -1; } else if (n == 0) { more = true; break; } } if (!more) { // We could write everything into the pipe buffer close(fds[1]); return fds[0]; } pid_t pid = fork(); if (pid < 0) { perror("fork"); close(fds[0]); close(fds[1]); return -1; } else if (pid == 0) { close(fds[0]); for (; i < lines->len; ++i) { struct mrsh_word *line = lines->data[i]; ssize_t n = write_here_document_line(fds[1], line, -1); if (n < 0) { close(fds[1]); exit(1); } } close(fds[1]); exit(0); } close(fds[1]); return fds[0]; } static int parse_fd(const char *str) { char *endptr; errno = 0; int fd = strtol(str, &endptr, 10); if (errno != 0) { return -1; } if (endptr[0] != '\0') { errno = EINVAL; return -1; } return fd; } int process_redir(const struct mrsh_io_redirect *redir, int *redir_fd) { // TODO: filename expansions char *filename = mrsh_word_str(redir->name); int fd = -1, default_redir_fd = -1; errno = 0; switch (redir->op) { case MRSH_IO_LESS: // < fd = open(filename, O_CLOEXEC | O_RDONLY); default_redir_fd = STDIN_FILENO; break; case MRSH_IO_GREAT: // > case MRSH_IO_CLOBBER: // >| fd = open(filename, O_CLOEXEC | O_WRONLY | O_CREAT | O_TRUNC, 0644); default_redir_fd = STDOUT_FILENO; break; case MRSH_IO_DGREAT: // >> fd = open(filename, O_CLOEXEC | O_WRONLY | O_CREAT | O_APPEND, 0644); default_redir_fd = STDOUT_FILENO; break; case MRSH_IO_LESSAND: // <& // TODO: parse "-" fd = parse_fd(filename); default_redir_fd = STDIN_FILENO; break; case MRSH_IO_GREATAND: // >& // TODO: parse "-" fd = parse_fd(filename); default_redir_fd = STDOUT_FILENO; break; case MRSH_IO_LESSGREAT: // <> fd = open(filename, O_CLOEXEC | O_RDWR | O_CREAT, 0644); default_redir_fd = STDIN_FILENO; break; case MRSH_IO_DLESS: // << case MRSH_IO_DLESSDASH: // <<- fd = create_here_document_fd(&redir->here_document); default_redir_fd = STDIN_FILENO; break; } if (fd < 0) { fprintf(stderr, "cannot open %s: %s\n", filename, strerror(errno)); return -1; } free(filename); *redir_fd = redir->io_number; if (*redir_fd < 0) { *redir_fd = default_redir_fd; } return fd; } ================================================ FILE: shell/shell.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include "shell/job.h" #include "shell/shell.h" #include "shell/process.h" void function_destroy(struct mrsh_function *fn) { if (!fn) { return; } mrsh_command_destroy(fn->body); free(fn); } struct mrsh_state *mrsh_state_create(void) { struct mrsh_state_priv *priv = calloc(1, sizeof(*priv)); if (priv == NULL) { return NULL; } priv->term_fd = STDIN_FILENO; struct mrsh_state *state = &priv->pub; state->exit = -1; struct mrsh_call_frame_priv *frame_priv = calloc(1, sizeof(struct mrsh_call_frame_priv)); if (frame_priv == NULL) { free(priv); return NULL; } state->frame = &frame_priv->pub; return state; } static const char *get_alias_func(const char *name, void *data) { struct mrsh_state *state = data; struct mrsh_state_priv *priv = state_get_priv(state); return mrsh_hashtable_get(&priv->aliases, name); } void mrsh_state_set_parser_alias_func( struct mrsh_state *state, struct mrsh_parser *parser) { mrsh_parser_set_alias_func(parser, get_alias_func, state); } static void state_string_finish_iterator(const char *key, void *value, void *user_data) { free(value); } static void variable_destroy(struct mrsh_variable *var) { if (!var) { return; } free(var->value); free(var); } static void state_var_finish_iterator(const char *key, void *value, void *user_data) { variable_destroy((struct mrsh_variable *)value); } static void state_fn_finish_iterator(const char *key, void *value, void *_) { function_destroy((struct mrsh_function *)value); } static void call_frame_destroy(struct mrsh_call_frame *frame) { for (int i = 0; i < frame->argc; ++i) { free(frame->argv[i]); } free(frame->argv); free(frame); } void mrsh_state_destroy(struct mrsh_state *state) { struct mrsh_state_priv *priv = state_get_priv(state); if (priv->job_control) { broadcast_sighup_to_jobs(state); } mrsh_hashtable_for_each(&priv->variables, state_var_finish_iterator, NULL); mrsh_hashtable_finish(&priv->variables); mrsh_hashtable_for_each(&priv->functions, state_fn_finish_iterator, NULL); mrsh_hashtable_finish(&priv->functions); mrsh_hashtable_for_each(&priv->aliases, state_string_finish_iterator, NULL); mrsh_hashtable_finish(&priv->aliases); while (priv->jobs.len > 0) { job_destroy(priv->jobs.data[priv->jobs.len - 1]); } mrsh_array_finish(&priv->jobs); while (priv->processes.len > 0) { process_destroy(priv->processes.data[priv->processes.len - 1]); } mrsh_array_finish(&priv->processes); struct mrsh_call_frame *frame = state->frame; while (frame) { struct mrsh_call_frame *prev = frame->prev; call_frame_destroy(frame); frame = prev; } for (size_t i = 0; i < MRSH_NSIG; i++) { mrsh_program_destroy(priv->traps[i].program); } free(state); } struct mrsh_state_priv *state_get_priv(struct mrsh_state *state) { return (struct mrsh_state_priv *)state; } void mrsh_env_set(struct mrsh_state *state, const char *key, const char *value, uint32_t attribs) { struct mrsh_state_priv *priv = state_get_priv(state); struct mrsh_variable *var = calloc(1, sizeof(struct mrsh_variable)); if (!var) { return; } var->value = strdup(value); var->attribs = attribs; struct mrsh_variable *old = mrsh_hashtable_set(&priv->variables, key, var); variable_destroy(old); } void mrsh_env_unset(struct mrsh_state *state, const char *key) { struct mrsh_state_priv *priv = state_get_priv(state); variable_destroy(mrsh_hashtable_del(&priv->variables, key)); } const char *mrsh_env_get(struct mrsh_state *state, const char *key, uint32_t *attribs) { struct mrsh_state_priv *priv = state_get_priv(state); struct mrsh_variable *var = mrsh_hashtable_get(&priv->variables, key); if (var && attribs) { *attribs = var->attribs; } return var ? var->value : NULL; } struct mrsh_call_frame_priv *call_frame_get_priv(struct mrsh_call_frame *frame) { return (struct mrsh_call_frame_priv *)frame; } void push_frame(struct mrsh_state *state, int argc, const char *argv[]) { struct mrsh_call_frame_priv *next = calloc(1, sizeof(*next)); next->pub.argc = argc; next->pub.argv = calloc(argc + 1, sizeof(char *)); for (int i = 0; i < argc; ++i) { next->pub.argv[i] = strdup(argv[i]); } next->pub.prev = state->frame; state->frame = &next->pub; } void pop_frame(struct mrsh_state *state) { struct mrsh_call_frame *frame = state->frame; assert(frame->prev != NULL); state->frame = frame->prev; call_frame_destroy(frame); } ================================================ FILE: shell/task/pipeline.c ================================================ #include #include #include #include #include #include "shell/task.h" /** * Put the process into its job's process group. This has to be done both in the * parent and the child because of potential race conditions. */ static struct mrsh_process *init_child(struct mrsh_context *ctx, pid_t pid) { struct mrsh_process *proc = process_create(ctx->state, pid); if (ctx->state->options & MRSH_OPT_MONITOR) { job_add_process(ctx->job, proc); if (ctx->state->interactive && !ctx->background) { job_set_foreground(ctx->job, true, false); } } return proc; } int run_pipeline(struct mrsh_context *ctx, struct mrsh_pipeline *pl) { struct mrsh_state_priv *priv = state_get_priv(ctx->state); // Create a new sub-context, because we want one job per pipeline. struct mrsh_context child_ctx = *ctx; if (child_ctx.job == NULL) { child_ctx.job = job_create(ctx->state, &pl->and_or_list.node); } assert(pl->commands.len > 0); if (pl->commands.len == 1) { int ret = run_command(&child_ctx, pl->commands.data[0]); if (pl->bang && ret >= 0) { ret = !ret; } return ret; } struct mrsh_array procs = {0}; mrsh_array_reserve(&procs, pl->commands.len); int next_stdin = -1, cur_stdin = -1, cur_stdout = -1; for (size_t i = 0; i < pl->commands.len; ++i) { struct mrsh_command *cmd = pl->commands.data[i]; if (i < pl->commands.len - 1) { int fds[2]; if (pipe(fds) != 0) { perror("pipe"); return TASK_STATUS_ERROR; } // We'll use the write end of the pipe as stdout, the read end will // be used as stdin by the next command assert(next_stdin == -1 && cur_stdout == -1); next_stdin = fds[0]; cur_stdout = fds[1]; } pid_t pid = fork(); if (pid < 0) { return TASK_STATUS_ERROR; } else if (pid == 0) { priv->child = true; init_child(&child_ctx, getpid()); if (ctx->state->options & MRSH_OPT_MONITOR) { init_job_child_process(ctx->state); } if (next_stdin >= 0) { close(next_stdin); } if (i > 0 && cur_stdin != STDIN_FILENO) { if (dup2(cur_stdin, STDIN_FILENO) < 0) { fprintf(stderr, "failed to duplicate stdin: %s\n", strerror(errno)); return false; } close(cur_stdin); } if (i < pl->commands.len - 1 && cur_stdout != STDOUT_FILENO) { if (dup2(cur_stdout, STDOUT_FILENO) < 0) { fprintf(stderr, "failed to duplicate stdout: %s\n", strerror(errno)); return false; } close(cur_stdout); } int ret = run_command(&child_ctx, cmd); if (ret < 0) { exit(127); } exit(ret); } struct mrsh_process *proc = init_child(&child_ctx, pid); mrsh_array_add(&procs, proc); if (cur_stdin >= 0) { close(cur_stdin); cur_stdin = -1; } if (cur_stdout >= 0) { close(cur_stdout); cur_stdout = -1; } cur_stdin = next_stdin; next_stdin = -1; } assert(next_stdin == -1 && cur_stdout == -1 && cur_stdin == -1); int ret = 0; for (size_t i = 0; i < procs.len; ++i) { struct mrsh_process *proc = procs.data[i]; ret = job_wait_process(proc); if (ret < 0) { break; } } mrsh_array_finish(&procs); if (pl->bang && ret >= 0) { ret = !ret; } return ret; } ================================================ FILE: shell/task/simple_command.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include "shell/shell.h" #include "shell/path.h" #include "shell/redir.h" #include "shell/word.h" #include "shell/task.h" static void populate_env_iterator(const char *key, void *_var, void *_) { struct mrsh_variable *var = _var; if ((var->attribs & MRSH_VAR_ATTRIB_EXPORT)) { setenv(key, var->value, 1); } } /** * Put the process into its job's process group. This has to be done both in the * parent and the child because of potential race conditions. */ static struct mrsh_process *init_child(struct mrsh_context *ctx, pid_t pid) { struct mrsh_process *proc = process_create(ctx->state, pid); if (ctx->state->options & MRSH_OPT_MONITOR) { job_add_process(ctx->job, proc); if (ctx->state->interactive && !ctx->background) { job_set_foreground(ctx->job, true, false); } } return proc; } static int run_process(struct mrsh_context *ctx, struct mrsh_simple_command *sc, char **argv) { struct mrsh_state *state = ctx->state; struct mrsh_state_priv *priv = state_get_priv(state); // The pipeline is responsible for creating the job assert(ctx->job != NULL); char *path = expand_path(ctx->state, argv[0], true, false); if (!path) { fprintf(stderr, "%s: not found\n", argv[0]); return 127; } pid_t pid = fork(); if (pid < 0) { perror("fork"); return TASK_STATUS_ERROR; } else if (pid == 0) { init_child(ctx, getpid()); if (state->options & MRSH_OPT_MONITOR) { init_job_child_process(state); } for (size_t i = 0; i < sc->assignments.len; ++i) { struct mrsh_assignment *assign = sc->assignments.data[i]; uint32_t prev_attribs; if (mrsh_env_get(state, assign->name, &prev_attribs) && (prev_attribs & MRSH_VAR_ATTRIB_READONLY)) { fprintf(stderr, "cannot modify readonly variable %s\n", assign->name); exit(1); } char *value = mrsh_word_str(assign->value); setenv(assign->name, value, true); free(value); } mrsh_hashtable_for_each(&priv->variables, populate_env_iterator, NULL); for (size_t i = 0; i < sc->io_redirects.len; ++i) { struct mrsh_io_redirect *redir = sc->io_redirects.data[i]; int redir_fd; int fd = process_redir(redir, &redir_fd); if (fd < 0) { exit(1); } if (fd == redir_fd) { continue; } int ret = dup2(fd, redir_fd); if (ret < 0) { fprintf(stderr, "cannot duplicate file descriptor: %s\n", strerror(errno)); exit(1); } } execv(path, argv); // Something went wrong fprintf(stderr, "%s: %s\n", argv[0], strerror(errno)); exit(127); } free(path); struct mrsh_process *process = init_child(ctx, pid); return job_wait_process(process); } struct saved_fd { int dup_fd; int redir_fd; }; static bool dup_and_save_fd(int fd, int redir_fd, struct saved_fd *saved) { saved->redir_fd = redir_fd; saved->dup_fd = -1; if (fd == redir_fd) { return true; } saved->dup_fd = dup(redir_fd); if (saved->dup_fd < 0) { fprintf(stderr, "failed to duplicate file descriptor: %s\n", strerror(errno)); return false; } if (dup2(fd, redir_fd) < 0) { fprintf(stderr, "failed to duplicate file descriptor: %s\n", strerror(errno)); return false; } return true; } static int run_builtin(struct mrsh_context *ctx, struct mrsh_simple_command *sc, int argc, char **argv) { // Duplicate old FDs to be able to restore them later // Zero-length VLAs are undefined behaviour struct saved_fd fds[sc->io_redirects.len + 1]; for (size_t i = 0; i < sizeof(fds) / sizeof(fds[0]); ++i) { fds[i].dup_fd = fds[i].redir_fd = -1; } for (size_t i = 0; i < sc->io_redirects.len; ++i) { struct mrsh_io_redirect *redir = sc->io_redirects.data[i]; struct saved_fd *saved = &fds[i]; int redir_fd; int fd = process_redir(redir, &redir_fd); if (fd < 0) { return TASK_STATUS_ERROR; } if (!dup_and_save_fd(fd, redir_fd, saved)) { return TASK_STATUS_ERROR; } } // TODO: environment from assignements int ret = mrsh_run_builtin(ctx->state, argc, argv); // In case stdout/stderr are pipes, we need to flush to ensure output lines // aren't out-of-order fflush(stdout); fflush(stderr); // Restore old FDs for (size_t i = 0; i < sizeof(fds) / sizeof(fds[0]); ++i) { if (fds[i].dup_fd < 0) { continue; } if (dup2(fds[i].dup_fd, fds[i].redir_fd) < 0) { fprintf(stderr, "failed to duplicate file descriptor: %s\n", strerror(errno)); return TASK_STATUS_ERROR; } close(fds[i].dup_fd); } return ret; } static int run_assignments(struct mrsh_context *ctx, struct mrsh_array *assignments) { for (size_t i = 0; i < assignments->len; ++i) { struct mrsh_assignment *assign = assignments->data[i]; char *new_value = mrsh_word_str(assign->value); uint32_t attribs = MRSH_VAR_ATTRIB_NONE; if ((ctx->state->options & MRSH_OPT_ALLEXPORT)) { attribs = MRSH_VAR_ATTRIB_EXPORT; } uint32_t prev_attribs = 0; if (mrsh_env_get(ctx->state, assign->name, &prev_attribs) != NULL && (prev_attribs & MRSH_VAR_ATTRIB_READONLY)) { free(new_value); fprintf(stderr, "cannot modify readonly variable %s\n", assign->name); return TASK_STATUS_ERROR; } mrsh_env_set(ctx->state, assign->name, new_value, attribs); free(new_value); } return 0; } static int expand_assignments(struct mrsh_context *ctx, struct mrsh_array *assignments) { for (size_t i = 0; i < assignments->len; ++i) { struct mrsh_assignment *assign = assignments->data[i]; expand_tilde(ctx->state, &assign->value, true); int ret = run_word(ctx, &assign->value); if (ret < 0) { return ret; } } return 0; } static struct mrsh_simple_command *copy_simple_command( const struct mrsh_simple_command *sc) { struct mrsh_command *cmd = mrsh_command_copy(&sc->command); return mrsh_command_get_simple_command(cmd); } int run_simple_command(struct mrsh_context *ctx, struct mrsh_simple_command *sc) { struct mrsh_state *state = ctx->state; struct mrsh_state_priv *priv = state_get_priv(state); if (sc->name == NULL) { // Copy each assignment from the AST, because during expansion and // substitution we'll mutate the tree struct mrsh_array assignments = {0}; mrsh_array_reserve(&assignments, sc->assignments.len); for (size_t i = 0; i < sc->assignments.len; ++i) { struct mrsh_assignment *assign = sc->assignments.data[i]; mrsh_array_add(&assignments, mrsh_assignment_copy(assign)); } int ret = expand_assignments(ctx, &assignments); if (ret < 0) { return ret; } ret = run_assignments(ctx, &assignments); if (ret < 0) { return ret; } for (size_t i = 0; i < assignments.len; ++i) { struct mrsh_assignment *assign = assignments.data[i]; mrsh_assignment_destroy(assign); } mrsh_array_finish(&assignments); return 0; } // Copy the command from the AST, because during expansion and substitution // we'll mutate the tree sc = copy_simple_command(sc); struct mrsh_array args = {0}; int ret = expand_word(ctx, sc->name, &args); if (ret < 0) { return ret; } for (size_t i = 0; i < sc->arguments.len; ++i) { struct mrsh_word *arg = sc->arguments.data[i]; ret = expand_word(ctx, arg, &args); if (ret < 0) { return ret; } } assert(args.len > 0); mrsh_array_add(&args, NULL); ret = expand_assignments(ctx, &sc->assignments); if (ret < 0) { return ret; } for (size_t i = 0; i < sc->io_redirects.len; ++i) { struct mrsh_io_redirect *redir = sc->io_redirects.data[i]; expand_tilde(state, &redir->name, false); ret = run_word(ctx, &redir->name); if (ret < 0) { return ret; } for (size_t j = 0; j < redir->here_document.len; ++j) { struct mrsh_word **line_word_ptr = (struct mrsh_word **)&redir->here_document.data[j]; expand_tilde(state, line_word_ptr, false); ret = run_word(ctx, line_word_ptr); if (ret < 0) { return ret; } } } char **argv = (char **)args.data; int argc = args.len - 1; // argv is NULL-terminated const char *argv_0 = argv[0]; if ((state->options & MRSH_OPT_XTRACE)) { char *ps4 = mrsh_get_ps4(state); fprintf(stderr, "%s", ps4); for (int i = 0; i < argc; ++i) { fprintf(stderr, "%s%s", i > 0 ? " " : "", argv[i]); } fprintf(stderr, "\n"); free(ps4); } ret = -1; const struct mrsh_function *fn_def = mrsh_hashtable_get(&priv->functions, argv_0); if (fn_def != NULL) { push_frame(state, argc, (const char **)argv); // fn_def may be free'd during run_command when overwritten with another // function, so we need to copy it. struct mrsh_command *body = mrsh_command_copy(fn_def->body); ret = run_command(ctx, body); mrsh_command_destroy(body); pop_frame(state); } else if (mrsh_has_builtin(argv_0)) { ret = run_builtin(ctx, sc, argc, argv); } else { ret = run_process(ctx, sc, argv); } mrsh_command_destroy(&sc->command); for (size_t i = 0; i < args.len; ++i) { free(args.data[i]); } mrsh_array_finish(&args); return ret; } ================================================ FILE: shell/task/task.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include "shell/shell.h" #include "shell/task.h" #include "shell/trap.h" static int run_subshell(struct mrsh_context *ctx, struct mrsh_array *array) { struct mrsh_state_priv *priv = state_get_priv(ctx->state); pid_t pid = fork(); if (pid < 0) { perror("fork"); return TASK_STATUS_ERROR; } else if (pid == 0) { priv->child = true; reset_caught_traps(ctx->state); if (!(ctx->state->options & MRSH_OPT_MONITOR)) { // If job control is disabled, stdin is /dev/null int fd = open("/dev/null", O_CLOEXEC | O_RDONLY); if (fd < 0) { fprintf(stderr, "failed to open /dev/null: %s\n", strerror(errno)); exit(1); } if (fd != STDIN_FILENO) { dup2(fd, STDIN_FILENO); close(fd); } } int ret = run_command_list_array(ctx, array); if (ret < 0) { exit(127); } if (ctx->state->exit >= 0) { exit(ctx->state->exit); } exit(ret); } struct mrsh_process *proc = process_create(ctx->state, pid); return job_wait_process(proc); } static int run_if_clause(struct mrsh_context *ctx, struct mrsh_if_clause *ic) { int ret = run_command_list_array(ctx, &ic->condition); if (ret < 0) { return ret; } if (ret == 0) { return run_command_list_array(ctx, &ic->body); } else { if (ic->else_part) { return run_command(ctx, ic->else_part); } return 0; } } static int run_loop_clause(struct mrsh_context *ctx, struct mrsh_loop_clause *lc) { struct mrsh_call_frame_priv *frame_priv = call_frame_get_priv(ctx->state->frame); int loop_num = ++frame_priv->nloops; int loop_ret = 0; while (ctx->state->exit == -1) { int ret = run_command_list_array(ctx, &lc->condition); if (ret == TASK_STATUS_INTERRUPTED) { goto interrupt; } else if (ret < 0) { return ret; } bool break_loop; switch (lc->type) { case MRSH_LOOP_WHILE: break_loop = ret > 0; break; case MRSH_LOOP_UNTIL: break_loop = ret == 0; break; } if (break_loop) { break; } loop_ret = run_command_list_array(ctx, &lc->body); if (loop_ret == TASK_STATUS_INTERRUPTED) { goto interrupt; } else if (loop_ret < 0) { return loop_ret; } continue; interrupt: if (frame_priv->nloops < loop_num) { loop_ret = TASK_STATUS_INTERRUPTED; // break to parent loop break; } switch (frame_priv->branch_control) { case MRSH_BRANCH_BREAK: case MRSH_BRANCH_RETURN: case MRSH_BRANCH_EXIT: break_loop = true; loop_ret = 0; break; case MRSH_BRANCH_CONTINUE: break; } if (break_loop) { break; } } --frame_priv->nloops; return loop_ret; } static int run_for_clause(struct mrsh_context *ctx, struct mrsh_for_clause *fc) { struct mrsh_call_frame_priv *frame_priv = call_frame_get_priv(ctx->state->frame); int loop_num = ++frame_priv->nloops; struct mrsh_array fields = {0}; for (size_t i = 0; i < fc->word_list.len; i++) { struct mrsh_word *word = fc->word_list.data[i]; int ret = expand_word(ctx, word, &fields); if (ret < 0) { return ret; } } int loop_ret = 0; size_t word_index = 0; while (ctx->state->exit == -1) { if (word_index == fields.len) { break; } mrsh_env_set(ctx->state, fc->name, fields.data[word_index], MRSH_VAR_ATTRIB_NONE); word_index++; loop_ret = run_command_list_array(ctx, &fc->body); if (loop_ret == TASK_STATUS_INTERRUPTED) { goto interrupt; } else if (loop_ret < 0) { return loop_ret; } continue; interrupt: if (frame_priv->nloops < loop_num) { loop_ret = TASK_STATUS_INTERRUPTED; // break to parent loop break; } bool break_loop = false; switch (frame_priv->branch_control) { case MRSH_BRANCH_BREAK: case MRSH_BRANCH_RETURN: case MRSH_BRANCH_EXIT: break_loop = true; loop_ret = 0; break; case MRSH_BRANCH_CONTINUE: break; } if (break_loop) { break; } } for (size_t i = 0; i < fields.len; i++) { free(fields.data[i]); } mrsh_array_finish(&fields); --frame_priv->nloops; return loop_ret; } static int run_case_clause(struct mrsh_context *ctx, struct mrsh_case_clause *cc) { struct mrsh_word *word = mrsh_word_copy(cc->word); expand_tilde(ctx->state, &word, false); int ret = run_word(ctx, &word); if (ret < 0) { mrsh_word_destroy(word); return ret; } char *word_str = mrsh_word_str(word); mrsh_word_destroy(word); int case_ret = 0; for (size_t i = 0; i < cc->items.len; ++i) { struct mrsh_case_item *ci = cc->items.data[i]; bool selected = false; for (size_t j = 0; j < ci->patterns.len; ++j) { // TODO: this mutates the AST struct mrsh_word **word_ptr = (struct mrsh_word **)&ci->patterns.data[j]; expand_tilde(ctx->state, word_ptr, false); int ret = run_word(ctx, word_ptr); if (ret < 0) { return ret; } char *pattern = word_to_pattern(*word_ptr); if (pattern != NULL) { selected = fnmatch(pattern, word_str, 0) == 0; free(pattern); } else { char *str = mrsh_word_str(*word_ptr); selected = strcmp(str, word_str) == 0; free(str); } if (selected) { break; } } if (selected) { case_ret = run_command_list_array(ctx, &ci->body); break; } } free(word_str); return case_ret; } static int run_function_definition(struct mrsh_context *ctx, struct mrsh_function_definition *fnd) { struct mrsh_state_priv *priv = state_get_priv(ctx->state); struct mrsh_function *fn = calloc(1, sizeof(struct mrsh_function)); fn->body = mrsh_command_copy(fnd->body); struct mrsh_function *old_fn = mrsh_hashtable_set(&priv->functions, fnd->name, fn); function_destroy(old_fn); return 0; } int run_command(struct mrsh_context *ctx, struct mrsh_command *cmd) { switch (cmd->type) { case MRSH_SIMPLE_COMMAND:; struct mrsh_simple_command *sc = mrsh_command_get_simple_command(cmd); return run_simple_command(ctx, sc); case MRSH_BRACE_GROUP:; struct mrsh_brace_group *bg = mrsh_command_get_brace_group(cmd); return run_command_list_array(ctx, &bg->body); case MRSH_SUBSHELL:; struct mrsh_subshell *s = mrsh_command_get_subshell(cmd); return run_subshell(ctx, &s->body); case MRSH_IF_CLAUSE:; struct mrsh_if_clause *ic = mrsh_command_get_if_clause(cmd); return run_if_clause(ctx, ic); case MRSH_LOOP_CLAUSE:; struct mrsh_loop_clause *lc = mrsh_command_get_loop_clause(cmd); return run_loop_clause(ctx, lc); case MRSH_FOR_CLAUSE:; struct mrsh_for_clause *fc = mrsh_command_get_for_clause(cmd); return run_for_clause(ctx, fc); case MRSH_CASE_CLAUSE:; struct mrsh_case_clause *cc = mrsh_command_get_case_clause(cmd); return run_case_clause(ctx, cc); case MRSH_FUNCTION_DEFINITION:; struct mrsh_function_definition *fnd = mrsh_command_get_function_definition(cmd); return run_function_definition(ctx, fnd); } abort(); } int run_and_or_list(struct mrsh_context *ctx, struct mrsh_and_or_list *and_or_list) { switch (and_or_list->type) { case MRSH_AND_OR_LIST_PIPELINE:; struct mrsh_pipeline *pl = mrsh_and_or_list_get_pipeline(and_or_list); return run_pipeline(ctx, pl); case MRSH_AND_OR_LIST_BINOP:; struct mrsh_binop *binop = mrsh_and_or_list_get_binop(and_or_list); int left_status = run_and_or_list(ctx, binop->left); switch (binop->type) { case MRSH_BINOP_AND: if (left_status != 0) { return left_status; } break; case MRSH_BINOP_OR: if (left_status == 0) { return 0; } break; } return run_and_or_list(ctx, binop->right); } abort(); } /** * Put the process into its job's process group. This has to be done both in the * parent and the child because of potential race conditions. */ static struct mrsh_process *init_async_child(struct mrsh_context *ctx, pid_t pid) { struct mrsh_process *proc = process_create(ctx->state, pid); if (ctx->state->options & MRSH_OPT_MONITOR) { job_add_process(ctx->job, proc); } return proc; } int run_command_list_array(struct mrsh_context *ctx, struct mrsh_array *array) { struct mrsh_state *state = ctx->state; struct mrsh_state_priv *priv = state_get_priv(state); int ret = 0; for (size_t i = 0; i < array->len; ++i) { struct mrsh_command_list *list = array->data[i]; if (list->ampersand) { struct mrsh_context child_ctx = *ctx; child_ctx.background = true; if (child_ctx.job == NULL) { child_ctx.job = job_create(state, &list->node); } pid_t pid = fork(); if (pid < 0) { perror("fork"); return TASK_STATUS_ERROR; } else if (pid == 0) { ctx = NULL; // Use child_ctx instead priv->child = true; init_async_child(&child_ctx, getpid()); if (state->options & MRSH_OPT_MONITOR) { init_job_child_process(state); } if (!(state->options & MRSH_OPT_MONITOR)) { // If job control is disabled, stdin is /dev/null int fd = open("/dev/null", O_CLOEXEC | O_RDONLY); if (fd < 0) { fprintf(stderr, "failed to open /dev/null: %s\n", strerror(errno)); exit(1); } if (fd != STDIN_FILENO) { dup2(fd, STDIN_FILENO); close(fd); } } int ret = run_and_or_list(&child_ctx, list->and_or_list); if (ret < 0) { exit(127); } exit(ret); } ret = 0; init_async_child(&child_ctx, pid); } else { ret = run_and_or_list(ctx, list->and_or_list); if (ret < 0) { return ret; } } if (ret >= 0) { state->last_status = ret; } } return ret; } static void show_job(struct mrsh_job *job, struct mrsh_job *current, struct mrsh_job *previous, bool r) { char curprev = ' '; if (job == current) { curprev = '+'; } else if (job == previous) { curprev = '-'; } char *cmd = mrsh_node_format(job->node); fprintf(stderr, "[%d] %c %s %s\n", job->job_id, curprev, job_state_str(job, r), cmd); free(cmd); } void mrsh_destroy_terminated_jobs(struct mrsh_state *state) { struct mrsh_state_priv *priv = state_get_priv(state); struct mrsh_job *current = job_by_id(state, "%+", false), *previous = job_by_id(state, "%-", false); bool r = rand() % 2 == 0; refresh_jobs_status(state); for (size_t i = 0; i < priv->jobs.len; ++i) { struct mrsh_job *job = priv->jobs.data[i]; int status = job_poll(job); if (state->options & MRSH_OPT_NOTIFY && job->pending_notification) { show_job(job, current, previous, r); job->pending_notification = false; } if (status >= 0) { job_destroy(job); --i; } } fflush(stderr); } int mrsh_run_program(struct mrsh_state *state, struct mrsh_program *prog) { struct mrsh_context ctx = { .state = state }; int ret = run_command_list_array(&ctx, &prog->body); run_pending_traps(state); return ret; } int mrsh_run_word(struct mrsh_state *state, struct mrsh_word **word) { expand_tilde(state, word, false); struct mrsh_context ctx = { .state = state }; int last_status = state->last_status; int ret = run_word(&ctx, word); state->last_status = last_status; return ret; } ================================================ FILE: shell/task/word.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include "builtin.h" #include "shell/process.h" #include "shell/task.h" #include "shell/word.h" #define READ_SIZE 1024 static bool buffer_read_from(struct mrsh_buffer *buf, int fd) { while (true) { char *dst = mrsh_buffer_reserve(buf, READ_SIZE); ssize_t n = read(fd, dst, READ_SIZE); if (n < 0 && errno == EINTR) { continue; } else if (n < 0) { perror("read"); return false; } else if (n == 0) { break; } buf->len += n; } return true; } static bool naive_word_streq(struct mrsh_word *word, const char *str) { while (word->type == MRSH_WORD_LIST) { struct mrsh_word_list *wl = mrsh_word_get_list(word); if (wl->children.len != 1) { return false; } word = wl->children.data[0]; } if (word->type != MRSH_WORD_STRING) { return false; } struct mrsh_word_string *ws = mrsh_word_get_string(word); return strcmp(ws->str, "trap") == 0; } static bool is_print_traps(struct mrsh_program *program) { if (program->body.len != 1) { return false; } struct mrsh_command_list *cl = program->body.data[0]; if (cl->ampersand || cl->and_or_list->type != MRSH_AND_OR_LIST_PIPELINE) { return false; } struct mrsh_pipeline *pipeline = mrsh_and_or_list_get_pipeline(cl->and_or_list); if (pipeline->bang || pipeline->commands.len != 1) { return false; } struct mrsh_command *cmd = pipeline->commands.data[0]; if (cmd->type != MRSH_SIMPLE_COMMAND) { return false; } struct mrsh_simple_command *sc = mrsh_command_get_simple_command(cmd); if (sc->name == NULL || !naive_word_streq(sc->name, "trap")) { return false; } if (sc->arguments.len == 1) { struct mrsh_word *arg = sc->arguments.data[0]; return naive_word_streq(arg, "--"); } else { return sc->arguments.len == 0; } } static void swap_words(struct mrsh_word **word_ptr, struct mrsh_word *new_word) { mrsh_word_destroy(*word_ptr); *word_ptr = new_word; } static int run_word_command(struct mrsh_context *ctx, struct mrsh_word **word_ptr) { struct mrsh_word_command *wc = mrsh_word_get_command(*word_ptr); int fds[2]; if (pipe(fds) != 0) { perror("pipe"); return TASK_STATUS_ERROR; } pid_t pid = fork(); if (pid < 0) { perror("fork"); close(fds[0]); close(fds[1]); return TASK_STATUS_ERROR; } else if (pid == 0) { close(fds[0]); if (fds[1] != STDOUT_FILENO) { dup2(fds[1], STDOUT_FILENO); close(fds[1]); } init_job_child_process(ctx->state); // When a subshell is entered, traps that are not being ignored shall // be set to the default actions, except in the case of a command // substitution containing only a single trap command, when the traps // need not be altered. if (!wc->program || !is_print_traps(wc->program)) { reset_caught_traps(ctx->state); } if (wc->program != NULL) { mrsh_run_program(ctx->state, wc->program); } exit(ctx->state->exit >= 0 ? ctx->state->exit : 0); } struct mrsh_process *process = process_create(ctx->state, pid); close(fds[1]); int child_fd = fds[0]; struct mrsh_buffer buf = {0}; if (!buffer_read_from(&buf, child_fd)) { mrsh_buffer_finish(&buf); close(child_fd); return TASK_STATUS_ERROR; } mrsh_buffer_append_char(&buf, '\0'); close(child_fd); // Trim newlines at the end ssize_t i = buf.len - 2; while (i >= 0 && buf.data[i] == '\n') { buf.data[i] = '\0'; --i; } struct mrsh_word_string *ws = mrsh_word_string_create(mrsh_buffer_steal(&buf), false); ws->split_fields = true; swap_words(word_ptr, &ws->word); return job_wait_process(process); } static const char *parameter_get_value(struct mrsh_state *state, const char *name) { struct mrsh_state_priv *priv = state_get_priv(state); static char value[16]; char *end; long lvalue = strtol(name, &end, 10); // Special cases if (strcmp(name, "@") == 0 || strcmp(name, "*") == 0) { // These are handled separately, because they evaluate to a word, not // a raw string. return NULL; } else if (strcmp(name, "#") == 0) { sprintf(value, "%d", state->frame->argc - 1); return value; } else if (strcmp(name, "?") == 0) { sprintf(value, "%d", state->last_status); return value; } else if (strcmp(name, "-") == 0) { return state_get_options(state); } else if (strcmp(name, "$") == 0) { sprintf(value, "%d", (int)getpid()); return value; } else if (strcmp(name, "!") == 0) { for (ssize_t i = priv->jobs.len - 1; i >= 0; i--) { struct mrsh_job *job = priv->jobs.data[i]; if (job->processes.len == 0) { continue; } struct mrsh_process *process = job->processes.data[job->processes.len - 1]; sprintf(value, "%d", process->pid); return value; } /* Standard is unclear on what to do in this case, mimic dash */ return ""; } else if (end[0] == '\0' && end != name) { if (lvalue >= state->frame->argc) { return NULL; } return state->frame->argv[lvalue]; } // User-set cases return mrsh_env_get(state, name, NULL); } static struct mrsh_word *expand_positional_params(struct mrsh_state *state, bool quote_args) { const char *ifs = mrsh_env_get(state, "IFS", NULL); char sep[2] = {0}; if (ifs == NULL) { sep[0] = ' '; } else { sep[0] = ifs[0]; } struct mrsh_array words = {0}; for (int i = 1; i < state->frame->argc; i++) { const char *arg = state->frame->argv[i]; if (i > 1 && sep[0] != '\0') { struct mrsh_word_string *ws = mrsh_word_string_create(strdup(sep), false); ws->split_fields = true; mrsh_array_add(&words, &ws->word); } struct mrsh_word_string *ws = mrsh_word_string_create(strdup(arg), quote_args); ws->split_fields = true; mrsh_array_add(&words, &ws->word); } struct mrsh_word_list *wl = mrsh_word_list_create(&words, false); return &wl->word; } static void mark_word_split_fields(struct mrsh_word *word) { switch (word->type) { case MRSH_WORD_STRING:; struct mrsh_word_string *ws = mrsh_word_get_string(word); ws->split_fields = true; break; case MRSH_WORD_LIST:; struct mrsh_word_list *wl = mrsh_word_get_list(word); for (size_t i = 0; i < wl->children.len; i++) { mark_word_split_fields(wl->children.data[i]); } break; default: break; } } static struct mrsh_word *create_word_string(const char *str) { struct mrsh_word_string *ws = mrsh_word_string_create(strdup(str), false); return &ws->word; } static struct mrsh_word *copy_word_or_null(struct mrsh_word *word) { if (word != NULL) { return mrsh_word_copy(word); } else { return create_word_string(""); } } static bool is_null_word(const struct mrsh_word *word) { switch (word->type) { case MRSH_WORD_STRING:; const struct mrsh_word_string *ws = mrsh_word_get_string(word); return ws->str[0] == '\0'; case MRSH_WORD_LIST:; const struct mrsh_word_list *wl = mrsh_word_get_list(word); for (size_t i = 0; i < wl->children.len; i++) { const struct mrsh_word *child = wl->children.data[i]; if (!is_null_word(child)) { return false; } } return true; default: abort(); } } static int apply_parameter_cond_op(struct mrsh_context *ctx, struct mrsh_word_parameter *wp, struct mrsh_word *value, struct mrsh_word **result) { switch (wp->op) { case MRSH_PARAM_NONE: *result = value; return 0; case MRSH_PARAM_MINUS: // Use Default Values if (value == NULL || (wp->colon && is_null_word(value))) { mrsh_word_destroy(value); *result = copy_word_or_null(wp->arg); } else { *result = value; } return 0; case MRSH_PARAM_EQUAL: // Assign Default Values if (value == NULL || (wp->colon && is_null_word(value))) { mrsh_word_destroy(value); *result = copy_word_or_null(wp->arg); int ret = run_word(ctx, result); if (ret < 0) { return ret; } char *str = mrsh_word_str(*result); mrsh_env_set(ctx->state, wp->name, str, 0); free(str); } else { *result = value; } return 0; case MRSH_PARAM_QMARK: // Indicate Error if Null or Unset if (value == NULL || (wp->colon && is_null_word(value))) { mrsh_word_destroy(value); char *err_msg; if (wp->arg != NULL) { struct mrsh_word *err_msg_word = mrsh_word_copy(wp->arg); int ret = run_word(ctx, &err_msg_word); if (ret < 0) { return ret; } err_msg = mrsh_word_str(err_msg_word); mrsh_word_destroy(err_msg_word); } else { err_msg = strdup("parameter not set or null"); } fprintf(stderr, "%s: %s: %s\n", ctx->state->frame->argv[0], wp->name, err_msg); free(err_msg); // TODO: make the shell exit if non-interactive return TASK_STATUS_ERROR; } else { *result = value; } return 0; case MRSH_PARAM_PLUS: // Use Alternative Value if (value == NULL || (wp->colon && is_null_word(value))) { *result = create_word_string(""); } else { *result = copy_word_or_null(wp->arg); } mrsh_word_destroy(value); return 0; default: abort(); // unreachable } } static char *trim_str(const char *str, const char *cut, bool suffix) { size_t len = strlen(str); size_t cut_len = strlen(cut); if (cut_len <= len) { if (!suffix && memcmp(str, cut, cut_len) == 0) { return strdup(str + cut_len); } if (suffix && memcmp(str + len - cut_len, cut, cut_len) == 0) { return strndup(str, len - cut_len); } } return strdup(str); } static char *trim_pattern(const char *str, const char *pattern, bool suffix, bool largest) { size_t len = strlen(str); ssize_t begin, end, delta; if ((!suffix && !largest) || (suffix && largest)) { begin = 0; end = len; delta = 1; } else { begin = len - 1; end = -1; delta = -1; } char *buf = strdup(str); for (ssize_t i = begin; i != end; i += delta) { char ch = buf[i]; buf[i] = '\0'; const char *match, *trimmed; if (!suffix) { match = buf; trimmed = str + i; } else { match = str + i; trimmed = buf; } if (fnmatch(pattern, match, 0) == 0) { char *result = strdup(trimmed); free(buf); return result; } buf[i] = ch; } free(buf); return strdup(str); } static int apply_parameter_str_op(struct mrsh_context *ctx, struct mrsh_word_parameter *wp, const char *str, struct mrsh_word **result) { switch (wp->op) { case MRSH_PARAM_LEADING_HASH: // String Length if (str == NULL && (ctx->state->options & MRSH_OPT_NOUNSET)) { *result = NULL; return 0; } int len = 0; if (str != NULL) { len = strlen(str); } char len_str[32]; snprintf(len_str, sizeof(len_str), "%d", len); *result = create_word_string(len_str); return 0; case MRSH_PARAM_PERCENT: // Remove Smallest Suffix Pattern case MRSH_PARAM_DPERCENT: // Remove Largest Suffix Pattern case MRSH_PARAM_HASH: // Remove Smallest Prefix Pattern case MRSH_PARAM_DHASH: // Remove Largest Prefix Pattern if (str == NULL) { *result = NULL; return 0; } else if (wp->arg == NULL) { *result = create_word_string(str); return 0; } bool suffix = wp->op == MRSH_PARAM_PERCENT || wp->op == MRSH_PARAM_DPERCENT; bool largest = wp->op == MRSH_PARAM_DPERCENT || wp->op == MRSH_PARAM_DHASH; struct mrsh_word *pattern = mrsh_word_copy(wp->arg); int ret = run_word(ctx, &pattern); if (ret < 0) { return ret; } char *result_str; char *pattern_str = word_to_pattern(pattern); if (pattern_str == NULL) { char *arg = mrsh_word_str(pattern); mrsh_word_destroy(pattern); result_str = trim_str(str, arg, suffix); free(arg); } else { mrsh_word_destroy(pattern); result_str = trim_pattern(str, pattern_str, suffix, largest); free(pattern_str); } struct mrsh_word_string *result_ws = mrsh_word_string_create(result_str, false); *result = &result_ws->word; return 0; default: abort(); } } static int _run_word(struct mrsh_context *ctx, struct mrsh_word **word_ptr, bool double_quoted) { struct mrsh_word *word = *word_ptr; int ret; switch (word->type) { case MRSH_WORD_STRING:; return 0; case MRSH_WORD_PARAMETER:; struct mrsh_word_parameter *wp = mrsh_word_get_parameter(word); const char *value = parameter_get_value(ctx->state, wp->name); char lineno[16]; if (value == NULL && strcmp(wp->name, "LINENO") == 0) { struct mrsh_position pos; mrsh_word_range(word, &pos, NULL); snprintf(lineno, sizeof(lineno), "%d", pos.line); value = lineno; } struct mrsh_word *result = NULL; switch (wp->op) { case MRSH_PARAM_NONE: case MRSH_PARAM_MINUS: case MRSH_PARAM_EQUAL: case MRSH_PARAM_QMARK: case MRSH_PARAM_PLUS:; struct mrsh_word *value_word; if (strcmp(wp->name, "@") == 0) { // $@ expands to quoted fields only if it's inside double quotes // TODO: error out if expansion is unspecified value_word = expand_positional_params(ctx->state, double_quoted); } else if (strcmp(wp->name, "*") == 0) { value_word = expand_positional_params(ctx->state, false); } else if (value != NULL) { value_word = create_word_string(value); } else { value_word = NULL; } ret = apply_parameter_cond_op(ctx, wp, value_word, &result); if (ret < 0) { return ret; } break; case MRSH_PARAM_LEADING_HASH: case MRSH_PARAM_PERCENT: case MRSH_PARAM_DPERCENT: case MRSH_PARAM_HASH: case MRSH_PARAM_DHASH: if (strcmp(wp->name, "@") == 0 || strcmp(wp->name, "*") == 0 || (wp->op != MRSH_PARAM_LEADING_HASH && strcmp(wp->name, "#") == 0)) { fprintf(stderr, "%s: using this parameter operator on $%s " "is undefined behaviour\n", ctx->state->frame->argv[0], wp->name); return TASK_STATUS_ERROR; } ret = apply_parameter_str_op(ctx, wp, value, &result); if (ret < 0) { return ret; } break; } if (result == NULL) { if ((ctx->state->options & MRSH_OPT_NOUNSET)) { fprintf(stderr, "%s: %s: unbound variable\n", ctx->state->frame->argv[0], wp->name); return TASK_STATUS_ERROR; } result = create_word_string(""); } mark_word_split_fields(result); if (result->type != MRSH_WORD_STRING) { ret = run_word(ctx, &result); if (ret < 0) { return ret; } } swap_words(word_ptr, result); return 0; case MRSH_WORD_COMMAND: return run_word_command(ctx, word_ptr); case MRSH_WORD_ARITHMETIC:; // For arithmetic words, we need to expand the arithmetic expression // before parsing and evaluating it struct mrsh_word_arithmetic *wa = mrsh_word_get_arithmetic(word); ret = run_word(ctx, &wa->body); if (ret < 0) { return ret; } char *body_str = mrsh_word_str(wa->body); struct mrsh_parser *parser = mrsh_parser_with_data(body_str, strlen(body_str)); free(body_str); struct mrsh_arithm_expr *expr = mrsh_parse_arithm_expr(parser); if (expr == NULL) { struct mrsh_position err_pos; const char *err_msg = mrsh_parser_error(parser, &err_pos); if (err_msg != NULL) { // TODO: improve error line/column fprintf(stderr, "%s (arithmetic %d:%d): %s\n", ctx->state->frame->argv[0], err_pos.line, err_pos.column, err_msg); } else { fprintf(stderr, "expected an arithmetic expression\n"); } ret = TASK_STATUS_ERROR; } else { long result; if (!mrsh_run_arithm_expr(ctx->state, expr, &result)) { ret = TASK_STATUS_ERROR; } else { char buf[32]; snprintf(buf, sizeof(buf), "%ld", result); struct mrsh_word_string *ws = mrsh_word_string_create(strdup(buf), false); ws->split_fields = true; swap_words(word_ptr, &ws->word); ret = 0; } } mrsh_arithm_expr_destroy(expr); mrsh_parser_destroy(parser); return ret; case MRSH_WORD_LIST:; struct mrsh_word_list *wl = mrsh_word_get_list(word); struct mrsh_array at_sign_words = {0}; for (size_t i = 0; i < wl->children.len; ++i) { struct mrsh_word **child_ptr = (struct mrsh_word **)&wl->children.data[i]; bool is_at_sign = false; if ((*child_ptr)->type == MRSH_WORD_PARAMETER) { struct mrsh_word_parameter *wp = mrsh_word_get_parameter(*child_ptr); is_at_sign = strcmp(wp->name, "@") == 0; } ret = _run_word(ctx, child_ptr, double_quoted || wl->double_quoted); if (ret < 0) { return ret; } if (wl->double_quoted && is_at_sign) { // Fucking $@ needs special handling: we need to extract the // fields it expands to outside of the double quotes mrsh_array_add(&at_sign_words, *child_ptr); } } if (at_sign_words.len > 0) { // We need to put $@ expansions outside of the double quotes. // Disclaimer: this is a PITA. struct mrsh_array quoted = {0}; struct mrsh_array unquoted = {0}; size_t at_sign_idx = 0; for (size_t i = 0; i < wl->children.len; i++) { struct mrsh_word *child = wl->children.data[i]; wl->children.data[i] = NULL; // steal the child if (at_sign_idx >= at_sign_words.len || child != at_sign_words.data[at_sign_idx]) { mrsh_array_add("ed, child); continue; } if (quoted.len > 0) { struct mrsh_word_list *quoted_wl = mrsh_word_list_create("ed, true); mrsh_array_add(&unquoted, "ed_wl->word); // `unquoted` has been stolen by mrsh_word_list_create quoted = (struct mrsh_array){0}; } mrsh_array_add(&unquoted, at_sign_words.data[at_sign_idx]); at_sign_idx++; } if (quoted.len > 0) { struct mrsh_word_list *quoted_wl = mrsh_word_list_create("ed, true); mrsh_array_add(&unquoted, "ed_wl->word); } struct mrsh_word_list *unquoted_wl = mrsh_word_list_create(&unquoted, false); swap_words(word_ptr, &unquoted_wl->word); } mrsh_array_finish(&at_sign_words); return 0; } abort(); } int run_word(struct mrsh_context *ctx, struct mrsh_word **word_ptr) { return _run_word(ctx, word_ptr, false); } int expand_word(struct mrsh_context *ctx, const struct mrsh_word *_word, struct mrsh_array *expanded_fields) { struct mrsh_word *word = mrsh_word_copy(_word); expand_tilde(ctx->state, &word, false); int ret = run_word(ctx, &word); if (ret < 0) { return ret; } struct mrsh_array fields = {0}; const char *ifs = mrsh_env_get(ctx->state, "IFS", NULL); split_fields(&fields, word, ifs); mrsh_word_destroy(word); if (ctx->state->options & MRSH_OPT_NOGLOB) { get_fields_str(expanded_fields, &fields); } else { if (!expand_pathnames(expanded_fields, &fields)) { return TASK_STATUS_ERROR; } } for (size_t i = 0; i < fields.len; ++i) { mrsh_word_destroy(fields.data[i]); } mrsh_array_finish(&fields); return ret; } ================================================ FILE: shell/trap.c ================================================ #define _POSIX_C_SOURCE 1 #include #include #include "shell/shell.h" #include "shell/trap.h" static const int ignored_job_control_sigs[] = { SIGINT, SIGQUIT, SIGTSTP, SIGTTIN, SIGTTOU, }; static const size_t ignored_job_control_sigs_len = sizeof(ignored_job_control_sigs) / sizeof(ignored_job_control_sigs[0]); static int pending_sigs[MRSH_NSIG] = {0}; static void handle_signal(int sig) { assert(sig < MRSH_NSIG); pending_sigs[sig]++; } bool set_trap(struct mrsh_state *state, int sig, enum mrsh_trap_action action, struct mrsh_program *program) { struct mrsh_state_priv *priv = state_get_priv(state); assert(action == MRSH_TRAP_CATCH || program == NULL); struct mrsh_trap *trap = &priv->traps[sig]; if (sig != 0) { if (!trap->set && !state->interactive) { // Signals that were ignored on entry to a non-interactive shell // cannot be trapped or reset struct sigaction sa; if (sigaction(sig, NULL, &sa) != 0) { perror("failed to get current signal action: sigaction"); return false; } if (sa.sa_handler == SIG_IGN) { fprintf(stderr, "cannot trap signal %d: " "ignored on non-interactive shell entry\n", sig); return false; } } if (action == MRSH_TRAP_DEFAULT && priv->job_control) { // When job control is enabled, some signals are ignored by default for (size_t i = 0; i < ignored_job_control_sigs_len; i++) { if (sig == ignored_job_control_sigs[i]) { action = MRSH_TRAP_IGNORE; break; } } } struct sigaction sa = {0}; switch (action) { case MRSH_TRAP_DEFAULT: sa.sa_handler = SIG_DFL; break; case MRSH_TRAP_IGNORE: sa.sa_handler = SIG_IGN; break; case MRSH_TRAP_CATCH: sa.sa_handler = handle_signal; break; } if (sigaction(sig, &sa, NULL) < 0) { perror("failed to set signal action: sigaction"); return false; } } trap->set = true; trap->action = action; mrsh_program_destroy(trap->program); trap->program = program; return true; } bool set_job_control_traps(struct mrsh_state *state, bool enabled) { struct mrsh_state_priv *priv = state_get_priv(state); for (size_t i = 0; i < ignored_job_control_sigs_len; i++) { int sig = ignored_job_control_sigs[i]; struct mrsh_trap *trap = &priv->traps[i]; struct sigaction sa = {0}; if (enabled) { sa.sa_handler = SIG_IGN; } else { sa.sa_handler = SIG_DFL; } if (sigaction(sig, &sa, NULL) < 0) { perror("sigaction"); return false; } trap->set = false; trap->action = MRSH_TRAP_DEFAULT; mrsh_program_destroy(trap->program); trap->program = NULL; } return true; } bool reset_caught_traps(struct mrsh_state *state) { struct mrsh_state_priv *priv = state_get_priv(state); for (size_t i = 0; i < MRSH_NSIG; i++) { struct mrsh_trap *trap = &priv->traps[i]; if (trap->set && trap->action != MRSH_TRAP_IGNORE) { if (!set_trap(state, i, MRSH_TRAP_DEFAULT, NULL)) { return false; } } } return true; } bool run_pending_traps(struct mrsh_state *state) { struct mrsh_state_priv *priv = state_get_priv(state); static bool in_trap = false; if (in_trap) { return true; } in_trap = true; int last_status = state->last_status; for (size_t i = 0; i < MRSH_NSIG; i++) { struct mrsh_trap *trap = &priv->traps[i]; while (pending_sigs[i] > 0) { if (!trap->set || trap->action != MRSH_TRAP_CATCH || trap->program == NULL) { break; } int ret = mrsh_run_program(state, trap->program); if (ret < 0) { return false; } pending_sigs[i]--; state->last_status = last_status; // Restore "$?" } pending_sigs[i] = 0; } in_trap = false; return true; } bool run_exit_trap(struct mrsh_state *state) { pending_sigs[0]++; return run_pending_traps(state); } ================================================ FILE: shell/word.c ================================================ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include "shell/shell.h" #include "shell/word.h" static bool is_logname_char(char c) { // See https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_282 return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '.' || c == '_' || c == '-'; } static ssize_t expand_tilde_at(struct mrsh_state *state, const char *str, bool last, char **expanded_ptr) { if (str[0] != '~') { return -1; } const char *cur; for (cur = str + 1; cur[0] != '\0' && cur[0] != '/'; cur++) { if (!is_logname_char(cur[0])) { return -1; } } if (cur[0] == '\0' && !last) { return -1; } const char *slash = cur; char *name = NULL; if (slash > str + 1) { name = strndup(str + 1, slash - str - 1); } const char *dir = NULL; if (name == NULL) { dir = mrsh_env_get(state, "HOME", NULL); } else { struct passwd *pw = getpwnam(name); if (pw != NULL) { dir = pw->pw_dir; } } free(name); if (dir == NULL) { return -1; } *expanded_ptr = strdup(dir); return slash - str; } static void _expand_tilde(struct mrsh_state *state, struct mrsh_word **word_ptr, bool assignment, bool first, bool last) { struct mrsh_word *word = *word_ptr; switch (word->type) { case MRSH_WORD_STRING:; struct mrsh_word_string *ws = mrsh_word_get_string(word); if (ws->single_quoted) { break; } struct mrsh_array words = {0}; const char *str = ws->str; if (first) { char *expanded; ssize_t offset = expand_tilde_at(state, str, last, &expanded); if (offset >= 0) { mrsh_array_add(&words, mrsh_word_string_create(expanded, true)); str += offset; } } if (assignment) { while (true) { const char *colon = strchr(str, ':'); if (colon == NULL) { break; } char *slice = strndup(str, colon - str + 1); mrsh_array_add(&words, mrsh_word_string_create(slice, false)); str = colon + 1; char *expanded; ssize_t offset = expand_tilde_at(state, str, last, &expanded); if (offset >= 0) { mrsh_array_add(&words, mrsh_word_string_create(expanded, true)); str += offset; } } } if (words.len > 0) { char *trailing = strdup(str); mrsh_array_add(&words, mrsh_word_string_create(trailing, false)); struct mrsh_word_list *wl = mrsh_word_list_create(&words, false); *word_ptr = &wl->word; mrsh_word_destroy(word); } break; case MRSH_WORD_LIST:; struct mrsh_word_list *wl = mrsh_word_get_list(word); if (wl->double_quoted) { break; } for (size_t i = 0; i < wl->children.len; ++i) { struct mrsh_word **child_ptr = (struct mrsh_word **)&wl->children.data[i]; _expand_tilde(state, child_ptr, assignment, first && i == 0, last && i == wl->children.len - 1); } break; default: break; } } void expand_tilde(struct mrsh_state *state, struct mrsh_word **word_ptr, bool assignment) { _expand_tilde(state, word_ptr, assignment, true, true); } struct split_fields_data { struct mrsh_array *fields; struct mrsh_word_list *cur_field; const char *ifs, *ifs_non_space; bool in_ifs, in_ifs_non_space; }; static void add_to_cur_field(struct split_fields_data *data, struct mrsh_word *word) { if (data->cur_field == NULL) { data->cur_field = mrsh_word_list_create(NULL, false); mrsh_array_add(data->fields, data->cur_field); } mrsh_array_add(&data->cur_field->children, word); } static void _split_fields(struct split_fields_data *data, const struct mrsh_word *word) { switch (word->type) { case MRSH_WORD_STRING:; const struct mrsh_word_string *ws = mrsh_word_get_string(word); if (ws->single_quoted || !ws->split_fields) { add_to_cur_field(data, mrsh_word_copy(word)); data->in_ifs = data->in_ifs_non_space = false; return; } struct mrsh_buffer buf = {0}; size_t len = strlen(ws->str); for (size_t i = 0; i < len; ++i) { char c = ws->str[i]; if (strchr(data->ifs, c) == NULL) { mrsh_buffer_append_char(&buf, c); data->in_ifs = data->in_ifs_non_space = false; continue; } bool is_ifs_non_space = strchr(data->ifs_non_space, c) != NULL; if (!data->in_ifs || (is_ifs_non_space && data->in_ifs_non_space)) { mrsh_buffer_append_char(&buf, '\0'); char *str = mrsh_buffer_steal(&buf); add_to_cur_field(data, &mrsh_word_string_create(str, false)->word); data->cur_field = NULL; data->in_ifs = true; } else if (is_ifs_non_space) { data->in_ifs_non_space = true; } } if (!data->in_ifs) { mrsh_buffer_append_char(&buf, '\0'); char *str = mrsh_buffer_steal(&buf); add_to_cur_field(data, &mrsh_word_string_create(str, false)->word); } mrsh_buffer_finish(&buf); break; case MRSH_WORD_LIST:; const struct mrsh_word_list *wl = mrsh_word_get_list(word); if (wl->double_quoted) { add_to_cur_field(data, mrsh_word_copy(word)); return; } for (size_t i = 0; i < wl->children.len; ++i) { const struct mrsh_word *child = wl->children.data[i]; _split_fields(data, child); } break; default: abort(); } } void split_fields(struct mrsh_array *fields, const struct mrsh_word *word, const char *ifs) { if (ifs == NULL) { ifs = " \t\n"; } else if (ifs[0] == '\0') { mrsh_array_add(fields, mrsh_word_copy(word)); return; } size_t ifs_len = strlen(ifs); char *ifs_non_space = calloc(ifs_len, sizeof(char)); size_t ifs_non_space_len = 0; for (size_t i = 0; i < ifs_len; ++i) { if (!isspace(ifs[i])) { ifs_non_space[ifs_non_space_len++] = ifs[i]; } } struct split_fields_data data = { .fields = fields, .ifs = ifs, .ifs_non_space = ifs_non_space, .in_ifs = true, }; _split_fields(&data, word); free(ifs_non_space); } void get_fields_str(struct mrsh_array *strs, const struct mrsh_array *fields) { for (size_t i = 0; i < fields->len; i++) { struct mrsh_word *word = fields->data[i]; mrsh_array_add(strs, mrsh_word_str(word)); } } static bool is_pathname_metachar(char c) { switch (c) { case '*': case '?': case '[': case ']': return true; default: return false; } } static bool needs_pathname_expansion(const struct mrsh_word *word) { switch (word->type) { case MRSH_WORD_STRING:; const struct mrsh_word_string *ws = mrsh_word_get_string(word); if (ws->single_quoted) { return false; } size_t len = strlen(ws->str); for (size_t i = 0; i < len; i++) { if (is_pathname_metachar(ws->str[i])) { return true; } } return false; case MRSH_WORD_LIST:; const struct mrsh_word_list *wl = mrsh_word_get_list(word); if (wl->double_quoted) { return false; } for (size_t i = 0; i < wl->children.len; i++) { const struct mrsh_word *child = wl->children.data[i]; if (needs_pathname_expansion(child)) { return true; } } return false; default: abort(); } } static void _word_to_pattern(struct mrsh_buffer *buf, const struct mrsh_word *word, bool quoted) { switch (word->type) { case MRSH_WORD_STRING:; const struct mrsh_word_string *ws = mrsh_word_get_string(word); size_t len = strlen(ws->str); for (size_t i = 0; i < len; i++) { char c = ws->str[i]; if (is_pathname_metachar(c) && (quoted || ws->single_quoted)) { mrsh_buffer_append_char(buf, '\\'); } mrsh_buffer_append_char(buf, c); } break; case MRSH_WORD_LIST:; const struct mrsh_word_list *wl = mrsh_word_get_list(word); for (size_t i = 0; i < wl->children.len; i++) { const struct mrsh_word *child = wl->children.data[i]; _word_to_pattern(buf, child, quoted || wl->double_quoted); } break; default: abort(); } } char *word_to_pattern(const struct mrsh_word *word) { if (!needs_pathname_expansion(word)) { return NULL; } struct mrsh_buffer buf = {0}; _word_to_pattern(&buf, word, false); mrsh_buffer_append_char(&buf, '\0'); return mrsh_buffer_steal(&buf); } bool expand_pathnames(struct mrsh_array *expanded, const struct mrsh_array *fields) { for (size_t i = 0; i < fields->len; ++i) { const struct mrsh_word *field = fields->data[i]; char *pattern = word_to_pattern(field); if (pattern == NULL) { mrsh_array_add(expanded, mrsh_word_str(field)); continue; } glob_t glob_buf; int ret = glob(pattern, GLOB_NOSORT, NULL, &glob_buf); if (ret == 0) { for (size_t i = 0; i < glob_buf.gl_pathc; ++i) { mrsh_array_add(expanded, strdup(glob_buf.gl_pathv[i])); } globfree(&glob_buf); } else if (ret == GLOB_NOMATCH) { mrsh_array_add(expanded, mrsh_word_str(field)); } else { fprintf(stderr, "glob failed: %d\n", ret); free(pattern); return false; } free(pattern); } return true; } ================================================ FILE: test/args.sh ================================================ func() ( getopts "abcd" opt ) func func -a func -ab func -abc ================================================ FILE: test/arithm.sh ================================================ #!/bin/sh -eu echo "1 =" $((1)) echo "2*5 =" $((2*5)) echo "2/5 =" $((2/5)) echo "2%5 =" $((2%5)) echo "2+5 =" $((2+5)) echo "2-5 =" $((2-5)) echo "2<<5 =" $((2<<5)) echo "2>>5 =" $((2>>5)) echo "2<5 =" $((2<5)) echo "2<=5 =" $((2<=5)) echo "2>5 =" $((2>5)) echo "2>=5 =" $((2>=5)) echo "2==5 =" $((2==5)) echo "2!=5 =" $((2!=5)) echo "2&5 =" $((2&5)) echo "2^5 =" $((2^5)) echo "2|5 =" $((2|5)) echo "2&&5 =" $((2&&5)) echo "2||5 =" $((2||5)) # Associativity echo "1+2+3 =" $((1+2+3)) echo "5-1-2 =" $((5-1-2)) echo "1+2*3 =" $((1+2*3)) echo "2*3+1 =" $((2*3+1)) echo "6/3/2 =" $((6/3/2)) echo "2*(3+1) =" $((2*(3+1))) echo "2*(3+1)+1 =" $((2*(3+1)+1)) echo "1+(1+(1+1))+1 =" $((1+(1+(1+1))+1)) echo "2|(1||1)" $((2|(1||1))) # 3 echo "(2|1)||1" $(((2|1)||1)) # 1 echo "2|1||1" $((2|1||1)) # 1 echo "1||1|2" $((1||1|2)) # 1 # Variables a=42 echo "a =" $((a)) echo "a+2 =" $((a+2)) echo "2*a-10 =" $((2*a-10)) echo "\$a =" $(($a)) echo "\$a+2 =" $(($a+2)) # Assignments a=0 echo "(a=42) =" $((a=42)) "->" $a echo "(a+=1) =" $((a+=1)) "->" $a echo "(a-=4) =" $((a-=4)) "->" $a echo "(a*=9) =" $((a*=9)) "->" $a echo "(a/=3) =" $((a/=3)) "->" $a ================================================ FILE: test/async.sh ================================================ #!/bin/sh echo >&2 "Returns immediately" (wait) echo >&2 "Run asynchronous list and wait" echo a & wait $! echo >&2 "Run two asynchronous lists in parallel and wait" echo a & p1=$! echo a & wait $p1 s1=$? wait $! s2=$? echo Job 1 exited with status $s1 echo Job 2 exited with status $s2 #echo >&2 "Run asynchronous list, kill it and wait" #sleep 1000 & #pid=$! #kill -kill $pid #wait $pid #echo $pid was terminated by a SIG$(kill -l $?) signal. ================================================ FILE: test/case.sh ================================================ #!/bin/sh x=hello echo "exact matching with variable expansion" case "$x" in hello) echo pass ;; world) echo fail ;; esac echo "* pattern" case "$x" in he*) echo pass ;; *) echo fail ;; esac echo "? pattern" case "$x" in foo) echo fail ;; he??o) echo pass ;; esac echo "default pattern" case "$x" in foo) echo fail ;; *) echo pass ;; esac echo "| pattern" case "$x" in world|hello) echo pass ;; *) echo fail ;; esac echo "[] pattern" case "$x" in hell[a-z]) echo pass ;; *) echo fail ;; esac y=hello echo "expanding patterns" case "$x" in $y) echo pass ;; *) echo fail ;; esac echo "quoted strings in patterns" case "$x" in "$y"'') echo pass ;; *) echo fail ;; esac echo ";; optional for last item" case hello in *) echo pass esac ================================================ FILE: test/command.sh ================================================ #!/bin/sh lla () { ls -la } alias ll="ls -l" command -v if echo "exists if $?" command -v cd echo "exits cd $?" command -v ls echo "exists ls $?" command -v ll echo "exists ll $?" command -v lla echo "exists lla $?" command -v idontexists if [ $? -ne 0 ] then echo "ok" else echo "ko" fi ================================================ FILE: test/conformance/2.2-quoted-characters.sh ================================================ # 2.2.1 Escape Character (Backslash) # The following must be quoted: printf '%s\n' \|\&\;\<\>\(\)\$\`\\\"\' # The following must be quoted only under certain conditions: printf '%s\n' *?[#˜=% # Escaping whitespace: printf 'arg one: %s; arg two: %s\n' with\ space with\ tab printf 'arg one: %s; arg two: %s\n' without \ newline printf 'arg one: %s; arg two: %s\n' without\ whitespace 'arg two' # 2.2.2 Single quotes printf '%s\n' '|&;<>()$`\"' # 2.2.3 Double quotes printf '%s\n' "|&;<>()'\"" # Parameter & arithmetic expansion should work: lval=2 rval=3 printf '%s\n' "$lval + ${rval} = $((lval+rval))" # Command expansion should work printf '%s\n' "cmd 1: $(echo "cmd 1")" printf '%s\n' "cmd 2: `echo "cmd 2"`" # Command expansion should work, recursively printf '%s\n' "cmd 3: $(echo $(echo "cmd 3"))" # Backquotes should work printf '%s\n' "cmd 4: `echo "cmd 4"`" # Backslashes should escape the following: printf '%s\n' "\$\`\"\\\ test" # But not the following: printf '%s\n' "\|\&\;\<\>\(\)\'" ================================================ FILE: test/conformance/2.2-quoted-characters.stdout ================================================ |&;<>()$`\"' *?[#˜=% arg one: with space; arg two: with tab arg one: without; arg two: newline arg one: withoutwhitespace; arg two: arg two |&;<>()$`\" |&;<>()'" 2 + 3 = 5 cmd 1: cmd 1 cmd 2: cmd 2 cmd 3: cmd 3 cmd 4: cmd 4 $`"\test \|\&\;\<\>\(\)\' ================================================ FILE: test/conformance/2.2.2-nested-single-quotes.fail.sh ================================================ # 2.2.2 Single quotes # Single quotes cannot contain single quotes printf '%s\n' ''' ================================================ FILE: test/conformance/2.2.3-alias-expansion.fail.sh ================================================ # 2.2.3 Double quotes # Should NOT apply alias expansion rules when finding the ) alias myalias="echo )" var="$(myalias arg-two)" ================================================ FILE: test/conformance/2.2.3-backquote-nonterminated-dquote.undefined.sh ================================================ printf '%s\n' "cmd: `echo "`" ================================================ FILE: test/conformance/2.2.3-backquote-nonterminated-squote.undefined.sh ================================================ printf '%s\n' "cmd: `echo '`" ================================================ FILE: test/conformance/2.2.3-dquote-nonterminated-backquote.undefined.sh ================================================ printf '%s\n' "`echo" ================================================ FILE: test/conformance/README ================================================ Tests in this directory test each specified behavior of a feature in the POSIX specification. If mrsh passes such a test, it is considered compliant with the appropriate part of the spec. ================================================ FILE: test/conformance/harness.sh ================================================ #!/bin/sh dir=$(dirname "$0") testcase="$1" stdout="${testcase%%.sh}.stdout" # Set TEST_SHELL to quickly compare the conformance of different shells mrsh=${TEST_SHELL:-$MRSH} actual_out=$("$mrsh" "$testcase") actual_ret=$? if [ -f "$stdout" ] then stdout="$(cat "$stdout")" if [ "$stdout" != "$actual_out" ] then exit 1 fi fi exit $actual_ret ================================================ FILE: test/conformance/meson.build ================================================ harness = find_program('./harness.sh') test_files = [ '2.2-quoted-characters.sh', ] failures = [ '2.2.2-nested-single-quotes.fail.sh', # TODO: https://github.com/emersion/mrsh/issues/145 #'2.2.3-alias-expansion.fail.sh', ] undefined = [ '2.2.3-backquote-nonterminated-squote.undefined.sh', '2.2.3-backquote-nonterminated-dquote.undefined.sh', '2.2.3-dquote-nonterminated-backquote.undefined.sh', ] testenv = [ 'MRSH=@0@'.format(mrsh_exe.full_path()), ] foreach test_file : test_files test( 'conformance/' + test_file, harness, env: testenv, args: [join_paths(meson.current_source_dir(), test_file)], ) endforeach foreach test_file : failures test( 'conformance/' + test_file, harness, env: testenv, args: [join_paths(meson.current_source_dir(), test_file)], should_fail: true, ) endforeach if get_option('test-undefined-behavior') foreach test_file : undefined test( 'conformance/' + test_file, harness, env: testenv, args: [join_paths(meson.current_source_dir(), test_file)], should_fail: true, ) endforeach endif ================================================ FILE: test/for.sh ================================================ #!/bin/sh echo "Simple for loop" for i in 1 2 3; do echo $i done echo "Word expansion in for loop" two=2 for i in 1 $two $(echo 3); do echo $i done echo $i echo "No-op for loop" for i in; do echo invalid done echo $i echo "Field splitting in for loop, expanded from parameter" asdf='a s d f' for c in $asdf; do echo $c done echo "Field splitting in for loop, expanded from command substitution" for c in $(echo a s d f); do echo $c done echo "Field splitting in for loop, with IFS set" ( IFS=':' asdf='a:s:d:f' for c in $asdf; do echo $c done ) ================================================ FILE: test/function.sh ================================================ #!/bin/sh -e func_a() { echo func_a } func_b() { echo func_b } func_b() { echo func_b revised } func_c() { echo func_c func_c() { echo func_c revised } } func_d() if true; then echo func_d; fi func_e() { echo $1 } func_a func_b func_a func_c func_c func_d func_e hello output=$(func_a) echo "output is $output" ================================================ FILE: test/harness.sh ================================================ #!/bin/sh dir=$(dirname "$0") testcase="$1" echo "Running with mrsh" mrsh_out=$("$MRSH" "$testcase") mrsh_ret=$? echo "Running with reference shell ($REF_SH)" ref_out=$("$REF_SH" "$testcase") ref_ret=$? if [ $mrsh_ret -ne $ref_ret ] || [ "$mrsh_out" != "$ref_out" ] then echo >&2 "$testcase: mismatch" echo >&2 "" echo >&2 "mrsh: $mrsh_ret" echo >&2 "$mrsh_out" echo >&2 "" echo >&2 "ref ($REF_SH): $ref_ret" echo >&2 "$ref_out" echo >&2 "" exit 1 fi ================================================ FILE: test/if.sh ================================================ #!/bin/sh # Reference stdout: # pass # pass # pass # pass # pass # pass echo "-> if..fi with true condition" if true then echo pass fi echo "-> if..fi with false condition" if false then echo >&2 "fail: This branch should not have run" && exit 1 fi echo pass echo "-> if..else..fi with true condition" if true then echo pass else echo >&2 "fail: This branch should not have run" && exit 1 fi echo "-> if..else..fi with false condition" if false then echo >&2 "fail: This branch should not have run" && exit 1 else echo pass fi echo "-> if..else..fi with true condition" if true then echo pass else echo >&2 "fail: This branch should not have run" && exit 1 fi echo "-> if..else..elif..fi with false condition" if false then echo >&2 "fail: This branch should not have run" && exit 1 elif true then echo pass else echo >&2 "fail: This branch should not have run" && exit 1 fi echo "-> test exit status" if true then ( exit 10 ) else ( exit 20 ) fi [ $# -eq 10 ] || { echo >&2 "fail: Expected status code = 10" && exit 1; } if false then ( exit 10 ) else ( exit 20 ) fi [ $# -eq 20 ] || { echo >&2 "fail: Expected status code = 20" && exit 1; } echo "-> test alternate syntax" # These tests are only expected to parse, they do not make assertions if true; then true; fi if true; then true; else true; fi if true; then true; elif true; then true; else true; fi ================================================ FILE: test/loop.sh ================================================ #!/bin/sh echo "basic while loop" n=asdf echo start while [ "$n" != "fdsa" ]; do echo "n: $n" n="fdsa" echo "n: $n" done echo stop echo "continue in while loop should skip the iteration" n=asdf echo start while [ "$n" != fdsa ]; do n=fdsa continue echo "this shouldn't be printed" done echo stop echo "break in while loop should stop the loop" n=asdf echo start while true; do if [ "$n" = fdsa ]; then break fi n=fdsa done echo stop echo "exit in infinite loop should exit immediately" while true do exit # https://github.com/emersion/mrsh/issues/37 echo bad done ================================================ FILE: test/meson.build ================================================ harness = find_program('./harness.sh') ref_sh = find_program(get_option('reference-shell'), required: false) test_files = [ 'args.sh', 'arithm.sh', 'async.sh', 'case.sh', 'command.sh', 'for.sh', 'function.sh', 'if.sh', 'loop.sh', 'pipeline.sh', 'read.sh', 'readonly.sh', 'redir.sh', 'return.sh', 'subshell.sh', 'syntax.sh', 'ulimit.sh', 'word.sh', ] foreach test_file : test_files test( test_file, harness, env: [ 'MRSH=@0@'.format(mrsh_exe.full_path()), 'REF_SH=@0@'.format(ref_sh.path()), ], args: [join_paths(meson.current_source_dir(), test_file)], ) endforeach subdir('conformance') ================================================ FILE: test/pipeline.sh ================================================ #!/bin/sh echo "Pipeline with 1 command" echo "a b c d" echo "Pipeline with 2 commands" echo "a b c d" | sed s/b/B/ echo "Pipeline with 3 commands" echo "a b c d" | sed s/b/B/ | sed s/c/C/ echo "Pipeline with bang" ! false echo $? echo "Pipeline with unknown command" idontexist echo $? # 127 # https://github.com/emersion/mrsh/issues/100 echo "Pipeline with subshell" (echo "a b"; echo "c d") | sed s/c/C/ # https://github.com/emersion/mrsh/issues/96 echo "Pipeline with brace group" { echo "a b"; echo "c d"; } | sed s/c/C/ # https://github.com/emersion/mrsh/issues/95 #echo "Pipeline with early close" #( # i=0 # while [ $i -lt 8096 ] # do # echo "Line $i" # i=$((i+1)) # done #) | head -n 1 ================================================ FILE: test/read.sh ================================================ #!/bin/sh -eu printf | read a echo $? i=0 printf "a\nb\nc\n" | while read line; do printf "%s\n" "${line:-blank}" i=$((i+1)) [ $i -gt 10 ] && break done [ $i = 3 ] && echo "correct!" ================================================ FILE: test/readonly.sh ================================================ #!/bin/sh echo "Print read-only parameters after setting one" readonly mrsh_readonly_param=b readonly -p | grep mrsh_readonly_param | wc -l #echo "Try setting a read-only parameter" #(mrsh_readonly_param=c) 2>/dev/null #echo $? ================================================ FILE: test/redir.sh ================================================ #!/bin/sh echo "to stdout" uname echo >&2 "stdout to stderr" uname >&2 echo 2>&1 "stderr to stdout" uname 2>&1 #(echo >&2 asdf) 2>&1 ================================================ FILE: test/return.sh ================================================ #!/bin/sh -e func_a() { echo func a return echo func a post-return } func_b() { echo func b return 1 echo func b post-return } func_c() { echo func c while : do echo func c loop return echo func c loop post-return done echo func c post loop } func_a if func_b then exit 1 fi func_c ================================================ FILE: test/subshell.sh ================================================ #!/bin/sh echo "Simple subshell" (echo hi) echo "Subshell assignment" a=a (a=b) echo $a echo "Subshell status" (exit 1) && echo "shouldn't happen" true # to clear the last status echo "Multi-line subshell" ( echo a echo b ) ================================================ FILE: test/syntax.sh ================================================ #!/bin/sh # This is a comment echo a b c echo d # e f echo 'a b c' echo "a b c" echo a"*"b'c"' echo "a\"b\\" "'hey'" '"hey"' '#' \''a' ================================================ FILE: test/ulimit.sh ================================================ #!/bin/sh -e mrsh_limits=`ulimit` [ "$mrsh_limits" = "unlimited" ] if [ -e /proc/self/limits ] then grep "Max file size" /proc/self/limits | grep "unlimited" fi ulimit -f 100 mrsh_limits=`ulimit` [ "$mrsh_limits" -eq 100 ] if [ -e /proc/self/limits ] then grep "Max file size" /proc/self/limits | grep 51200 fi ================================================ FILE: test/word.sh ================================================ #!/bin/sh echo "" echo "Tilde Expansion" echo ~ ~/stuff ~/"stuff" "~/stuff" echo '~/stuff' ~"/stuff" "/"~ "a"~"a" echo ~root a=~/stuff echo $a a=~/foo:~/bar:~/baz echo $a echo "" echo "Parameter Expansion" a=a b=B hello=hello null="" echo $a ${b} ">$a<" echo \$a '$a' echo ${a:-BAD} ${idontexist:-GOOD} ${null:-GOOD} ${idontexist:-} echo ${a-BAD} ${idontexist-GOOD} ${null-BAD} ${null-} echo ${c:=GOOD} $c; echo ${c:=BAD} $c; c=""; echo ${c:=GOOD} $c; unset c echo ${c=GOOD} $c; echo ${c=BAD} $c; c=""; echo ${c=BAD} $c; unset c echo ${a:+GOOD} ${idontexist:+BAD} ${null:+BAD} ${idontexist:+} echo ${a+GOOD} ${idontexist+BAD} ${null+GOOD} ${null+} echo ${#hello} ${#null} ${#idontexist} nargs() { echo "$#" } set a 'b c' d ' e' echo $* nargs $* echo "$*" nargs "$*" echo $@ nargs $@ echo "$@" nargs "$@" echo "1 $@ 2" nargs "1 $@ 2" echo ${null:-"$@"} nargs ${null:-"$@"} ( IFS=':' asdf='a:s:d:f' echo $asdf nargs $asdf ) echo "a b" echo 'a b' # Examples from the spec # ${parameter}: dash and busybox choke on this #a=1 #set 2 #echo ${a}b-$ab-${1}0-${10}-$10 # ${parameter-word} foo=asdf echo ${foo-bar}xyz} foo= echo ${foo-bar}xyz} unset foo echo ${foo-bar}xyz} # ${parameter:-word} #unset x #echo ${x:-$(echo >&2 GOOD)} 2>&1 #x=x #echo ${x:-$(echo >&2 BAD)} 2>&1 # ${parameter:=word} unset X echo ${X:=abc} # ${parameter:?word} #unset posix #echo ${posix:?} # ${parameter:+word} set a b c echo ${3:+posix} # ${#parameter} posix=/usr/posix echo ${#posix} # ${parameter%word} x=file.c echo ${x%.c}.o # ${parameter%%word} x=posix/src/std echo ${x%%/*} # ${parameter#word} x=$HOME/src/cmd echo ${x#$HOME} # ${parameter##word} x=/one/two/three echo ${x##*/} echo "" echo "Command Substitution" echo $(echo asdf) echo `echo asdf` echo $( echo a echo b ) echo ` echo asdf ` # Field Splitting # Pathname Expansion # Quote Removal