[
  {
    "path": ".builds/alpine.yml",
    "content": "image: alpine/edge\npackages:\n  - gcc\n  - clang\n  - meson\n  - dash\nsources:\n  - https://git.sr.ht/~emersion/mrsh\ntasks:\n  - setup: |\n      cd mrsh\n      CC=gcc meson build-gcc -Dreference-shell=dash\n      CC=clang meson build-clang -Dreference-shell=dash\n  - build-gcc: |\n      cd mrsh\n      ninja -C build-gcc\n  - build-clang: |\n      cd mrsh\n      ninja -C build-clang\n  - test-gcc: |\n      cd mrsh\n      ninja -C build-gcc test\n  - test-clang: |\n      cd mrsh\n      ninja -C build-clang test\ntriggers:\n  - action: email\n    condition: failure\n    to: \"<contact@emersion.fr>\"\n"
  },
  {
    "path": ".builds/archlinux.yml",
    "content": "image: archlinux\npackages:\n  - gcc\n  - meson\n  - readline\nsources:\n  - https://git.sr.ht/~emersion/mrsh\ntasks:\n  - setup: |\n      cd mrsh\n      meson build -Db_sanitize=address,undefined -Dauto_features=enabled\n  - build: |\n      cd mrsh\n      ninja -C build\n  - test: |\n      cd mrsh\n      ninja -C build test\n  - build-release: |\n      cd mrsh\n      meson configure build --buildtype release\n      ninja -C build\n  - build-minimal: |\n      cd mrsh\n      meson configure build -Dauto_features=disabled\n      ninja -C build\ntriggers:\n  - action: email\n    condition: failure\n    to: \"<contact@emersion.fr>\"\n"
  },
  {
    "path": ".builds/freebsd.yml",
    "content": "image: freebsd/latest\npackages:\n  - meson\n  - libedit\n  - pkgconf\nsources:\n  - https://git.sr.ht/~emersion/mrsh\ntasks:\n  - setup: |\n      cd mrsh\n      meson build -Dauto_features=enabled -Dreadline-provider=editline\n  - build: |\n      cd mrsh\n      ninja -C build\n  - test: |\n      cd mrsh\n      ninja -C build test\ntriggers:\n  - action: email\n    condition: failure\n    to: \"<contact@emersion.fr>\"\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nend_of_line = lf\ninsert_final_newline = true\ncharset = utf-8\nindent_style = tab\ntrim_trailing_whitespace = true\nindent_size = 4\n\n[*.yml]\nindent_size = 2\nindent_style = space\n"
  },
  {
    "path": ".gitignore",
    "content": "# Prerequisites\n*.d\n\n# Object files\n*.o\n*.ko\n*.obj\n*.elf\n\n# Linker output\n*.ilk\n*.map\n*.exp\n\n# Precompiled Headers\n*.gch\n*.pch\n\n# Libraries\n*.lib\n*.a\n*.la\n*.lo\n\n# Shared objects (inc. Windows DLLs)\n*.dll\n*.so\n*.so.*\n*.dylib\n\n# Executables\n*.exe\n*.out\n*.app\n*.i*86\n*.x86_64\n*.hex\n\n# Debug files\n*.dSYM/\n*.su\n*.idb\n*.pdb\n\n# Kernel Module Compile Results\n*.mod*\n*.cmd\n.tmp_versions/\nmodules.order\nModule.symvers\nMkfile.old\ndkms.conf\n\n/build\n/build-*\n\n/.build\n/highlight\n/mrsh\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018 emersion\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": ".POSIX:\n.SUFFIXES:\nOUTDIR=.build\ninclude $(OUTDIR)/config.mk\n\nINCLUDE=-Iinclude\n\npublic_includes=\\\n\t\tinclude/mrsh/arithm.h \\\n\t\tinclude/mrsh/array.h \\\n\t\tinclude/mrsh/ast.h \\\n\t\tinclude/mrsh/buffer.h \\\n\t\tinclude/mrsh/builtin.h \\\n\t\tinclude/mrsh/entry.h \\\n\t\tinclude/mrsh/hashtable.h \\\n\t\tinclude/mrsh/parser.h \\\n\t\tinclude/mrsh/shell.h\n\ntests=\\\n\t\ttest/args.sh \\\n\t\ttest/arithm.sh \\\n\t\ttest/async.sh \\\n\t\ttest/case.sh \\\n\t\ttest/command.sh \\\n\t\ttest/for.sh \\\n\t\ttest/function.sh \\\n\t\ttest/if.sh \\\n\t\ttest/loop.sh \\\n\t\ttest/pipeline.sh \\\n\t\ttest/read.sh \\\n\t\ttest/readonly.sh \\\n\t\ttest/redir.sh \\\n\t\ttest/return.sh \\\n\t\ttest/subshell.sh \\\n\t\ttest/syntax.sh \\\n\t\ttest/ulimit.sh \\\n\t\ttest/word.sh\n\ninclude $(OUTDIR)/cppcache\n\n.SUFFIXES: .c .o\n\n.c.o:\n\t@mkdir -p $$(dirname \"$@\")\n\t@printf 'CC\\t$@\\n'\n\t@touch $(OUTDIR)/cppcache\n\t@grep $< $(OUTDIR)/cppcache >/dev/null || \\\n\t\t$(CPP) $(INCLUDE) -MM -MT $@ $< >> $(OUTDIR)/cppcache\n\t@$(CC) -c $(CFLAGS) $(INCLUDE) -o $@ $<\n\n$(OUTDIR)/libmrsh.a: $(libmrsh_objects)\n\t@printf 'AR\\t$@\\n'\n\t@$(AR) -csr $@ $(libmrsh_objects)\n\nlibmrsh.so.$(SOVERSION): $(OUTDIR)/libmrsh.a\n\t@printf 'LD\\t$@\\n'\n\t@$(CC) -shared $(LDFLAGS) -o $@ $(OUTDIR)/libmrsh.a\n\n$(OUTDIR)/mrsh.pc:\n\t@printf 'MKPC\\t$@\\n'\n\t@PREFIX=$(PREFIX) ./mkpc $@\n\nmrsh: $(OUTDIR)/libmrsh.a $(mrsh_objects)\n\t@printf 'CCLD\\t$@\\n'\n\t@$(CC) -o $@ $(LDFLAGS) $(mrsh_objects) -L$(OUTDIR) -lmrsh $(LIBS)\n\nhighlight: $(OUTDIR)/libmrsh.a $(highlight_objects)\n\t@printf 'CCLD\\t$@\\n'\n\t@$(CC) -o $@ $(LDFLAGS) $(highlight_objects) -L$(OUTDIR) -lmrsh $(LIBS)\n\ncheck: mrsh $(tests)\n\t@for t in $(tests); do \\\n\t\tprintf '%-30s... ' \"$$t\" && \\\n\t\tMRSH=./mrsh REF_SH=$${REF_SH:-sh} ./test/harness.sh $$t >/dev/null && \\\n\t\techo OK || echo FAIL; \\\n\tdone\n\ninstall: mrsh libmrsh.so.$(SOVERSION) $(OUTDIR)/mrsh.pc\n\tmkdir -p $(BINDIR) $(LIBDIR) $(INCDIR)/mrsh $(PCDIR)\n\tinstall -m755 mrsh $(BINDIR)/mrsh\n\tinstall -m755 libmrsh.so.$(SOVERSION) $(LIBDIR)/libmrsh.so.$(SOVERSION)\n\tfor inc in $(public_includes); do \\\n\t\tinstall -m644 $$inc $(INCDIR)/mrsh/$$(basename $$inc); \\\n\tdone\n\tinstall -m644 $(OUTDIR)/mrsh.pc $(PCDIR)/mrsh.pc\n\nuninstall:\n\trm -f $(BINDIR)/mrsh\n\trm -f $(LIBDIR)/libmrsh.so.$(SOVERSION)\n\tfor inc in $(public_includes); do \\\n\t\trm -f $(INCDIR)/mrsh/$$(basename $$inc); \\\n\tdone\n\trm -f $(PCDIR)/mrsh.pc\n\nclean:\n\trm -rf \\\n\t\t$(libmrsh_objects) \\\n\t\t$(mrsh_objects) \\\n\t\t$(highlight_objects) \\\n\t\tmrsh highlight libmrsh.so.$(SOVERSION) $(OUTDIR)/mrsh.pc\n\nmrproper: clean\n\trm -rf $(OUTDIR)\n\n.PHONY: all install clean check\n"
  },
  {
    "path": "README.md",
    "content": "# mrsh\n\nA minimal [POSIX] shell.\n\n[![builds.sr.ht status](https://builds.sr.ht/~emersion/mrsh/commits/master.svg)](https://builds.sr.ht/~emersion/mrsh/commits/master?)\n\n* POSIX compliant, no less, no more\n* Simple, readable code without magic\n* Library to build more elaborate shells\n\nThis project is a [work in progress].\n\n## Build\n\nBoth Meson and POSIX make are supported. To use Meson:\n\n    meson build/\n    ninja -C build/\n    build/mrsh\n\nTo use POSIX make:\n\n    ./configure\n    make\n    ./mrsh\n\n## Contributing\n\nEither [send GitHub pull requests][GitHub] or [send patches on the mailing\nlist][ML].\n\n## License\n\nMIT\n\n[POSIX]: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html\n[work in progress]: https://github.com/emersion/mrsh/issues/8\n[GitHub]: https://github.com/emersion/mrsh\n[ML]: https://lists.sr.ht/%7Eemersion/mrsh-dev\n"
  },
  {
    "path": "arithm.c",
    "content": "#include <assert.h>\n#include <mrsh/arithm.h>\n#include <mrsh/ast.h>\n#include <stdlib.h>\n\nvoid mrsh_arithm_expr_destroy(struct mrsh_arithm_expr *expr) {\n\tif (expr == NULL) {\n\t\treturn;\n\t}\n\tswitch (expr->type) {\n\tcase MRSH_ARITHM_LITERAL:;\n\t\tstruct mrsh_arithm_literal *al = mrsh_arithm_expr_get_literal(expr);\n\t\tfree(al);\n\t\treturn;\n\tcase MRSH_ARITHM_VARIABLE:;\n\t\tstruct mrsh_arithm_variable *av = mrsh_arithm_expr_get_variable(expr);\n\t\tfree(av->name);\n\t\tfree(av);\n\t\treturn;\n\tcase MRSH_ARITHM_UNOP:;\n\t\tstruct mrsh_arithm_unop *au = mrsh_arithm_expr_get_unop(expr);\n\t\tmrsh_arithm_expr_destroy(au->body);\n\t\tfree(au);\n\t\treturn;\n\tcase MRSH_ARITHM_BINOP:;\n\t\tstruct mrsh_arithm_binop *ab = mrsh_arithm_expr_get_binop(expr);\n\t\tmrsh_arithm_expr_destroy(ab->left);\n\t\tmrsh_arithm_expr_destroy(ab->right);\n\t\tfree(ab);\n\t\treturn;\n\tcase MRSH_ARITHM_COND:;\n\t\tstruct mrsh_arithm_cond *ac = mrsh_arithm_expr_get_cond(expr);\n\t\tmrsh_arithm_expr_destroy(ac->condition);\n\t\tmrsh_arithm_expr_destroy(ac->body);\n\t\tmrsh_arithm_expr_destroy(ac->else_part);\n\t\tfree(ac);\n\t\treturn;\n\tcase MRSH_ARITHM_ASSIGN:;\n\t\tstruct mrsh_arithm_assign *aa = mrsh_arithm_expr_get_assign(expr);\n\t\tfree(aa->name);\n\t\tmrsh_arithm_expr_destroy(aa->value);\n\t\tfree(aa);\n\t\treturn;\n\t}\n\tabort();\n}\n\nstruct mrsh_arithm_literal *mrsh_arithm_literal_create(long value) {\n\tstruct mrsh_arithm_literal *al =\n\t\tcalloc(1, sizeof(struct mrsh_arithm_literal));\n\tif (al == NULL) {\n\t\treturn NULL;\n\t}\n\tal->expr.type = MRSH_ARITHM_LITERAL;\n\tal->value = value;\n\treturn al;\n}\n\nstruct mrsh_arithm_variable *mrsh_arithm_variable_create(char *name) {\n\tstruct mrsh_arithm_variable *av =\n\t\tcalloc(1, sizeof(struct mrsh_arithm_variable));\n\tif (av == NULL) {\n\t\treturn NULL;\n\t}\n\tav->expr.type = MRSH_ARITHM_VARIABLE;\n\tav->name = name;\n\treturn av;\n}\n\nstruct mrsh_arithm_unop *mrsh_arithm_unop_create(\n\t\tenum mrsh_arithm_unop_type type, struct mrsh_arithm_expr *body) {\n\tstruct mrsh_arithm_unop *au =\n\t\tcalloc(1, sizeof(struct mrsh_arithm_unop));\n\tif (au == NULL) {\n\t\treturn NULL;\n\t}\n\tau->expr.type = MRSH_ARITHM_UNOP;\n\tau->type = type;\n\tau->body = body;\n\treturn au;\n}\n\nstruct mrsh_arithm_binop *mrsh_arithm_binop_create(\n\t\tenum mrsh_arithm_binop_type type, struct mrsh_arithm_expr *left,\n\t\tstruct mrsh_arithm_expr *right) {\n\tstruct mrsh_arithm_binop *ab =\n\t\tcalloc(1, sizeof(struct mrsh_arithm_binop));\n\tif (ab == NULL) {\n\t\treturn NULL;\n\t}\n\tab->expr.type = MRSH_ARITHM_BINOP;\n\tab->type = type;\n\tab->left = left;\n\tab->right = right;\n\treturn ab;\n}\n\nstruct mrsh_arithm_cond *mrsh_arithm_cond_create(\n\t\tstruct mrsh_arithm_expr *condition, struct mrsh_arithm_expr *body,\n\t\tstruct mrsh_arithm_expr *else_part) {\n\tstruct mrsh_arithm_cond *ac =\n\t\tcalloc(1, sizeof(struct mrsh_arithm_cond));\n\tif (ac == NULL) {\n\t\treturn NULL;\n\t}\n\tac->expr.type = MRSH_ARITHM_COND;\n\tac->condition = condition;\n\tac->body = body;\n\tac->else_part = else_part;\n\treturn ac;\n}\n\nstruct mrsh_arithm_assign *mrsh_arithm_assign_create(\n\t\tenum mrsh_arithm_assign_op op, char *name,\n\t\tstruct mrsh_arithm_expr *value) {\n\tstruct mrsh_arithm_assign *aa =\n\t\tcalloc(1, sizeof(struct mrsh_arithm_assign));\n\tif (aa == NULL) {\n\t\treturn NULL;\n\t}\n\taa->expr.type = MRSH_ARITHM_ASSIGN;\n\taa->op = op;\n\taa->name = name;\n\taa->value = value;\n\treturn aa;\n}\n\nstruct mrsh_arithm_literal *mrsh_arithm_expr_get_literal(\n\t\tconst struct mrsh_arithm_expr *expr) {\n\tassert(expr->type == MRSH_ARITHM_LITERAL);\n\treturn (struct mrsh_arithm_literal *)expr;\n}\n\nstruct mrsh_arithm_variable *mrsh_arithm_expr_get_variable(\n\t\tconst struct mrsh_arithm_expr *expr) {\n\tassert(expr->type == MRSH_ARITHM_VARIABLE);\n\treturn (struct mrsh_arithm_variable *)expr;\n}\n\nstruct mrsh_arithm_unop *mrsh_arithm_expr_get_unop(\n\t\tconst struct mrsh_arithm_expr *expr) {\n\tassert(expr->type == MRSH_ARITHM_UNOP);\n\treturn (struct mrsh_arithm_unop *)expr;\n}\n\nstruct mrsh_arithm_binop *mrsh_arithm_expr_get_binop(\n\t\tconst struct mrsh_arithm_expr *expr) {\n\tassert(expr->type == MRSH_ARITHM_BINOP);\n\treturn (struct mrsh_arithm_binop *)expr;\n}\n\nstruct mrsh_arithm_cond *mrsh_arithm_expr_get_cond(\n\t\tconst struct mrsh_arithm_expr *expr) {\n\tassert(expr->type == MRSH_ARITHM_COND);\n\treturn (struct mrsh_arithm_cond *)expr;\n}\n\nstruct mrsh_arithm_assign *mrsh_arithm_expr_get_assign(\n\t\tconst struct mrsh_arithm_expr *expr) {\n\tassert(expr->type == MRSH_ARITHM_ASSIGN);\n\treturn (struct mrsh_arithm_assign *)expr;\n}\n"
  },
  {
    "path": "array.c",
    "content": "#include <assert.h>\n#include <mrsh/array.h>\n#include <stdlib.h>\n\n#define INITIAL_SIZE 8\n\nbool mrsh_array_reserve(struct mrsh_array *array, size_t new_cap) {\n\tif (array->cap >= new_cap) {\n\t\treturn true;\n\t}\n\n\tvoid *new_data = realloc(array->data, new_cap * sizeof(void *));\n\tif (new_data == NULL) {\n\t\treturn false;\n\t}\n\tarray->data = new_data;\n\tarray->cap = new_cap;\n\treturn true;\n}\n\nssize_t mrsh_array_add(struct mrsh_array *array, void *value) {\n\tassert(array->len <= array->cap);\n\n\tif (array->len == array->cap) {\n\t\tsize_t new_cap = 2 * array->cap;\n\t\tif (new_cap < INITIAL_SIZE) {\n\t\t\tnew_cap = INITIAL_SIZE;\n\t\t}\n\t\tif (!mrsh_array_reserve(array, new_cap)) {\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\tsize_t i = array->len;\n\tarray->data[i] = value;\n\tarray->len++;\n\treturn i;\n}\n\nvoid mrsh_array_finish(struct mrsh_array *array) {\n\tfree(array->data);\n\tarray->cap = array->len = 0;\n}\n"
  },
  {
    "path": "ast.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <assert.h>\n#include <mrsh/buffer.h>\n#include <stdbool.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"ast.h\"\n\nbool mrsh_position_valid(const struct mrsh_position *pos) {\n\treturn pos->line > 0;\n}\n\nbool mrsh_range_valid(const struct mrsh_range *range) {\n\treturn mrsh_position_valid(&range->begin) &&\n\t\tmrsh_position_valid(&range->end);\n}\n\nvoid mrsh_node_destroy(struct mrsh_node *node) {\n\tswitch (node->type) {\n\tcase MRSH_NODE_PROGRAM:;\n\t\tstruct mrsh_program *prog = mrsh_node_get_program(node);\n\t\tmrsh_program_destroy(prog);\n\t\treturn;\n\tcase MRSH_NODE_COMMAND_LIST:;\n\t\tstruct mrsh_command_list *cl = mrsh_node_get_command_list(node);\n\t\tmrsh_command_list_destroy(cl);\n\t\treturn;\n\tcase MRSH_NODE_AND_OR_LIST:;\n\t\tstruct mrsh_and_or_list *aol = mrsh_node_get_and_or_list(node);\n\t\tmrsh_and_or_list_destroy(aol);\n\t\treturn;\n\tcase MRSH_NODE_COMMAND:;\n\t\tstruct mrsh_command *cmd = mrsh_node_get_command(node);\n\t\tmrsh_command_destroy(cmd);\n\t\treturn;\n\tcase MRSH_NODE_WORD:;\n\t\tstruct mrsh_word *word = mrsh_node_get_word(node);\n\t\tmrsh_word_destroy(word);\n\t\treturn;\n\t}\n\tabort();\n}\n\nvoid mrsh_word_destroy(struct mrsh_word *word) {\n\tif (word == NULL) {\n\t\treturn;\n\t}\n\n\tswitch (word->type) {\n\tcase MRSH_WORD_STRING:;\n\t\tstruct mrsh_word_string *ws = mrsh_word_get_string(word);\n\t\tfree(ws->str);\n\t\tfree(ws);\n\t\treturn;\n\tcase MRSH_WORD_PARAMETER:;\n\t\tstruct mrsh_word_parameter *wp = mrsh_word_get_parameter(word);\n\t\tfree(wp->name);\n\t\tmrsh_word_destroy(wp->arg);\n\t\tfree(wp);\n\t\treturn;\n\tcase MRSH_WORD_COMMAND:;\n\t\tstruct mrsh_word_command *wc = mrsh_word_get_command(word);\n\t\tmrsh_program_destroy(wc->program);\n\t\tfree(wc);\n\t\treturn;\n\tcase MRSH_WORD_ARITHMETIC:;\n\t\tstruct mrsh_word_arithmetic *wa = mrsh_word_get_arithmetic(word);\n\t\tmrsh_word_destroy(wa->body);\n\t\tfree(wa);\n\t\treturn;\n\tcase MRSH_WORD_LIST:;\n\t\tstruct mrsh_word_list *wl = mrsh_word_get_list(word);\n\t\tfor (size_t i = 0; i < wl->children.len; ++i) {\n\t\t\tstruct mrsh_word *child = wl->children.data[i];\n\t\t\tmrsh_word_destroy(child);\n\t\t}\n\t\tmrsh_array_finish(&wl->children);\n\t\tfree(wl);\n\t\treturn;\n\t}\n\tabort();\n}\n\nvoid mrsh_io_redirect_destroy(struct mrsh_io_redirect *redir) {\n\tif (redir == NULL) {\n\t\treturn;\n\t}\n\tmrsh_word_destroy(redir->name);\n\tfor (size_t i = 0; i < redir->here_document.len; ++i) {\n\t\tstruct mrsh_word *line = redir->here_document.data[i];\n\t\tmrsh_word_destroy(line);\n\t}\n\tmrsh_array_finish(&redir->here_document);\n\tfree(redir);\n}\n\nvoid mrsh_assignment_destroy(struct mrsh_assignment *assign) {\n\tif (assign == NULL) {\n\t\treturn;\n\t}\n\tfree(assign->name);\n\tmrsh_word_destroy(assign->value);\n\tfree(assign);\n}\n\nvoid command_list_array_finish(struct mrsh_array *cmds) {\n\tfor (size_t i = 0; i < cmds->len; ++i) {\n\t\tstruct mrsh_command_list *l = cmds->data[i];\n\t\tmrsh_command_list_destroy(l);\n\t}\n\tmrsh_array_finish(cmds);\n}\n\nvoid case_item_destroy(struct mrsh_case_item *item) {\n\tfor (size_t j = 0; j < item->patterns.len; ++j) {\n\t\tstruct mrsh_word *pattern = item->patterns.data[j];\n\t\tmrsh_word_destroy(pattern);\n\t}\n\tmrsh_array_finish(&item->patterns);\n\tcommand_list_array_finish(&item->body);\n\tfree(item);\n}\n\nvoid mrsh_command_destroy(struct mrsh_command *cmd) {\n\tif (cmd == NULL) {\n\t\treturn;\n\t}\n\n\tswitch (cmd->type) {\n\tcase MRSH_SIMPLE_COMMAND:;\n\t\tstruct mrsh_simple_command *sc = mrsh_command_get_simple_command(cmd);\n\t\tmrsh_word_destroy(sc->name);\n\t\tfor (size_t i = 0; i < sc->arguments.len; ++i) {\n\t\t\tstruct mrsh_word *arg = sc->arguments.data[i];\n\t\t\tmrsh_word_destroy(arg);\n\t\t}\n\t\tmrsh_array_finish(&sc->arguments);\n\t\tfor (size_t i = 0; i < sc->io_redirects.len; ++i) {\n\t\t\tstruct mrsh_io_redirect *redir = sc->io_redirects.data[i];\n\t\t\tmrsh_io_redirect_destroy(redir);\n\t\t}\n\t\tmrsh_array_finish(&sc->io_redirects);\n\t\tfor (size_t i = 0; i < sc->assignments.len; ++i) {\n\t\t\tstruct mrsh_assignment *assign = sc->assignments.data[i];\n\t\t\tmrsh_assignment_destroy(assign);\n\t\t}\n\t\tmrsh_array_finish(&sc->assignments);\n\t\tfree(sc);\n\t\treturn;\n\tcase MRSH_BRACE_GROUP:;\n\t\tstruct mrsh_brace_group *bg = mrsh_command_get_brace_group(cmd);\n\t\tcommand_list_array_finish(&bg->body);\n\t\tfree(bg);\n\t\treturn;\n\tcase MRSH_SUBSHELL:;\n\t\tstruct mrsh_subshell *s = mrsh_command_get_subshell(cmd);\n\t\tcommand_list_array_finish(&s->body);\n\t\tfree(s);\n\t\treturn;\n\tcase MRSH_IF_CLAUSE:;\n\t\tstruct mrsh_if_clause *ic = mrsh_command_get_if_clause(cmd);\n\t\tcommand_list_array_finish(&ic->condition);\n\t\tcommand_list_array_finish(&ic->body);\n\t\tmrsh_command_destroy(ic->else_part);\n\t\tfree(ic);\n\t\treturn;\n\tcase MRSH_FOR_CLAUSE:;\n\t\tstruct mrsh_for_clause *fc = mrsh_command_get_for_clause(cmd);\n\t\tfree(fc->name);\n\t\tfor (size_t i = 0; i < fc->word_list.len; ++i) {\n\t\t\tstruct mrsh_word *word = fc->word_list.data[i];\n\t\t\tmrsh_word_destroy(word);\n\t\t}\n\t\tmrsh_array_finish(&fc->word_list);\n\t\tcommand_list_array_finish(&fc->body);\n\t\tfree(fc);\n\t\treturn;\n\tcase MRSH_LOOP_CLAUSE:;\n\t\tstruct mrsh_loop_clause *lc = mrsh_command_get_loop_clause(cmd);\n\t\tcommand_list_array_finish(&lc->condition);\n\t\tcommand_list_array_finish(&lc->body);\n\t\tfree(lc);\n\t\treturn;\n\tcase MRSH_CASE_CLAUSE:;\n\t\tstruct mrsh_case_clause *cc = mrsh_command_get_case_clause(cmd);\n\t\tmrsh_word_destroy(cc->word);\n\t\tfor (size_t i = 0; i < cc->items.len; ++i) {\n\t\t\tstruct mrsh_case_item *item = cc->items.data[i];\n\t\t\tcase_item_destroy(item);\n\t\t}\n\t\tmrsh_array_finish(&cc->items);\n\t\tfree(cc);\n\t\treturn;\n\tcase MRSH_FUNCTION_DEFINITION:;\n\t\tstruct mrsh_function_definition *fd =\n\t\t\tmrsh_command_get_function_definition(cmd);\n\t\tfree(fd->name);\n\t\tmrsh_command_destroy(fd->body);\n\t\tfor (size_t i = 0; i < fd->io_redirects.len; ++i) {\n\t\t\tstruct mrsh_io_redirect *redir = fd->io_redirects.data[i];\n\t\t\tmrsh_io_redirect_destroy(redir);\n\t\t}\n\t\tmrsh_array_finish(&fd->io_redirects);\n\t\tfree(fd);\n\t\treturn;\n\t}\n\tabort();\n}\n\nvoid mrsh_and_or_list_destroy(struct mrsh_and_or_list *and_or_list) {\n\tif (and_or_list == NULL) {\n\t\treturn;\n\t}\n\n\tswitch (and_or_list->type) {\n\tcase MRSH_AND_OR_LIST_PIPELINE:;\n\t\tstruct mrsh_pipeline *p = mrsh_and_or_list_get_pipeline(and_or_list);\n\t\tfor (size_t i = 0; i < p->commands.len; ++i) {\n\t\t\tstruct mrsh_command *cmd = p->commands.data[i];\n\t\t\tmrsh_command_destroy(cmd);\n\t\t}\n\t\tmrsh_array_finish(&p->commands);\n\t\tfree(p);\n\t\treturn;\n\tcase MRSH_AND_OR_LIST_BINOP:;\n\t\tstruct mrsh_binop *binop = mrsh_and_or_list_get_binop(and_or_list);\n\t\tmrsh_and_or_list_destroy(binop->left);\n\t\tmrsh_and_or_list_destroy(binop->right);\n\t\tfree(binop);\n\t\treturn;\n\t}\n\tabort();\n}\n\nstruct mrsh_command_list *mrsh_command_list_create(void) {\n\tstruct mrsh_command_list *list = calloc(1, sizeof(struct mrsh_command_list));\n\tlist->node.type = MRSH_NODE_COMMAND_LIST;\n\treturn list;\n}\n\nvoid mrsh_command_list_destroy(struct mrsh_command_list *l) {\n\tif (l == NULL) {\n\t\treturn;\n\t}\n\n\tmrsh_and_or_list_destroy(l->and_or_list);\n\tfree(l);\n}\n\nstruct mrsh_program *mrsh_program_create(void) {\n\tstruct mrsh_program *prog = calloc(1, sizeof(struct mrsh_program));\n\tprog->node.type = MRSH_NODE_PROGRAM;\n\treturn prog;\n}\n\nvoid mrsh_program_destroy(struct mrsh_program *prog) {\n\tif (prog == NULL) {\n\t\treturn;\n\t}\n\n\tcommand_list_array_finish(&prog->body);\n\tfree(prog);\n}\n\nstruct mrsh_word *mrsh_node_get_word(const struct mrsh_node *node) {\n\tassert(node->type == MRSH_NODE_WORD);\n\treturn (struct mrsh_word *)node;\n}\n\nstruct mrsh_command *mrsh_node_get_command(const struct mrsh_node *node) {\n\tassert(node->type == MRSH_NODE_COMMAND);\n\treturn (struct mrsh_command *)node;\n}\n\nstruct mrsh_and_or_list *mrsh_node_get_and_or_list(\n\t\tconst struct mrsh_node *node) {\n\tassert(node->type == MRSH_NODE_AND_OR_LIST);\n\treturn (struct mrsh_and_or_list *)node;\n}\n\nstruct mrsh_command_list *mrsh_node_get_command_list(\n\t\tconst struct mrsh_node *node) {\n\tassert(node->type == MRSH_NODE_COMMAND_LIST);\n\treturn (struct mrsh_command_list *)node;\n}\n\nstruct mrsh_program *mrsh_node_get_program(const struct mrsh_node *node) {\n\tassert(node->type == MRSH_NODE_PROGRAM);\n\treturn (struct mrsh_program *)node;\n}\n\nstruct mrsh_word_string *mrsh_word_string_create(char *str,\n\t\tbool single_quoted) {\n\tstruct mrsh_word_string *ws = calloc(1, sizeof(struct mrsh_word_string));\n\tws->word.node.type = MRSH_NODE_WORD;\n\tws->word.type = MRSH_WORD_STRING;\n\tws->str = str;\n\tws->single_quoted = single_quoted;\n\treturn ws;\n}\n\nstruct mrsh_word_parameter *mrsh_word_parameter_create(char *name,\n\t\tenum mrsh_word_parameter_op op, bool colon, struct mrsh_word *arg) {\n\tstruct mrsh_word_parameter *wp =\n\t\tcalloc(1, sizeof(struct mrsh_word_parameter));\n\twp->word.node.type = MRSH_NODE_WORD;\n\twp->word.type = MRSH_WORD_PARAMETER;\n\twp->name = name;\n\twp->op = op;\n\twp->colon = colon;\n\twp->arg = arg;\n\treturn wp;\n}\n\nstruct mrsh_word_command *mrsh_word_command_create(struct mrsh_program *prog,\n\t\tbool back_quoted) {\n\tstruct mrsh_word_command *wc =\n\t\tcalloc(1, sizeof(struct mrsh_word_command));\n\twc->word.node.type = MRSH_NODE_WORD;\n\twc->word.type = MRSH_WORD_COMMAND;\n\twc->program = prog;\n\twc->back_quoted = back_quoted;\n\treturn wc;\n}\n\nstruct mrsh_word_arithmetic *mrsh_word_arithmetic_create(\n\t\tstruct mrsh_word *body) {\n\tstruct mrsh_word_arithmetic *wa =\n\t\tcalloc(1, sizeof(struct mrsh_word_arithmetic));\n\twa->word.node.type = MRSH_NODE_WORD;\n\twa->word.type = MRSH_WORD_ARITHMETIC;\n\twa->body = body;\n\treturn wa;\n}\n\nstruct mrsh_word_list *mrsh_word_list_create(struct mrsh_array *children,\n\t\tbool double_quoted) {\n\tstruct mrsh_word_list *wl = calloc(1, sizeof(struct mrsh_word_list));\n\twl->word.node.type = MRSH_NODE_WORD;\n\twl->word.type = MRSH_WORD_LIST;\n\tif (children != NULL) {\n\t\twl->children = *children;\n\t}\n\twl->double_quoted = double_quoted;\n\treturn wl;\n}\n\nstruct mrsh_word_string *mrsh_word_get_string(const struct mrsh_word *word) {\n\tassert(word->type == MRSH_WORD_STRING);\n\treturn (struct mrsh_word_string *)word;\n}\n\nstruct mrsh_word_parameter *mrsh_word_get_parameter(\n\t\tconst struct mrsh_word *word) {\n\tassert(word->type == MRSH_WORD_PARAMETER);\n\treturn (struct mrsh_word_parameter *)word;\n}\n\nstruct mrsh_word_command *mrsh_word_get_command(const struct mrsh_word *word) {\n\tassert(word->type == MRSH_WORD_COMMAND);\n\treturn (struct mrsh_word_command *)word;\n}\n\nstruct mrsh_word_arithmetic *mrsh_word_get_arithmetic(\n\t\tconst struct mrsh_word *word) {\n\tassert(word->type == MRSH_WORD_ARITHMETIC);\n\treturn (struct mrsh_word_arithmetic *)word;\n}\n\nstruct mrsh_word_list *mrsh_word_get_list(const struct mrsh_word *word) {\n\tassert(word->type == MRSH_WORD_LIST);\n\treturn (struct mrsh_word_list *)word;\n}\n\nstruct mrsh_simple_command *mrsh_simple_command_create(struct mrsh_word *name,\n\t\tstruct mrsh_array *arguments, struct mrsh_array *io_redirects,\n\t\tstruct mrsh_array *assignments) {\n\tstruct mrsh_simple_command *cmd =\n\t\tcalloc(1, sizeof(struct mrsh_simple_command));\n\tcmd->command.node.type = MRSH_NODE_COMMAND;\n\tcmd->command.type = MRSH_SIMPLE_COMMAND;\n\tcmd->name = name;\n\tcmd->arguments = *arguments;\n\tcmd->io_redirects = *io_redirects;\n\tcmd->assignments = *assignments;\n\treturn cmd;\n}\n\nstruct mrsh_brace_group *mrsh_brace_group_create(struct mrsh_array *body) {\n\tstruct mrsh_brace_group *bg = calloc(1, sizeof(struct mrsh_brace_group));\n\tbg->command.node.type = MRSH_NODE_COMMAND;\n\tbg->command.type = MRSH_BRACE_GROUP;\n\tbg->body = *body;\n\treturn bg;\n}\n\nstruct mrsh_subshell *mrsh_subshell_create(struct mrsh_array *body) {\n\tstruct mrsh_subshell *s = calloc(1, sizeof(struct mrsh_subshell));\n\ts->command.node.type = MRSH_NODE_COMMAND;\n\ts->command.type = MRSH_SUBSHELL;\n\ts->body = *body;\n\treturn s;\n}\n\nstruct mrsh_if_clause *mrsh_if_clause_create(struct mrsh_array *condition,\n\t\tstruct mrsh_array *body, struct mrsh_command *else_part) {\n\tstruct mrsh_if_clause *ic = calloc(1, sizeof(struct mrsh_if_clause));\n\tic->command.node.type = MRSH_NODE_COMMAND;\n\tic->command.type = MRSH_IF_CLAUSE;\n\tic->condition = *condition;\n\tic->body = *body;\n\tic->else_part = else_part;\n\treturn ic;\n}\n\nstruct mrsh_for_clause *mrsh_for_clause_create(char *name, bool in,\n\t\tstruct mrsh_array *word_list, struct mrsh_array *body) {\n\tstruct mrsh_for_clause *fc = calloc(1, sizeof(struct mrsh_for_clause));\n\tfc->command.node.type = MRSH_NODE_COMMAND;\n\tfc->command.type = MRSH_FOR_CLAUSE;\n\tfc->name = name;\n\tfc->in = in;\n\tfc->word_list = *word_list;\n\tfc->body = *body;\n\treturn fc;\n}\n\nstruct mrsh_loop_clause *mrsh_loop_clause_create(enum mrsh_loop_type type,\n\t\tstruct mrsh_array *condition, struct mrsh_array *body) {\n\tstruct mrsh_loop_clause *lc = calloc(1, sizeof(struct mrsh_loop_clause));\n\tlc->command.node.type = MRSH_NODE_COMMAND;\n\tlc->command.type = MRSH_LOOP_CLAUSE;\n\tlc->type = type;\n\tlc->condition = *condition;\n\tlc->body = *body;\n\treturn lc;\n}\n\nstruct mrsh_case_clause *mrsh_case_clause_create(struct mrsh_word *word,\n\t\tstruct mrsh_array *items) {\n\tstruct mrsh_case_clause *cc = calloc(1, sizeof(struct mrsh_case_clause));\n\tcc->command.node.type = MRSH_NODE_COMMAND;\n\tcc->command.type = MRSH_CASE_CLAUSE;\n\tcc->word = word;\n\tcc->items = *items;\n\treturn cc;\n}\n\nstruct mrsh_function_definition *mrsh_function_definition_create(char *name,\n\t\tstruct mrsh_command *body, struct mrsh_array *io_redirects) {\n\tstruct mrsh_function_definition *fd =\n\t\tcalloc(1, sizeof(struct mrsh_function_definition));\n\tfd->command.node.type = MRSH_NODE_COMMAND;\n\tfd->command.type = MRSH_FUNCTION_DEFINITION;\n\tfd->name = name;\n\tfd->body = body;\n\tfd->io_redirects = *io_redirects;\n\treturn fd;\n}\n\nstruct mrsh_simple_command *mrsh_command_get_simple_command(\n\t\tconst struct mrsh_command *cmd) {\n\tassert(cmd->type == MRSH_SIMPLE_COMMAND);\n\treturn (struct mrsh_simple_command *)cmd;\n}\n\nstruct mrsh_brace_group *mrsh_command_get_brace_group(\n\t\tconst struct mrsh_command *cmd) {\n\tassert(cmd->type == MRSH_BRACE_GROUP);\n\treturn (struct mrsh_brace_group *)cmd;\n}\n\nstruct mrsh_subshell *mrsh_command_get_subshell(\n\t\tconst struct mrsh_command *cmd) {\n\tassert(cmd->type == MRSH_SUBSHELL);\n\treturn (struct mrsh_subshell *)cmd;\n}\n\nstruct mrsh_if_clause *mrsh_command_get_if_clause(\n\t\tconst struct mrsh_command *cmd) {\n\tassert(cmd->type == MRSH_IF_CLAUSE);\n\treturn (struct mrsh_if_clause *)cmd;\n}\n\nstruct mrsh_for_clause *mrsh_command_get_for_clause(\n\t\tconst struct mrsh_command *cmd) {\n\tassert(cmd->type == MRSH_FOR_CLAUSE);\n\treturn (struct mrsh_for_clause *)cmd;\n}\n\nstruct mrsh_loop_clause *mrsh_command_get_loop_clause(\n\t\tconst struct mrsh_command *cmd) {\n\tassert(cmd->type == MRSH_LOOP_CLAUSE);\n\treturn (struct mrsh_loop_clause *)cmd;\n}\n\nstruct mrsh_case_clause *mrsh_command_get_case_clause(\n\t\tconst struct mrsh_command *cmd) {\n\tassert(cmd->type == MRSH_CASE_CLAUSE);\n\treturn (struct mrsh_case_clause *)cmd;\n}\n\nstruct mrsh_function_definition *mrsh_command_get_function_definition(\n\t\tconst struct mrsh_command *cmd) {\n\tassert(cmd->type == MRSH_FUNCTION_DEFINITION);\n\treturn (struct mrsh_function_definition *)cmd;\n}\n\nstruct mrsh_pipeline *mrsh_pipeline_create(struct mrsh_array *commands,\n\t\tbool bang) {\n\tstruct mrsh_pipeline *pl = calloc(1, sizeof(struct mrsh_pipeline));\n\tpl->and_or_list.node.type = MRSH_NODE_AND_OR_LIST;\n\tpl->and_or_list.type = MRSH_AND_OR_LIST_PIPELINE;\n\tpl->commands = *commands;\n\tpl->bang = bang;\n\treturn pl;\n}\n\nstruct mrsh_binop *mrsh_binop_create(enum mrsh_binop_type type,\n\t\tstruct mrsh_and_or_list *left, struct mrsh_and_or_list *right) {\n\tstruct mrsh_binop *binop = calloc(1, sizeof(struct mrsh_binop));\n\tbinop->and_or_list.node.type = MRSH_NODE_AND_OR_LIST;\n\tbinop->and_or_list.type = MRSH_AND_OR_LIST_BINOP;\n\tbinop->type = type;\n\tbinop->left = left;\n\tbinop->right = right;\n\treturn binop;\n}\n\nstruct mrsh_pipeline *mrsh_and_or_list_get_pipeline(\n\t\tconst struct mrsh_and_or_list *and_or_list) {\n\tassert(and_or_list->type == MRSH_AND_OR_LIST_PIPELINE);\n\treturn (struct mrsh_pipeline *)and_or_list;\n}\n\nstruct mrsh_binop *mrsh_and_or_list_get_binop(\n\t\tconst struct mrsh_and_or_list *and_or_list) {\n\tassert(and_or_list->type == MRSH_AND_OR_LIST_BINOP);\n\treturn (struct mrsh_binop *)and_or_list;\n}\n\nstatic void node_array_for_each(struct mrsh_array *nodes,\n\t\tmrsh_node_iterator_func iterator, void *user_data) {\n\tfor (size_t i = 0; i < nodes->len; ++i) {\n\t\tstruct mrsh_node *node = nodes->data[i];\n\t\tmrsh_node_for_each(node, iterator, user_data);\n\t}\n}\n\nvoid mrsh_node_for_each(struct mrsh_node *node,\n\t\tmrsh_node_iterator_func iterator, void *user_data) {\n\titerator(node, user_data);\n\n\tswitch (node->type) {\n\tcase MRSH_NODE_PROGRAM:;\n\t\tstruct mrsh_program *program = mrsh_node_get_program(node);\n\t\tnode_array_for_each(&program->body, iterator, user_data);\n\t\treturn;\n\tcase MRSH_NODE_COMMAND_LIST:;\n\t\tstruct mrsh_command_list *list = mrsh_node_get_command_list(node);\n\t\tmrsh_node_for_each(&list->and_or_list->node, iterator, user_data);\n\t\treturn;\n\tcase MRSH_NODE_AND_OR_LIST:;\n\t\tstruct mrsh_and_or_list *and_or_list = mrsh_node_get_and_or_list(node);\n\t\tswitch (and_or_list->type) {\n\t\tcase MRSH_AND_OR_LIST_BINOP:;\n\t\t\tstruct mrsh_binop *binop = mrsh_and_or_list_get_binop(and_or_list);\n\t\t\tmrsh_node_for_each(&binop->left->node, iterator, user_data);\n\t\t\tmrsh_node_for_each(&binop->right->node, iterator, user_data);\n\t\t\treturn;\n\t\tcase MRSH_AND_OR_LIST_PIPELINE:;\n\t\t\tstruct mrsh_pipeline *pipeline =\n\t\t\t\tmrsh_and_or_list_get_pipeline(and_or_list);\n\t\t\tnode_array_for_each(&pipeline->commands, iterator, user_data);\n\t\t\treturn;\n\t\t}\n\t\tabort();\n\tcase MRSH_NODE_COMMAND:;\n\t\tstruct mrsh_command *cmd = mrsh_node_get_command(node);\n\t\tswitch (cmd->type) {\n\t\tcase MRSH_SIMPLE_COMMAND:;\n\t\t\tstruct mrsh_simple_command *sc =\n\t\t\t\tmrsh_command_get_simple_command(cmd);\n\t\t\tif (sc->name != NULL) {\n\t\t\t\tmrsh_node_for_each(&sc->name->node, iterator, user_data);\n\t\t\t}\n\t\t\tnode_array_for_each(&sc->arguments, iterator, user_data);\n\t\t\t// TODO: io_redirects, assignments\n\t\t\treturn;\n\t\tcase MRSH_BRACE_GROUP:;\n\t\t\tstruct mrsh_brace_group *bg = mrsh_command_get_brace_group(cmd);\n\t\t\tnode_array_for_each(&bg->body, iterator, user_data);\n\t\t\treturn;\n\t\tcase MRSH_SUBSHELL:;\n\t\t\tstruct mrsh_subshell *ss = mrsh_command_get_subshell(cmd);\n\t\t\tnode_array_for_each(&ss->body, iterator, user_data);\n\t\t\treturn;\n\t\tcase MRSH_IF_CLAUSE:;\n\t\t\tstruct mrsh_if_clause *ic = mrsh_command_get_if_clause(cmd);\n\t\t\tnode_array_for_each(&ic->condition, iterator, user_data);\n\t\t\tnode_array_for_each(&ic->body, iterator, user_data);\n\t\t\tif (ic->else_part != NULL) {\n\t\t\t\tmrsh_node_for_each(&ic->else_part->node, iterator, user_data);\n\t\t\t}\n\t\t\treturn;\n\t\tcase MRSH_FOR_CLAUSE:;\n\t\t\tstruct mrsh_for_clause *fc = mrsh_command_get_for_clause(cmd);\n\t\t\tnode_array_for_each(&fc->word_list, iterator, user_data);\n\t\t\tnode_array_for_each(&fc->body, iterator, user_data);\n\t\t\treturn;\n\t\tcase MRSH_LOOP_CLAUSE:;\n\t\t\tstruct mrsh_loop_clause *lc = mrsh_command_get_loop_clause(cmd);\n\t\t\tnode_array_for_each(&lc->condition, iterator, user_data);\n\t\t\tnode_array_for_each(&lc->body, iterator, user_data);\n\t\t\treturn;\n\t\tcase MRSH_CASE_CLAUSE:;\n\t\t\tstruct mrsh_case_clause *cc = mrsh_command_get_case_clause(cmd);\n\t\t\tmrsh_node_for_each(&cc->word->node, iterator, user_data);\n\t\t\t// TODO: items\n\t\t\treturn;\n\t\tcase MRSH_FUNCTION_DEFINITION:;\n\t\t\tstruct mrsh_function_definition *fn =\n\t\t\t\tmrsh_command_get_function_definition(cmd);\n\t\t\tmrsh_node_for_each(&fn->body->node, iterator, user_data);\n\t\t\treturn;\n\t\t}\n\t\tabort();\n\tcase MRSH_NODE_WORD:;\n\t\tstruct mrsh_word *word = mrsh_node_get_word(node);\n\t\tswitch (word->type) {\n\t\tcase MRSH_WORD_STRING:\n\t\t\treturn;\n\t\tcase MRSH_WORD_PARAMETER:;\n\t\t\tstruct mrsh_word_parameter *wp = mrsh_word_get_parameter(word);\n\t\t\tif (wp->arg != NULL) {\n\t\t\t\tmrsh_node_for_each(&wp->arg->node, iterator, user_data);\n\t\t\t}\n\t\t\treturn;\n\t\tcase MRSH_WORD_COMMAND:;\n\t\t\tstruct mrsh_word_command *wc =\tmrsh_word_get_command(word);\n\t\t\tif (wc->program != NULL) {\n\t\t\t\tmrsh_node_for_each(&wc->program->node, iterator, user_data);\n\t\t\t}\n\t\t\treturn;\n\t\tcase MRSH_WORD_ARITHMETIC:;\n\t\t\tstruct mrsh_word_arithmetic *wa = mrsh_word_get_arithmetic(word);\n\t\t\tmrsh_node_for_each(&wa->body->node, iterator, user_data);\n\t\t\treturn;\n\t\tcase MRSH_WORD_LIST:;\n\t\t\tstruct mrsh_word_list *wl = mrsh_word_get_list(word);\n\t\t\tnode_array_for_each(&wl->children, iterator, user_data);\n\t\t\treturn;\n\t\t}\n\t\tabort();\n\t}\n\tabort();\n}\n\nstatic void position_next(struct mrsh_position *dst,\n\t\tconst struct mrsh_position *src) {\n\t*dst = *src;\n\t++dst->offset;\n\t++dst->column;\n}\n\nvoid mrsh_word_range(struct mrsh_word *word, struct mrsh_position *begin,\n\t\tstruct mrsh_position *end) {\n\tif (begin == NULL && end == NULL) {\n\t\treturn;\n\t}\n\n\tstruct mrsh_position _begin, _end;\n\tif (begin == NULL) {\n\t\tbegin = &_begin;\n\t}\n\tif (end == NULL) {\n\t\tend = &_end;\n\t}\n\n\tswitch (word->type) {\n\tcase MRSH_WORD_STRING:;\n\t\tstruct mrsh_word_string *ws = mrsh_word_get_string(word);\n\t\t*begin = ws->range.begin;\n\t\t*end = ws->range.end;\n\t\treturn;\n\tcase MRSH_WORD_PARAMETER:;\n\t\tstruct mrsh_word_parameter *wp = mrsh_word_get_parameter(word);\n\t\t*begin = wp->dollar_pos;\n\t\tif (mrsh_position_valid(&wp->rbrace_pos)) {\n\t\t\tposition_next(end, &wp->rbrace_pos);\n\t\t} else {\n\t\t\t*end = wp->name_range.end;\n\t\t}\n\t\treturn;\n\tcase MRSH_WORD_COMMAND:;\n\t\tstruct mrsh_word_command *wc = mrsh_word_get_command(word);\n\t\t*begin = wc->range.begin;\n\t\t*end = wc->range.end;\n\t\treturn;\n\tcase MRSH_WORD_ARITHMETIC:\n\t\tabort(); // TODO\n\tcase MRSH_WORD_LIST:;\n\t\tstruct mrsh_word_list *wl = mrsh_word_get_list(word);\n\t\tif (wl->children.len == 0) {\n\t\t\t*begin = *end = (struct mrsh_position){0};\n\t\t} else {\n\t\t\tstruct mrsh_word *first = wl->children.data[0];\n\t\t\tstruct mrsh_word *last = wl->children.data[wl->children.len - 1];\n\t\t\tmrsh_word_range(first, begin, NULL);\n\t\t\tmrsh_word_range(last, NULL, end);\n\t\t}\n\t\treturn;\n\t}\n\tabort();\n}\n\nvoid mrsh_command_range(struct mrsh_command *cmd, struct mrsh_position *begin,\n\t\tstruct mrsh_position *end) {\n\tif (begin == NULL && end == NULL) {\n\t\treturn;\n\t}\n\n\tstruct mrsh_position _begin, _end;\n\tif (begin == NULL) {\n\t\tbegin = &_begin;\n\t}\n\tif (end == NULL) {\n\t\tend = &_end;\n\t}\n\n\tswitch (cmd->type) {\n\tcase MRSH_SIMPLE_COMMAND:;\n\t\tstruct mrsh_simple_command *sc = mrsh_command_get_simple_command(cmd);\n\n\t\tif (sc->name != NULL) {\n\t\t\tmrsh_word_range(sc->name, begin, end);\n\t\t} else {\n\t\t\tassert(sc->assignments.len > 0);\n\t\t\tstruct mrsh_assignment *first = sc->assignments.data[0];\n\t\t\t*begin = first->name_range.begin;\n\t\t\t*end = *begin; // That's a lie, but it'll be fixed by the code below\n\t\t}\n\n\t\tstruct mrsh_position maybe_end;\n\t\tfor (size_t i = 0; i < sc->arguments.len; ++i) {\n\t\t\tstruct mrsh_word *arg = sc->arguments.data[i];\n\t\t\tmrsh_word_range(arg, NULL, &maybe_end);\n\t\t\tif (maybe_end.offset > end->offset) {\n\t\t\t\t*end = maybe_end;\n\t\t\t}\n\t\t}\n\t\tfor (size_t i = 0; i < sc->io_redirects.len; ++i) {\n\t\t\tstruct mrsh_io_redirect *redir = sc->io_redirects.data[i];\n\t\t\tmrsh_word_range(redir->name, NULL, &maybe_end);\n\t\t\tif (maybe_end.offset > end->offset) {\n\t\t\t\t*end = maybe_end;\n\t\t\t}\n\t\t}\n\t\tfor (size_t i = 0; i < sc->assignments.len; ++i) {\n\t\t\tstruct mrsh_assignment *assign = sc->assignments.data[i];\n\t\t\tmrsh_word_range(assign->value, NULL, &maybe_end);\n\t\t\tif (maybe_end.offset > end->offset) {\n\t\t\t\t*end = maybe_end;\n\t\t\t}\n\t\t}\n\t\treturn;\n\tcase MRSH_BRACE_GROUP:;\n\t\tstruct mrsh_brace_group *bg = mrsh_command_get_brace_group(cmd);\n\t\t*begin = bg->lbrace_pos;\n\t\tposition_next(end, &bg->rbrace_pos);\n\t\treturn;\n\tcase MRSH_SUBSHELL:;\n\t\tstruct mrsh_subshell *s = mrsh_command_get_subshell(cmd);\n\t\t*begin = s->lparen_pos;\n\t\tposition_next(end, &s->rparen_pos);\n\t\treturn;\n\tcase MRSH_IF_CLAUSE:;\n\t\tstruct mrsh_if_clause *ic = mrsh_command_get_if_clause(cmd);\n\t\t*begin = ic->if_range.begin;\n\t\t*end = ic->fi_range.end;\n\t\treturn;\n\tcase MRSH_FOR_CLAUSE:;\n\t\tstruct mrsh_for_clause *fc = mrsh_command_get_for_clause(cmd);\n\t\t*begin = fc->for_range.begin;\n\t\t*end = fc->done_range.end;\n\t\treturn;\n\tcase MRSH_LOOP_CLAUSE:;\n\t\tstruct mrsh_loop_clause *lc = mrsh_command_get_loop_clause(cmd);\n\t\t*begin = lc->while_until_range.begin;\n\t\t*end = lc->done_range.end;\n\t\treturn;\n\tcase MRSH_CASE_CLAUSE:;\n\t\tstruct mrsh_case_clause *cc = mrsh_command_get_case_clause(cmd);\n\t\t*begin = cc->case_range.begin;\n\t\t*end = cc->esac_range.end;\n\t\treturn;\n\tcase MRSH_FUNCTION_DEFINITION:;\n\t\tstruct mrsh_function_definition *fd =\n\t\t\tmrsh_command_get_function_definition(cmd);\n\t\t*begin = fd->name_range.begin;\n\t\tmrsh_command_range(fd->body, NULL, end);\n\t}\n\tabort();\n}\n\nstatic void buffer_append_str(struct mrsh_buffer *buf, const char *str) {\n\tmrsh_buffer_append(buf, str, strlen(str));\n}\n\nstatic void word_str(const struct mrsh_word *word, struct mrsh_buffer *buf) {\n\tswitch (word->type) {\n\tcase MRSH_WORD_STRING:;\n\t\tconst struct mrsh_word_string *ws = mrsh_word_get_string(word);\n\t\tbuffer_append_str(buf, ws->str);\n\t\treturn;\n\tcase MRSH_WORD_PARAMETER:\n\tcase MRSH_WORD_COMMAND:\n\tcase MRSH_WORD_ARITHMETIC:\n\t\tabort();\n\tcase MRSH_WORD_LIST:;\n\t\tconst struct mrsh_word_list *wl = mrsh_word_get_list(word);\n\t\tfor (size_t i = 0; i < wl->children.len; ++i) {\n\t\t\tconst struct mrsh_word *child = wl->children.data[i];\n\t\t\tword_str(child, buf);\n\t\t}\n\t\treturn;\n\t}\n\tabort();\n}\n\nchar *mrsh_word_str(const struct mrsh_word *word) {\n\tstruct mrsh_buffer buf = {0};\n\tword_str(word, &buf);\n\tmrsh_buffer_append_char(&buf, '\\0');\n\treturn mrsh_buffer_steal(&buf);\n}\n\nstatic const char *binop_type_str(enum mrsh_binop_type t) {\n\tswitch (t) {\n\tcase MRSH_BINOP_AND:\n\t\treturn \"&&\";\n\tcase MRSH_BINOP_OR:\n\t\treturn \"||\";\n\t}\n\tabort();\n}\n\nstatic void node_format(struct mrsh_node *node, struct mrsh_buffer *buf);\n\nstatic void node_array_format(struct mrsh_array *array, const char *sep,\n\t\tstruct mrsh_buffer *buf) {\n\tfor (size_t i = 0; i < array->len; i++) {\n\t\tstruct mrsh_node *node = array->data[i];\n\t\tif (i > 0) {\n\t\t\tbuffer_append_str(buf, sep);\n\t\t}\n\t\tnode_format(node, buf);\n\t}\n}\n\nstatic void node_format(struct mrsh_node *node, struct mrsh_buffer *buf) {\n\tswitch (node->type) {\n\tcase MRSH_NODE_PROGRAM:;\n\t\tstruct mrsh_program *program = mrsh_node_get_program(node);\n\t\tnode_array_format(&program->body, \" \", buf);\n\t\treturn;\n\tcase MRSH_NODE_COMMAND_LIST:;\n\t\tstruct mrsh_command_list *list = mrsh_node_get_command_list(node);\n\t\tnode_format(&list->and_or_list->node, buf);\n\t\tbuffer_append_str(buf, list->ampersand ? \" &\" : \";\");\n\t\treturn;\n\tcase MRSH_NODE_AND_OR_LIST:;\n\t\tstruct mrsh_and_or_list *and_or_list = mrsh_node_get_and_or_list(node);\n\t\tswitch (and_or_list->type) {\n\t\tcase MRSH_AND_OR_LIST_BINOP:;\n\t\t\tstruct mrsh_binop *binop = mrsh_and_or_list_get_binop(and_or_list);\n\t\t\tnode_format(&binop->left->node, buf);\n\t\t\tmrsh_buffer_append_char(buf, ' ');\n\t\t\tbuffer_append_str(buf, binop_type_str(binop->type));\n\t\t\tmrsh_buffer_append_char(buf, ' ');\n\t\t\tnode_format(&binop->right->node, buf);\n\t\t\treturn;\n\t\tcase MRSH_AND_OR_LIST_PIPELINE:;\n\t\t\tstruct mrsh_pipeline *pipeline =\n\t\t\t\tmrsh_and_or_list_get_pipeline(and_or_list);\n\t\t\tif (pipeline->bang) {\n\t\t\t\tbuffer_append_str(buf, \"! \");\n\t\t\t}\n\t\t\tnode_array_format(&pipeline->commands, \" | \", buf);\n\t\t\treturn;\n\t\t}\n\t\tabort();\n\tcase MRSH_NODE_COMMAND:;\n\t\tstruct mrsh_command *cmd = mrsh_node_get_command(node);\n\t\tswitch (cmd->type) {\n\t\tcase MRSH_SIMPLE_COMMAND:;\n\t\t\tstruct mrsh_simple_command *sc =\n\t\t\t\tmrsh_command_get_simple_command(cmd);\n\t\t\tif (sc->name != NULL) {\n\t\t\t\tnode_format(&sc->name->node, buf);\n\t\t\t\tmrsh_buffer_append_char(buf, ' ');\n\t\t\t}\n\t\t\tnode_array_format(&sc->arguments, \" \", buf);\n\t\t\t// TODO: io_redirects, assignments\n\t\t\treturn;\n\t\tcase MRSH_BRACE_GROUP:;\n\t\t\tstruct mrsh_brace_group *bg = mrsh_command_get_brace_group(cmd);\n\t\t\tbuffer_append_str(buf, \"{ \");\n\t\t\tnode_array_format(&bg->body, \" \", buf);\n\t\t\tbuffer_append_str(buf, \"; }\");\n\t\t\treturn;\n\t\tcase MRSH_SUBSHELL:;\n\t\t\tstruct mrsh_subshell *ss = mrsh_command_get_subshell(cmd);\n\t\t\tmrsh_buffer_append_char(buf, '(');\n\t\t\tnode_array_format(&ss->body, \" \", buf);\n\t\t\tmrsh_buffer_append_char(buf, ')');\n\t\t\treturn;\n\t\tcase MRSH_IF_CLAUSE:;\n\t\t\tstruct mrsh_if_clause *ic = mrsh_command_get_if_clause(cmd);\n\t\t\tbuffer_append_str(buf, \"if \");\n\t\t\tnode_array_format(&ic->condition, \" \", buf);\n\t\t\tbuffer_append_str(buf, \"then \");\n\t\t\tnode_array_format(&ic->body, \" \", buf);\n\t\t\tif (ic->else_part != NULL) {\n\t\t\t\t// TODO: elif\n\t\t\t\tbuffer_append_str(buf, \"else \");\n\t\t\t\tnode_format(&ic->else_part->node, buf);\n\t\t\t}\n\t\t\tbuffer_append_str(buf, \"fi\");\n\t\t\treturn;\n\t\tcase MRSH_FOR_CLAUSE:;\n\t\t\t//struct mrsh_for_clause *fc = mrsh_command_get_for_clause(cmd);\n\t\t\t// TODO\n\t\t\treturn;\n\t\tcase MRSH_LOOP_CLAUSE:;\n\t\t\tstruct mrsh_loop_clause *lc = mrsh_command_get_loop_clause(cmd);\n\t\t\tbuffer_append_str(buf,\n\t\t\t\tlc->type == MRSH_LOOP_WHILE ? \"while \" : \"until \");\n\t\t\tnode_array_format(&lc->condition, \" \", buf);\n\t\t\tbuffer_append_str(buf, \"do \");\n\t\t\tnode_array_format(&lc->body, \" \", buf);\n\t\t\tbuffer_append_str(buf, \"done\");\n\t\t\treturn;\n\t\tcase MRSH_CASE_CLAUSE:;\n\t\t\t//struct mrsh_case_clause *cc = mrsh_command_get_case_clause(cmd);\n\t\t\t// TODO\n\t\t\treturn;\n\t\tcase MRSH_FUNCTION_DEFINITION:;\n\t\t\tstruct mrsh_function_definition *fn =\n\t\t\t\tmrsh_command_get_function_definition(cmd);\n\t\t\tbuffer_append_str(buf, fn->name);\n\t\t\tbuffer_append_str(buf, \"()\");\n\t\t\tnode_format(&fn->body->node, buf);\n\t\t\t// TODO: io-redirect\n\t\t\treturn;\n\t\t}\n\t\tabort();\n\tcase MRSH_NODE_WORD:;\n\t\t// TODO: quoting\n\t\tstruct mrsh_word *word = mrsh_node_get_word(node);\n\t\tswitch (word->type) {\n\t\tcase MRSH_WORD_STRING:;\n\t\t\tstruct mrsh_word_string *ws = mrsh_word_get_string(word);\n\t\t\tif (ws->single_quoted) {\n\t\t\t\tmrsh_buffer_append_char(buf, '\\'');\n\t\t\t}\n\t\t\tbuffer_append_str(buf, ws->str);\n\t\t\tif (ws->single_quoted) {\n\t\t\t\tmrsh_buffer_append_char(buf, '\\'');\n\t\t\t}\n\t\t\treturn;\n\t\tcase MRSH_WORD_PARAMETER:;\n\t\t\tstruct mrsh_word_parameter *wp = mrsh_word_get_parameter(word);\n\t\t\tbuffer_append_str(buf, \"${\");\n\t\t\tif (wp->arg != NULL) {\n\t\t\t\tnode_format(&wp->arg->node, buf);\n\t\t\t}\n\t\t\tbuffer_append_str(buf, \"}\");\n\t\t\treturn;\n\t\tcase MRSH_WORD_COMMAND:;\n\t\t\tstruct mrsh_word_command *wc =\tmrsh_word_get_command(word);\n\t\t\tbuffer_append_str(buf, wc->back_quoted ? \"`\" : \"$(\");\n\t\t\tif (wc->program != NULL) {\n\t\t\t\tnode_format(&wc->program->node, buf);\n\t\t\t}\n\t\t\tbuffer_append_str(buf, wc->back_quoted ? \"`\" : \")\");\n\t\t\treturn;\n\t\tcase MRSH_WORD_ARITHMETIC:;\n\t\t\tstruct mrsh_word_arithmetic *wa = mrsh_word_get_arithmetic(word);\n\t\t\tnode_format(&wa->body->node, buf);\n\t\t\treturn;\n\t\tcase MRSH_WORD_LIST:;\n\t\t\tstruct mrsh_word_list *wl = mrsh_word_get_list(word);\n\t\t\tif (wl->double_quoted) {\n\t\t\t\tmrsh_buffer_append_char(buf, '\"');\n\t\t\t}\n\t\t\tnode_array_format(&wl->children, \"\", buf);\n\t\t\tif (wl->double_quoted) {\n\t\t\t\tmrsh_buffer_append_char(buf, '\"');\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tabort();\n\t}\n\tabort();\n}\n\nchar *mrsh_node_format(struct mrsh_node *node) {\n\tstruct mrsh_buffer buf = {0};\n\tnode_format(node, &buf);\n\tmrsh_buffer_append_char(&buf, '\\0');\n\treturn mrsh_buffer_steal(&buf);\n}\n\nstruct mrsh_node *mrsh_node_copy(const struct mrsh_node *node) {\n\tswitch (node->type) {\n\tcase MRSH_NODE_PROGRAM:;\n\t\tstruct mrsh_program *prog = mrsh_node_get_program(node);\n\t\tstruct mrsh_program *prog_copy = mrsh_program_copy(prog);\n\t\treturn &prog_copy->node;\n\tcase MRSH_NODE_COMMAND_LIST:;\n\t\tstruct mrsh_command_list *cl = mrsh_node_get_command_list(node);\n\t\tstruct mrsh_command_list *cl_copy = mrsh_command_list_copy(cl);\n\t\treturn &cl_copy->node;\n\tcase MRSH_NODE_AND_OR_LIST:;\n\t\tstruct mrsh_and_or_list *aol = mrsh_node_get_and_or_list(node);\n\t\tstruct mrsh_and_or_list *aol_copy = mrsh_and_or_list_copy(aol);\n\t\treturn &aol_copy->node;\n\tcase MRSH_NODE_COMMAND:;\n\t\tstruct mrsh_command *cmd = mrsh_node_get_command(node);\n\t\tstruct mrsh_command *cmd_copy = mrsh_command_copy(cmd);\n\t\treturn &cmd_copy->node;\n\tcase MRSH_NODE_WORD:;\n\t\tstruct mrsh_word *word = mrsh_node_get_word(node);\n\t\tstruct mrsh_word *word_copy = mrsh_word_copy(word);\n\t\treturn &word_copy->node;\n\t}\n\tabort();\n}\n\nstruct mrsh_word *mrsh_word_copy(const struct mrsh_word *word) {\n\tswitch (word->type) {\n\tcase MRSH_WORD_STRING:;\n\t\tstruct mrsh_word_string *ws = mrsh_word_get_string(word);\n\t\tstruct mrsh_word_string *ws_copy =\n\t\t\tmrsh_word_string_create(strdup(ws->str), ws->single_quoted);\n\t\treturn &ws_copy->word;\n\tcase MRSH_WORD_PARAMETER:;\n\t\tstruct mrsh_word_parameter *wp = mrsh_word_get_parameter(word);\n\n\t\tstruct mrsh_word *arg = NULL;\n\t\tif (wp->arg != NULL) {\n\t\t\targ = mrsh_word_copy(wp->arg);\n\t\t}\n\n\t\tstruct mrsh_word_parameter *wp_copy = mrsh_word_parameter_create(\n\t\t\tstrdup(wp->name), wp->op, wp->colon, arg);\n\t\treturn &wp_copy->word;\n\tcase MRSH_WORD_COMMAND:;\n\t\tstruct mrsh_word_command *wc = mrsh_word_get_command(word);\n\t\tstruct mrsh_word_command *wc_copy = mrsh_word_command_create(\n\t\t\tmrsh_program_copy(wc->program), wc->back_quoted);\n\t\treturn &wc_copy->word;\n\tcase MRSH_WORD_ARITHMETIC:;\n\t\tstruct mrsh_word_arithmetic *wa = mrsh_word_get_arithmetic(word);\n\t\tstruct mrsh_word_arithmetic *wa_copy = mrsh_word_arithmetic_create(\n\t\t\tmrsh_word_copy(wa->body));\n\t\treturn &wa_copy->word;\n\tcase MRSH_WORD_LIST:;\n\t\tstruct mrsh_word_list *wl = mrsh_word_get_list(word);\n\t\tstruct mrsh_array children = {0};\n\t\tmrsh_array_reserve(&children, wl->children.len);\n\t\tfor (size_t i = 0; i < wl->children.len; ++i) {\n\t\t\tstruct mrsh_word *child = wl->children.data[i];\n\t\t\tmrsh_array_add(&children, mrsh_word_copy(child));\n\t\t}\n\t\tstruct mrsh_word_list *wl_copy =\n\t\t\tmrsh_word_list_create(&children, wl->double_quoted);\n\t\treturn &wl_copy->word;\n\t}\n\tabort();\n}\n\nstruct mrsh_io_redirect *mrsh_io_redirect_copy(\n\t\tconst struct mrsh_io_redirect *redir) {\n\tstruct mrsh_io_redirect *redir_copy =\n\t\tcalloc(1, sizeof(struct mrsh_io_redirect));\n\tredir_copy->io_number = redir->io_number;\n\tredir_copy->op = redir->op;\n\tredir_copy->name = mrsh_word_copy(redir->name);\n\n\tmrsh_array_reserve(&redir_copy->here_document, redir->here_document.len);\n\tfor (size_t i = 0; i < redir->here_document.len; ++i) {\n\t\tstruct mrsh_word *line = redir->here_document.data[i];\n\t\tmrsh_array_add(&redir_copy->here_document, mrsh_word_copy(line));\n\t}\n\n\treturn redir_copy;\n}\n\nstruct mrsh_assignment *mrsh_assignment_copy(\n\t\tconst struct mrsh_assignment *assign) {\n\tstruct mrsh_assignment *assign_copy =\n\t\tcalloc(1, sizeof(struct mrsh_assignment));\n\tassign_copy->name = strdup(assign->name);\n\tassign_copy->value = mrsh_word_copy(assign->value);\n\treturn assign_copy;\n}\n\nstatic void command_list_array_copy(struct mrsh_array *dst,\n\t\tconst struct mrsh_array *src) {\n\tmrsh_array_reserve(dst, src->len);\n\tfor (size_t i = 0; i < src->len; ++i) {\n\t\tstruct mrsh_command_list *l = src->data[i];\n\t\tmrsh_array_add(dst, mrsh_command_list_copy(l));\n\t}\n}\n\nstatic struct mrsh_case_item *case_item_copy(const struct mrsh_case_item *ci) {\n\tstruct mrsh_case_item *ci_copy = calloc(1, sizeof(struct mrsh_case_item));\n\n\tmrsh_array_reserve(&ci_copy->patterns, ci->patterns.len);\n\tfor (size_t i = 0; i < ci->patterns.len; ++i) {\n\t\tstruct mrsh_word *pattern = ci->patterns.data[i];\n\t\tmrsh_array_add(&ci_copy->patterns, mrsh_word_copy(pattern));\n\t}\n\n\tcommand_list_array_copy(&ci_copy->body, &ci->body);\n\n\treturn ci_copy;\n}\n\nstruct mrsh_command *mrsh_command_copy(const struct mrsh_command *cmd) {\n\tstruct mrsh_array io_redirects = {0};\n\tswitch (cmd->type) {\n\tcase MRSH_SIMPLE_COMMAND:;\n\t\tstruct mrsh_simple_command *sc = mrsh_command_get_simple_command(cmd);\n\n\t\tstruct mrsh_word *name = NULL;\n\t\tif (sc->name != NULL) {\n\t\t\tname = mrsh_word_copy(sc->name);\n\t\t}\n\n\t\tstruct mrsh_array arguments = {0};\n\t\tmrsh_array_reserve(&arguments, sc->arguments.len);\n\t\tfor (size_t i = 0; i < sc->arguments.len; ++i) {\n\t\t\tstruct mrsh_word *arg = sc->arguments.data[i];\n\t\t\tmrsh_array_add(&arguments, mrsh_word_copy(arg));\n\t\t}\n\n\t\tmrsh_array_reserve(&io_redirects, sc->io_redirects.len);\n\t\tfor (size_t i = 0; i < sc->io_redirects.len; ++i) {\n\t\t\tstruct mrsh_io_redirect *redir = sc->io_redirects.data[i];\n\t\t\tmrsh_array_add(&io_redirects, mrsh_io_redirect_copy(redir));\n\t\t}\n\n\t\tstruct mrsh_array assignments = {0};\n\t\tmrsh_array_reserve(&assignments, sc->assignments.len);\n\t\tfor (size_t i = 0; i < sc->assignments.len; ++i) {\n\t\t\tstruct mrsh_assignment *assign = sc->assignments.data[i];\n\t\t\tmrsh_array_add(&assignments, mrsh_assignment_copy(assign));\n\t\t}\n\n\t\tstruct mrsh_simple_command *sc_copy = mrsh_simple_command_create(\n\t\t\tname, &arguments, &io_redirects, &assignments);\n\t\treturn &sc_copy->command;\n\tcase MRSH_BRACE_GROUP:;\n\t\tstruct mrsh_brace_group *bg = mrsh_command_get_brace_group(cmd);\n\t\tstruct mrsh_array bg_body = {0};\n\t\tcommand_list_array_copy(&bg_body, &bg->body);\n\t\tstruct mrsh_brace_group *bg_copy = mrsh_brace_group_create(&bg_body);\n\t\treturn &bg_copy->command;\n\tcase MRSH_SUBSHELL:;\n\t\tstruct mrsh_subshell *ss = mrsh_command_get_subshell(cmd);\n\t\tstruct mrsh_array ss_body = {0};\n\t\tcommand_list_array_copy(&ss_body, &ss->body);\n\t\tstruct mrsh_subshell *ss_copy = mrsh_subshell_create(&ss_body);\n\t\treturn &ss_copy->command;\n\tcase MRSH_IF_CLAUSE:;\n\t\tstruct mrsh_if_clause *ic = mrsh_command_get_if_clause(cmd);\n\n\t\tstruct mrsh_array ic_condition = {0};\n\t\tcommand_list_array_copy(&ic_condition, &ic->condition);\n\n\t\tstruct mrsh_array ic_body = {0};\n\t\tcommand_list_array_copy(&ic_body, &ic->body);\n\n\t\tstruct mrsh_command *else_part = NULL;\n\t\tif (ic->else_part != NULL) {\n\t\t\telse_part = mrsh_command_copy(ic->else_part);\n\t\t}\n\n\t\tstruct mrsh_if_clause *ic_copy =\n\t\t\tmrsh_if_clause_create(&ic_condition, &ic_body, else_part);\n\t\treturn &ic_copy->command;\n\tcase MRSH_FOR_CLAUSE:;\n\t\tstruct mrsh_for_clause *fc = mrsh_command_get_for_clause(cmd);\n\n\t\tstruct mrsh_array word_list = {0};\n\t\tmrsh_array_reserve(&word_list, fc->word_list.len);\n\t\tfor (size_t i = 0; i < fc->word_list.len; ++i) {\n\t\t\tstruct mrsh_word *word = fc->word_list.data[i];\n\t\t\tmrsh_array_add(&word_list, mrsh_word_copy(word));\n\t\t}\n\n\t\tstruct mrsh_array fc_body = {0};\n\t\tcommand_list_array_copy(&fc_body, &fc->body);\n\n\t\tstruct mrsh_for_clause *fc_copy = mrsh_for_clause_create(\n\t\t\tstrdup(fc->name), fc->in, &word_list, &fc_body);\n\t\treturn &fc_copy->command;\n\tcase MRSH_LOOP_CLAUSE:;\n\t\tstruct mrsh_loop_clause *lc = mrsh_command_get_loop_clause(cmd);\n\n\t\tstruct mrsh_array lc_condition = {0};\n\t\tcommand_list_array_copy(&lc_condition, &lc->condition);\n\n\t\tstruct mrsh_array lc_body = {0};\n\t\tcommand_list_array_copy(&lc_body, &lc->body);\n\n\t\tstruct mrsh_loop_clause *lc_copy =\n\t\t\tmrsh_loop_clause_create(lc->type, &lc_condition, &lc_body);\n\t\treturn &lc_copy->command;\n\tcase MRSH_CASE_CLAUSE:;\n\t\tstruct mrsh_case_clause *cc = mrsh_command_get_case_clause(cmd);\n\n\t\tstruct mrsh_array items = {0};\n\t\tmrsh_array_reserve(&items, cc->items.len);\n\t\tfor (size_t i = 0; i < cc->items.len; ++i) {\n\t\t\tstruct mrsh_case_item *ci = cc->items.data[i];\n\t\t\tmrsh_array_add(&items, case_item_copy(ci));\n\t\t}\n\n\t\tstruct mrsh_case_clause *cc_copy =\n\t\t\tmrsh_case_clause_create(mrsh_word_copy(cc->word), &items);\n\t\treturn &cc_copy->command;\n\tcase MRSH_FUNCTION_DEFINITION:;\n\t\tstruct mrsh_function_definition *fd =\n\t\t\tmrsh_command_get_function_definition(cmd);\n\n\t\tmrsh_array_reserve(&io_redirects, fd->io_redirects.len);\n\t\tfor (size_t i = 0; i < fd->io_redirects.len; ++i) {\n\t\t\tstruct mrsh_io_redirect *redir = fd->io_redirects.data[i];\n\t\t\tmrsh_array_add(&io_redirects, mrsh_io_redirect_copy(redir));\n\t\t}\n\n\t\tstruct mrsh_function_definition *fd_copy =\n\t\t\tmrsh_function_definition_create(strdup(fd->name),\n\t\t\t\tmrsh_command_copy(fd->body), &io_redirects);\n\t\treturn &fd_copy->command;\n\t}\n\tabort();\n}\n\nstruct mrsh_and_or_list *mrsh_and_or_list_copy(\n\t\tconst struct mrsh_and_or_list *and_or_list) {\n\tswitch (and_or_list->type) {\n\tcase MRSH_AND_OR_LIST_PIPELINE:;\n\t\tstruct mrsh_pipeline *pl = mrsh_and_or_list_get_pipeline(and_or_list);\n\t\tstruct mrsh_array commands = {0};\n\t\tmrsh_array_reserve(&commands, pl->commands.len);\n\t\tfor (size_t i = 0; i < pl->commands.len; ++i) {\n\t\t\tstruct mrsh_command *cmd = pl->commands.data[i];\n\t\t\tmrsh_array_add(&commands, mrsh_command_copy(cmd));\n\t\t}\n\t\tstruct mrsh_pipeline *p_copy =\n\t\t\tmrsh_pipeline_create(&commands, pl->bang);\n\t\treturn &p_copy->and_or_list;\n\tcase MRSH_AND_OR_LIST_BINOP:;\n\t\tstruct mrsh_binop *binop = mrsh_and_or_list_get_binop(and_or_list);\n\t\tstruct mrsh_binop *binop_copy = mrsh_binop_create(binop->type,\n\t\t\tmrsh_and_or_list_copy(binop->left),\n\t\t\tmrsh_and_or_list_copy(binop->right));\n\t\treturn &binop_copy->and_or_list;\n\t}\n\tabort();\n}\n\nstruct mrsh_command_list *mrsh_command_list_copy(\n\t\tconst struct mrsh_command_list *l) {\n\tstruct mrsh_command_list *l_copy = mrsh_command_list_create();\n\tl_copy->and_or_list = mrsh_and_or_list_copy(l->and_or_list);\n\tl_copy->ampersand = l->ampersand;\n\treturn l_copy;\n}\n\nstruct mrsh_program *mrsh_program_copy(const struct mrsh_program *prog) {\n\tstruct mrsh_program *prog_copy = mrsh_program_create();\n\tcommand_list_array_copy(&prog_copy->body, &prog->body);\n\treturn prog_copy;\n}\n"
  },
  {
    "path": "ast_print.c",
    "content": "#include <assert.h>\n#include <mrsh/ast.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#define L_LINE \"│ \"\n#define L_VAL  \"├─\"\n#define L_LAST \"└─\"\n#define L_GAP  \"  \"\n\nstatic size_t make_sub_prefix(const char *prefix, bool last, char *buf) {\n\tif (buf != NULL) {\n\t\tmemcpy(buf, prefix, strlen(prefix) + 1);\n\t\tstrcat(buf, last ? L_GAP : L_LINE);\n\t}\n\treturn strlen(prefix) + strlen(L_LINE) + 1;\n}\n\nstatic void print_prefix(const char *prefix, bool last) {\n\tprintf(\"%s%s\", prefix, last ? L_LAST : L_VAL);\n}\n\nstatic void print_range(const struct mrsh_range *range) {\n\tprintf(\"[%d:%d → %d:%d]\", range->begin.line, range->begin.column,\n\t\trange->end.line, range->end.column);\n}\n\nstatic const char *word_parameter_op_str(enum mrsh_word_parameter_op op) {\n\tswitch (op) {\n\tcase MRSH_PARAM_NONE:\n\t\treturn NULL;\n\tcase MRSH_PARAM_MINUS:\n\t\treturn \"-\";\n\tcase MRSH_PARAM_EQUAL:\n\t\treturn \"=\";\n\tcase MRSH_PARAM_QMARK:\n\t\treturn \"?\";\n\tcase MRSH_PARAM_PLUS:\n\t\treturn \"+\";\n\tcase MRSH_PARAM_LEADING_HASH:\n\t\treturn \"# (leading)\";\n\tcase MRSH_PARAM_PERCENT:\n\t\treturn \"%\";\n\tcase MRSH_PARAM_DPERCENT:\n\t\treturn \"%%\";\n\tcase MRSH_PARAM_HASH:\n\t\treturn \"#\";\n\tcase MRSH_PARAM_DHASH:\n\t\treturn \"##\";\n\t}\n\tabort();\n}\n\nstatic void print_program(struct mrsh_program *prog, const char *prefix);\n\nstatic void print_word(struct mrsh_word *word, const char *prefix) {\n\tchar sub_prefix[make_sub_prefix(prefix, true, NULL)];\n\n\tswitch (word->type) {\n\tcase MRSH_WORD_STRING:;\n\t\tstruct mrsh_word_string *ws = mrsh_word_get_string(word);\n\t\tprintf(\"word_string%s \", ws->single_quoted ? \" (quoted)\" : \"\");\n\t\tprint_range(&ws->range);\n\t\tprintf(\" %s\\n\", ws->str);\n\t\tbreak;\n\tcase MRSH_WORD_PARAMETER:;\n\t\tstruct mrsh_word_parameter *wp = mrsh_word_get_parameter(word);\n\t\tprintf(\"word_parameter\\n\");\n\n\t\tprint_prefix(prefix, wp->op == MRSH_PARAM_NONE && wp->arg == NULL);\n\t\tprintf(\"name %s\\n\", wp->name);\n\n\t\tif (wp->op != MRSH_PARAM_NONE) {\n\t\t\tprint_prefix(prefix, wp->arg == NULL);\n\t\t\tprintf(\"op %s%s\\n\",\n\t\t\t\twp->colon ? \":\" : \"\", word_parameter_op_str(wp->op));\n\t\t}\n\n\t\tif (wp->arg != NULL) {\n\t\t\tmake_sub_prefix(prefix, true, sub_prefix);\n\n\t\t\tprint_prefix(prefix, true);\n\t\t\tprintf(\"arg ─ \");\n\t\t\tprint_word(wp->arg, sub_prefix);\n\t\t}\n\t\tbreak;\n\tcase MRSH_WORD_COMMAND:;\n\t\tstruct mrsh_word_command *wc = mrsh_word_get_command(word);\n\t\tprintf(\"word_command%s ─ \", wc->back_quoted ? \" (quoted)\" : \"\");\n\t\tprint_program(wc->program, prefix);\n\t\tbreak;\n\tcase MRSH_WORD_ARITHMETIC:;\n\t\tstruct mrsh_word_arithmetic *wa = mrsh_word_get_arithmetic(word);\n\t\tprintf(\"word_arithmetic ─ \");\n\t\tprint_word(wa->body, prefix);\n\t\tbreak;\n\tcase MRSH_WORD_LIST:;\n\t\tstruct mrsh_word_list *wl = mrsh_word_get_list(word);\n\t\tprintf(\"word_list%s\\n\", wl->double_quoted ? \" (quoted)\" : \"\");\n\n\t\tfor (size_t i = 0; i < wl->children.len; ++i) {\n\t\t\tstruct mrsh_word *child = wl->children.data[i];\n\t\t\tbool last = i == wl->children.len - 1;\n\n\t\t\tmake_sub_prefix(prefix, last, sub_prefix);\n\n\t\t\tprint_prefix(prefix, last);\n\t\t\tprint_word(child, sub_prefix);\n\t\t}\n\t\tbreak;\n\t}\n}\n\nstatic const char *io_redirect_op_str(enum mrsh_io_redirect_op op) {\n\tswitch (op) {\n\tcase MRSH_IO_LESS:\n\t\treturn \"<\";\n\tcase MRSH_IO_GREAT:\n\t\treturn \">\";\n\tcase MRSH_IO_CLOBBER:\n\t\treturn \">|\";\n\tcase MRSH_IO_DGREAT:\n\t\treturn \">>\";\n\tcase MRSH_IO_LESSAND:\n\t\treturn \"<&\";\n\tcase MRSH_IO_GREATAND:\n\t\treturn \">&\";\n\tcase MRSH_IO_LESSGREAT:\n\t\treturn \"<>\";\n\tcase MRSH_IO_DLESS:\n\t\treturn \"<<\";\n\tcase MRSH_IO_DLESSDASH:\n\t\treturn \"<<-\";\n\t}\n\tabort();\n}\n\nstatic void print_word_array(struct mrsh_array *words, const char *prefix);\n\nstatic void print_io_redirect(struct mrsh_io_redirect *redir,\n\t\tconst char *prefix) {\n\tprintf(\"io_redirect\\n\");\n\n\tprint_prefix(prefix, false);\n\tprintf(\"io_number %d\\n\", redir->io_number);\n\n\tprint_prefix(prefix, false);\n\tprintf(\"op %s\\n\", io_redirect_op_str(redir->op));\n\n\tbool name_is_last = redir->here_document.len == 0;\n\tchar sub_prefix[make_sub_prefix(prefix, name_is_last, NULL)];\n\tmake_sub_prefix(prefix, name_is_last, sub_prefix);\n\n\tprint_prefix(prefix, name_is_last);\n\tprintf(\"name ─ \");\n\tprint_word(redir->name, sub_prefix);\n\n\tif (redir->here_document.len > 0) {\n\t\tmake_sub_prefix(prefix, true, sub_prefix);\n\n\t\tprint_prefix(prefix, true);\n\t\tprintf(\"here_document\\n\");\n\t\tprint_word_array(&redir->here_document, sub_prefix);\n\t}\n}\n\nstatic void print_assignment(struct mrsh_assignment *assign,\n\t\tconst char *prefix) {\n\tprintf(\"assignment\\n\");\n\n\tprint_prefix(prefix, false);\n\tprintf(\"name %s\\n\", assign->name);\n\n\tchar sub_prefix[make_sub_prefix(prefix, true, NULL)];\n\tmake_sub_prefix(prefix, true, sub_prefix);\n\n\tprint_prefix(prefix, true);\n\tprintf(\"value ─ \");\n\tprint_word(assign->value, sub_prefix);\n}\n\nstatic void print_simple_command(struct mrsh_simple_command *cmd,\n\t\tconst char *prefix) {\n\tprintf(\"simple_command\\n\");\n\n\tchar sub_prefix[make_sub_prefix(prefix, false, NULL)];\n\n\tif (cmd->name != NULL) {\n\t\tbool last = cmd->arguments.len == 0 && cmd->io_redirects.len == 0\n\t\t\t&& cmd->assignments.len == 0;\n\t\tmake_sub_prefix(prefix, last, sub_prefix);\n\n\t\tprint_prefix(prefix, last);\n\t\tprintf(\"name ─ \");\n\t\tprint_word(cmd->name, sub_prefix);\n\t}\n\n\tfor (size_t i = 0; i < cmd->arguments.len; ++i) {\n\t\tstruct mrsh_word *arg = cmd->arguments.data[i];\n\t\tbool last = i == cmd->arguments.len - 1 && cmd->io_redirects.len == 0\n\t\t\t&& cmd->assignments.len == 0;\n\n\t\tmake_sub_prefix(prefix, last, sub_prefix);\n\n\t\tprint_prefix(prefix, last);\n\t\tprintf(\"argument %zu ─ \", i + 1);\n\t\tprint_word(arg, sub_prefix);\n\t}\n\n\tfor (size_t i = 0; i < cmd->io_redirects.len; ++i) {\n\t\tstruct mrsh_io_redirect *redir = cmd->io_redirects.data[i];\n\t\tbool last = i == cmd->io_redirects.len - 1 && cmd->assignments.len == 0;\n\n\t\tmake_sub_prefix(prefix, last, sub_prefix);\n\n\t\tprint_prefix(prefix, last);\n\t\tprint_io_redirect(redir, sub_prefix);\n\t}\n\n\tfor (size_t i = 0; i < cmd->assignments.len; ++i) {\n\t\tstruct mrsh_assignment *assign = cmd->assignments.data[i];\n\t\tbool last = i == cmd->assignments.len - 1;\n\n\t\tmake_sub_prefix(prefix, last, sub_prefix);\n\n\t\tprint_prefix(prefix, last);\n\t\tprint_assignment(assign, sub_prefix);\n\t}\n}\n\nstatic void print_command_list(struct mrsh_command_list *l, const char *prefix);\n\nstatic void print_command_list_array(struct mrsh_array *array,\n\t\tconst char *prefix) {\n\tfor (size_t i = 0; i < array->len; ++i) {\n\t\tstruct mrsh_command_list *l = array->data[i];\n\t\tbool last = i == array->len - 1;\n\n\t\tchar sub_prefix[make_sub_prefix(prefix, last, NULL)];\n\t\tmake_sub_prefix(prefix, last, sub_prefix);\n\n\t\tprint_prefix(prefix, last);\n\t\tprint_command_list(l, sub_prefix);\n\t}\n}\n\nstatic void print_command(struct mrsh_command *cmd, const char *prefix);\n\nstatic void print_if_clause(struct mrsh_if_clause *ic, const char *prefix) {\n\tprintf(\"if_clause\\n\");\n\n\tchar sub_prefix[make_sub_prefix(prefix, false, NULL)];\n\tmake_sub_prefix(prefix, false, sub_prefix);\n\n\tprint_prefix(prefix, false);\n\tprintf(\"condition\\n\");\n\tprint_command_list_array(&ic->condition, sub_prefix);\n\n\tbool last = ic->else_part == NULL;\n\tmake_sub_prefix(prefix, last, sub_prefix);\n\n\tprint_prefix(prefix, last);\n\tprintf(\"body\\n\");\n\tprint_command_list_array(&ic->body, sub_prefix);\n\n\tif (ic->else_part != NULL) {\n\t\tmake_sub_prefix(prefix, true, sub_prefix);\n\n\t\tprint_prefix(prefix, true);\n\t\tprintf(\"else_part ─ \");\n\t\tprint_command(ic->else_part, sub_prefix);\n\t}\n}\n\nstatic void print_word_array(struct mrsh_array *words, const char *prefix) {\n\tfor (size_t i = 0; i < words->len; ++i) {\n\t\tstruct mrsh_word *word = words->data[i];\n\t\tbool last = i == words->len - 1;\n\n\t\tchar sub_prefix[make_sub_prefix(prefix, last, NULL)];\n\t\tmake_sub_prefix(prefix, last, sub_prefix);\n\n\t\tprint_prefix(prefix, last);\n\t\tprint_word(word, sub_prefix);\n\t}\n}\n\nstatic void print_for_clause(struct mrsh_for_clause *fc, const char *prefix) {\n\tprintf(\"for_clause\\n\");\n\n\tchar sub_prefix[make_sub_prefix(prefix, false, NULL)];\n\tmake_sub_prefix(prefix, false, sub_prefix);\n\n\tprint_prefix(prefix, false);\n\tprintf(\"name %s\\n\", fc->name);\n\n\tif (fc->in) {\n\t\tprint_prefix(prefix, false);\n\t\tprintf(\"in\\n\");\n\t\tprint_word_array(&fc->word_list, sub_prefix);\n\t}\n\n\tmake_sub_prefix(prefix, true, sub_prefix);\n\n\tprint_prefix(prefix, true);\n\tprintf(\"body\\n\");\n\tprint_command_list_array(&fc->body, sub_prefix);\n}\n\nstatic const char *loop_type_str(enum mrsh_loop_type type) {\n\tswitch (type) {\n\tcase MRSH_LOOP_UNTIL:\n\t\treturn \"until\";\n\tcase MRSH_LOOP_WHILE:\n\t\treturn \"while\";\n\t}\n\tabort();\n}\n\nstatic void print_loop_clause(struct mrsh_loop_clause *lc, const char *prefix) {\n\tprintf(\"loop_clause %s\\n\", loop_type_str(lc->type));\n\n\tchar sub_prefix[make_sub_prefix(prefix, false, NULL)];\n\tmake_sub_prefix(prefix, false, sub_prefix);\n\n\tprint_prefix(prefix, false);\n\tprintf(\"condition\\n\");\n\tprint_command_list_array(&lc->condition, sub_prefix);\n\n\tmake_sub_prefix(prefix, true, sub_prefix);\n\n\tprint_prefix(prefix, true);\n\tprintf(\"body\\n\");\n\tprint_command_list_array(&lc->body, sub_prefix);\n}\n\nstatic void print_case_item(struct mrsh_case_item *item, const char *prefix) {\n\tprintf(\"case_item\\n\");\n\n\tchar sub_prefix[make_sub_prefix(prefix, false, NULL)];\n\tmake_sub_prefix(prefix, false, sub_prefix);\n\n\tprint_prefix(prefix, false);\n\tprintf(\"patterns\\n\");\n\tprint_word_array(&item->patterns, sub_prefix);\n\n\tmake_sub_prefix(prefix, true, sub_prefix);\n\n\tprint_prefix(prefix, true);\n\tprintf(\"body\\n\");\n\tprint_command_list_array(&item->body, sub_prefix);\n}\n\nstatic void print_case_item_array(struct mrsh_array *items,\n\t\tconst char *prefix) {\n\tfor (size_t i = 0; i < items->len; ++i) {\n\t\tstruct mrsh_case_item *item = items->data[i];\n\t\tbool last = i == items->len - 1;\n\n\t\tchar sub_prefix[make_sub_prefix(prefix, last, NULL)];\n\t\tmake_sub_prefix(prefix, last, sub_prefix);\n\n\t\tprint_prefix(prefix, last);\n\t\tprint_case_item(item, sub_prefix);\n\t}\n}\n\nstatic void print_case_clause(struct mrsh_case_clause *cc, const char *prefix) {\n\tprintf(\"case_clause\\n\");\n\n\tchar sub_prefix[make_sub_prefix(prefix, false, NULL)];\n\tmake_sub_prefix(prefix, false, sub_prefix);\n\n\tprint_prefix(prefix, false);\n\tprintf(\"word ─ \");\n\tprint_word(cc->word, sub_prefix);\n\n\tmake_sub_prefix(prefix, true, sub_prefix);\n\n\tprint_prefix(prefix, true);\n\tprintf(\"items\\n\");\n\tprint_case_item_array(&cc->items, sub_prefix);\n}\n\nstatic void print_function_definition(struct mrsh_function_definition *fd,\n\t\tconst char *prefix) {\n\tprintf(\"function_definition %s ─ \", fd->name);\n\tprint_command(fd->body, prefix);\n\t// TODO: print io_redirects\n}\n\nstatic void print_command(struct mrsh_command *cmd, const char *prefix) {\n\tswitch (cmd->type) {\n\tcase MRSH_SIMPLE_COMMAND:;\n\t\tstruct mrsh_simple_command *sc = mrsh_command_get_simple_command(cmd);\n\t\tprint_simple_command(sc, prefix);\n\t\tbreak;\n\tcase MRSH_BRACE_GROUP:;\n\t\tstruct mrsh_brace_group *bg = mrsh_command_get_brace_group(cmd);\n\t\tprintf(\"brace_group\\n\");\n\t\tprint_command_list_array(&bg->body, prefix);\n\t\tbreak;\n\tcase MRSH_SUBSHELL:;\n\t\tstruct mrsh_subshell *s = mrsh_command_get_subshell(cmd);\n\t\tprintf(\"subshell\\n\");\n\t\tprint_command_list_array(&s->body, prefix);\n\t\tbreak;\n\tcase MRSH_IF_CLAUSE:;\n\t\tstruct mrsh_if_clause *ic = mrsh_command_get_if_clause(cmd);\n\t\tprint_if_clause(ic, prefix);\n\t\tbreak;\n\tcase MRSH_FOR_CLAUSE:;\n\t\tstruct mrsh_for_clause *fc = mrsh_command_get_for_clause(cmd);\n\t\tprint_for_clause(fc, prefix);\n\t\tbreak;\n\tcase MRSH_LOOP_CLAUSE:;\n\t\tstruct mrsh_loop_clause *lc = mrsh_command_get_loop_clause(cmd);\n\t\tprint_loop_clause(lc, prefix);\n\t\tbreak;\n\tcase MRSH_CASE_CLAUSE:;\n\t\tstruct mrsh_case_clause *cc = mrsh_command_get_case_clause(cmd);\n\t\tprint_case_clause(cc, prefix);\n\t\tbreak;\n\tcase MRSH_FUNCTION_DEFINITION:;\n\t\tstruct mrsh_function_definition *fd =\n\t\t\tmrsh_command_get_function_definition(cmd);\n\t\tprint_function_definition(fd, prefix);\n\t\tbreak;\n\t}\n}\n\nstatic void print_pipeline(struct mrsh_pipeline *pl, const char *prefix) {\n\tprintf(\"pipeline%s\\n\", pl->bang ? \" !\" : \"\");\n\n\tfor (size_t i = 0; i < pl->commands.len; ++i) {\n\t\tstruct mrsh_command *cmd = pl->commands.data[i];\n\t\tbool last = i == pl->commands.len - 1;\n\n\t\tchar sub_prefix[make_sub_prefix(prefix, last, NULL)];\n\t\tmake_sub_prefix(prefix, last, sub_prefix);\n\n\t\tprint_prefix(prefix, last);\n\t\tprint_command(cmd, sub_prefix);\n\t}\n}\n\nstatic const char *binop_type_str(enum mrsh_binop_type type) {\n\tswitch (type) {\n\tcase MRSH_BINOP_AND:\n\t\treturn \"&&\";\n\tcase MRSH_BINOP_OR:\n\t\treturn \"||\";\n\t}\n\treturn NULL;\n}\n\nstatic void print_and_or_list(struct mrsh_and_or_list *and_or_list, const char *prefix);\n\nstatic void print_binop(struct mrsh_binop *binop, const char *prefix) {\n\tprintf(\"binop %s\\n\", binop_type_str(binop->type));\n\n\tchar sub_prefix[make_sub_prefix(prefix, false, NULL)];\n\n\tmake_sub_prefix(prefix, false, sub_prefix);\n\tprint_prefix(prefix, false);\n\tprint_and_or_list(binop->left, sub_prefix);\n\n\tmake_sub_prefix(prefix, true, sub_prefix);\n\tprint_prefix(prefix, true);\n\tprint_and_or_list(binop->right, sub_prefix);\n}\n\nstatic void print_and_or_list(struct mrsh_and_or_list *and_or_list, const char *prefix) {\n\tswitch (and_or_list->type) {\n\tcase MRSH_AND_OR_LIST_PIPELINE:;\n\t\tstruct mrsh_pipeline *pl = mrsh_and_or_list_get_pipeline(and_or_list);\n\t\tprint_pipeline(pl, prefix);\n\t\tbreak;\n\tcase MRSH_AND_OR_LIST_BINOP:;\n\t\tstruct mrsh_binop *binop = mrsh_and_or_list_get_binop(and_or_list);\n\t\tprint_binop(binop, prefix);\n\t\tbreak;\n\t}\n}\n\nstatic void print_command_list(struct mrsh_command_list *list,\n\t\tconst char *prefix) {\n\tprintf(\"command_list%s ─ \", list->ampersand ? \" &\" : \"\");\n\n\tprint_and_or_list(list->and_or_list, prefix);\n}\n\nstatic void print_program(struct mrsh_program *prog, const char *prefix) {\n\tprintf(\"program\\n\");\n\n\tprint_command_list_array(&prog->body, prefix);\n}\n\nvoid mrsh_program_print(struct mrsh_program *prog) {\n\tprint_program(prog, \"\");\n}\n"
  },
  {
    "path": "buffer.c",
    "content": "#include <mrsh/buffer.h>\n#include <stdlib.h>\n#include <string.h>\n\nchar *mrsh_buffer_reserve(struct mrsh_buffer *buf, size_t size) {\n\tsize_t new_len = buf->len + size;\n\tif (new_len > buf->cap) {\n\t\tsize_t new_cap = 2 * buf->cap;\n\t\tif (new_cap == 0) {\n\t\t\tnew_cap = 32;\n\t\t}\n\t\tif (new_cap < new_len) {\n\t\t\tnew_cap = new_len;\n\t\t}\n\t\tchar *new_buf = realloc(buf->data, new_cap);\n\t\tif (new_buf == NULL) {\n\t\t\treturn NULL;\n\t\t}\n\n\t\tbuf->data = new_buf;\n\t\tbuf->cap = new_cap;\n\t}\n\n\treturn &buf->data[buf->len];\n}\n\nchar *mrsh_buffer_add(struct mrsh_buffer *buf, size_t size) {\n\tchar *data = mrsh_buffer_reserve(buf, size);\n\tif (data == NULL) {\n\t\treturn NULL;\n\t}\n\n\tbuf->len += size;\n\treturn data;\n}\n\nbool mrsh_buffer_append(struct mrsh_buffer *buf, const char *data, size_t size) {\n\tchar *dst = mrsh_buffer_add(buf, size);\n\tif (dst == NULL) {\n\t\treturn false;\n\t}\n\n\tmemcpy(dst, data, size);\n\treturn true;\n}\n\nbool mrsh_buffer_append_char(struct mrsh_buffer *buf, char c) {\n\tchar *dst = mrsh_buffer_add(buf, sizeof(char));\n\tif (dst == NULL) {\n\t\treturn false;\n\t}\n\n\t*dst = c;\n\treturn true;\n}\n\nchar *mrsh_buffer_steal(struct mrsh_buffer *buf) {\n\tchar *data = buf->data;\n\tbuf->data = NULL;\n\tbuf->cap = buf->len = 0;\n\treturn data;\n}\n\nvoid mrsh_buffer_finish(struct mrsh_buffer *buf) {\n\tfree(buf->data);\n\tbuf->data = NULL;\n\tbuf->cap = buf->len = 0;\n}\n"
  },
  {
    "path": "builtin/alias.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <mrsh/builtin.h>\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include \"builtin.h\"\n#include \"mrsh_getopt.h\"\n#include \"shell/shell.h\"\n\nstatic const char alias_usage[] = \"usage: alias [alias-name[=string]...]\\n\";\n\nstatic void print_alias_iterator(const char *key, void *_value,\n\t\tvoid *user_data) {\n\tconst char *value = _value;\n\tprintf(\"%s=\", key);\n\tprint_escaped(value);\n\tprintf(\"\\n\");\n}\n\nint builtin_alias(struct mrsh_state *state, int argc, char *argv[]) {\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\n\t_mrsh_optind = 0;\n\tif (_mrsh_getopt(argc, argv, \":\") != -1) {\n\t\tfprintf(stderr, \"alias: unknown option -- %c\\n\", _mrsh_optopt);\n\t\tfprintf(stderr, \"%s\", alias_usage);\n\t\treturn 1;\n\t}\n\n\tif (_mrsh_optind == argc) {\n\t\tmrsh_hashtable_for_each(&priv->aliases, print_alias_iterator, NULL);\n\t\treturn 0;\n\t}\n\n\tfor (int i = _mrsh_optind; i < argc; ++i) {\n\t\tchar *alias = argv[i];\n\t\tchar *equal = strchr(alias, '=');\n\t\tif (equal != NULL) {\n\t\t\tchar *value = strdup(equal + 1);\n\t\t\t*equal = '\\0';\n\n\t\t\tchar *old_value = mrsh_hashtable_set(&priv->aliases, alias, value);\n\t\t\tfree(old_value);\n\t\t} else {\n\t\t\tconst char *value = mrsh_hashtable_get(&priv->aliases, alias);\n\t\t\tif (value == NULL) {\n\t\t\t\tfprintf(stderr, \"%s: %s not found\\n\", argv[0], alias);\n\t\t\t\treturn 1;\n\t\t\t}\n\n\t\t\tprintf(\"%s=\", alias);\n\t\t\tprint_escaped(value);\n\t\t\tprintf(\"\\n\");\n\t\t}\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "builtin/bg.c",
    "content": "#include <stdio.h>\n#include <stdlib.h>\n#include \"builtin.h\"\n#include \"mrsh_getopt.h\"\n#include \"shell/job.h\"\n#include \"shell/shell.h\"\n#include \"shell/task.h\"\n\nstatic const char bg_usage[] = \"usage: bg [job_id...]\\n\";\n\nint builtin_bg(struct mrsh_state *state, int argc, char *argv[]) {\n\t_mrsh_optind = 0;\n\tint opt;\n\twhile ((opt = _mrsh_getopt(argc, argv, \":\")) != -1) {\n\t\tswitch (opt) {\n\t\tdefault:\n\t\t\tfprintf(stderr, \"bg: unknown option -- %c\\n\", _mrsh_optopt);\n\t\t\tfprintf(stderr, bg_usage);\n\t\t\treturn EXIT_FAILURE;\n\t\t}\n\t}\n\tif (_mrsh_optind == argc) {\n\t\tstruct mrsh_job *job = job_by_id(state, \"%%\", true);\n\t\tif (!job) {\n\t\t\treturn EXIT_FAILURE;\n\t\t}\n\t\tif (!job_set_foreground(job, false, true)) {\n\t\t\treturn EXIT_FAILURE;\n\t\t}\n\t\treturn EXIT_SUCCESS;\n\t}\n\n\tfor (int i = _mrsh_optind; i < argc; ++i) {\n\t\tstruct mrsh_job *job = job_by_id(state, argv[i], true);\n\t\tif (!job) {\n\t\t\treturn EXIT_FAILURE;\n\t\t}\n\t\tif (!job_set_foreground(job, false, true)) {\n\t\t\treturn EXIT_FAILURE;\n\t\t}\n\t}\n\treturn EXIT_SUCCESS;\n}\n"
  },
  {
    "path": "builtin/break.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <mrsh/builtin.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include \"builtin.h\"\n#include \"shell/shell.h\"\n#include \"shell/task.h\"\n\nstatic const char break_usage[] = \"usage: %s [n]\\n\";\n\nint builtin_break(struct mrsh_state *state, int argc, char *argv[]) {\n\tif (argc > 2) {\n\t\tfprintf(stderr, break_usage, argv[0]);\n\t\treturn 1;\n\t}\n\n\tint n = 1;\n\tif (argc == 2) {\n\t\tchar *end;\n\t\tn = strtol(argv[1], &end, 10);\n\t\tif (end[0] != '\\0' || argv[0][0] == '\\0' || n < 0) {\n\t\t\tfprintf(stderr, \"%s: invalid loop number '%s'\\n\", argv[0], argv[1]);\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\tstruct mrsh_call_frame_priv *frame_priv = call_frame_get_priv(state->frame);\n\n\tif (n > frame_priv->nloops) {\n\t\tn = frame_priv->nloops;\n\t}\n\n\tframe_priv->nloops -= n - 1;\n\tframe_priv->branch_control =\n\t\tstrcmp(argv[0], \"break\") == 0 ? MRSH_BRANCH_BREAK : MRSH_BRANCH_CONTINUE;\n\treturn TASK_STATUS_INTERRUPTED;\n}\n"
  },
  {
    "path": "builtin/builtin.c",
    "content": "#include <assert.h>\n#include <mrsh/builtin.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <ctype.h>\n#include \"builtin.h\"\n#include \"shell/shell.h\"\n\nstruct builtin {\n\tconst char *name;\n\tmrsh_builtin_func func;\n\tbool special;\n};\n\nstatic const struct builtin builtins[] = {\n\t// Keep alpha sorted\n\t{ \".\", builtin_dot, true },\n\t{ \":\", builtin_colon, true },\n\t{ \"alias\", builtin_alias, false },\n\t{ \"bg\", builtin_bg, false },\n\t{ \"break\", builtin_break, true },\n\t{ \"cd\", builtin_cd, false },\n\t{ \"command\", builtin_command, false },\n\t{ \"continue\", builtin_break, true },\n\t{ \"eval\", builtin_eval, true },\n\t{ \"exec\", builtin_exec, true },\n\t{ \"exit\", builtin_exit, true },\n\t{ \"export\", builtin_export, true },\n\t{ \"false\", builtin_false, false },\n//\t{ \"fc\", builtin_fc, false },\n\t{ \"fg\", builtin_fg, false },\n\t{ \"getopts\", builtin_getopts, false },\n\t{ \"hash\", builtin_hash, false },\n\t{ \"jobs\", builtin_jobs, false },\n//\t{ \"kill\", builtin_kill, false },\n//\t{ \"newgrp\", builtin_newgrp, false },\n\t{ \"pwd\", builtin_pwd, false },\n\t{ \"read\", builtin_read, false },\n\t{ \"readonly\", builtin_export, true },\n\t{ \"return\", builtin_return, true },\n\t{ \"set\", builtin_set, true },\n\t{ \"shift\", builtin_shift, true },\n\t{ \"times\", builtin_times, true },\n\t{ \"trap\", builtin_trap, true },\n\t{ \"true\", builtin_true, false },\n\t{ \"type\", builtin_type, false },\n\t{ \"ulimit\", builtin_ulimit, false },\n\t{ \"umask\", builtin_umask, false },\n\t{ \"unalias\", builtin_unalias, false },\n\t{ \"unset\", builtin_unset, true },\n\t{ \"wait\", builtin_wait, false },\n};\n\n// The following commands are explicitly unspecified by POSIX\nstatic const char *unspecified_names[] = {\n\t\"alloc\", \"autoload\", \"bind\", \"bindkey\", \"builtin\", \"bye\", \"caller\", \"cap\",\n\t\"chdir\", \"clone\", \"comparguments\", \"compcall\", \"compctl\", \"compdescribe\",\n\t\"compfiles\", \"compgen\", \"compgroups\", \"complete\", \"compquote\", \"comptags\",\n\t\"comptry\", \"compvalues\", \"declare\", \"dirs\", \"disable\", \"disown\", \"dosh\",\n\t\"echotc\", \"echoti\", \"help\", \"history\", \"hist\", \"let\", \"local\", \"login\",\n\t\"logout\", \"map\", \"mapfile\", \"popd\", \"print\", \"pushd\", \"readarray\", \"repeat\",\n\t\"savehistory\", \"source\", \"shopt\", \"stop\", \"suspend\", \"typeset\", \"whence\"\n};\n\nstatic const struct builtin unspecified = {\n\t.name = \"unspecified\",\n\t.func = builtin_unspecified,\n\t.special = false,\n};\n\nstatic int builtin_compare(const void *_a, const void *_b) {\n\tconst struct builtin *a = _a, *b = _b;\n\treturn strcmp(a->name, b->name);\n}\n\nstatic int unspecified_compare(const void *_a, const void *_b) {\n\tconst char *a = _a;\n\tconst char *const *b = _b;\n\treturn strcmp(a, *b);\n}\n\nstatic const struct builtin *get_builtin(const char *name) {\n\tif (bsearch(name, unspecified_names,\n\t\t\tsizeof(unspecified_names) / sizeof(unspecified_names[0]),\n\t\t\tsizeof(unspecified_names[0]), unspecified_compare)) {\n\t\treturn &unspecified;\n\t}\n\tstruct builtin key = { .name = name };\n\treturn bsearch(&key, builtins, sizeof(builtins) / sizeof(builtins[0]),\n\t\tsizeof(builtins[0]), builtin_compare);\n}\n\nbool mrsh_has_builtin(const char *name) {\n\treturn get_builtin(name) != NULL;\n}\n\nbool mrsh_has_special_builtin(const char *name) {\n\tconst struct builtin *builtin = get_builtin(name);\n\treturn builtin != NULL && builtin->special;\n}\n\nint mrsh_run_builtin(struct mrsh_state *state, int argc, char *argv[]) {\n\tassert(argc > 0);\n\n\tconst char *name = argv[0];\n\tconst struct builtin *builtin = get_builtin(name);\n\tif (builtin == NULL) {\n\t\treturn -1;\n\t}\n\n\treturn builtin->func(state, argc, argv);\n}\n\nvoid print_escaped(const char *value) {\n\tconst char safe[] = \"@%+=:,./-\";\n\tsize_t i;\n\tfor (i = 0; value[i] != '\\0'; ++i) {\n\t\tif (!isalnum(value[i]) && !strchr(safe, value[i])) {\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (value[i] == '\\0' && i > 0) {\n\t\tprintf(\"%s\", value);\n\t} else {\n\t\tprintf(\"'\");\n\t\tfor (size_t i = 0; value[i] != '\\0'; ++i) {\n\t\t\tif (value[i] == '\\'') {\n\t\t\t\tprintf(\"'\\\"'\\\"'\");\n\t\t\t} else {\n\t\t\t\tprintf(\"%c\", value[i]);\n\t\t\t}\n\t\t}\n\t\tprintf(\"'\");\n\t}\n}\n\nstruct collect_iter {\n\tsize_t cap, len;\n\tuint32_t attribs;\n\tstruct mrsh_collect_var *values;\n};\n\nstatic void collect_vars_iterator(const char *key, void *_var, void *data) {\n\tconst struct mrsh_variable *var = _var;\n\tstruct collect_iter *iter = data;\n\tif (iter->attribs != MRSH_VAR_ATTRIB_NONE\n\t\t\t&& !(var->attribs & iter->attribs)) {\n\t\treturn;\n\t}\n\tif ((iter->len + 1) * sizeof(struct mrsh_collect_var) >= iter->cap) {\n\t\titer->cap *= 2;\n\t\titer->values = realloc(iter->values,\n\t\t\t\titer->cap * sizeof(struct mrsh_collect_var));\n\t}\n\titer->values[iter->len].key = key;\n\titer->values[iter->len++].value = var->value;\n}\n\nstatic int varcmp(const void *p1, const void *p2) {\n\tconst struct mrsh_collect_var *v1 = p1;\n\tconst struct mrsh_collect_var *v2 = p2;\n\treturn strcmp(v1->key, v2->key);\n}\n\nstruct mrsh_collect_var *collect_vars(struct mrsh_state *state,\n\t\tuint32_t attribs, size_t *count) {\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\n\tstruct collect_iter iter = {\n\t\t.cap = 64,\n\t\t.len = 0,\n\t\t.values = malloc(64 * sizeof(struct mrsh_collect_var)),\n\t\t.attribs = attribs,\n\t};\n\tmrsh_hashtable_for_each(&priv->variables, collect_vars_iterator, &iter);\n\tqsort(iter.values, iter.len, sizeof(struct mrsh_collect_var), varcmp);\n\t*count = iter.len;\n\treturn iter.values;\n}\n"
  },
  {
    "path": "builtin/cd.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <errno.h>\n#include <mrsh/shell.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/stat.h>\n#include <unistd.h>\n#include \"builtin.h\"\n#include \"mrsh_getopt.h\"\n#include \"shell/path.h\"\n\nstatic const char cd_usage[] = \"usage: cd [-L|-P] [-|directory]\\n\";\n\nstatic int cd(struct mrsh_state *state, const char *path) {\n\tconst char *oldPWD = mrsh_env_get(state, \"PWD\", NULL);\n\tif (chdir(path) != 0) {\n\t\t// TODO make better error messages\n\t\tfprintf(stderr, \"cd: %s\\n\", strerror(errno));\n\t\treturn 1;\n\t}\n\tchar *cwd = current_working_dir();\n\tif (cwd == NULL) {\n\t\tperror(\"current_working_dir failed\");\n\t\treturn 1;\n\t}\n\tmrsh_env_set(state, \"OLDPWD\", oldPWD, MRSH_VAR_ATTRIB_NONE);\n\tmrsh_env_set(state, \"PWD\", cwd, MRSH_VAR_ATTRIB_EXPORT);\n\tfree(cwd);\n\treturn 0;\n}\n\nstatic int isdir(char *path) {\n\tstruct stat s;\n\tstat(path, &s);\n\treturn S_ISDIR(s.st_mode);\n}\n\nint builtin_cd(struct mrsh_state *state, int argc, char *argv[]) {\n\t_mrsh_optind = 0;\n\tint opt;\n\twhile ((opt = _mrsh_getopt(argc, argv, \":LP\")) != -1) {\n\t\tswitch (opt) {\n\t\tcase 'L':\n\t\tcase 'P':\n\t\t\t// TODO implement `-L` and `-P`\n\t\t\tfprintf(stderr, \"cd: `-L` and `-P` not yet implemented\\n\");\n\t\t\treturn 1;\n\t\tdefault:\n\t\t\tfprintf(stderr, \"cd: unknown option -- %c\\n\", _mrsh_optopt);\n\t\t\tfprintf(stderr, cd_usage);\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\tif (_mrsh_optind == argc) {\n\t\tconst char *home = mrsh_env_get(state, \"HOME\", NULL);\n\t\tif (home && home[0] != '\\0') {\n\t\t\treturn cd(state, home);\n\t\t}\n\t\tfprintf(stderr, \"cd: No arguments were given and $HOME \"\n\t\t\t\"is not defined.\\n\");\n\t\treturn 1;\n\t}\n\n\tchar *curpath = argv[_mrsh_optind];\n\t// `cd -`\n\tif (strcmp(curpath, \"-\") == 0) {\n\t\t// This case is special as we print `pwd` at the end\n\t\tconst char *oldpwd = mrsh_env_get(state, \"OLDPWD\", NULL);\n\t\tconst char *pwd = mrsh_env_get(state, \"PWD\", NULL);\n\t\tif (!pwd) {\n\t\t\tfprintf(stderr, \"cd: PWD is not set\\n\");\n\t\t\treturn 1;\n\t\t}\n\t\tif (!oldpwd) {\n\t\t\tfprintf(stderr, \"cd: OLDPWD is not set\\n\");\n\t\t\treturn 1;\n\t\t}\n\t\tif (chdir(oldpwd) != 0) {\n\t\t\tfprintf(stderr, \"cd: %s\\n\", strerror(errno));\n\t\t\treturn 1;\n\t\t}\n\t\tchar *_pwd = strdup(pwd);\n\t\tputs(oldpwd);\n\t\tmrsh_env_set(state, \"PWD\", oldpwd, MRSH_VAR_ATTRIB_EXPORT);\n\t\tmrsh_env_set(state, \"OLDPWD\", _pwd, MRSH_VAR_ATTRIB_NONE);\n\t\tfree(_pwd);\n\t\treturn 0;\n\t}\n\t// $CDPATH\n\tif (curpath[0] != '/' && strncmp(curpath, \"./\", 2) != 0 &&\n\t\t\tstrncmp(curpath, \"../\", 3) != 0) {\n\t\tconst char *_cdpath = mrsh_env_get(state, \"CDPATH\", NULL);\n\t\tchar *cdpath = NULL;\n\t\tif (_cdpath) {\n\t\t\tcdpath = strdup(_cdpath);\n\t\t}\n\t\tchar *c = cdpath;\n\t\twhile (c != NULL) {\n\t\t\tchar *next = strchr(c, ':');\n\t\t\tchar *slash = strrchr(c, '/');\n\t\t\tif (next != NULL) {\n\t\t\t\t*next = '\\0';\n\t\t\t\t++next;\n\t\t\t}\n\t\t\tif (c[0] == '\\0') {\n\t\t\t\t// path is empty\n\t\t\t\tc = \".\";\n\t\t\t\tslash = NULL;\n\t\t\t}\n\n\t\t\tconst char *sep = (slash == NULL || slash[1] != '\\0') ? \"/\" : \"\";\n\t\t\tint len = snprintf(NULL, 0, \"%s%s%s\", c, sep, curpath);\n\t\t\tif (len < 0) {\n\t\t\t\tperror(\"snprintf failed\");\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tchar *path = malloc(len + 1);\n\t\t\tif (path == NULL) {\n\t\t\t\tperror(\"malloc failed\");\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tsnprintf(path, len + 1, \"%s%s%s\", c, sep, curpath);\n\n\t\t\tif (isdir(path)) {\n\t\t\t\tfree(cdpath);\n\t\t\t\tint ret = cd(state, path);\n\t\t\t\tfree(path);\n\t\t\t\treturn ret;\n\t\t\t}\n\n\t\t\tfree(path);\n\t\t\tc = next;\n\t\t}\n\t\tfree(cdpath);\n\t}\n\treturn cd(state, curpath);\n}\n"
  },
  {
    "path": "builtin/colon.c",
    "content": "#include <mrsh/builtin.h>\n#include <stdlib.h>\n#include \"builtin.h\"\n\nint builtin_colon(struct mrsh_state *state, int argc, char *argv[]) {\n\treturn 0; // This builtin does not do anything\n}\n"
  },
  {
    "path": "builtin/command.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include \"builtin.h\"\n#include \"mrsh_getopt.h\"\n#include \"parser.h\"\n#include \"shell/job.h\"\n#include \"shell/path.h\"\n#include \"shell/process.h\"\n#include \"shell/shell.h\"\n\nstatic const char command_usage[] = \"usage: command [-v|-V|-p] \"\n\t\"command_name [argument...]\\n\";\n\nstatic int verify_command(struct mrsh_state *state, const char *command_name,\n\t\tbool default_path) {\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\n\tsize_t len_command_name = strlen(command_name);\n\n\tconst char *look_alias =\n\t\tmrsh_hashtable_get(&priv->aliases, command_name);\n\tif (look_alias != NULL) {\n\t\tprintf(\"alias %s='%s'\\n\", command_name, look_alias);\n\t\treturn 0;\n\t}\n\n\tconst char *look_fn =\n\t\tmrsh_hashtable_get(&priv->functions, command_name);\n\tif (look_fn != NULL) {\n\t\tprintf(\"%s\\n\", command_name);\n\t\treturn 0;\n\t}\n\n\tif (mrsh_has_builtin(command_name)) {\n\t\tprintf(\"%s\\n\", command_name);\n\t\treturn 0;\n\t}\n\n\tfor (size_t i = 0; i < keywords_len; ++i) {\n\t\tif (strlen(keywords[i]) == len_command_name &&\n\t\t\t\tstrcmp(command_name, keywords[i]) == 0) {\n\t\t\tprintf(\"%s\\n\", command_name);\n\t\t\treturn 0;\n\t\t}\n\t}\n\n\tchar *expanded = expand_path(state, command_name, true, default_path);\n\tif (expanded != NULL) {\n\t\tprintf(\"%s\\n\", expanded);\n\t\tfree(expanded);\n\t\treturn 0;\n\t}\n\n\treturn 1;\n}\n\nstatic int run_command(struct mrsh_state *state, int argc, char *argv[],\n\t\tbool default_path) {\n\tif (mrsh_has_builtin(argv[0])) {\n\t\treturn mrsh_run_builtin(state, argc - _mrsh_optind, &argv[_mrsh_optind]);\n\t}\n\n\tchar *path = expand_path(state, argv[0], true, default_path);\n\tif (path == NULL) {\n\t\tfprintf(stderr, \"%s: not found\\n\", argv[0]);\n\t\treturn 127;\n\t}\n\n\t// TODO: job control support\n\tpid_t pid = fork();\n\tif (pid < 0) {\n\t\tperror(\"fork\");\n\t\treturn 126;\n\t} else if (pid == 0) {\n\t\texecv(path, argv);\n\n\t\t// Something went wrong\n\t\tperror(argv[0]);\n\t\texit(126);\n\t}\n\n\tfree(path);\n\n\tstruct mrsh_process *proc = process_create(state, pid);\n\treturn job_wait_process(proc);\n}\n\nint builtin_command(struct mrsh_state *state, int argc, char *argv[]) {\n\t_mrsh_optind = 0;\n\tint opt;\n\n\tbool verify = false, default_path = false;\n\twhile ((opt = _mrsh_getopt(argc, argv, \":vVp\")) != -1) {\n\t\tswitch (opt) {\n\t\tcase 'v':\n\t\t\tverify = true;\n\t\t\tbreak;\n\t\tcase 'V':\n\t\t\tfprintf(stderr, \"command: `-V` has an unspecified output format, \"\n\t\t\t\t\"use `-v` instead\\n\");\n\t\t\treturn 0;\n\t\tcase 'p':\n\t\t\tdefault_path = true;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tfprintf(stderr, \"command: unknown option -- %c\\n\", _mrsh_optopt);\n\t\t\tfprintf(stderr, command_usage);\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\tif (_mrsh_optind >= argc) {\n\t\tfprintf(stderr, command_usage);\n\t\treturn 1;\n\t}\n\n\tif (verify) {\n\t\tif (_mrsh_optind != argc - 1) {\n\t\t\tfprintf(stderr, command_usage);\n\t\t\treturn 1;\n\t\t}\n\t\treturn verify_command(state, argv[_mrsh_optind], default_path);\n\t}\n\n\treturn run_command(state, argc - _mrsh_optind, &argv[_mrsh_optind],\n\t\tdefault_path);\n}\n"
  },
  {
    "path": "builtin/dot.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <assert.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <mrsh/builtin.h>\n#include <mrsh/parser.h>\n#include <mrsh/shell.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include \"builtin.h\"\n#include \"shell/path.h\"\n\nstatic const char source_usage[] = \"usage: . <path>\\n\";\n\nint builtin_dot(struct mrsh_state *state, int argc, char *argv[]) {\n\tif (argc != 2) {\n\t\tfprintf(stderr, source_usage);\n\t\treturn 1;\n\t}\n\n\tchar *path = expand_path(state, argv[1], false, false);\n\tif (!path) {\n\t\tfprintf(stderr, \"%s: not found\\n\", argv[1]);\n\t\tif (!state->interactive) {\n\t\t\tstate->exit = 1;\n\t\t}\n\t\treturn 1;\n\t}\n\n\tint fd = open(path, O_RDONLY | O_CLOEXEC);\n\tif (fd < 0) {\n\t\tfprintf(stderr, \"unable to open %s for reading: %s\\n\",\n\t\t\targv[1], strerror(errno));\n\t\tgoto error;\n\t}\n\tfree(path);\n\n\tstruct mrsh_parser *parser = mrsh_parser_with_fd(fd);\n\tstruct mrsh_program *program = mrsh_parse_program(parser);\n\n\tint ret;\n\tstruct mrsh_position err_pos;\n\tconst char *err_msg = mrsh_parser_error(parser, &err_pos);\n\tif (err_msg != NULL) {\n\t\tfprintf(stderr, \"%s %d:%d: %s\\n\",\n\t\t\targv[1], err_pos.line, err_pos.column, err_msg);\n\t\tret = 1;\n\t} else if (program != NULL) {\n\t\tret = mrsh_run_program(state, program);\n\t} else {\n\t\tret = 0;\n\t}\n\n\tmrsh_program_destroy(program);\n\tmrsh_parser_destroy(parser);\n\tclose(fd);\n\treturn ret;\n\nerror:\n\tif (!state->interactive) {\n\t\tstate->exit = 1;\n\t}\n\treturn 1;\n}\n"
  },
  {
    "path": "builtin/eval.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <assert.h>\n#include <errno.h>\n#include <mrsh/buffer.h>\n#include <mrsh/builtin.h>\n#include <mrsh/parser.h>\n#include <mrsh/shell.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include \"builtin.h\"\n\nstatic const char eval_usage[] = \"usage: eval [cmds...]\\n\";\n\nint builtin_eval(struct mrsh_state *state, int argc, char *argv[]) {\n\tif (argc == 1) {\n\t\tfprintf(stderr, eval_usage);\n\t\treturn 1;\n\t}\n\n\tstruct mrsh_buffer buf = {0};\n\n\tfor (int i = 1; i < argc; ++i) {\n\t\tmrsh_buffer_append(&buf, argv[i], strlen(argv[i]));\n\t\tif (i != argc - 1) {\n\t\t\tmrsh_buffer_append_char(&buf, ' ');\n\t\t}\n\t}\n\tmrsh_buffer_append_char(&buf, '\\n');\n\n\tstruct mrsh_parser *parser = mrsh_parser_with_data(buf.data, buf.len);\n\tstruct mrsh_program *program = mrsh_parse_program(parser);\n\n\tint ret;\n\tstruct mrsh_position err_pos;\n\tconst char *err_msg = mrsh_parser_error(parser, &err_pos);\n\tif (err_msg != NULL) {\n\t\tfprintf(stderr, \"%s %d:%d: %s\\n\",\n\t\t\targv[1], err_pos.line, err_pos.column, err_msg);\n\t\tret = 1;\n\t} else if (program != NULL) {\n\t\tret = mrsh_run_program(state, program);\n\t} else {\n\t\tret = 0;\n\t}\n\n\tmrsh_program_destroy(program);\n\tmrsh_parser_destroy(parser);\n\tmrsh_buffer_finish(&buf);\n\treturn ret;\n}\n"
  },
  {
    "path": "builtin/exec.c",
    "content": "#include <stdio.h>\n#include <unistd.h>\n#include \"builtin.h\"\n#include \"mrsh_getopt.h\"\n#include \"shell/path.h\"\n\nstatic const char exec_usage[] = \"usage: exec [command [argument...]]\\n\";\n\nint builtin_exec(struct mrsh_state *state, int argc, char *argv[]) {\n\t_mrsh_optind = 0;\n\tif (_mrsh_getopt(argc, argv, \":\") != -1) {\n\t\tfprintf(stderr, \"exec: unknown option -- %c\\n\", _mrsh_optopt);\n\t\tfprintf(stderr, exec_usage);\n\t\treturn 1;\n\t}\n\tif (_mrsh_optind == argc) {\n\t\treturn 0;\n\t}\n\n\tchar *path = expand_path(state, argv[_mrsh_optind], false, false);\n\tif (path == NULL) {\n\t\tfprintf(stderr, \"exec: %s: command not found\\n\", argv[_mrsh_optind]);\n\t\treturn 127;\n\t}\n\tif (access(path, X_OK) != 0) {\n\t\tfprintf(stderr, \"exec: %s: not executable\\n\", path);\n\t\treturn 126;\n\t}\n\n\texecv(path, &argv[_mrsh_optind]);\n\tperror(\"exec\");\n\treturn 1;\n}\n"
  },
  {
    "path": "builtin/exit.c",
    "content": "#include <errno.h>\n#include <mrsh/builtin.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include \"builtin.h\"\n#include \"shell/task.h\"\n\nstatic const char exit_usage[] = \"usage: exit [n]\\n\";\n\nint builtin_exit(struct mrsh_state *state, int argc, char *argv[]) {\n\tif (argc > 2) {\n\t\tfprintf(stderr, exit_usage);\n\t\treturn 1;\n\t}\n\n\tint status = 0;\n\tif (argc > 1) {\n\t\tchar *endptr;\n\t\terrno = 0;\n\t\tlong status_long = strtol(argv[1], &endptr, 10);\n\t\tif (endptr[0] != '\\0' || errno != 0 ||\n\t\t\t\tstatus_long < 0 || status_long > 255) {\n\t\t\tfprintf(stderr, exit_usage);\n\t\t\treturn 1;\n\t\t}\n\t\tstatus = (int)status_long;\n\t}\n\n\tstruct mrsh_call_frame_priv *frame_priv = call_frame_get_priv(state->frame);\n\n\tstate->exit = status;\n\tframe_priv->branch_control = MRSH_BRANCH_EXIT;\n\treturn TASK_STATUS_INTERRUPTED;\n}\n"
  },
  {
    "path": "builtin/export.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <mrsh/builtin.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include \"builtin.h\"\n#include \"shell/word.h\"\n\nstatic const char export_usage[] = \"usage: %s -p|name[=word]...\\n\";\n\nint builtin_export(struct mrsh_state *state, int argc, char *argv[]) {\n\tuint32_t attrib = MRSH_VAR_ATTRIB_EXPORT;\n\tif (strcmp(argv[0], \"readonly\") == 0) {\n\t\tattrib = MRSH_VAR_ATTRIB_READONLY;\n\t}\n\n\tif (argc < 2) {\n\t\tfprintf(stderr, export_usage, argv[0]);\n\t\treturn 1;\n\t} else if (argc == 2 && strcmp(argv[1], \"-p\") == 0) {\n\t\tsize_t count;\n\t\tstruct mrsh_collect_var *vars = collect_vars(\n\t\t\tstate, attrib, &count);\n\t\tfor (size_t i = 0; i < count; ++i) {\n\t\t\tprintf(\"%s %s=\", argv[0], vars[i].key);\n\t\t\tprint_escaped(vars[i].value);\n\t\t\tprintf(\"\\n\");\n\t\t}\n\t\tfree(vars);\n\t\treturn 0;\n\t}\n\n\tfor (int i = 1; i < argc; ++i) {\n\t\tchar *eql, *key;\n\t\tconst char *val;\n\t\tuint32_t prev_attribs = 0;\n\t\teql = strchr(argv[i], '=');\n\t\tif (eql) {\n\t\t\tsize_t klen = eql - argv[i];\n\t\t\tkey = strndup(argv[i], klen);\n\t\t\tval = &eql[1];\n\t\t\tmrsh_env_get(state, key, &prev_attribs);\n\t\t} else {\n\t\t\tkey = strdup(argv[i]);\n\t\t\tval = mrsh_env_get(state, key, &prev_attribs);\n\t\t\tif (!val) {\n\t\t\t\tval = \"\";\n\t\t\t}\n\t\t}\n\t\tif ((prev_attribs & MRSH_VAR_ATTRIB_READONLY)) {\n\t\t\tfprintf(stderr, \"%s: cannot modify readonly variable %s\\n\",\n\t\t\t\t\targv[0], key);\n\t\t\tfree(key);\n\t\t\treturn 1;\n\t\t}\n\t\tstruct mrsh_word_string *ws =\n\t\t\tmrsh_word_string_create(strdup(val), false);\n\t\tstruct mrsh_word *word = &ws->word;\n\t\texpand_tilde(state, &word, true);\n\t\tchar *new_val = mrsh_word_str(word);\n\t\tmrsh_word_destroy(word);\n\t\tmrsh_env_set(state, key, new_val, attrib | prev_attribs);\n\t\tfree(key);\n\t\tfree(new_val);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "builtin/false.c",
    "content": "#include <mrsh/shell.h>\n#include <stdlib.h>\n#include \"builtin.h\"\n\nint builtin_false(struct mrsh_state *state, int argc, char *argv[]) {\n\treturn 1;\n}\n"
  },
  {
    "path": "builtin/fg.c",
    "content": "#include <stdio.h>\n#include <stdlib.h>\n#include \"builtin.h\"\n#include \"mrsh_getopt.h\"\n#include \"shell/job.h\"\n#include \"shell/shell.h\"\n\nstatic const char fg_usage[] = \"usage: fg [job_id]\\n\";\n\nint builtin_fg(struct mrsh_state *state, int argc, char *argv[]) {\n\t_mrsh_optind = 0;\n\tint opt;\n\twhile ((opt = _mrsh_getopt(argc, argv, \":\")) != -1) {\n\t\tswitch (opt) {\n\t\tdefault:\n\t\t\tfprintf(stderr, \"fg: unknown option -- %c\\n\", _mrsh_optopt);\n\t\t\tfprintf(stderr, fg_usage);\n\t\t\treturn EXIT_FAILURE;\n\t\t}\n\t}\n\n\tstruct mrsh_job *job;\n\tif (_mrsh_optind == argc) {\n\t\tjob = job_by_id(state, \"%%\", true);\n\t} else if (_mrsh_optind == argc - 1) {\n\t\tjob = job_by_id(state, argv[_mrsh_optind], true);\n\t} else {\n\t\tfprintf(stderr, fg_usage);\n\t\treturn EXIT_FAILURE;\n\t}\n\tif (!job) {\n\t\treturn EXIT_FAILURE;\n\t}\n\n\tif (!job_set_foreground(job, true, true)) {\n\t\treturn EXIT_FAILURE;\n\t}\n\treturn job_wait(job);\n}\n"
  },
  {
    "path": "builtin/getopts.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <errno.h>\n#include <limits.h>\n#include <mrsh/buffer.h>\n#include <mrsh/shell.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include \"builtin.h\"\n#include \"mrsh_getopt.h\"\n\nstatic const char getopts_usage[] = \"usage: getopts optstring name [arg...]\\n\";\n\nint builtin_getopts(struct mrsh_state *state, int argc, char *argv[]) {\n\t_mrsh_optind = 0;\n\tif (_mrsh_getopt(argc, argv, \":\") != -1) {\n\t\tfprintf(stderr, \"getopts: unknown option -- %c\\n\", _mrsh_optopt);\n\t\tfprintf(stderr, getopts_usage);\n\t\treturn 1;\n\t}\n\tif (_mrsh_optind + 2 < argc) {\n\t\tfprintf(stderr, getopts_usage);\n\t\treturn 1;\n\t}\n\n\tint optc;\n\tchar **optv;\n\tif (_mrsh_optind + 2 > argc) {\n\t\toptc = argc - _mrsh_optind - 2;\n\t\toptv = &argv[_mrsh_optind + 2];\n\t} else {\n\t\toptc = state->frame->argc;\n\t\toptv = state->frame->argv;\n\t}\n\tchar *optstring = argv[_mrsh_optind];\n\tchar *name = argv[_mrsh_optind + 1];\n\n\tconst char *optind_str = mrsh_env_get(state, \"OPTIND\", NULL);\n\tif (optind_str == NULL) {\n\t\tfprintf(stderr, \"getopts: OPTIND is not defined\\n\");\n\t\treturn 1;\n\t}\n\tchar *endptr;\n\tlong optind_long = strtol(optind_str, &endptr, 10);\n\tif (endptr[0] != '\\0' || optind_long <= 0 || optind_long > INT_MAX) {\n\t\tfprintf(stderr, \"getopts: OPTIND is not a positive integer\\n\");\n\t\treturn 1;\n\t}\n\t_mrsh_optind = (int)optind_long;\n\n\t_mrsh_optopt = 0;\n\tint opt = _mrsh_getopt(optc, optv, optstring);\n\n\tchar optind_fmt[16];\n\tsnprintf(optind_fmt, sizeof(optind_fmt), \"%d\", _mrsh_optind);\n\tmrsh_env_set(state, \"OPTIND\", optind_fmt, MRSH_VAR_ATTRIB_NONE);\n\n\tif (_mrsh_optopt != 0) {\n\t\tif (opt == ':') {\n\t\t\tchar value[] = {(char)_mrsh_optopt, '\\0'};\n\t\t\tmrsh_env_set(state, \"OPTARG\", value, MRSH_VAR_ATTRIB_NONE);\n\t\t} else if (optstring[0] != ':') {\n\t\t\tmrsh_env_unset(state, \"OPTARG\");\n\t\t} else {\n\t\t\t// either missing option-argument or unknown option character\n\t\t\t// in the former case, unset OPTARG\n\t\t\t// in the latter case, set OPTARG to _mrsh_optopt\n\t\t\tbool opt_exists = false;\n\t\t\tsize_t len = strlen(optstring);\n\t\t\tfor (size_t i = 0; i < len; ++i) {\n\t\t\t\tif (optstring[i] == _mrsh_optopt) {\n\t\t\t\t\topt_exists = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (opt_exists) {\n\t\t\t\tmrsh_env_unset(state, \"OPTARG\");\n\t\t\t} else {\n\t\t\t\tchar value[] = {(char)_mrsh_optopt, '\\0'};\n\t\t\t\tmrsh_env_set(state, \"OPTARG\", value, MRSH_VAR_ATTRIB_NONE);\n\t\t\t}\n\t\t}\n\t} else if (_mrsh_optarg != NULL) {\n\t\tmrsh_env_set(state, \"OPTARG\", _mrsh_optarg, MRSH_VAR_ATTRIB_NONE);\n\t} else {\n\t\tmrsh_env_unset(state, \"OPTARG\");\n\t}\n\n\tchar value[] = {opt == -1 ? (char)'?' : (char)opt, '\\0'};\n\tmrsh_env_set(state, name, value, MRSH_VAR_ATTRIB_NONE);\n\n\tif (opt == -1) {\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n"
  },
  {
    "path": "builtin/hash.c",
    "content": "#include <mrsh/builtin.h>\n#include <shell/path.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include \"builtin.h\"\n#include \"mrsh_getopt.h\"\n\nstatic const char hash_usage[] = \"usage: hash -r|utility...\\n\";\n\nint builtin_hash(struct mrsh_state *state, int argc, char *argv[]) {\n\t/* Hashing and remembering executable location isn't implemented. Thus most\n\t * of this builtin just does nothing. */\n\t_mrsh_optind = 0;\n\tint opt;\n\twhile ((opt = _mrsh_getopt(argc, argv, \":r\")) != -1) {\n\t\tswitch (opt) {\n\t\tcase 'r':\n\t\t\t/* no-op: reset list of cached utilities */\n\t\t\treturn 0;\n\t\tdefault:\n\t\t\tfprintf(stderr, \"hash: unknown option -- %c\\n\", _mrsh_optopt);\n\t\t\tfprintf(stderr, hash_usage);\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\tif (argc == 1) {\n\t\t/* no-op: print list of cached utilities */\n\t\treturn 0;\n\t}\n\n\tfor (int i = 1; i < argc; i++) {\n\t\tconst char *utility = argv[i];\n\t\tif (strchr(utility, '/') != NULL) {\n\t\t\tfprintf(stderr,\n\t\t\t\t\"hash: undefined behaviour: utility contains a slash\\n\");\n\t\t\treturn 1;\n\t\t}\n\n\t\tif (mrsh_has_builtin(utility)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tchar *path = expand_path(state, utility, true, false);\n\t\tif (path == NULL) {\n\t\t\tfprintf(stderr, \"hash: command not found: %s\\n\", utility);\n\t\t\treturn 1;\n\t\t}\n\t\tfree(path);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "builtin/jobs.c",
    "content": "#include <assert.h>\n#include <limits.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <signal.h>\n#include \"builtin.h\"\n#include \"mrsh_getopt.h\"\n#include \"shell/job.h\"\n#include \"shell/process.h\"\n#include \"shell/shell.h\"\n#include \"shell/task.h\"\n\nstatic const char jobs_usage[] = \"usage: jobs [-l|-p] [job_id...]\\n\";\n\nstruct jobs_context {\n\tstruct mrsh_job *current, *previous;\n\tbool pids;\n\tbool pgids;\n\tbool r;\n};\n\nstatic void show_job(struct mrsh_job *job, const struct jobs_context *ctx) {\n\tif (job_poll(job) >= 0) {\n\t\treturn;\n\t}\n\tchar curprev = ' ';\n\tif (job == ctx->current) {\n\t\tcurprev = '+';\n\t} else if (job == ctx->previous) {\n\t\tcurprev = '-';\n\t}\n\tif (ctx->pids) {\n\t\tfor (size_t i = 0; i < job->processes.len; ++i) {\n\t\t\tstruct mrsh_process *proc = job->processes.data[i];\n\t\t\tprintf(\"%d\\n\", proc->pid);\n\t\t}\n\t} else if (ctx->pgids) {\n\t\tchar *cmd = mrsh_node_format(job->node);\n\t\tprintf(\"[%d] %c %d %s %s\\n\", job->job_id, curprev, job->pgid,\n\t\t\t\tjob_state_str(job, ctx->r), cmd);\n\t\tfree(cmd);\n\t} else {\n\t\tchar *cmd = mrsh_node_format(job->node);\n\t\tprintf(\"[%d] %c %s %s\\n\", job->job_id, curprev,\n\t\t\t\tjob_state_str(job, ctx->r), cmd);\n\t\tfree(cmd);\n\t}\n}\n\nint builtin_jobs(struct mrsh_state *state, int argc, char *argv[]) {\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\n\tstruct mrsh_job *current = job_by_id(state, \"%+\", false),\n\t\t*previous = job_by_id(state, \"%-\", false);\n\n\tstruct jobs_context ctx = {\n\t\t.current = current,\n\t\t.previous = previous,\n\t\t.pids = false,\n\t\t.pgids = false,\n\t\t.r = rand() % 2 == 0,\n\t};\n\n\t_mrsh_optind = 0;\n\tint opt;\n\twhile ((opt = _mrsh_getopt(argc, argv, \":lp\")) != -1) {\n\t\tswitch (opt) {\n\t\tcase 'l':\n\t\t\tif (ctx.pids) {\n\t\t\t\tfprintf(stderr, \"jobs: the -p and -l options are \"\n\t\t\t\t\t\t\"mutually exclusive\\n\");\n\t\t\t\treturn EXIT_FAILURE;\n\t\t\t}\n\t\t\tctx.pgids = true;\n\t\t\tbreak;\n\t\tcase 'p':\n\t\t\tif (ctx.pgids) {\n\t\t\t\tfprintf(stderr, \"jobs: the -p and -l options are \"\n\t\t\t\t\t\t\"mutually exclusive\\n\");\n\t\t\t\treturn EXIT_FAILURE;\n\t\t\t}\n\t\t\tctx.pids = true;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tfprintf(stderr, \"jobs: unknown option -- %c\\n\", _mrsh_optopt);\n\t\t\tfprintf(stderr, jobs_usage);\n\t\t\treturn EXIT_FAILURE;\n\t\t}\n\t}\n\n\tif (_mrsh_optind == argc) {\n\t\tfor (size_t i = 0; i < priv->jobs.len; i++) {\n\t\t\tstruct mrsh_job *job = priv->jobs.data[i];\n\t\t\tshow_job(job, &ctx);\n\t\t}\n\t} else {\n\t\tfor (int i = _mrsh_optind; i < argc; i++) {\n\t\t\tstruct mrsh_job *job = job_by_id(state, argv[i], true);\n\t\t\tif (!job) {\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\tshow_job(job, &ctx);\n\t\t}\n\t}\n\n\treturn EXIT_SUCCESS;\n}\n"
  },
  {
    "path": "builtin/pwd.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <assert.h>\n#include <mrsh/shell.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include \"builtin.h\"\n#include \"mrsh_getopt.h\"\n\nstatic const char pwd_usage[] = \"usage: pwd [-L|-P]\\n\";\n\nint builtin_pwd(struct mrsh_state *state, int argc, char *argv[]) {\n\t_mrsh_optind = 0;\n\tint opt;\n\twhile ((opt = _mrsh_getopt(argc, argv, \":LP\")) != -1) {\n\t\tswitch (opt) {\n\t\tcase 'L':\n\t\tcase 'P':\n\t\t\t/* This space deliberately left blank */\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tfprintf(stderr, \"pwd: unknown option -- %c\\n\", _mrsh_optopt);\n\t\t\tfprintf(stderr, pwd_usage);\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\tif (_mrsh_optind < argc) {\n\t\tfprintf(stderr, pwd_usage);\n\t\treturn 1;\n\t}\n\n\tconst char *pwd = mrsh_env_get(state, \"PWD\", NULL);\n\tassert(pwd != NULL);\n\tputs(pwd);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "builtin/read.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <errno.h>\n#include <mrsh/buffer.h>\n#include <mrsh/shell.h>\n#include <shell/word.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include \"builtin.h\"\n#include \"mrsh_getopt.h\"\n\nstatic const char read_usage[] = \"usage: read [-r] var...\\n\";\n\nint builtin_read(struct mrsh_state *state, int argc, char *argv[]) {\n\tbool raw = false;\n\n\t_mrsh_optind = 0;\n\tint opt;\n\twhile ((opt = _mrsh_getopt(argc, argv, \":r\")) != -1) {\n\t\tswitch (opt) {\n\t\tcase 'r':\n\t\t\traw = true;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tfprintf(stderr, \"read: unknown option -- %c\\n\", _mrsh_optopt);\n\t\t\tfprintf(stderr, read_usage);\n\t\t\treturn 1;\n\t\t}\n\t}\n\tif (_mrsh_optind == argc) {\n\t\tfprintf(stderr, read_usage);\n\t\treturn 1;\n\t}\n\n\tstruct mrsh_buffer buf = {0};\n\tbool escaped = false;\n\tint c;\n\twhile ((c = fgetc(stdin)) != EOF) {\n\t\tif (!raw && !escaped && c == '\\\\') {\n\t\t\tescaped = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (c == '\\n') {\n\t\t\tif (escaped) {\n\t\t\t\tescaped = false;\n\t\t\t\tconst char *ps2 = mrsh_env_get(state, \"PS2\", NULL);\n\t\t\t\tfprintf(stderr, \"%s\", ps2 != NULL ? ps2 : \"> \");\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tescaped = false;\n\t\tmrsh_buffer_append_char(&buf, (char)c);\n\t}\n\tmrsh_buffer_append_char(&buf, '\\0');\n\n\tstruct mrsh_array fields = {0};\n\n\tstruct mrsh_word_string *ws = mrsh_word_string_create(mrsh_buffer_steal(&buf), false);\n\tsplit_fields(&fields, &ws->word, mrsh_env_get(state, \"IFS\", NULL));\n\tmrsh_word_destroy(&ws->word);\n\n\tstruct mrsh_array strs = {0};\n\tget_fields_str(&strs, &fields);\n\tfor (size_t i = 0; i < fields.len; ++i) {\n\t\tmrsh_word_destroy(fields.data[i]);\n\t}\n\tmrsh_array_finish(&fields);\n\tfields = strs;\n\n\tif (fields.len <= (size_t)(argc - _mrsh_optind)) {\n\t\tfor (size_t i = 0; i < fields.len; ++i) {\n\t\t\tmrsh_env_set(state, argv[_mrsh_optind + i], (char *)fields.data[i], MRSH_VAR_ATTRIB_NONE);\n\t\t}\n\t\tfor (size_t i = fields.len; i < (size_t)(argc - _mrsh_optind); ++i) {\n\t\t\tmrsh_env_set(state, argv[_mrsh_optind + i], \"\", MRSH_VAR_ATTRIB_NONE);\n\t\t}\n\t} else {\n\t\tfor (int i = 0; i < argc - _mrsh_optind - 1; ++i) {\n\t\t\tmrsh_env_set(state, argv[_mrsh_optind + i], (char *)fields.data[i], MRSH_VAR_ATTRIB_NONE);\n\t\t}\n\t\tstruct mrsh_buffer buf_last = {0};\n\t\tfor (size_t i = (size_t)(argc - _mrsh_optind - 1); i < fields.len; ++i) {\n\t\t\tchar *field = (char *)fields.data[i];\n\t\t\tmrsh_buffer_append(&buf_last, field, strlen(field));\n\t\t\tif (i != fields.len - 1) {\n\t\t\t\t// TODO add the field delimiter rather than space (bash and dash always use spaces)\n\t\t\t\tmrsh_buffer_append_char(&buf_last, ' ');\n\t\t\t}\n\t\t}\n\t\tmrsh_buffer_append_char(&buf_last, '\\0');\n\t\tmrsh_env_set(state, argv[argc - 1], buf_last.data, MRSH_VAR_ATTRIB_NONE);\n\t\tmrsh_buffer_finish(&buf_last);\n\t}\n\n\tfor (size_t i = 0; i < fields.len; ++i) {\n\t\tfree(fields.data[i]);\n\t}\n\tmrsh_array_finish(&fields);\n\n\tif (c == EOF) {\n\t\treturn 1;\n\t} else {\n\t\treturn 0;\n\t}\n}\n"
  },
  {
    "path": "builtin/return.c",
    "content": "#include <mrsh/builtin.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include \"builtin.h\"\n#include \"shell/task.h\"\n\nstatic const char return_usage[] = \"usage: %s [n]\\n\";\n\nint builtin_return(struct mrsh_state *state, int argc, char *argv[]) {\n\tif (argc > 2) {\n\t\tfprintf(stderr, return_usage, argv[0]);\n\t\treturn 1;\n\t}\n\n\tint n = 0;\n\tif (argc == 2) {\n\t\tchar *end;\n\t\tn = strtol(argv[1], &end, 10);\n\t\tif (end[0] != '\\0' || argv[0][0] == '\\0' || n < 0 || n > 255) {\n\t\t\tfprintf(stderr, \"%s: invalid return number '%s'\\n\", argv[0], argv[1]);\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\tstruct mrsh_call_frame_priv *frame_priv = call_frame_get_priv(state->frame);\n\n\tframe_priv->nloops = 0;\n\tframe_priv->branch_control = MRSH_BRANCH_RETURN;\n\tstate->last_status = n;\n\treturn TASK_STATUS_INTERRUPTED;\n}\n"
  },
  {
    "path": "builtin/set.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <errno.h>\n#include <mrsh/builtin.h>\n#include <mrsh/shell.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include \"builtin.h\"\n#include \"shell/shell.h\"\n\nstatic const char set_usage[] =\n\t\"usage: set [(-|+)abCefhmnuvx] [-o option] [args...]\\n\"\n\t\"       set [(-|+)abCefhmnuvx] [+o option] [args...]\\n\"\n\t\"       set -- [args...]\\n\"\n\t\"       set -o\\n\"\n\t\"       set +o\\n\";\n\nstruct option_map {\n\tconst char *name;\n\tchar short_name;\n\tenum mrsh_option value;\n};\n\nstatic const struct option_map options[] = {\n\t{ \"allexport\", 'a', MRSH_OPT_ALLEXPORT },\n\t{ \"notify\", 'b', MRSH_OPT_NOTIFY },\n\t{ \"noclobber\", 'C', MRSH_OPT_NOCLOBBER },\n\t{ \"errexit\", 'e', MRSH_OPT_ERREXIT },\n\t{ \"noglob\", 'f', MRSH_OPT_NOGLOB },\n\t{ NULL, 'h', MRSH_OPT_PRELOOKUP },\n\t{ \"monitor\", 'm', MRSH_OPT_MONITOR },\n\t{ \"noexec\", 'n', MRSH_OPT_NOEXEC },\n\t{ \"ignoreeof\", 0, MRSH_OPT_IGNOREEOF },\n\t{ \"nolog\", 0, MRSH_OPT_NOLOG },\n\t{ \"vi\", 0, MRSH_OPT_VI },\n\t{ \"nounset\", 'u', MRSH_OPT_NOUNSET },\n\t{ \"verbose\", 'v', MRSH_OPT_VERBOSE },\n\t{ \"xtrace\", 'x', MRSH_OPT_XTRACE },\n};\n\nconst char *state_get_options(struct mrsh_state *state) {\n\tstatic char opts[sizeof(options) / sizeof(options[0]) + 1];\n\tint i = 0;\n\tfor (size_t j = 0; j < sizeof(options) / sizeof(options[0]); ++j) {\n\t\tif (options[j].short_name != '\\0' &&\n\t\t\t\t(state->options & options[j].value)) {\n\t\t\topts[i++] = options[j].short_name;\n\t\t}\n\t}\n\topts[i] = '\\0';\n\treturn opts;\n}\n\nstatic void print_options(struct mrsh_state *state) {\n\tfor (size_t j = 0; j < sizeof(options) / sizeof(options[0]); ++j) {\n\t\tif (options[j].name != NULL) {\n\t\t\tprintf(\"set %co %s\\n\",\n\t\t\t\t(state->options & options[j].value) ? '-' : '+',\n\t\t\t\toptions[j].name);\n\t\t}\n\t}\n}\n\nstatic const struct option_map *find_option(char opt) {\n\tfor (size_t i = 0; i < sizeof(options) / sizeof(options[0]); ++i) {\n\t\tif (options[i].short_name && options[i].short_name == opt) {\n\t\t\treturn &options[i];\n\t\t}\n\t}\n\treturn NULL;\n}\n\nstatic const struct option_map *find_long_option(const char *opt) {\n\tfor (size_t i = 0; i < sizeof(options) / sizeof(options[0]); ++i) {\n\t\tif (options[i].name && strcmp(options[i].name, opt) == 0) {\n\t\t\treturn &options[i];\n\t\t}\n\t}\n\treturn NULL;\n}\n\nstatic char **argv_dup(char *argv_0, int argc, char *argv[]) {\n\tchar **_argv = calloc(argc + 1, sizeof(char *));\n\t_argv[0] = argv_0;\n\tfor (int i = 1; i < argc; ++i) {\n\t\t_argv[i] = strdup(argv[i - 1]);\n\t}\n\treturn _argv;\n}\n\nstatic void argv_free(int argc, char **argv) {\n\tif (!argv) {\n\t\treturn;\n\t}\n\tfor (int i = 0; i < argc; ++i) {\n\t\tfree(argv[i]);\n\t}\n\tfree(argv);\n}\n\nstatic int set(struct mrsh_state *state, int argc, char *argv[],\n\t\tstruct mrsh_init_args *init_args, uint32_t *populated_opts) {\n\tif (argc == 1 && init_args == NULL) {\n\t\tsize_t count;\n\t\tstruct mrsh_collect_var *vars = collect_vars(\n\t\t\tstate, MRSH_VAR_ATTRIB_NONE, &count);\n\t\tfor (size_t i = 0; i < count; ++i) {\n\t\t\tprintf(\"%s=\", vars[i].key);\n\t\t\tprint_escaped(vars[i].value);\n\t\t\tprintf(\"\\n\");\n\t\t}\n\t\tfree(vars);\n\t\treturn 0;\n\t}\n\n\tbool force_positional = false;\n\tint i;\n\tfor (i = 1; i < argc; ++i) {\n\t\tif (strcmp(argv[i], \"--\") == 0) {\n\t\t\tforce_positional = true;\n\t\t\t++i;\n\t\t\tbreak;\n\t\t}\n\t\tif (argv[i][0] != '-' && argv[i][0] != '+') {\n\t\t\tbreak;\n\t\t}\n\t\tif (argv[i][1] == '\\0') {\n\t\t\tfprintf(stderr, set_usage);\n\t\t\treturn 1;\n\t\t}\n\t\tconst struct option_map *option;\n\t\tswitch (argv[i][1]) {\n\t\tcase 'o':\n\t\t\tif (i + 1 == argc) {\n\t\t\t\tprint_options(state);\n\t\t\t\tgoto out; // we must populate state->argv\n\t\t\t}\n\t\t\toption = find_long_option(argv[i + 1]);\n\t\t\tif (!option) {\n\t\t\t\tfprintf(stderr, set_usage);\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\tif (argv[i][0] == '-') {\n\t\t\t\tstate->options |= option->value;\n\t\t\t} else {\n\t\t\t\tstate->options &= ~option->value;\n\t\t\t}\n\t\t\tif (populated_opts != NULL) {\n\t\t\t\t*populated_opts |= option->value;\n\t\t\t}\n\t\t\t++i;\n\t\t\tcontinue;\n\t\tcase 'c':\n\t\t\tif (init_args == NULL) {\n\t\t\t\tfprintf(stderr, set_usage);\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\tinit_args->command_str = argv[i + 1];\n\t\t\t++i;\n\t\t\tbreak;\n\t\tcase 's':\n\t\t\tif (init_args == NULL) {\n\t\t\t\tfprintf(stderr, set_usage);\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\tinit_args->command_str = NULL;\n\t\t\tinit_args->command_file = NULL;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tfor (int j = 1; argv[i][j]; ++j) {\n\t\t\t\toption = find_option(argv[i][j]);\n\t\t\t\tif (!option) {\n\t\t\t\t\tfprintf(stderr, set_usage);\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t\tif (argv[i][0] == '-') {\n\t\t\t\t\tstate->options |= option->value;\n\t\t\t\t} else {\n\t\t\t\t\tstate->options &= ~option->value;\n\t\t\t\t}\n\t\t\t\tif (populated_opts != NULL) {\n\t\t\t\t\t*populated_opts |= option->value;\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (i != argc || force_positional) {\n\t\tchar *argv_0;\n\t\tif (init_args != NULL) {\n\t\t\targv_0 = strdup(argv[i++]);\n\t\t\tinit_args->command_file = argv_0;\n\t\t} else if (state->frame->argv) {\n\t\t\targv_0 = strdup(state->frame->argv[0]);\n\t\t} else {\n\t\t\tfprintf(stderr, set_usage);\n\t\t\treturn 1;\n\t\t}\n\t\targv_free(state->frame->argc, state->frame->argv);\n\t\tstate->frame->argc = argc - i + 1;\n\t\tstate->frame->argv = argv_dup(argv_0, state->frame->argc, &argv[i]);\n\t} else\nout:\n\t  if (init_args != NULL) {\n\t\t// No args given, but we need to initialize state->argv\n\t\tstate->frame->argc = 1;\n\t\tstate->frame->argv = argv_dup(strdup(argv[0]), 1, argv);\n\t}\n\n\treturn 0;\n}\n\nint builtin_set(struct mrsh_state *state, int argc, char *argv[]) {\n\tuint32_t populated_opts = 0;\n\tint ret = set(state, argc, argv, NULL, &populated_opts);\n\tif (ret != 0) {\n\t\treturn ret;\n\t}\n\n\tif (populated_opts & MRSH_OPT_MONITOR) {\n\t\tif (!mrsh_set_job_control(state, state->options & MRSH_OPT_MONITOR)) {\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nint mrsh_process_args(struct mrsh_state *state, struct mrsh_init_args *init_args,\n\t\tint argc, char *argv[]) {\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\n\tuint32_t populated_opts = 0;\n\tint ret = set(state, argc, argv, init_args, &populated_opts);\n\tif (ret != 0) {\n\t\treturn ret;\n\t}\n\n\tstate->interactive = isatty(priv->term_fd) &&\n\t\tinit_args->command_str == NULL && init_args->command_file == NULL;\n\tif (state->interactive && !(populated_opts & MRSH_OPT_MONITOR)) {\n\t\tstate->options |= MRSH_OPT_MONITOR;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "builtin/shift.c",
    "content": "#include <errno.h>\n#include <mrsh/builtin.h>\n#include <mrsh/shell.h>\n#include <stdlib.h>\n#include \"builtin.h\"\n\nstatic const char shift_usage[] = \"usage: shift [n]\\n\";\n\nint builtin_shift(struct mrsh_state *state, int argc, char *argv[]) {\n\tif (argc > 2) {\n\t\tfprintf(stderr, shift_usage);\n\t\treturn 1;\n\t}\n\tint n = 1;\n\tif (argc == 2) {\n\t\tchar *endptr;\n\t\terrno = 0;\n\t\tlong n_long = strtol(argv[1], &endptr, 10);\n\t\tif (*endptr != '\\0' || errno != 0) {\n\t\t\tfprintf(stderr, shift_usage);\n\t\t\tif (!state->interactive) {\n\t\t\t\tstate->exit = 1;\n\t\t\t}\n\t\t\treturn 1;\n\t\t}\n\t\tn = (int)n_long;\n\t}\n\tif (n == 0) {\n\t\treturn 0;\n\t} else if (n < 1) {\n\t\tfprintf(stderr, \"shift: [n] must be positive\\n\");\n\t\tif (!state->interactive) {\n\t\t\tstate->exit = 1;\n\t\t}\n\t\treturn 1;\n\t} else if (n > state->frame->argc - 1) {\n\t\tfprintf(stderr, \"shift: [n] must be less than $#\\n\");\n\t\tif (!state->interactive) {\n\t\t\tstate->exit = 1;\n\t\t}\n\t\treturn 1;\n\t}\n\tfor (int i = 1, j = n + 1; j < state->frame->argc; ++i, ++j) {\n\t\tif (j <= state->frame->argc - n) {\n\t\t\tstate->frame->argv[i] = state->frame->argv[j];\n\t\t} else {\n\t\t\tfree(state->frame->argv[i]);\n\t\t}\n\t}\n\tstate->frame->argc -= n;\n\treturn 0;\n}\n"
  },
  {
    "path": "builtin/times.c",
    "content": "#include <errno.h>\n#include <mrsh/builtin.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/times.h>\n#include <unistd.h>\n#include \"builtin.h\"\n\nstatic const char times_usage[] = \"usage: times\\n\";\n\nint builtin_times(struct mrsh_state *state, int argc, char *argv[]) {\n\tif (argc > 1) {\n\t\tfprintf(stderr, times_usage);\n\t\treturn 1;\n\t}\n\n\tstruct tms buf;\n\tlong clk_tck = sysconf(_SC_CLK_TCK);\n\tif (clk_tck == -1) {\n\t\tperror(\"sysconf\");\n\t\treturn 1;\n\t}\n\n\tif (times(&buf) == (clock_t)-1) {\n\t\tperror(\"times\");\n\t\treturn 1;\n\t}\n\n\tprintf(\"%dm%fs %dm%fs\\n%dm%fs %dm%fs\\n\",\n\t\t\t(int)(buf.tms_utime / clk_tck / 60),\n\t\t\t((double) buf.tms_utime) / clk_tck,\n\t\t\t(int)(buf.tms_stime / clk_tck / 60),\n\t\t\t((double) buf.tms_stime) / clk_tck,\n\t\t\t(int)(buf.tms_cutime / clk_tck / 60),\n\t\t\t((double) buf.tms_cutime) / clk_tck,\n\t\t\t(int)(buf.tms_cstime / clk_tck / 60),\n\t\t\t((double)buf.tms_cstime) / clk_tck);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "builtin/trap.c",
    "content": "#define _XOPEN_SOURCE 1 // for SIGPOLL and SIGVTALRM\n#include <assert.h>\n#include <mrsh/parser.h>\n#include <mrsh/shell.h>\n#include <signal.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include \"builtin.h\"\n#include \"mrsh_getopt.h\"\n#include \"shell/shell.h\"\n#include \"shell/trap.h\"\n\nstatic const char trap_usage[] =\n\t\"usage: trap <n> [condition...]\\n\"\n\t\"       trap [action condition...]\\n\";\n\nstatic const char *sig_names[] = {\n\t[SIGABRT] = \"ABRT\",\n\t[SIGALRM] = \"ALRM\",\n\t[SIGBUS] = \"BUS\",\n\t[SIGCHLD] = \"CHLD\",\n\t[SIGCONT] = \"CONT\",\n\t[SIGFPE] = \"FPE\",\n\t[SIGHUP] = \"HUP\",\n\t[SIGILL] = \"ILL\",\n\t[SIGINT] = \"INT\",\n\t[SIGKILL] = \"KILL\",\n\t[SIGPIPE] = \"PIPE\",\n\t[SIGQUIT] = \"QUIT\",\n\t[SIGSEGV] = \"SEGV\",\n\t[SIGSTOP] = \"STOP\",\n\t[SIGTERM] = \"TERM\",\n\t[SIGTSTP] = \"TSTP\",\n\t[SIGTTIN] = \"TTIN\",\n\t[SIGTTOU] = \"TTOU\",\n\t[SIGUSR1] = \"USR1\",\n\t[SIGUSR2] = \"USR2\",\n\t// Some BSDs have decided against implementing SIGPOLL for functional and\n\t// security reasons\n#ifdef SIGPOLL\n\t[SIGPOLL] = \"POLL\",\n#endif\n\t[SIGPROF] = \"PROF\",\n\t[SIGSYS] = \"SYS\",\n\t[SIGTRAP] = \"TRAP\",\n\t[SIGURG] = \"URG\",\n\t[SIGVTALRM] = \"VTALRM\",\n\t[SIGXCPU] = \"XCPU\",\n\t[SIGXFSZ] = \"XFSZ\",\n};\n\nstatic bool is_decimal_str(const char *str) {\n\tfor (size_t i = 0; str[i] != '\\0'; i++) {\n\t\tif (str[i] < '0' || str[i] > '9') {\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n}\n\nstatic int parse_sig(const char *str) {\n\tif (strcmp(str, \"0\") == 0 || strcmp(str, \"EXIT\") == 0) {\n\t\treturn 0;\n\t}\n\n\t// XSI-conformant systems need to recognize a few more numeric signal\n\t// numbers\n\tif (strcmp(str, \"1\") == 0) {\n\t\treturn SIGHUP;\n\t} else if (strcmp(str, \"2\") == 0) {\n\t\treturn SIGINT;\n\t} else if (strcmp(str, \"3\") == 0) {\n\t\treturn SIGQUIT;\n\t} else if (strcmp(str, \"6\") == 0) {\n\t\treturn SIGABRT;\n\t} else if (strcmp(str, \"9\") == 0) {\n\t\treturn SIGKILL;\n\t} else if (strcmp(str, \"14\") == 0) {\n\t\treturn SIGALRM;\n\t} else if (strcmp(str, \"15\") == 0) {\n\t\treturn SIGTERM;\n\t}\n\n\tfor (size_t i = 0; i < sizeof(sig_names) / sizeof(sig_names[0]); i++) {\n\t\tif (sig_names[i] == NULL) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (strcmp(str, sig_names[i]) == 0) {\n\t\t\treturn (int)i;\n\t\t}\n\t}\n\n\tfprintf(stderr, \"trap: failed to parse condition: %s\\n\", str);\n\treturn -1;\n}\n\nstatic const char *sig_str(int sig) {\n\tif (sig == 0) {\n\t\treturn \"EXIT\";\n\t}\n\n\tassert(sig > 0);\n\tassert((size_t)sig < sizeof(sig_names) / sizeof(sig_names[0]));\n\tassert(sig_names[sig] != NULL);\n\treturn sig_names[sig];\n}\n\nstatic void print_traps(struct mrsh_state *state) {\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\tfor (size_t i = 0; i < sizeof(priv->traps) / sizeof(priv->traps[0]); i++) {\n\t\tstruct mrsh_trap *trap = &priv->traps[i];\n\t\tif (!trap->set) {\n\t\t\tcontinue;\n\t\t}\n\t\tprintf(\"trap -- \");\n\t\tswitch (trap->action) {\n\t\tcase MRSH_TRAP_DEFAULT:\n\t\t\tprintf(\"-\");\n\t\t\tbreak;\n\t\tcase MRSH_TRAP_IGNORE:\n\t\t\tprintf(\"''\");\n\t\t\tbreak;\n\t\tcase MRSH_TRAP_CATCH:;\n\t\t\tchar *cmd = mrsh_node_format(&trap->program->node);\n\t\t\tprint_escaped(cmd);\n\t\t\tfree(cmd);\n\t\t\tbreak;\n\t\t}\n\t\tprintf(\" %s\\n\", sig_str(i));\n\t}\n}\n\nint builtin_trap(struct mrsh_state *state, int argc, char *argv[]) {\n\t_mrsh_optind = 0;\n\tif (_mrsh_getopt(argc, argv, \":\") != -1) {\n\t\tfprintf(stderr, \"trap: unknown option -- %c\\n\", _mrsh_optopt);\n\t\tfprintf(stderr, trap_usage);\n\t\treturn 1;\n\t}\n\tif (_mrsh_optind == argc) {\n\t\tprint_traps(state);\n\t\treturn 0;\n\t}\n\n\tconst char *action_str;\n\tif (is_decimal_str(argv[_mrsh_optind])) {\n\t\taction_str = \"-\";\n\t} else {\n\t\taction_str = argv[_mrsh_optind];\n\t\t_mrsh_optind++;\n\t}\n\n\tenum mrsh_trap_action action;\n\tstruct mrsh_program *program = NULL;\n\tif (action_str[0] == '\\0') {\n\t\taction = MRSH_TRAP_IGNORE;\n\t} else if (strcmp(action_str, \"-\") == 0) {\n\t\taction = MRSH_TRAP_DEFAULT;\n\t} else {\n\t\taction = MRSH_TRAP_CATCH;\n\n\t\tstruct mrsh_parser *parser =\n\t\t\tmrsh_parser_with_data(action_str, strlen(action_str));\n\t\tprogram = mrsh_parse_program(parser);\n\n\t\tstruct mrsh_position err_pos;\n\t\tconst char *err_msg = mrsh_parser_error(parser, &err_pos);\n\t\tif (err_msg != NULL) {\n\t\t\tfprintf(stderr, \"trap: %d:%d: %s\\n\",\n\t\t\t\terr_pos.line, err_pos.column, err_msg);\n\t\t\tmrsh_parser_destroy(parser);\n\t\t\tmrsh_program_destroy(program);\n\t\t\treturn 1;\n\t\t}\n\t\tmrsh_parser_destroy(parser);\n\t}\n\n\tfor (int i = _mrsh_optind; i < argc; i++) {\n\t\tint sig = parse_sig(argv[i]);\n\t\tif (sig < 0) {\n\t\t\treturn 1;\n\t\t}\n\t\tif (sig == SIGKILL || sig == SIGSTOP) {\n\t\t\tfprintf(stderr, \"trap: setting a trap for SIGKILL or SIGSTOP \"\n\t\t\t\t\"produces undefined results\\n\");\n\t\t\treturn 1;\n\t\t}\n\n\t\tif (!set_trap(state, sig, action, program)) {\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "builtin/true.c",
    "content": "#include <mrsh/shell.h>\n#include <stdlib.h>\n#include \"builtin.h\"\n\nint builtin_true(struct mrsh_state *state, int argc, char *argv[]) {\n\treturn 0;\n}\n"
  },
  {
    "path": "builtin/type.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <mrsh/builtin.h>\n#include <shell/path.h>\n#include <stdlib.h>\n#include \"builtin.h\"\n#include \"mrsh_getopt.h\"\n#include \"shell/shell.h\"\n\nstatic const char type_usage[] = \"usage: type name...\\n\";\n\nint builtin_type(struct mrsh_state *state, int argc, char *argv[]) {\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\n\t_mrsh_optind = 0;\n\tif (_mrsh_getopt(argc, argv, \":\") != -1) {\n\t\tfprintf(stderr, \"type: unknown option -- %c\\n\", _mrsh_optopt);\n\t\tfprintf(stderr, type_usage);\n\t\treturn 1;\n\t}\n\tif (_mrsh_optind == argc) {\n\t\tfprintf(stderr, type_usage);\n\t\treturn 1;\n\t}\n\n\tbool error = false;\n\tfor (int i = _mrsh_optind; i < argc; ++i) {\n\t\tchar *name = argv[i];\n\n\t\tchar *alias = mrsh_hashtable_get(&priv->aliases, name);\n\t\tif (alias != NULL) {\n\t\t\tfprintf(stdout, \"%s is an alias for %s\\n\", name, alias);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (mrsh_has_special_builtin(name)) {\n\t\t\tfprintf(stdout, \"%s is a special shell builtin\\n\", name);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (mrsh_has_builtin(name)) {\n\t\t\tfprintf(stdout, \"%s is a shell builtin\\n\", name);\n\t\t\tcontinue;\n\t\t}\n\n\t\tchar *path = expand_path(state, name, true, false);\n\t\tif (path != NULL) {\n\t\t\tfprintf(stdout, \"%s is %s\\n\", name, path);\n\t\t\tfree(path);\n\t\t\tcontinue;\n\t\t}\n\n\t\tfprintf(stdout, \"%s: not found\\n\", name);\n\t\terror = true;\n\t}\n\n\treturn error ? 1 : 0;\n}\n"
  },
  {
    "path": "builtin/ulimit.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <errno.h>\n#include <inttypes.h>\n#include <limits.h>\n#include <mrsh/shell.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/resource.h>\n#include <sys/time.h>\n#include \"builtin.h\"\n#include \"mrsh_getopt.h\"\n\nstatic const char ulimit_usage[] = \"usage: ulimit [-f] [blocks]\\n\";\n\nint builtin_ulimit(struct mrsh_state *state, int argc, char *argv[]) {\n\t_mrsh_optind = 0;\n\tint opt;\n\twhile ((opt = _mrsh_getopt(argc, argv, \":f\")) != -1) {\n\t\tif (opt == 'f') {\n\t\t\t// Nothing here\n\t\t} else {\n\t\t\tfprintf(stderr, \"%s\", ulimit_usage);\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\tif (_mrsh_optind == argc - 1) {\n\t\tchar *arg = argv[_mrsh_optind];\n\t\tchar *end;\n\t\tlong int new_limit = strtol(arg, &end, 10);\n\t\tif (end == arg || end[0] != '\\0') {\n\t\t\tfprintf(stderr, \"ulimit: invalid argument: %s\\n\", arg);\n\t\t\treturn 1;\n\t\t}\n\t\tstruct rlimit new = {\n\t\t\t.rlim_cur = new_limit * 512,\n\t\t\t.rlim_max = new_limit * 512\n\t\t};\n\t\tif (setrlimit(RLIMIT_FSIZE, &new) != 0) {\n\t\t\tperror(\"setrlimit\");\n\t\t\treturn 1;\n\t\t}\n\t} else if (_mrsh_optind == argc) {\n\t\tstruct rlimit old = { 0 };\n\t\tif (getrlimit(RLIMIT_FSIZE, &old) != 0) {\n\t\t\tperror(\"getrlimit\");\n\t\t\treturn 1;\n\t\t}\n\t\tif (old.rlim_max == RLIM_INFINITY) {\n\t\t\tprintf(\"unlimited\\n\");\n\t\t} else {\n\t\t\tprintf(\"%\" PRIuMAX \"\\n\", (uintmax_t)(old.rlim_max / 512));\n\t\t}\n\t} else {\n\t\tfprintf(stderr, \"%s\", ulimit_usage);\n\t\treturn 1;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "builtin/umask.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <mrsh/builtin.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include \"builtin.h\"\n#include \"mrsh_getopt.h\"\n\nstatic const char umask_usage[] = \"usage: umask [-S] [mode]\\n\";\n\nenum umask_symbolic_state {\n\tUMASK_WHO,\n\tUMASK_PERM,\n};\n\nstatic mode_t umask_current_mask(void) {\n\tconst mode_t default_mode = 0022;\n\n\tmode_t mask = umask(default_mode);\n\tumask(mask);\n\treturn mask;\n}\n\nstatic bool umask_update_mode(mode_t *mode, char action, mode_t *perm_mask, mode_t *who_mask) {\n\tswitch (action) {\n\tcase '+':\n\t\t*mode |= (*who_mask & *perm_mask);\n\t\tbreak;\n\tcase '=':\n\t\t*mode = (*mode & ~(*who_mask)) | (*perm_mask & *who_mask);\n\t\tbreak;\n\tcase '-':\n\t\t*mode &= (0777 & ~(*who_mask & *perm_mask));\n\t\tbreak;\n\tdefault:\n\t\tfprintf(stderr, \"unknown action -- '%c'\\n\", action);\n\t\treturn false;\n\t}\n\n\t*perm_mask = *who_mask = 0;\n\treturn true;\n}\n\nstatic bool umask_mode(mode_t *mode, char *symbolic) {\n\tmode_t tmp_mode = 0777 & ~(umask_current_mask());\n\tenum umask_symbolic_state state = UMASK_WHO;\n\tmode_t who_mask = 0;\n\tmode_t perm_mask = 0;\n\tchar action = '\\0';\n\n\tfor (char *c = symbolic; *c != '\\0'; c++) {\n\t\tswitch (state) {\n\t\tcase UMASK_WHO:\n\t\t\tswitch (*c) {\n\t\t\tcase 'u':\n\t\t\t\twho_mask |= S_IRWXU;\n\t\t\t\tbreak;\n\t\t\tcase 'g':\n\t\t\t\twho_mask |= S_IRWXG;\n\t\t\t\tbreak;\n\t\t\tcase 'o':\n\t\t\t\twho_mask |= S_IRWXO;\n\t\t\t\tbreak;\n\t\t\tcase 'a':\n\t\t\t\twho_mask |= (S_IRWXU | S_IRWXG | S_IRWXO);\n\t\t\t\tbreak;\n\t\t\tcase '+':\n\t\t\tcase '-':\n\t\t\tcase '=':\n\t\t\t\tif (who_mask == 0) {\n\t\t\t\t\twho_mask |= (S_IRWXU | S_IRWXG | S_IRWXO);\n\t\t\t\t}\n\t\t\t\taction = *c;\n\t\t\t\tstate = UMASK_PERM;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tfprintf(stderr, \"Unknown who -- '%c'\\n\", *c);\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tbreak;\n\n\t\tcase UMASK_PERM:\n\t\t\tswitch (*c) {\n\t\t\tcase 'u':\n\t\t\t\tperm_mask = (tmp_mode & S_IRWXU) | ((tmp_mode & S_IRWXU) >> 3) | ((tmp_mode & S_IRWXU) >> 6);\n\t\t\t\tbreak;\n\t\t\tcase 'g':\n\t\t\t\tperm_mask = ((tmp_mode & S_IRWXG) << 3) | (tmp_mode & S_IRWXG) | ((tmp_mode & S_IRWXG) >> 3);\n\t\t\t\tbreak;\n\t\t\tcase 'o':\n\t\t\t\tperm_mask = ((tmp_mode & S_IRWXO) << 6) | ((tmp_mode & S_IRWXO) << 3) | (tmp_mode & S_IRWXO);\n\t\t\t\tbreak;\n\t\t\tcase 'r':\n\t\t\t\tperm_mask |= (S_IRUSR | S_IRGRP | S_IROTH);\n\t\t\t\tbreak;\n\t\t\tcase 'w':\n\t\t\t\tperm_mask |= (S_IWUSR | S_IWGRP | S_IWOTH);\n\t\t\t\tbreak;\n\t\t\tcase 'x':\n\t\t\t\tperm_mask |= (S_IXUSR | S_IXGRP | S_IXOTH);\n\t\t\t\tbreak;\n\t\t\tcase ',':\n\t\t\t\tstate = UMASK_WHO;\n\t\t\t\tif (!umask_update_mode(&tmp_mode, action, &perm_mask, &who_mask)) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tfprintf(stderr, \"Invalid permission -- '%c'\\n\", *c);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (state == UMASK_PERM) {\n\t\tif (!umask_update_mode(&tmp_mode, action, &perm_mask, &who_mask)) {\n\t\t\treturn false;\n\t\t}\n\t} else {\n\t\tfprintf(stderr, \"Missing permission from symbolic mode\\n\");\n\t\treturn false;\n\t}\n\n\t*mode = 0777 & ~tmp_mode;\n\n\treturn true;\n}\n\nstatic void umask_modestring(char string[static 4], mode_t mode) {\n\tsize_t i = 0;\n\n\tif (S_IROTH & mode) {\n\t\tstring[i++] = 'r';\n\t}\n\n\tif (S_IWOTH & mode) {\n\t\tstring[i++] = 'w';\n\t}\n\n\tif (S_IXOTH & mode) {\n\t\tstring[i++] = 'x';\n\t}\n}\n\nstatic void umask_print_symbolic(mode_t mask) {\n\tchar user[4] = {0};\n\tchar group[4] = {0};\n\tchar other[4] = {0};\n\tmode_t mode = 0777 & ~mask;\n\n\tumask_modestring(user, (mode & 0700) >> 6);\n\tumask_modestring(group, (mode & 0070) >> 3);\n\tumask_modestring(other, (mode & 0007));\n\n\tprintf(\"u=%s,g=%s,o=%s\\n\", user, group, other);\n}\n\nint builtin_umask(struct mrsh_state *state, int argc, char *argv[]) {\n\tmode_t mode;\n\tbool umask_symbolic = false;\n\n\t_mrsh_optind = 0;\n\tint opt;\n\n\twhile ((opt = _mrsh_getopt(argc, argv, \":S\")) != -1) {\n\t\tswitch (opt) {\n\t\tcase 'S':\n\t\t\tumask_symbolic = true;\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tfprintf(stderr, \"Unknown option -- '%c'\\n\", _mrsh_optopt);\n\t\t\tfprintf(stderr, umask_usage);\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\tif (_mrsh_optind == argc) {\n\t\tmode = umask_current_mask();\n\n\t\tif (umask_symbolic) {\n\t\t\tumask_print_symbolic(mode);\n\t\t} else {\n\t\t\tprintf(\"%04o\\n\", mode);\n\t\t}\n\n\t\treturn 0;\n\t}\n\n\tchar *endptr;\n\tmode = strtol(argv[_mrsh_optind], &endptr, 8);\n\n\tif (*endptr != '\\0') {\n\t\tif (!umask_mode(&mode, argv[_mrsh_optind])) {\n\t\t\tfprintf(stderr, umask_usage);\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\tumask(mode);\n\treturn 0;\n}\n"
  },
  {
    "path": "builtin/unalias.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <mrsh/builtin.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include \"builtin.h\"\n#include \"mrsh_getopt.h\"\n#include \"shell/shell.h\"\n\nstatic const char unalias_usage[] = \"usage: unalias -a|alias-name...\\n\";\n\nstatic void delete_alias_iterator(const char *key, void *_value,\n\t\tvoid *user_data) {\n\tfree(mrsh_hashtable_del((struct mrsh_hashtable*)user_data, key));\n}\n\nint builtin_unalias(struct mrsh_state *state, int argc, char *argv[]) {\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\n\tbool all = false;\n\n\t_mrsh_optind = 0;\n\tint opt;\n\twhile ((opt = _mrsh_getopt(argc, argv, \":a\")) != -1) {\n\t\tswitch (opt) {\n\t\tcase 'a':\n\t\t\tall = true;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tfprintf(stderr, \"unalias: unknown option -- %c\\n\", _mrsh_optopt);\n\t\t\tfprintf(stderr, unalias_usage);\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\tif (all) {\n\t\tif (_mrsh_optind < argc) {\n\t\t\tfprintf(stderr, unalias_usage);\n\t\t\treturn 1;\n\t\t}\n\t\tmrsh_hashtable_for_each(&priv->aliases, delete_alias_iterator,\n\t\t\t&priv->aliases);\n\t\treturn 0;\n\t}\n\n\tif (_mrsh_optind == argc) {\n\t\tfprintf(stderr, unalias_usage);\n\t\treturn 1;\n\t}\n\n\tfor (int i = _mrsh_optind; i < argc; ++i) {\n\t\tfree(mrsh_hashtable_del(&priv->aliases, argv[i]));\n\t}\n\treturn 0;\n}\n"
  },
  {
    "path": "builtin/unset.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <mrsh/builtin.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include \"builtin.h\"\n#include \"mrsh_getopt.h\"\n#include \"shell/shell.h\"\n\nstatic const char unset_usage[] = \"usage: unset [-fv] name...\\n\";\n\nint builtin_unset(struct mrsh_state *state, int argc, char *argv[]) {\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\n\tbool funcs = false;\n\n\t_mrsh_optind = 0;\n\tint opt;\n\twhile ((opt = _mrsh_getopt(argc, argv, \":fv\")) != -1) {\n\t\tswitch (opt) {\n\t\tcase 'f':\n\t\t\tfuncs = true;\n\t\t\tbreak;\n\t\tcase 'v':\n\t\t\tfuncs = false;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tfprintf(stderr, \"unset: unknown option -- %c\\n\", _mrsh_optopt);\n\t\t\tfprintf(stderr, unset_usage);\n\t\t\treturn 1;\n\t\t}\n\t}\n\tif (_mrsh_optind >= argc) {\n\t\tfprintf(stderr, unset_usage);\n\t\treturn 1;\n\t}\n\tfor (int i = _mrsh_optind; i < argc; ++i) {\n\t\tif (!funcs) {\n\t\t\tuint32_t prev_attribs = 0;\n\t\t\tif (mrsh_env_get(state, argv[i], &prev_attribs)) {\n\t\t\t\tif ((prev_attribs & MRSH_VAR_ATTRIB_READONLY)) {\n\t\t\t\t\tfprintf(stderr,\n\t\t\t\t\t\t\"unset: cannot modify readonly variable %s\\n\", argv[i]);\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t\tmrsh_env_unset(state, argv[i]);\n\t\t\t}\n\t\t} else {\n\t\t\tstruct mrsh_function *oldfn =\n\t\t\t\tmrsh_hashtable_del(&priv->functions, argv[i]);\n\t\t\tfunction_destroy(oldfn);\n\t\t}\n\t}\n\treturn 0;\n}\n"
  },
  {
    "path": "builtin/unspecified.c",
    "content": "#include <mrsh/shell.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include \"builtin.h\"\n\nint builtin_unspecified(struct mrsh_state *state, int argc, char *argv[]) {\n\t// Ref: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_09_01_01\n\tif (state->interactive) {\n\t\tfprintf(stderr,\n\t\t\t\t\"%s: The behavior of this command is undefined.\\n\", argv[0]);\n\t} else {\n\t\tfprintf(stderr, \"%s: The behavior of this command is undefined. \"\n\t\t\t\t\"This is an error in your script. Aborting.\\n\", argv[0]);\n\t\tstate->exit = 1;\n\t}\n\n\treturn 1;\n}\n"
  },
  {
    "path": "builtin/wait.c",
    "content": "#define _POSIX_C_SOURCE 200112L\n#include <errno.h>\n#include <stdbool.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n#include <sys/wait.h>\n#include \"builtin.h\"\n#include \"shell/process.h\"\n#include \"shell/shell.h\"\n\nstruct wait_handle {\n\tpid_t pid;\n\tint status;\n};\n\nint builtin_wait(struct mrsh_state *state, int argc, char *argv[]) {\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\n\tint npids = argc - 1;\n\tif (npids == 0) {\n\t\tnpids = priv->processes.len;\n\t}\n\tstruct wait_handle *pids = malloc(npids * sizeof(struct wait_handle));\n\tif (pids == NULL) {\n\t\tfprintf(stderr, \"wait: unable to allocate pid list\");\n\t\treturn EXIT_FAILURE;\n\t}\n\n\tif (argc == 1) {\n\t\t/* All known processes */\n\t\tint _npids = 0;\n\t\tfor (size_t j = 0; j < priv->processes.len; ++j) {\n\t\t\tstruct mrsh_process *process = priv->processes.data[j];\n\t\t\tif (process->terminated) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tpids[_npids].pid = process->pid;\n\t\t\tpids[_npids].status = -1;\n\t\t\t++_npids;\n\t\t}\n\t\tnpids = _npids;\n\t} else {\n\t\tfor (int i = 1; i < argc; ++i) {\n\t\t\tif (argv[i][0] == '%') {\n\t\t\t\tstruct mrsh_job *job = job_by_id(state, argv[i], true);\n\t\t\t\tif (!job) {\n\t\t\t\t\tgoto failure;\n\t\t\t\t}\n\t\t\t\tpids[i - 1].pid = job->pgid;\n\t\t\t\tpids[i - 1].status = -1;\n\t\t\t} else {\n\t\t\t\tchar *endptr;\n\t\t\t\tpid_t pid = (pid_t)strtol(argv[i], &endptr, 10);\n\t\t\t\tif (*endptr != '\\0' || argv[i][0] == '\\0') {\n\t\t\t\t\tfprintf(stderr, \"wait: error parsing pid '%s'\", argv[i]);\n\t\t\t\t\tgoto failure;\n\t\t\t\t}\n\t\t\t\tif (pid <= 0) {\n\t\t\t\t\tfprintf(stderr, \"wait: invalid process ID\\n\");\n\t\t\t\t\tgoto failure;\n\t\t\t\t}\n\t\t\t\tpids[i - 1].pid = pid;\n\t\t\t\tpids[i - 1].status = -1;\n\t\t\t\t/* Check if this pid is known */\n\t\t\t\tbool found = false;\n\t\t\t\tfor (size_t j = 0; j < priv->processes.len; ++j) {\n\t\t\t\t\tstruct mrsh_process *process = priv->processes.data[j];\n\t\t\t\t\tif (process->pid == pid) {\n\t\t\t\t\t\tif (process->terminated) {\n\t\t\t\t\t\t\tpids[i - 1].status = process->stat;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfound = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!found) {\n\t\t\t\t\t/* Unknown pids are assumed to have exited 127 */\n\t\t\t\t\tpids[i - 1].status = 127;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (int i = 0; i < npids; ++i) {\n\t\tint stat;\n\t\tpid_t waited = waitpid(pids[i].pid, &stat, 0);\n\t\t// TODO: update jobs internal state?\n\t\tif (waited == -1) {\n\t\t\tif (errno == ECHILD) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tperror(\"wait\");\n\t\t\tgoto failure;\n\t\t}\n\t\tupdate_process(state, waited, stat);\n\t\tif (WIFEXITED(stat)) {\n\t\t\tpids[i].status = WEXITSTATUS(stat);\n\t\t} else {\n\t\t\tpids[i].status = 129;\n\t\t}\n\t}\n\n\tint status;\n\tif (argc == 1) {\n\t\tstatus = EXIT_SUCCESS;\n\t} else {\n\t\tstatus = pids[npids - 1].status;\n\t}\n\n\tfree(pids);\n\treturn status;\n\nfailure:\n\tfree(pids);\n\treturn EXIT_FAILURE;\n}\n"
  },
  {
    "path": "configure",
    "content": "#!/bin/sh -e\nSOVERSION=0.0.0\n\npkg_config=${PKG_CONFIG:-pkg-config}\noutdir=${OUTDIR:-.build}\nsrcdir=${SRCDIR:-$(dirname \"$0\")}\nCC=${CC:-cc}\nLIBS=\n\nuse_readline=-1\nreadline=readline\n\nstatic=\n\nfor arg\ndo\n\tcase \"$arg\" in\n\t\t--prefix=*)\n\t\t\tPREFIX=${arg#*=}\n\t\t\t;;\n\t\t--without-readline)\n\t\t\tuse_readline=0\n\t\t\t;;\n\t\t--with-readline=*)\n\t\t\tuse_readline=1\n\t\t\treadline=${arg#*=}\n\t\t\t;;\n\t\t--static)\n\t\t\tstatic=$arg\n\t\t\t;;\n\t\t--dynamic)\n\t\t\tstatic=\n\t\t\t;;\n\tesac\ndone\n\nlibmrsh() {\n\tgenrules libmrsh \\\n\t\t'arithm.c' \\\n\t\t'array.c' \\\n\t\t'ast_print.c' \\\n\t\t'ast.c' \\\n\t\t'buffer.c' \\\n\t\t'builtin/alias.c' \\\n\t\t'builtin/bg.c' \\\n\t\t'builtin/break.c' \\\n\t\t'builtin/builtin.c' \\\n\t\t'builtin/cd.c' \\\n\t\t'builtin/colon.c' \\\n\t\t'builtin/command.c' \\\n\t\t'builtin/dot.c' \\\n\t\t'builtin/eval.c' \\\n\t\t'builtin/exec.c' \\\n\t\t'builtin/exit.c' \\\n\t\t'builtin/export.c' \\\n\t\t'builtin/false.c' \\\n\t\t'builtin/fg.c' \\\n\t\t'builtin/getopts.c' \\\n\t\t'builtin/hash.c' \\\n\t\t'builtin/jobs.c' \\\n\t\t'builtin/pwd.c' \\\n\t\t'builtin/read.c' \\\n\t\t'builtin/return.c' \\\n\t\t'builtin/set.c' \\\n\t\t'builtin/shift.c' \\\n\t\t'builtin/times.c' \\\n\t\t'builtin/trap.c' \\\n\t\t'builtin/true.c' \\\n\t\t'builtin/type.c' \\\n\t\t'builtin/ulimit.c' \\\n\t\t'builtin/umask.c' \\\n\t\t'builtin/unalias.c' \\\n\t\t'builtin/unset.c' \\\n\t\t'builtin/unspecified.c' \\\n\t\t'builtin/wait.c' \\\n\t\t'getopt.c' \\\n\t\t'hashtable.c' \\\n\t\t'parser/arithm.c' \\\n\t\t'parser/parser.c' \\\n\t\t'parser/program.c' \\\n\t\t'parser/word.c' \\\n\t\t'shell/arithm.c' \\\n\t\t'shell/entry.c' \\\n\t\t'shell/job.c' \\\n\t\t'shell/path.c' \\\n\t\t'shell/process.c' \\\n\t\t'shell/redir.c' \\\n\t\t'shell/shell.c' \\\n\t\t'shell/task/pipeline.c' \\\n\t\t'shell/task/simple_command.c' \\\n\t\t'shell/task/task.c' \\\n\t\t'shell/task/word.c' \\\n\t\t'shell/trap.c' \\\n\t\t'shell/word.c'\n}\n\nmrsh() {\n\tif [ $use_readline -eq 1 ]\n\tthen\n\t\tgenrules mrsh \\\n\t\t\t'main.c' \\\n\t\t\t'frontend/readline.c'\n\telse\n\t\tgenrules mrsh \\\n\t\t\t'main.c' \\\n\t\t\t'frontend/basic.c'\n\tfi\n}\n\nhighlight() {\n\tgenrules highlight example/highlight.c\n}\n\ngenrules() {\n\ttarget=\"$1\"\n\tshift\n\tprintf '# Begin generated rules for %s\\n' \"$target\"\n\tfor file in \"$@\"\n\tdo\n\t\tfile=\"${file%.*}\"\n\t\tprintf '%s.o: %s.c\\n' \"$file\" \"$file\"\n\tdone\n\tprintf '%s_objects=\\\\\\n' \"$target\"\n\tn=0\n\tfor file in \"$@\"\n\tdo\n\t\tfile=\"${file%.*}\"\n\t\tn=$((n+1))\n\t\tif [ $n -eq $# ]\n\t\tthen\n\t\t\tprintf '\\t%s.o\\n' \"$file\"\n\t\telse\n\t\t\tprintf '\\t%s.o \\\\\\n' \"$file\"\n\t\tfi\n\tdone\n\tprintf '# End generated rules for %s\\n' \"$target\"\n}\n\nappend_cflags() {\n\tfor flag\n\tdo\n\t\tCFLAGS=\"$(printf '%s \\\\\\n\\t%s' \"$CFLAGS\" \"$flag\")\"\n\tdone\n}\n\nappend_ldflags() {\n\tfor flag\n\tdo\n\t\tLDFLAGS=\"$(printf '%s \\\\\\n\\t%s' \"$LDFLAGS\" \"$flag\")\"\n\tdone\n}\n\nappend_libs() {\n\tfor flag\n\tdo\n\t\tLIBS=\"$(printf '%s \\\\\\n\\t%s' \"$LIBS\" \"$flag\")\"\n\tdone\n}\n\ntest_cflags() {\n\t[ ! -e \"$outdir\"/check.c ] && cat <<-EOF > \"$outdir\"/check.c\n\tint main(void) { return 0; }\n\tEOF\n\twerror=\"\"\n\tcase \"$CFLAGS\" in\n\t\t*-Werror*)\n\t\t\twerror=\"-Werror\"\n\t\t\t;;\n\tesac\n\tif $CC $werror \"$@\" -o /dev/null \"$outdir\"/check.c >/dev/null 2>&1\n\tthen\n\t\tappend_cflags \"$@\"\n\telse\n\t\treturn 1\n\tfi\n}\n\ntest_ldflags() {\n\t[ ! -e \"$outdir\"/check.c ] && cat <<-EOF > \"$outdir\"/check.c\n\tint main(void) { return 0; }\n\tEOF\n\tif $CC \"$@\" -o /dev/null \"$outdir\"/check.c >/dev/null 2>&1\n\tthen\n\t\tappend_ldflags \"$@\"\n\telse\n\t\treturn 1\n\tfi\n}\n\nmkdir -p \"$outdir\"\n\nif [ -n \"$static\" ]\nthen\n\ttest_ldflags $static\nfi\n\nfor flag in \\\n\t-g -std=c99 -pedantic -Werror -Wundef -Wlogical-op \\\n\t-Wmissing-include-dirs -Wold-style-definition -Wpointer-arith -Winit-self \\\n\t-Wfloat-equal -Wstrict-prototypes -Wredundant-decls \\\n\t-Wimplicit-fallthrough=2 -Wendif-labels -Wstrict-aliasing=2 -Woverflow \\\n\t-Wformat=2 -Wno-missing-braces -Wno-missing-field-initializers \\\n\t-Wno-unused-parameter -Wno-unused-result\ndo\n\tprintf \"Checking for $flag... \"\n\tif test_cflags \"$flag\"\n\tthen\n\t\techo yes\n\telse\n\t\techo no\n\tfi\ndone\n\nfor flag in -fPIC -Wl,--no-undefined -Wl,--as-needed\ndo\n\ttest_ldflags \"$flag\"\ndone\n\nsoname=libmrsh.so.$(echo \"$SOVERSION\" | cut -d. -f1)\nprintf \"Checking for specifying soname for shared lib... \"\nif ! \\\n\ttest_ldflags -Wl,-soname,$soname || \\\n\ttest_ldflags -Wl,-install_name,$soname\nthen\n\techo no\n\techo \"Unable to specify soname (is $(uname) supported?)\" >&2\n\texit 1\nelse\n\techo yes\nfi\n\nprintf \"Checking for exported symbol restrictions... \"\nif ! \\\n\ttest_ldflags -Wl,--version-script=\"libmrsh.gnu.sym\" || \\\n\ttest_ldflags -Wl,-exported_symbols_list,\"libmrsh.darwin.sym\"\nthen\n\techo no\n\techo \"Unable to specify exported symbols (is $(uname) supported?)\" >&2\n\texit 1\nelse\n\techo yes\nfi\n\nif [ $use_readline -eq -1 ]\nthen\n\tprintf \"Checking for readline... \"\n\tif $pkg_config readline\n\tthen\n\t\treadline=readline\n\t\tuse_readline=1\n\t\tappend_cflags -DHAVE_READLINE\n\t\t# TODO: check for rl_replace_line\n\t\tappend_cflags -DHAVE_READLINE_REPLACE_LINE\n\t\techo yes\n\telse\n\t\techo no\n\tfi\nfi\nif [ $use_readline -eq -1 ]\nthen\n\tprintf \"Checking for libedit... \"\n\tif $pkg_config libedit\n\tthen\n\t\techo yes\n\t\treadline=libedit\n\t\tuse_readline=1\n\t\tappend_cflags -DHAVE_EDITLINE\n\telse\n\t\techo no\n\tfi\nfi\n\nif [ $use_readline -eq 1 ]\nthen\n\tappend_cflags $($pkg_config $static --cflags-only-I $readline)\n\tappend_libs $($pkg_config $static --libs $readline)\nfi\n\nprintf \"Creating %s/config.mk... \" \"$outdir\"\ncat <<EOF > \"$outdir\"/config.mk\nSOVERSION=$SOVERSION\nCC=$CC\nPREFIX=${PREFIX:-/usr/local}\n_INSTDIR=\\$(DESTDIR)\\$(PREFIX)\nBINDIR?=${BINDIR:-\\$(_INSTDIR)/bin}\nLIBDIR?=${LIBDIR:-\\$(_INSTDIR)/lib}\nINCDIR?=${INCDIR:-\\$(_INSTDIR)/include}\nMANDIR?=${MANDIR:-\\$(_INSTDIR)/share/man}\nPCDIR?=${PCDIR:-\\$(_INSTDIR)/lib/pkgconfig}\nCFLAGS=${CFLAGS}\nLDFLAGS=${LDFLAGS}\nLIBS=${LIBS}\nSRCDIR=${srcdir}\n\nall: mrsh highlight libmrsh.so.\\$(SOVERSION) \\$(OUTDIR)/mrsh.pc\nEOF\nlibmrsh >>\"$outdir\"/config.mk\nmrsh >>\"$outdir\"/config.mk\nhighlight >>\"$outdir\"/config.mk\necho done\n\ntouch \"$outdir\"/cppcache\n"
  },
  {
    "path": "example/highlight.c",
    "content": "#include <assert.h>\n#include <errno.h>\n#include <mrsh/buffer.h>\n#include <mrsh/parser.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n\n#define READ_SIZE 4096\n#define FORMAT_STACK_SIZE 64\n\nenum format {\n\tFORMAT_RESET = 0,\n\tFORMAT_GREEN = 32,\n\tFORMAT_YELLOW = 33,\n\tFORMAT_BLUE = 34,\n\tFORMAT_CYAN = 36,\n\tFORMAT_DEFAULT = 39,\n\tFORMAT_LIGHT_BLUE = 94,\n};\n\nstruct highlight_state {\n\tconst char *buf;\n\tsize_t offset;\n\n\tenum format fmt_stack[FORMAT_STACK_SIZE];\n\tsize_t fmt_stack_len;\n};\n\nstatic void highlight(struct highlight_state *state, struct mrsh_position *pos,\n\t\tenum format fmt) {\n\tassert(pos->offset >= state->offset);\n\n\tfwrite(&state->buf[state->offset], sizeof(char),\n\t\tpos->offset - state->offset, stdout);\n\tstate->offset = pos->offset;\n\n\tif (fmt == FORMAT_RESET) {\n\t\tassert(state->fmt_stack_len > 0);\n\t\t--state->fmt_stack_len;\n\t\tif (state->fmt_stack_len > 0) {\n\t\t\tfmt = state->fmt_stack[state->fmt_stack_len - 1];\n\t\t}\n\t} else {\n\t\tif (state->fmt_stack_len >= FORMAT_STACK_SIZE) {\n\t\t\tfprintf(stderr, \"format stack overflow\\n\");\n\t\t\texit(1);\n\t\t}\n\t\tstate->fmt_stack[state->fmt_stack_len] = fmt;\n\t\t++state->fmt_stack_len;\n\t}\n\n\tfprintf(stdout, \"%c[%dm\", 0x1B, fmt);\n}\n\nstatic void highlight_str(struct highlight_state *state,\n\t\tstruct mrsh_range *range, enum format fmt) {\n\thighlight(state, &range->begin, fmt);\n\thighlight(state, &range->end, FORMAT_RESET);\n}\n\nstatic void highlight_char(struct highlight_state *state,\n\t\tstruct mrsh_position *pos, enum format fmt) {\n\tstruct mrsh_position next = { .offset = pos->offset + 1 };\n\thighlight(state, pos, fmt);\n\thighlight(state, &next, FORMAT_RESET);\n}\n\nstatic void highlight_word(struct highlight_state *state,\n\t\tstruct mrsh_word *word, bool cmd_name, bool quoted) {\n\tswitch (word->type) {\n\tcase MRSH_WORD_STRING:;\n\t\tstruct mrsh_word_string *ws = mrsh_word_get_string(word);\n\t\tif (!quoted) {\n\t\t\tenum format fmt = FORMAT_CYAN;\n\t\t\tif (ws->single_quoted) {\n\t\t\t\tfmt = FORMAT_YELLOW;\n\t\t\t} else if (cmd_name) {\n\t\t\t\tfmt = FORMAT_BLUE;\n\t\t\t}\n\t\t\thighlight_str(state, &ws->range, fmt);\n\t\t}\n\t\tbreak;\n\tcase MRSH_WORD_PARAMETER:;\n\t\tstruct mrsh_word_parameter *wp = mrsh_word_get_parameter(word);\n\t\thighlight(state, &wp->dollar_pos, FORMAT_CYAN);\n\t\thighlight_char(state, &wp->dollar_pos, FORMAT_GREEN);\n\t\tif (mrsh_position_valid(&wp->lbrace_pos)) {\n\t\t\thighlight_char(state, &wp->lbrace_pos, FORMAT_GREEN);\n\t\t}\n\t\tif (mrsh_range_valid(&wp->op_range)) {\n\t\t\thighlight_str(state, &wp->op_range, FORMAT_GREEN);\n\t\t}\n\t\tif (wp->arg != NULL) {\n\t\t\thighlight_word(state, wp->arg, false, false);\n\t\t}\n\t\tstruct mrsh_position end = {0};\n\t\tif (mrsh_position_valid(&wp->rbrace_pos)) {\n\t\t\thighlight_char(state, &wp->rbrace_pos, FORMAT_GREEN);\n\t\t\tend.offset = wp->rbrace_pos.offset + 1;\n\t\t} else {\n\t\t\tend = wp->name_range.end;\n\t\t}\n\t\thighlight(state, &end, FORMAT_RESET);\n\t\tbreak;\n\tcase MRSH_WORD_COMMAND:;\n\t\tstruct mrsh_word_command *wc = mrsh_word_get_command(word);\n\t\tif (wc->back_quoted) {\n\t\t\thighlight_char(state, &wc->range.begin, FORMAT_GREEN);\n\t\t}\n\t\t// TODO: highlight inside\n\t\tif (wc->back_quoted) {\n\t\t\tstruct mrsh_position rquote = {\n\t\t\t\t.offset = wc->range.end.offset - 1,\n\t\t\t};\n\t\t\thighlight_char(state, &rquote, FORMAT_GREEN);\n\t\t}\n\t\tbreak;\n\tcase MRSH_WORD_ARITHMETIC:\n\t\tabort(); // TODO\n\tcase MRSH_WORD_LIST:;\n\t\tstruct mrsh_word_list *wl = mrsh_word_get_list(word);\n\t\tif (wl->children.len == 0) {\n\t\t\tbreak;\n\t\t}\n\n\t\tif (wl->double_quoted) {\n\t\t\thighlight(state, &wl->lquote_pos, FORMAT_YELLOW);\n\t\t}\n\n\t\tfor (size_t i = 0; i < wl->children.len; ++i) {\n\t\t\tstruct mrsh_word *child = wl->children.data[i];\n\t\t\thighlight_word(state, child, cmd_name, wl->double_quoted);\n\t\t}\n\n\t\tif (wl->double_quoted) {\n\t\t\tstruct mrsh_position end = { .offset = wl->rquote_pos.offset + 1 };\n\t\t\thighlight(state, &end, FORMAT_RESET);\n\t\t}\n\t\tbreak;\n\t}\n}\n\nstatic void highlight_simple_command(struct highlight_state *state,\n\t\tstruct mrsh_simple_command *cmd) {\n\tif (cmd->name != NULL) {\n\t\thighlight_word(state, cmd->name, true, false);\n\t}\n\n\tfor (size_t i = 0; i < cmd->arguments.len; ++i) {\n\t\tstruct mrsh_word *arg = cmd->arguments.data[i];\n\t\thighlight_word(state, arg, false, false);\n\t}\n\n\t// TODO: cmd->io_redirects, cmd->assignments\n}\n\nstatic void highlight_command_list_array(struct highlight_state *state,\n\tstruct mrsh_array *array);\n\nstatic void highlight_command(struct highlight_state *state,\n\t\tstruct mrsh_command *cmd) {\n\tswitch (cmd->type) {\n\tcase MRSH_SIMPLE_COMMAND:;\n\t\tstruct mrsh_simple_command *sc = mrsh_command_get_simple_command(cmd);\n\t\thighlight_simple_command(state, sc);\n\t\tbreak;\n\tcase MRSH_BRACE_GROUP:;\n\t\tstruct mrsh_brace_group *bg = mrsh_command_get_brace_group(cmd);\n\t\thighlight_char(state, &bg->lbrace_pos, FORMAT_GREEN);\n\t\thighlight_command_list_array(state, &bg->body);\n\t\thighlight_char(state, &bg->rbrace_pos, FORMAT_GREEN);\n\t\tbreak;\n\tcase MRSH_SUBSHELL:;\n\t\tstruct mrsh_subshell *s = mrsh_command_get_subshell(cmd);\n\t\thighlight_char(state, &s->lparen_pos, FORMAT_GREEN);\n\t\thighlight_command_list_array(state, &s->body);\n\t\thighlight_char(state, &s->rparen_pos, FORMAT_GREEN);\n\t\tbreak;\n\tcase MRSH_IF_CLAUSE:;\n\t\tstruct mrsh_if_clause *ic = mrsh_command_get_if_clause(cmd);\n\t\thighlight_str(state, &ic->if_range, FORMAT_BLUE);\n\t\thighlight_command_list_array(state, &ic->condition);\n\t\thighlight_str(state, &ic->then_range, FORMAT_BLUE);\n\t\thighlight_command_list_array(state, &ic->body);\n\t\tif (ic->else_part != NULL) {\n\t\t\tif (mrsh_range_valid(&ic->else_range)) {\n\t\t\t\thighlight_str(state, &ic->else_range, FORMAT_BLUE);\n\t\t\t}\n\t\t\thighlight_command(state, ic->else_part);\n\t\t}\n\t\tif (mrsh_range_valid(&ic->fi_range)) {\n\t\t\thighlight_str(state, &ic->fi_range, FORMAT_BLUE);\n\t\t}\n\t\tbreak;\n\tcase MRSH_FOR_CLAUSE:;\n\t\tstruct mrsh_for_clause *fc = mrsh_command_get_for_clause(cmd);\n\t\thighlight_str(state, &fc->for_range, FORMAT_BLUE);\n\t\thighlight_str(state, &fc->name_range, FORMAT_CYAN);\n\t\tif (mrsh_range_valid(&fc->in_range)) {\n\t\t\thighlight_str(state, &fc->in_range, FORMAT_BLUE);\n\t\t}\n\t\tfor (size_t i = 0; i < fc->word_list.len; ++i) {\n\t\t\tstruct mrsh_word *word = fc->word_list.data[i];\n\t\t\thighlight_word(state, word, false, false);\n\t\t}\n\t\thighlight_str(state, &fc->do_range, FORMAT_BLUE);\n\t\thighlight_command_list_array(state, &fc->body);\n\t\thighlight_str(state, &fc->done_range, FORMAT_BLUE);\n\t\tbreak;\n\tcase MRSH_LOOP_CLAUSE:;\n\t\tstruct mrsh_loop_clause *lc = mrsh_command_get_loop_clause(cmd);\n\t\thighlight_str(state, &lc->while_until_range, FORMAT_BLUE);\n\t\thighlight_command_list_array(state, &lc->condition);\n\t\thighlight_str(state, &lc->do_range, FORMAT_BLUE);\n\t\thighlight_command_list_array(state, &lc->body);\n\t\thighlight_str(state, &lc->done_range, FORMAT_BLUE);\n\t\tbreak;\n\tcase MRSH_CASE_CLAUSE:;\n\t\tstruct mrsh_case_clause *cc = mrsh_command_get_case_clause(cmd);\n\t\thighlight_str(state, &cc->case_range, FORMAT_BLUE);\n\t\thighlight_word(state, cc->word, false, false);\n\t\thighlight_str(state, &cc->in_range, FORMAT_BLUE);\n\t\tfor (size_t i = 0; i < cc->items.len; ++i) {\n\t\t\tstruct mrsh_case_item *item = cc->items.data[i];\n\t\t\tif (mrsh_position_valid(&item->lparen_pos)) {\n\t\t\t\thighlight_char(state, &item->lparen_pos, FORMAT_GREEN);\n\t\t\t}\n\t\t\tfor (size_t j = 0; j < item->patterns.len; ++j) {\n\t\t\t\tstruct mrsh_word *pattern = item->patterns.data[j];\n\t\t\t\thighlight_word(state, pattern, false, false);\n\t\t\t}\n\t\t\thighlight_char(state, &item->rparen_pos, FORMAT_GREEN);\n\t\t\thighlight_command_list_array(state, &item->body);\n\t\t\tif (mrsh_range_valid(&item->dsemi_range)) {\n\t\t\t\thighlight_str(state, &item->dsemi_range, FORMAT_GREEN);\n\t\t\t}\n\t\t}\n\t\thighlight_str(state, &cc->esac_range, FORMAT_BLUE);\n\t\tbreak;\n\tcase MRSH_FUNCTION_DEFINITION:;\n\t\tstruct mrsh_function_definition *fd =\n\t\t\tmrsh_command_get_function_definition(cmd);\n\t\thighlight_str(state, &fd->name_range, FORMAT_BLUE);\n\t\thighlight_char(state, &fd->lparen_pos, FORMAT_GREEN);\n\t\thighlight_char(state, &fd->rparen_pos, FORMAT_GREEN);\n\t\thighlight_command(state, fd->body);\n\t\tbreak;\n\t}\n}\n\nstatic void highlight_and_or_list(struct highlight_state *state,\n\t\tstruct mrsh_and_or_list *and_or_list) {\n\tswitch (and_or_list->type) {\n\tcase MRSH_AND_OR_LIST_PIPELINE:;\n\t\tstruct mrsh_pipeline *pl = mrsh_and_or_list_get_pipeline(and_or_list);\n\t\tif (mrsh_position_valid(&pl->bang_pos)) {\n\t\t\thighlight_char(state, &pl->bang_pos, FORMAT_GREEN);\n\t\t}\n\t\tfor (size_t i = 0; i < pl->commands.len; ++i) {\n\t\t\tstruct mrsh_command *cmd = pl->commands.data[i];\n\t\t\thighlight_command(state, cmd);\n\t\t}\n\t\tbreak;\n\tcase MRSH_AND_OR_LIST_BINOP:;\n\t\tstruct mrsh_binop *binop = mrsh_and_or_list_get_binop(and_or_list);\n\t\thighlight_and_or_list(state, binop->left);\n\t\thighlight_str(state, &binop->op_range, FORMAT_GREEN);\n\t\thighlight_and_or_list(state, binop->right);\n\t\tbreak;\n\t}\n}\n\nstatic void highlight_command_list(struct highlight_state *state,\n\t\tstruct mrsh_command_list *list) {\n\thighlight_and_or_list(state, list->and_or_list);\n\tif (mrsh_position_valid(&list->separator_pos)) {\n\t\thighlight_char(state, &list->separator_pos, FORMAT_GREEN);\n\t}\n}\n\nstatic void highlight_command_list_array(struct highlight_state *state,\n\t\tstruct mrsh_array *array) {\n\tfor (size_t i = 0; i < array->len; ++i) {\n\t\tstruct mrsh_command_list *l = array->data[i];\n\t\thighlight_command_list(state, l);\n\t}\n}\n\nstatic void highlight_program(struct highlight_state *state,\n\t\tstruct mrsh_program *prog) {\n\thighlight_command_list_array(state, &prog->body);\n}\n\nint main(int argc, char *argv[]) {\n\tstruct mrsh_buffer buf = {0};\n\twhile (true) {\n\t\tchar *dst = mrsh_buffer_reserve(&buf, READ_SIZE);\n\t\tssize_t n_read = read(STDIN_FILENO, dst, READ_SIZE);\n\t\tif (n_read < 0) {\n\t\t\tperror(\"read\");\n\t\t\treturn 1;\n\t\t} else if (n_read == 0) {\n\t\t\tbreak;\n\t\t}\n\t\tbuf.len += n_read;\n\t}\n\n\tstruct mrsh_parser *parser = mrsh_parser_with_data(buf.data, buf.len);\n\tstruct mrsh_program *prog = mrsh_parse_program(parser);\n\tconst char *err_msg = mrsh_parser_error(parser, NULL);\n\tif (err_msg != NULL) {\n\t\tfprintf(stderr, \"failed to parse script: %s\\n\", err_msg);\n\t\treturn 1;\n\t}\n\tif (prog == NULL) {\n\t\treturn 0;\n\t}\n\tmrsh_parser_destroy(parser);\n\n\tstruct highlight_state state = {\n\t\t.buf = buf.data,\n\t};\n\n\tstruct mrsh_position begin = { .offset = 0 };\n\thighlight(&state, &begin, FORMAT_DEFAULT);\n\n\thighlight_program(&state, prog);\n\n\tstruct mrsh_position end = { .offset = buf.len };\n\thighlight(&state, &end, FORMAT_RESET);\n\tassert(state.fmt_stack_len == 0);\n\n\tmrsh_buffer_finish(&buf);\n\treturn 0;\n}\n"
  },
  {
    "path": "example/meson.build",
    "content": "executable(\n\t'highlight',\n\tfiles('highlight.c'),\n\tdependencies: [mrsh],\n\tbuild_by_default: get_option('examples'),\n)\n"
  },
  {
    "path": "frontend/basic.c",
    "content": "/* Basic, strictly POSIX interactive line interface */\n#define _POSIX_C_SOURCE 200809L\n#include <errno.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <mrsh/shell.h>\n#include <mrsh/parser.h>\n#include \"frontend.h\"\n\nvoid interactive_init(struct mrsh_state *state) {\n\t// no-op\n}\n\nsize_t interactive_next(struct mrsh_state *state,\n\t\tchar **line, const char *prompt) {\n\tfprintf(stderr, \"%s\", prompt);\n\tsize_t len = 0;\n\tchar *_line = NULL;\n\terrno = 0;\n\tssize_t n_read = getline(&_line, &len, stdin);\n\tif (n_read < 0) {\n\t\tfree(_line);\n\t\tif (errno != 0) {\n\t\t\tperror(\"getline\");\n\t\t}\n\t\treturn 0;\n\t}\n\t*line = _line;\n\treturn n_read;\n}\n"
  },
  {
    "path": "frontend/readline.c",
    "content": "// readline/editline interactive line interface\n#define _POSIX_C_SOURCE 200809L\n#include <mrsh/parser.h>\n#include <mrsh/shell.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <signal.h>\n#include <unistd.h>\n#if defined(HAVE_READLINE)\n#include <readline/history.h>\n#include <readline/readline.h>\n#elif defined(HAVE_EDITLINE)\n#include <editline/readline.h>\n#include <histedit.h>\n#endif\n#include \"frontend.h\"\n\n#if defined(HAVE_READLINE)\n#if !defined(HAVE_READLINE_REPLACE_LINE)\nstatic void rl_replace_line(const char *text,\n                            int clear_undo) {\n    return;\n}\n#endif\n\nstatic void sigint_handler(int n) {\n\t/* Signal safety is done here on a best-effort basis. rl_redisplay is not\n\t * signal safe, but under these circumstances it's very likely that the\n\t * interrupted function will not be affected. */\n\tchar newline = '\\n';\n\t(void)write(STDOUT_FILENO, &newline, 1);\n\trl_on_new_line();\n\trl_replace_line(\"\", 0);\n\trl_redisplay();\n}\n#endif\n\nstatic char *get_history_path(void) {\n\tconst char *home = getenv(\"HOME\");\n\tint len = snprintf(NULL, 0, \"%s/.mrsh_history\", home);\n\tchar *path = malloc(len + 1);\n\tif (path == NULL) {\n\t\treturn NULL;\n\t}\n\tsnprintf(path, len + 1, \"%s/.mrsh_history\", home);\n\treturn path;\n}\n\nvoid interactive_init(struct mrsh_state *state) {\n\trl_initialize();\n\tchar *history_path = get_history_path();\n\tread_history(history_path);\n\tfree(history_path);\n}\n\nsize_t interactive_next(struct mrsh_state *state,\n\t\tchar **line, const char *prompt) {\n\t/* TODO: make SIGINT handling work with editline */\n#if defined(HAVE_READLINE)\n\tstruct sigaction sa = { .sa_handler = sigint_handler }, old;\n\tsigaction(SIGINT, &sa, &old);\n#endif\n\tchar *rline = readline(prompt);\n#if defined(HAVE_READLINE)\n\tsigaction(SIGINT, &old, NULL);\n#endif\n\n\tif (!rline) {\n\t\treturn 0;\n\t}\n\tsize_t len = strlen(rline);\n\tif (!(state->options & MRSH_OPT_NOLOG)) {\n\t\tadd_history(rline);\n\t\tchar *history_path = get_history_path();\n\t\twrite_history(history_path);\n\t\tfree(history_path);\n\t}\n\t*line = malloc(len + 2);\n\tstrcpy(*line, rline);\n\tstrcat(*line, \"\\n\");\n\tfree(rline);\n\treturn len + 1;\n}\n"
  },
  {
    "path": "getopt.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <assert.h>\n#include <stdio.h>\n#include \"mrsh_getopt.h\"\n\nchar *_mrsh_optarg = NULL;\nint _mrsh_optind = 1;\nint _mrsh_opterr = 1;\nint _mrsh_optopt = 0;\nint _mrsh_optpos = 1;\n\nint _mrsh_getopt(int argc, char *const argv[], const char *optstring) {\n\tassert(argv[argc] == NULL);\n\t_mrsh_optarg = NULL;\n\n\tif (_mrsh_optind == 0) {\n\t\t_mrsh_optind = 1;\n\t\t_mrsh_optpos = 1;\n\t}\n\n\tif (_mrsh_optind >= argc) {\n\t\treturn -1;\n\t}\n\n\tif (argv[_mrsh_optind][0] != '-') {\n\t\treturn -1;\n\t}\n\n\tif (argv[_mrsh_optind][1] == '\\0') {\n\t\treturn -1;\n\t}\n\n\tif (argv[_mrsh_optind][1] == '-') {\n\t\t_mrsh_optind++;\n\t\treturn -1;\n\t}\n\n\tconst char *c = optstring;\n\tif (*c == ':') {\n\t\tc++;\n\t}\n\n\t_mrsh_optopt = 0;\n\tint opt = argv[_mrsh_optind][_mrsh_optpos];\n\tfor (; *c != '\\0'; c++) {\n\t\tif (*c != opt) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (c[1] != ':') {\n\t\t\tif (argv[_mrsh_optind][_mrsh_optpos + 1] == '\\0') {\n\t\t\t\t_mrsh_optind++;\n\t\t\t\t_mrsh_optpos = 1;\n\t\t\t} else {\n\t\t\t\t_mrsh_optpos++;\n\t\t\t}\n\t\t\treturn opt;\n\t\t}\n\n\t\tif (argv[_mrsh_optind][_mrsh_optpos + 1] != '\\0') {\n\t\t\t_mrsh_optarg = &argv[_mrsh_optind][_mrsh_optpos + 1];\n\t\t} else {\n\t\t\tif (_mrsh_optind + 2 > argc) {\n\t\t\t\t_mrsh_optopt = opt;\n\t\t\t\tif (_mrsh_opterr != 0 && optstring[0] != ':') {\n\t\t\t\t\tfprintf(stderr, \"%s: Option '%c' requires an argument.\\n\",\n\t\t\t\t\t\targv[0], _mrsh_optopt);\n\t\t\t\t}\n\n\t\t\t\treturn optstring[0] == ':' ? ':' : '?';\n\t\t\t}\n\n\t\t\t_mrsh_optarg = argv[++_mrsh_optind];\n\t\t}\n\n\t\t_mrsh_optind++;\n\t\treturn opt;\n\t}\n\n\tif (_mrsh_opterr != 0 && optstring[0] != ':') {\n\t\tfprintf(stderr, \"%s: Option '%c' not found.\\n\", argv[0], opt);\n\t}\n\n\treturn '?';\n}\n"
  },
  {
    "path": "hashtable.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <mrsh/hashtable.h>\n#include <stdlib.h>\n#include <string.h>\n\nstatic unsigned int djb2(const char *str) {\n\tunsigned int hash = 5381;\n\tchar c;\n\twhile ((c = *str++)) {\n\t\thash = ((hash << 5) + hash) + c;\n\t}\n\treturn hash;\n}\n\nvoid *mrsh_hashtable_get(struct mrsh_hashtable *table, const char *key) {\n\tunsigned int hash = djb2(key);\n\tunsigned int bucket = hash % MRSH_HASHTABLE_BUCKETS;\n\tstruct mrsh_hashtable_entry *entry = table->buckets[bucket];\n\n\twhile (entry != NULL) {\n\t\tif (entry->hash == hash && strcmp(entry->key, key) == 0) {\n\t\t\treturn entry->value;\n\t\t}\n\t\tentry = entry->next;\n\t}\n\n\treturn NULL;\n}\n\nvoid *mrsh_hashtable_set(struct mrsh_hashtable *table, const char *key,\n\t\tvoid *value) {\n\tunsigned int hash = djb2(key);\n\tunsigned int bucket = hash % MRSH_HASHTABLE_BUCKETS;\n\tstruct mrsh_hashtable_entry *entry = table->buckets[bucket];\n\n\tstruct mrsh_hashtable_entry *previous = NULL;\n\twhile (entry != NULL) {\n\t\tif (entry->hash == hash && strcmp(entry->key, key) == 0) {\n\t\t\tbreak;\n\t\t}\n\t\tprevious = entry;\n\t\tentry = entry->next;\n\t}\n\n\tif (entry == NULL) {\n\t\tentry = calloc(1, sizeof(struct mrsh_hashtable_entry));\n\t\tentry->hash = hash;\n\t\tentry->key = strdup(key);\n\t\tif (previous != NULL) {\n\t\t\tprevious->next = entry;\n\t\t} else {\n\t\t\ttable->buckets[bucket] = entry;\n\t\t}\n\t}\n\n\tvoid *old_value = entry->value;\n\tentry->value = value;\n\treturn old_value;\n}\n\nstatic void hashtable_entry_destroy(struct mrsh_hashtable_entry *entry) {\n\tif (entry == NULL) {\n\t\treturn;\n\t}\n\tfree(entry->key);\n\tfree(entry);\n}\n\nvoid *mrsh_hashtable_del(struct mrsh_hashtable *table, const char *key) {\n\tunsigned int hash = djb2(key);\n\tunsigned int bucket = hash % MRSH_HASHTABLE_BUCKETS;\n\tstruct mrsh_hashtable_entry *entry = table->buckets[bucket];\n\n\tstruct mrsh_hashtable_entry *previous = NULL;\n\twhile (entry != NULL) {\n\t\tif (entry->hash == hash && strcmp(entry->key, key) == 0) {\n\t\t\tbreak;\n\t\t}\n\t\tprevious = entry;\n\t\tentry = entry->next;\n\t}\n\n\tif (entry == NULL) {\n\t\treturn NULL;\n\t}\n\n\tif (previous != NULL) {\n\t\tprevious->next = entry->next;\n\t} else {\n\t\ttable->buckets[bucket] = entry->next;\n\t}\n\tvoid *old_value = entry->value;\n\thashtable_entry_destroy(entry);\n\treturn old_value;\n}\n\nvoid mrsh_hashtable_finish(struct mrsh_hashtable *table) {\n\tfor (size_t i = 0; i < MRSH_HASHTABLE_BUCKETS; ++i) {\n\t\tstruct mrsh_hashtable_entry *entry = table->buckets[i];\n\t\twhile (entry != NULL) {\n\t\t\tstruct mrsh_hashtable_entry *next = entry->next;\n\t\t\thashtable_entry_destroy(entry);\n\t\t\tentry = next;\n\t\t}\n\t}\n}\n\nvoid mrsh_hashtable_for_each(struct mrsh_hashtable *table,\n\t\tmrsh_hashtable_iterator_func iterator, void *user_data) {\n\tfor (size_t i = 0; i < MRSH_HASHTABLE_BUCKETS; ++i) {\n\t\tstruct mrsh_hashtable_entry *entry = table->buckets[i];\n\t\twhile (entry != NULL) {\n\t\t\tstruct mrsh_hashtable_entry *next = entry->next;\n\t\t\titerator(entry->key, entry->value, user_data);\n\t\t\tentry = next;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "include/ast.h",
    "content": "#ifndef AST_H\n#define AST_H\n\n#include <mrsh/ast.h>\n\nvoid command_list_array_finish(struct mrsh_array *cmds);\nvoid case_item_destroy(struct mrsh_case_item *item);\n\n#endif\n"
  },
  {
    "path": "include/builtin.h",
    "content": "#ifndef BUILTIN_H\n#define BUILTIN_H\n\n#include <mrsh/builtin.h>\n\nstruct mrsh_state;\n\ntypedef int (*mrsh_builtin_func)(struct mrsh_state *state,\n\tint argc, char *argv[]);\n\nvoid print_escaped(const char *value);\n\nint builtin_alias(struct mrsh_state *state, int argc, char *argv[]);\nint builtin_bg(struct mrsh_state *state, int argc, char *argv[]);\nint builtin_break(struct mrsh_state *state, int argc, char *argv[]);\nint builtin_cd(struct mrsh_state *state, int argc, char *argv[]);\nint builtin_command(struct mrsh_state *state, int argc, char *argv[]);\nint builtin_colon(struct mrsh_state *state, int argc, char *argv[]);\nint builtin_dot(struct mrsh_state *state, int argc, char *argv[]);\nint builtin_eval(struct mrsh_state *state, int argc, char *argv[]);\nint builtin_exec(struct mrsh_state *state, int argc, char *argv[]);\nint builtin_exit(struct mrsh_state *state, int argc, char *argv[]);\nint builtin_export(struct mrsh_state *state, int argc, char *argv[]);\nint builtin_false(struct mrsh_state *state, int argc, char *argv[]);\nint builtin_fg(struct mrsh_state *state, int argc, char *argv[]);\nint builtin_getopts(struct mrsh_state *state, int argc, char *argv[]);\nint builtin_hash(struct mrsh_state *state, int argc, char *argv[]);\nint builtin_jobs(struct mrsh_state *state, int argc, char *argv[]);\nint builtin_pwd(struct mrsh_state *state, int argc, char *argv[]);\nint builtin_read(struct mrsh_state *state, int argc, char *argv[]);\nint builtin_return(struct mrsh_state *state, int argc, char *argv[]);\nint builtin_set(struct mrsh_state *state, int argc, char *argv[]);\nint builtin_shift(struct mrsh_state *state, int argc, char *argv[]);\nint builtin_times(struct mrsh_state *state, int argc, char *argv[]);\nint builtin_trap(struct mrsh_state *state, int argc, char *argv[]);\nint builtin_true(struct mrsh_state *state, int argc, char *argv[]);\nint builtin_type(struct mrsh_state *state, int argc, char *argv[]);\nint builtin_ulimit(struct mrsh_state *state, int argc, char *argv[]);\nint builtin_umask(struct mrsh_state *state, int argc, char *argv[]);\nint builtin_unalias(struct mrsh_state *state, int argc, char *argv[]);\nint builtin_unset(struct mrsh_state *state, int argc, char *argv[]);\nint builtin_wait(struct mrsh_state *state, int argc, char *argv[]);\n\nint builtin_unspecified(struct mrsh_state *state, int argc, char *argv[]);\n\nconst char *state_get_options(struct mrsh_state *state);\n\nstruct mrsh_collect_var {\n\tconst char *key, *value;\n};\n\n/** Collects and alpha-sorts variables matching attribs. Count will be set to\n * the number of matching variables. You are responsible for freeing the return\n * value when you're done.*/\nstruct mrsh_collect_var *collect_vars(struct mrsh_state *state,\n\tuint32_t attribs, size_t *count);\n\n#endif\n"
  },
  {
    "path": "include/frontend.h",
    "content": "#ifndef FRONTEND_H\n#define FRONTEND_H\n\n#include <stddef.h>\n#include <stdio.h>\n\nvoid interactive_init(struct mrsh_state *state);\nsize_t interactive_next(struct mrsh_state *state,\n\t\tchar **restrict line, const char *prompt);\n\n#endif\n"
  },
  {
    "path": "include/mrsh/arithm.h",
    "content": "#ifndef MRSH_AST_ARITHM_H\n#define MRSH_AST_ARITHM_H\n\nenum mrsh_arithm_expr_type {\n\tMRSH_ARITHM_LITERAL,\n\tMRSH_ARITHM_VARIABLE,\n\tMRSH_ARITHM_UNOP,\n\tMRSH_ARITHM_BINOP,\n\tMRSH_ARITHM_COND,\n\tMRSH_ARITHM_ASSIGN,\n};\n\n/**\n * An aritmetic expression. One of:\n * - A literal\n * - A variable\n * - An unary operation\n * - A binary operation\n * - A condition\n * - An assignment\n */\nstruct mrsh_arithm_expr {\n\tenum mrsh_arithm_expr_type type;\n};\n\nstruct mrsh_arithm_literal {\n\tstruct mrsh_arithm_expr expr;\n\tlong value;\n};\n\nstruct mrsh_arithm_variable {\n\tstruct mrsh_arithm_expr expr;\n\tchar *name;\n};\n\nenum mrsh_arithm_unop_type {\n\tMRSH_ARITHM_UNOP_PLUS,\n\tMRSH_ARITHM_UNOP_MINUS,\n\tMRSH_ARITHM_UNOP_TILDE,\n\tMRSH_ARITHM_UNOP_BANG,\n};\n\nstruct mrsh_arithm_unop {\n\tstruct mrsh_arithm_expr expr;\n\tenum mrsh_arithm_unop_type type;\n\tstruct mrsh_arithm_expr *body;\n};\n\nenum mrsh_arithm_binop_type {\n\tMRSH_ARITHM_BINOP_ASTERISK,\n\tMRSH_ARITHM_BINOP_SLASH,\n\tMRSH_ARITHM_BINOP_PERCENT,\n\tMRSH_ARITHM_BINOP_PLUS,\n\tMRSH_ARITHM_BINOP_MINUS,\n\tMRSH_ARITHM_BINOP_DLESS,\n\tMRSH_ARITHM_BINOP_DGREAT,\n\tMRSH_ARITHM_BINOP_LESS,\n\tMRSH_ARITHM_BINOP_LESSEQ,\n\tMRSH_ARITHM_BINOP_GREAT,\n\tMRSH_ARITHM_BINOP_GREATEQ,\n\tMRSH_ARITHM_BINOP_DEQ,\n\tMRSH_ARITHM_BINOP_BANGEQ,\n\tMRSH_ARITHM_BINOP_AND,\n\tMRSH_ARITHM_BINOP_CIRC,\n\tMRSH_ARITHM_BINOP_OR,\n\tMRSH_ARITHM_BINOP_DAND,\n\tMRSH_ARITHM_BINOP_DOR,\n};\n\nstruct mrsh_arithm_binop {\n\tstruct mrsh_arithm_expr expr;\n\tenum mrsh_arithm_binop_type type;\n\tstruct mrsh_arithm_expr *left, *right;\n};\n\nstruct mrsh_arithm_cond {\n\tstruct mrsh_arithm_expr expr;\n\tstruct mrsh_arithm_expr *condition, *body, *else_part;\n};\n\nenum mrsh_arithm_assign_op {\n\tMRSH_ARITHM_ASSIGN_NONE,\n\tMRSH_ARITHM_ASSIGN_ASTERISK,\n\tMRSH_ARITHM_ASSIGN_SLASH,\n\tMRSH_ARITHM_ASSIGN_PERCENT,\n\tMRSH_ARITHM_ASSIGN_PLUS,\n\tMRSH_ARITHM_ASSIGN_MINUS,\n\tMRSH_ARITHM_ASSIGN_DLESS,\n\tMRSH_ARITHM_ASSIGN_DGREAT,\n\tMRSH_ARITHM_ASSIGN_AND,\n\tMRSH_ARITHM_ASSIGN_CIRC,\n\tMRSH_ARITHM_ASSIGN_OR,\n};\n\nstruct mrsh_arithm_assign {\n\tstruct mrsh_arithm_expr expr;\n\tenum mrsh_arithm_assign_op op;\n\tchar *name;\n\tstruct mrsh_arithm_expr *value;\n};\n\nvoid mrsh_arithm_expr_destroy(struct mrsh_arithm_expr *expr);\nstruct mrsh_arithm_literal *mrsh_arithm_literal_create(long value);\nstruct mrsh_arithm_variable *mrsh_arithm_variable_create(char *name);\nstruct mrsh_arithm_unop *mrsh_arithm_unop_create(\n\tenum mrsh_arithm_unop_type type, struct mrsh_arithm_expr *body);\nstruct mrsh_arithm_binop *mrsh_arithm_binop_create(\n\tenum mrsh_arithm_binop_type type, struct mrsh_arithm_expr *left,\n\tstruct mrsh_arithm_expr *right);\nstruct mrsh_arithm_cond *mrsh_arithm_cond_create(\n\tstruct mrsh_arithm_expr *condition, struct mrsh_arithm_expr *body,\n\tstruct mrsh_arithm_expr *else_part);\nstruct mrsh_arithm_assign *mrsh_arithm_assign_create(\n\tenum mrsh_arithm_assign_op op, char *name,\n\tstruct mrsh_arithm_expr *value);\nstruct mrsh_arithm_literal *mrsh_arithm_expr_get_literal(\n\tconst struct mrsh_arithm_expr *expr);\nstruct mrsh_arithm_variable *mrsh_arithm_expr_get_variable(\n\tconst struct mrsh_arithm_expr *expr);\nstruct mrsh_arithm_unop *mrsh_arithm_expr_get_unop(\n\tconst struct mrsh_arithm_expr *expr);\nstruct mrsh_arithm_binop *mrsh_arithm_expr_get_binop(\n\tconst struct mrsh_arithm_expr *expr);\nstruct mrsh_arithm_cond *mrsh_arithm_expr_get_cond(\n\tconst struct mrsh_arithm_expr *expr);\nstruct mrsh_arithm_assign *mrsh_arithm_expr_get_assign(\n\tconst struct mrsh_arithm_expr *expr);\n\n#endif\n"
  },
  {
    "path": "include/mrsh/array.h",
    "content": "#ifndef MRSH_ARRAY_H\n#define MRSH_ARRAY_H\n\n#include <stdbool.h>\n#include <stddef.h>\n#include <sys/types.h>\n\nstruct mrsh_array {\n\tvoid **data;\n\tsize_t len, cap;\n};\n\nbool mrsh_array_reserve(struct mrsh_array *array, size_t size);\nssize_t mrsh_array_add(struct mrsh_array *array, void *value);\nvoid mrsh_array_finish(struct mrsh_array *array);\n\n#endif\n"
  },
  {
    "path": "include/mrsh/ast.h",
    "content": "#ifndef MRSH_AST_H\n#define MRSH_AST_H\n\n#include <mrsh/array.h>\n#include <stdbool.h>\n\n/**\n * Position describes an arbitrary source position including line and column\n * location.\n */\nstruct mrsh_position {\n\tsize_t offset; // starting at 0\n\tint line; // starting at 1\n\tint column; // starting at 1\n};\n\n/**\n * Range describes a continuous source region. It has a beginning position and\n * a non-included ending position.\n */\nstruct mrsh_range {\n\tstruct mrsh_position begin, end;\n};\n\nenum mrsh_node_type {\n\tMRSH_NODE_PROGRAM,\n\tMRSH_NODE_COMMAND_LIST,\n\tMRSH_NODE_AND_OR_LIST,\n\tMRSH_NODE_COMMAND,\n\tMRSH_NODE_WORD,\n};\n\nstruct mrsh_node {\n\tenum mrsh_node_type type;\n};\n\nenum mrsh_word_type {\n\tMRSH_WORD_STRING,\n\tMRSH_WORD_PARAMETER,\n\tMRSH_WORD_COMMAND,\n\tMRSH_WORD_ARITHMETIC,\n\tMRSH_WORD_LIST,\n};\n\n/**\n * A word can be:\n * - An unquoted or a single-quoted string\n * - A candidate for parameter expansion\n * - A candidate for command substitution\n * - A candidate for arithmetic expansion\n * - An unquoted or a double-quoted list of words\n */\nstruct mrsh_word {\n\tstruct mrsh_node node;\n\tenum mrsh_word_type type;\n};\n\n/**\n * A string word is a type of word. It can be unquoted or single-quoted.\n */\nstruct mrsh_word_string {\n\tstruct mrsh_word word;\n\tchar *str;\n\tbool single_quoted;\n\n\t// true if candidate for field splitting (ie. result of parameter\n\t// expansion, command substitution or arithmetic expansion)\n\tbool split_fields;\n\n\tstruct mrsh_range range;\n};\n\nenum mrsh_word_parameter_op {\n\tMRSH_PARAM_NONE, // `$name` or `${parameter}`, no-op\n\tMRSH_PARAM_MINUS, // `${parameter:-[word]}`, Use Default Values\n\tMRSH_PARAM_EQUAL, // `${parameter:=[word]}`, Assign Default Values\n\tMRSH_PARAM_QMARK, // `${parameter:?[word]}`, Indicate Error if Null or Unset\n\tMRSH_PARAM_PLUS, // `${parameter:+[word]}`, Use Alternative Value\n\tMRSH_PARAM_LEADING_HASH, // `${#parameter}`, String Length\n\tMRSH_PARAM_PERCENT, // `${parameter%[word]}`, Remove Smallest Suffix Pattern\n\tMRSH_PARAM_DPERCENT, // `${parameter%%[word]}`, Remove Largest Suffix Pattern\n\tMRSH_PARAM_HASH, // `${parameter#[word]}`, Remove Smallest Prefix Pattern\n\tMRSH_PARAM_DHASH, // `${parameter##[word]}`, Remove Largest Prefix Pattern\n};\n\n/**\n * A word parameter is a type of word candidate for parameter expansion. The\n * format is either `$name` or `${expression}`.\n */\nstruct mrsh_word_parameter {\n\tstruct mrsh_word word;\n\tchar *name;\n\tenum mrsh_word_parameter_op op;\n\tbool colon; // only for -, =, ?, +\n\tstruct mrsh_word *arg; // can be NULL\n\n\tstruct mrsh_position dollar_pos;\n\tstruct mrsh_range name_range;\n\tstruct mrsh_range op_range; // can be invalid\n\tstruct mrsh_position lbrace_pos, rbrace_pos; // can be invalid\n};\n\n/**\n * A word command is a type of word candidate for command substitution. The\n * format is either `` `command` `` or `$(command)`.\n */\nstruct mrsh_word_command {\n\tstruct mrsh_word word;\n\tstruct mrsh_program *program; // can be NULL\n\tbool back_quoted;\n\n\tstruct mrsh_range range;\n};\n\n/**\n * An arithmetic word is a type of word containing an arithmetic expression. The\n * format is `$((expression))`.\n */\nstruct mrsh_word_arithmetic {\n\tstruct mrsh_word word;\n\tstruct mrsh_word *body;\n};\n\n/**\n * A word list is a type of word. It can be unquoted or double-quoted. Its\n * children are _not_ separated by blanks. Here's an example:\n *\n *   abc\"d ef\"g'h i'\n */\nstruct mrsh_word_list {\n\tstruct mrsh_word word;\n\tstruct mrsh_array children; // struct mrsh_word *\n\tbool double_quoted;\n\n\tstruct mrsh_position lquote_pos, rquote_pos; // can be invalid\n};\n\nenum mrsh_io_redirect_op {\n\tMRSH_IO_LESS, // <\n\tMRSH_IO_GREAT, // >\n\tMRSH_IO_CLOBBER, // >|\n\tMRSH_IO_DGREAT, // >>\n\tMRSH_IO_LESSAND, // <&\n\tMRSH_IO_GREATAND, // >&\n\tMRSH_IO_LESSGREAT, // <>\n\tMRSH_IO_DLESS, // <<\n\tMRSH_IO_DLESSDASH, // <<-\n};\n\n/**\n * An IO redirection operator. The format is: `[io_number]op name`.\n */\nstruct mrsh_io_redirect {\n\tint io_number; // -1 if unspecified\n\tenum mrsh_io_redirect_op op;\n\tstruct mrsh_word *name; // filename or here-document delimiter\n\tstruct mrsh_array here_document; // struct mrsh_word *, only for << and <<-\n\n\tstruct mrsh_position io_number_pos; // can be invalid\n\tstruct mrsh_range op_range;\n};\n\n/**\n * A variable assignment. The format is: `name=value`.\n */\nstruct mrsh_assignment {\n\tchar *name;\n\tstruct mrsh_word *value;\n\n\tstruct mrsh_range name_range;\n\tstruct mrsh_position equal_pos;\n};\n\nenum mrsh_command_type {\n\tMRSH_SIMPLE_COMMAND,\n\tMRSH_BRACE_GROUP,\n\tMRSH_SUBSHELL,\n\tMRSH_IF_CLAUSE,\n\tMRSH_FOR_CLAUSE,\n\tMRSH_LOOP_CLAUSE, // `while` or `until`\n\tMRSH_CASE_CLAUSE,\n\tMRSH_FUNCTION_DEFINITION,\n};\n\n/**\n * A command. It is either a simple command, a brace group or an if clause.\n */\nstruct mrsh_command {\n\tstruct mrsh_node node;\n\tenum mrsh_command_type type;\n};\n\n/**\n * A simple command is a type of command. It contains a command name, followed\n * by command arguments. It can also contain IO redirections and variable\n * assignments.\n */\nstruct mrsh_simple_command {\n\tstruct mrsh_command command;\n\tstruct mrsh_word *name; // can be NULL if it contains only assignments\n\tstruct mrsh_array arguments; // struct mrsh_word *\n\tstruct mrsh_array io_redirects; // struct mrsh_io_redirect *\n\tstruct mrsh_array assignments; // struct mrsh_assignment *\n};\n\n/**\n * A brace group is a type of command. It contains command lists and executes\n * them in the current process environment. The format is:\n * `{ compound-list ; }`.\n */\nstruct mrsh_brace_group {\n\tstruct mrsh_command command;\n\tstruct mrsh_array body; // struct mrsh_command_list *\n\n\tstruct mrsh_position lbrace_pos, rbrace_pos;\n};\n\n/**\n * A subshell is a type of command. It contains command lists and executes\n * them in a subshell environment. The format is: `( compound-list )`.\n */\nstruct mrsh_subshell {\n\tstruct mrsh_command command;\n\tstruct mrsh_array body; // struct mrsh_command_list *\n\n\tstruct mrsh_position lparen_pos, rparen_pos;\n};\n\n/**\n * An if clause is a type of command. The format is:\n *\n *   if compound-list\n *   then\n *       compound-list\n *   [elif compound-list\n *   then\n *       compound-list] ...\n *   [else\n *       compound-list]\n *   fi\n */\nstruct mrsh_if_clause {\n\tstruct mrsh_command command;\n\tstruct mrsh_array condition; // struct mrsh_command_list *\n\tstruct mrsh_array body; // struct mrsh_command_list *\n\tstruct mrsh_command *else_part; // can be NULL\n\n\tstruct mrsh_range if_range; // for `if` or `elif`\n\tstruct mrsh_range then_range, fi_range;\n\tstruct mrsh_range else_range; // can be invalid\n};\n\n/**\n * A for clause is a type of command. The format is:\n *\n *   for name [ in [word ... ]]\n *   do\n *       compound-list\n *   done\n */\nstruct mrsh_for_clause {\n\tstruct mrsh_command command;\n\tchar *name;\n\tbool in;\n\tstruct mrsh_array word_list; // struct mrsh_word *\n\tstruct mrsh_array body; // struct mrsh_command_list *\n\n\tstruct mrsh_range for_range, name_range, do_range, done_range;\n\tstruct mrsh_range in_range; // can be invalid\n};\n\nenum mrsh_loop_type {\n\tMRSH_LOOP_WHILE,\n\tMRSH_LOOP_UNTIL,\n};\n\n/**\n * A loop clause is a type of command. The format is:\n *\n *   while/until compound-list-1\n *   do\n *       compound-list-2\n *   done\n */\nstruct mrsh_loop_clause {\n\tstruct mrsh_command command;\n\tenum mrsh_loop_type type;\n\tstruct mrsh_array condition; // struct mrsh_command_list *\n\tstruct mrsh_array body; // struct mrsh_command_list *\n\n\tstruct mrsh_range while_until_range; // for `while` or `until`\n\tstruct mrsh_range do_range, done_range;\n};\n\n/**\n * A case item contains one or more patterns with a body. The format is:\n *\n *   [(] pattern[ | pattern] ... ) compound-list ;;\n *\n * The double-semicolumn is optional if it's the last item.\n */\nstruct mrsh_case_item {\n\tstruct mrsh_array patterns; // struct mrsh_word *\n\tstruct mrsh_array body; // struct mrsh_command_list *\n\n\tstruct mrsh_position lparen_pos; // can be invalid\n\t// TODO: pipe positions between each pattern\n\tstruct mrsh_position rparen_pos;\n\tstruct mrsh_range dsemi_range; // can be invalid\n};\n\n/**\n * A case clause is a type of command. The format is:\n *\n *   case word in\n *       [(] pattern1 ) compound-list ;;\n *       [[(] pattern[ | pattern] ... ) compound-list ;;] ...\n *       [[(] pattern[ | pattern] ... ) compound-list]\n *   esac\n */\nstruct mrsh_case_clause {\n\tstruct mrsh_command command;\n\tstruct mrsh_word *word;\n\tstruct mrsh_array items; // struct mrsh_case_item *\n\n\tstruct mrsh_range case_range, in_range, esac_range;\n};\n\n/**\n * A function definition is a type of command. The format is:\n *\n *   fname ( ) compound-command [io-redirect ...]\n */\nstruct mrsh_function_definition {\n\tstruct mrsh_command command;\n\tchar *name;\n\tstruct mrsh_command *body;\n\tstruct mrsh_array io_redirects; // struct mrsh_io_redirect *\n\n\tstruct mrsh_range name_range;\n\tstruct mrsh_position lparen_pos, rparen_pos;\n};\n\nenum mrsh_and_or_list_type {\n\tMRSH_AND_OR_LIST_PIPELINE,\n\tMRSH_AND_OR_LIST_BINOP,\n};\n\n/**\n * An AND-OR list is a tree of pipelines and binary operations.\n */\nstruct mrsh_and_or_list {\n\tstruct mrsh_node node;\n\tenum mrsh_and_or_list_type type;\n};\n\n/**\n * A pipeline is a type of AND-OR list which consists of multiple commands\n * separated by `|`. The format is: `[!] command1 [ | command2 ...]`.\n */\nstruct mrsh_pipeline {\n\tstruct mrsh_and_or_list and_or_list;\n\tstruct mrsh_array commands; // struct mrsh_command *\n\tbool bang; // whether the pipeline begins with `!`\n\n\tstruct mrsh_position bang_pos; // can be invalid\n\t// TODO: pipe positions between each command\n};\n\nenum mrsh_binop_type {\n\tMRSH_BINOP_AND, // `&&`\n\tMRSH_BINOP_OR, // `||`\n};\n\n/**\n * A binary operation is a type of AND-OR list which consists of multiple\n * pipelines separated by `&&` or `||`.\n */\nstruct mrsh_binop {\n\tstruct mrsh_and_or_list and_or_list;\n\tenum mrsh_binop_type type;\n\tstruct mrsh_and_or_list *left, *right;\n\n\tstruct mrsh_range op_range;\n};\n\n/**\n * A command list contains AND-OR lists separated by `;` (for sequential\n * execution) or `&` (for asynchronous execution).\n */\nstruct mrsh_command_list {\n\tstruct mrsh_node node;\n\tstruct mrsh_and_or_list *and_or_list;\n\tbool ampersand; // whether the command list ends with `&`\n\n\tstruct mrsh_position separator_pos; // can be invalid\n};\n\n/**\n * A shell program. It contains command lists.\n */\nstruct mrsh_program {\n\tstruct mrsh_node node;\n\tstruct mrsh_array body; // struct mrsh_command_list *\n};\n\ntypedef void (*mrsh_node_iterator_func)(struct mrsh_node *node,\n\tvoid *user_data);\n\nbool mrsh_position_valid(const struct mrsh_position *pos);\nbool mrsh_range_valid(const struct mrsh_range *range);\n\nstruct mrsh_word_string *mrsh_word_string_create(char *str,\n\tbool single_quoted);\nstruct mrsh_word_parameter *mrsh_word_parameter_create(char *name,\n\tenum mrsh_word_parameter_op op, bool colon, struct mrsh_word *arg);\nstruct mrsh_word_command *mrsh_word_command_create(struct mrsh_program *prog,\n\tbool back_quoted);\nstruct mrsh_word_arithmetic *mrsh_word_arithmetic_create(\n\tstruct mrsh_word *body);\nstruct mrsh_word_list *mrsh_word_list_create(struct mrsh_array *children,\n\tbool double_quoted);\nstruct mrsh_simple_command *mrsh_simple_command_create(struct mrsh_word *name,\n\tstruct mrsh_array *arguments, struct mrsh_array *io_redirects,\n\tstruct mrsh_array *assignments);\nstruct mrsh_brace_group *mrsh_brace_group_create(struct mrsh_array *body);\nstruct mrsh_subshell *mrsh_subshell_create(struct mrsh_array *body);\nstruct mrsh_if_clause *mrsh_if_clause_create(struct mrsh_array *condition,\n\tstruct mrsh_array *body, struct mrsh_command *else_part);\nstruct mrsh_for_clause *mrsh_for_clause_create(char *name, bool in,\n\tstruct mrsh_array *word_list, struct mrsh_array *body);\nstruct mrsh_loop_clause *mrsh_loop_clause_create(enum mrsh_loop_type type,\n\tstruct mrsh_array *condition, struct mrsh_array *body);\nstruct mrsh_case_clause *mrsh_case_clause_create(struct mrsh_word *word,\n\tstruct mrsh_array *items);\nstruct mrsh_function_definition *mrsh_function_definition_create(char *name,\n\tstruct mrsh_command *body, struct mrsh_array *io_redirects);\nstruct mrsh_pipeline *mrsh_pipeline_create(struct mrsh_array *commands,\n\tbool bang);\nstruct mrsh_binop *mrsh_binop_create(enum mrsh_binop_type type,\n\tstruct mrsh_and_or_list *left, struct mrsh_and_or_list *right);\nstruct mrsh_command_list *mrsh_command_list_create(void);\nstruct mrsh_program *mrsh_program_create(void);\n\nvoid mrsh_node_destroy(struct mrsh_node *node);\nvoid mrsh_word_destroy(struct mrsh_word *word);\nvoid mrsh_io_redirect_destroy(struct mrsh_io_redirect *redir);\nvoid mrsh_assignment_destroy(struct mrsh_assignment *assign);\nvoid mrsh_command_destroy(struct mrsh_command *cmd);\nvoid mrsh_and_or_list_destroy(struct mrsh_and_or_list *and_or_list);\nvoid mrsh_command_list_destroy(struct mrsh_command_list *l);\nvoid mrsh_program_destroy(struct mrsh_program *prog);\n\nstruct mrsh_word *mrsh_node_get_word(const struct mrsh_node *node);\nstruct mrsh_command *mrsh_node_get_command(const struct mrsh_node *node);\nstruct mrsh_and_or_list *mrsh_node_get_and_or_list(\n\tconst struct mrsh_node *node);\nstruct mrsh_command_list *mrsh_node_get_command_list(\n\tconst struct mrsh_node *node);\nstruct mrsh_program *mrsh_node_get_program(const struct mrsh_node *node);\n\nstruct mrsh_word_string *mrsh_word_get_string(const struct mrsh_word *word);\nstruct mrsh_word_parameter *mrsh_word_get_parameter(\n\tconst struct mrsh_word *word);\nstruct mrsh_word_command *mrsh_word_get_command(const struct mrsh_word *word);\nstruct mrsh_word_arithmetic *mrsh_word_get_arithmetic(\n\tconst struct mrsh_word *word);\nstruct mrsh_word_list *mrsh_word_get_list(const struct mrsh_word *word);\n\nstruct mrsh_simple_command *mrsh_command_get_simple_command(\n\tconst struct mrsh_command *cmd);\nstruct mrsh_brace_group *mrsh_command_get_brace_group(\n\tconst struct mrsh_command *cmd);\nstruct mrsh_subshell *mrsh_command_get_subshell(\n\tconst struct mrsh_command *cmd);\nstruct mrsh_if_clause *mrsh_command_get_if_clause(\n\tconst struct mrsh_command *cmd);\nstruct mrsh_for_clause *mrsh_command_get_for_clause(\n\tconst struct mrsh_command *cmd);\nstruct mrsh_loop_clause *mrsh_command_get_loop_clause(\n\tconst struct mrsh_command *cmd);\nstruct mrsh_case_clause *mrsh_command_get_case_clause(\n\tconst struct mrsh_command *cmd);\nstruct mrsh_function_definition *mrsh_command_get_function_definition(\n\tconst struct mrsh_command *cmd);\n\nstruct mrsh_pipeline *mrsh_and_or_list_get_pipeline(\n\tconst struct mrsh_and_or_list *and_or_list);\nstruct mrsh_binop *mrsh_and_or_list_get_binop(\n\tconst struct mrsh_and_or_list *and_or_list);\n\nvoid mrsh_node_for_each(struct mrsh_node *node,\n\tmrsh_node_iterator_func iterator, void *user_data);\n\nvoid mrsh_word_range(struct mrsh_word *word, struct mrsh_position *begin,\n\tstruct mrsh_position *end);\nvoid mrsh_command_range(struct mrsh_command *cmd, struct mrsh_position *begin,\n\tstruct mrsh_position *end);\nchar *mrsh_word_str(const struct mrsh_word *word);\nchar *mrsh_node_format(struct mrsh_node *node);\nvoid mrsh_program_print(struct mrsh_program *prog);\n\nstruct mrsh_node *mrsh_node_copy(const struct mrsh_node *node);\nstruct mrsh_word *mrsh_word_copy(const struct mrsh_word *word);\nstruct mrsh_io_redirect *mrsh_io_redirect_copy(\n\tconst struct mrsh_io_redirect *redir);\nstruct mrsh_assignment *mrsh_assignment_copy(\n\tconst struct mrsh_assignment *assign);\nstruct mrsh_command *mrsh_command_copy(const struct mrsh_command *cmd);\nstruct mrsh_and_or_list *mrsh_and_or_list_copy(\n\tconst struct mrsh_and_or_list *and_or_list);\nstruct mrsh_command_list *mrsh_command_list_copy(\n\tconst struct mrsh_command_list *l);\nstruct mrsh_program *mrsh_program_copy(const struct mrsh_program *prog);\n\n#endif\n"
  },
  {
    "path": "include/mrsh/buffer.h",
    "content": "#ifndef MRSH_BUFFER_H\n#define MRSH_BUFFER_H\n\n#include <stdbool.h>\n#include <stddef.h>\n\nstruct mrsh_buffer {\n\tchar *data;\n\tsize_t len, cap;\n};\n\n/**\n * Makes sure at least `size` bytes can be written to the buffer, without\n * increasing its length. Returns a pointer to the end of the buffer, or NULL if\n * resizing fails. Callers are responsible for manually increasing `buf->len`.\n *\n * This function is useful when e.g. reading from a file. Example with error\n * handling left out:\n *\n *   char *dst = mrsh_buffer_reserve(buf, READ_SIZE);\n *   ssize_t n = read(fd, dst, READ_SIZE);\n *   buf->len += n;\n */\nchar *mrsh_buffer_reserve(struct mrsh_buffer *buf, size_t size);\n/**\n * Increases the length of the buffer by `size` bytes. Returns a pointer to the\n * beginning of the newly appended space, or NULL if resizing fails.\n */\nchar *mrsh_buffer_add(struct mrsh_buffer *buf, size_t size);\nbool mrsh_buffer_append(struct mrsh_buffer *buf, const char *data, size_t size);\nbool mrsh_buffer_append_char(struct mrsh_buffer *buf, char c);\n/**\n * Get the buffer's current data and reset it.\n */\nchar *mrsh_buffer_steal(struct mrsh_buffer *buf);\nvoid mrsh_buffer_finish(struct mrsh_buffer *buf);\n\n#endif\n"
  },
  {
    "path": "include/mrsh/builtin.h",
    "content": "#ifndef MRSH_BUILTIN_H\n#define MRSH_BUILTIN_H\n\n#include <mrsh/shell.h>\n\nbool mrsh_has_builtin(const char *name);\nbool mrsh_has_special_builtin(const char *name);\nint mrsh_run_builtin(struct mrsh_state *state, int argc, char *argv[]);\n\nstruct mrsh_init_args {\n\tconst char *command_file;\n\tconst char *command_str;\n};\n\nint mrsh_process_args(struct mrsh_state *state, struct mrsh_init_args *args,\n\tint argc, char *argv[]);\n\n#endif\n"
  },
  {
    "path": "include/mrsh/entry.h",
    "content": "#ifndef MRSH_ENTRY_H\n#define MRSH_ENTRY_H\n\n#include <mrsh/shell.h>\n#include <stdbool.h>\n\n/**\n * Expands $PS1 or returns the POSIX-specified default of \"$\" or \"#\". The caller\n * must free the return value.\n */\nchar *mrsh_get_ps1(struct mrsh_state *state, int next_history_id);\n\n/**\n * Expands $PS2 or returns the POSIX-specified default of \">\". The caller must\n * free the return value.\n */\nchar *mrsh_get_ps2(struct mrsh_state *state);\n\n/**\n * Expands $PS4 or returns the POSIX-specified default of \"+ \". The caller must\n * free the return value.\n */\nchar *mrsh_get_ps4(struct mrsh_state *state);\n\n/**\n * Copies variables from the environment and sets up internal variables like\n * IFS, PPID, PWD, etc.\n */\nbool mrsh_populate_env(struct mrsh_state *state, char **environ);\n\n/**\n * Sources /etc/profile and $HOME/.profile. Note that this behavior is not\n * specified by POSIX. It is recommended to call this file in login shells\n * (for which argv[0][0] == '-' by convention).\n */\nvoid mrsh_source_profile(struct mrsh_state *state);\n\n/** Sources $ENV. It is recommended to source this in interactive shells. */\nvoid mrsh_source_env(struct mrsh_state *state);\n\n/**\n * Run the trap registered on EXIT. It is recommended to call this function\n * right before exiting the shell.\n */\nbool mrsh_run_exit_trap(struct mrsh_state *state);\n\n#endif\n"
  },
  {
    "path": "include/mrsh/hashtable.h",
    "content": "#ifndef MRSH_HASHTABLE_H\n#define MRSH_HASHTABLE_H\n\n#define MRSH_HASHTABLE_BUCKETS 256\n\nstruct mrsh_hashtable_entry {\n\tstruct mrsh_hashtable_entry *next;\n\tunsigned int hash;\n\tchar *key;\n\tvoid *value;\n};\n\nstruct mrsh_hashtable {\n\tstruct mrsh_hashtable_entry *buckets[MRSH_HASHTABLE_BUCKETS];\n};\n\ntypedef void (*mrsh_hashtable_iterator_func)(const char *key, void *value,\n\tvoid *user_data);\n\nvoid mrsh_hashtable_finish(struct mrsh_hashtable *table);\nvoid *mrsh_hashtable_get(struct mrsh_hashtable *table, const char *key);\nvoid *mrsh_hashtable_set(struct mrsh_hashtable *table, const char *key,\n\tvoid *value);\nvoid *mrsh_hashtable_del(struct mrsh_hashtable *table, const char *key);\n/**\n * Calls `iterator` for each (key, value) pair in the hash table. It is safe to\n * call `mrsh_hashtable_del` on the current element, however it is not safe to\n * do so an any other element.\n */\nvoid mrsh_hashtable_for_each(struct mrsh_hashtable *table,\n\tmrsh_hashtable_iterator_func iterator, void *user_data);\n\n#endif\n"
  },
  {
    "path": "include/mrsh/parser.h",
    "content": "#ifndef MRSH_PARSER_H\n#define MRSH_PARSER_H\n\n#include <mrsh/ast.h>\n#include <stdio.h>\n\nstruct mrsh_parser;\nstruct mrsh_buffer;\n\n/**\n * An alias callback. The alias named is given as a parameter and the alias\n * value should be returned. NULL should be returned if the alias doesn't exist.\n */\ntypedef const char *(*mrsh_parser_alias_func)(const char *name,\n\tvoid *user_data);\n\n/**\n * Create a parser from a file descriptor.\n */\nstruct mrsh_parser *mrsh_parser_with_fd(int fd);\n/**\n * Create a parser from a static buffer.\n */\nstruct mrsh_parser *mrsh_parser_with_data(const char *buf, size_t len);\n/**\n * Create a parser with a shared buffer. Data will be read from `buf` each time\n * the parser needs input data.\n */\nstruct mrsh_parser *mrsh_parser_with_buffer(struct mrsh_buffer *buf);\nvoid mrsh_parser_destroy(struct mrsh_parser *parser);\n/**\n * Parse a complete multi-line program.\n */\nstruct mrsh_program *mrsh_parse_program(struct mrsh_parser *parser);\n/**\n * Parse a program line. Continuation lines are consumed.\n */\nstruct mrsh_program *mrsh_parse_line(struct mrsh_parser *parser);\n\n/**\n * Parse an arithmetic expression.\n */\nstruct mrsh_arithm_expr *mrsh_parse_arithm_expr(struct mrsh_parser *parser);\n/**\n * Check if the input has been completely consumed.\n */\nbool mrsh_parser_eof(struct mrsh_parser *parser);\n/**\n * Set the alias callback.\n */\nvoid mrsh_parser_set_alias_func(struct mrsh_parser *parser,\n\tmrsh_parser_alias_func alias, void *user_data);\n/**\n * Check if the parser ended with a syntax error. The error message is returned.\n * The error position can optionally be obtained.\n */\nconst char *mrsh_parser_error(struct mrsh_parser *parser,\n\tstruct mrsh_position *pos);\n/**\n * Check if the input ends on a continuation line.\n */\nbool mrsh_parser_continuation_line(struct mrsh_parser *parser);\n/**\n * Reset the parser state.\n */\nvoid mrsh_parser_reset(struct mrsh_parser *parser);\n\n#endif\n"
  },
  {
    "path": "include/mrsh/shell.h",
    "content": "#ifndef MRSH_SHELL_H\n#define MRSH_SHELL_H\n\n#include <mrsh/arithm.h>\n#include <mrsh/ast.h>\n#include <mrsh/hashtable.h>\n#include <stdint.h>\n#include <stdio.h>\n\nenum mrsh_option {\n\t// -a: When this option is on, the export attribute shall be set for each\n\t// variable to which an assignment is performed.\n\tMRSH_OPT_ALLEXPORT = 1 << 0,\n\t// -b: Shall cause the shell to notify the user asynchronously of background\n\t// job completions.\n\tMRSH_OPT_NOTIFY = 1 << 1,\n\t// -C: Prevent existing files from being overwritten by the shell's '>'\n\t// redirection operator; the \">|\" redirection operator shall override this\n\t// noclobber option for an individual file.\n\tMRSH_OPT_NOCLOBBER = 1 << 2,\n\t// -e: When this option is on, when any command fails (for any of the\n\t// reasons listed in Consequences of Shell Errors or by returning an exit\n\t// status greater than zero), the shell immediately shall exit\n\tMRSH_OPT_ERREXIT = 1 << 3,\n\t// -f: The shell shall disable pathname expansion.\n\tMRSH_OPT_NOGLOB = 1 << 4,\n\t// -h: Locate and remember utilities invoked by functions as those functions\n\t// are defined (the utilities are normally located when the function is\n\t// executed).\n\tMRSH_OPT_PRELOOKUP = 1 << 5,\n\t// -m: All jobs shall be run in their own process groups. Immediately before\n\t// the shell issues a prompt after completion of the background job, a\n\t// message reporting the exit status of the background job shall be written\n\t// to standard error. If a foreground job stops, the shell shall write a\n\t// message to standard error to that effect, formatted as described by the\n\t// jobs utility.\n\tMRSH_OPT_MONITOR = 1 << 6,\n\t// -n: The shell shall read commands but does not execute them; this can be\n\t// used to check for shell script syntax errors. An interactive shell may\n\t// ignore this option.\n\tMRSH_OPT_NOEXEC = 1 << 7,\n\t// -o ignoreeof: Prevent an interactive shell from exiting on end-of-file.\n\tMRSH_OPT_IGNOREEOF = 1 << 8,\n\t// -o nolog: Prevent the entry of function definitions into the command\n\t// history\n\tMRSH_OPT_NOLOG = 1 << 9,\n\t// -o vi: Allow shell command line editing using the built-in vi editor.\n\tMRSH_OPT_VI = 1 << 10,\n\t// -u: When the shell tries to expand an unset parameter other than the '@'\n\t// and '*' special parameters, it shall write a message to standard error\n\t// and the expansion shall fail.\n\tMRSH_OPT_NOUNSET = 1 << 11,\n\t// -v: The shell shall write its input to standard error as it is read.\n\tMRSH_OPT_VERBOSE = 1 << 12,\n\t// -x: The shell shall write to standard error a trace for each command\n\t// after it expands the command and before it executes it.\n\tMRSH_OPT_XTRACE = 1 << 13,\n};\n\nenum mrsh_variable_attrib {\n\tMRSH_VAR_ATTRIB_NONE = 0,\n\tMRSH_VAR_ATTRIB_EXPORT = 1 << 0,\n\tMRSH_VAR_ATTRIB_READONLY = 1 << 1,\n};\n\nstruct mrsh_call_frame {\n\tchar **argv;\n\tint argc;\n\tstruct mrsh_call_frame *prev;\n};\n\nstruct mrsh_state {\n\tint exit;\n\tuint32_t options; // enum mrsh_option\n\tstruct mrsh_call_frame *frame; // last call frame\n\tbool interactive;\n\tint last_status;\n};\n\nstruct mrsh_parser;\n\nstruct mrsh_state *mrsh_state_create(void);\nvoid mrsh_state_destroy(struct mrsh_state *state);\nvoid mrsh_state_set_parser_alias_func(\n\t\tstruct mrsh_state *state, struct mrsh_parser *parser);\nvoid mrsh_env_set(struct mrsh_state *state,\n\tconst char *key, const char *value, uint32_t attribs);\nvoid mrsh_env_unset(struct mrsh_state *state, const char *key);\nconst char *mrsh_env_get(struct mrsh_state *state,\n\tconst char *key, uint32_t *attribs);\nint mrsh_run_program(struct mrsh_state *state, struct mrsh_program *prog);\nint mrsh_run_word(struct mrsh_state *state, struct mrsh_word **word);\nbool mrsh_run_arithm_expr(struct mrsh_state *state,\n\tstruct mrsh_arithm_expr *expr, long *result);\n/**\n * Enable or disable job control. This will setup signal handlers, process\n * groups and the terminal accordingly.\n */\nbool mrsh_set_job_control(struct mrsh_state *state, bool enabled);\n/**\n * Destroy terminated jobs and print job notifications. This function should be\n * called after mrsh_run_program.\n */\nvoid mrsh_destroy_terminated_jobs(struct mrsh_state *state);\n\n#endif\n"
  },
  {
    "path": "include/mrsh_getopt.h",
    "content": "#ifndef MRSH_GETOPT_H\n#define MRSH_GETOPT_H\n\nextern char *_mrsh_optarg;\nextern int _mrsh_opterr, _mrsh_optind, _mrsh_optopt;\n\nint _mrsh_getopt(int argc, char * const argv[], const char *optstring);\n\n#endif\n"
  },
  {
    "path": "include/parser.h",
    "content": "#ifndef PARSER_H\n#define PARSER_H\n\n#include <stdio.h>\n#include <mrsh/buffer.h>\n#include <mrsh/parser.h>\n\nenum symbol_name {\n\tEOF_TOKEN,\n\tTOKEN,\n\n\tNEWLINE,\n\n\t// The following are the operators (see XBD Operator) containing more than\n\t// one character.\n\n\tAND_IF,\n\tOR_IF,\n\tDSEMI,\n\n\tDLESS,\n\tDGREAT,\n\tLESSAND,\n\tGREATAND,\n\tLESSGREAT,\n\tDLESSDASH,\n\n\tCLOBBER,\n};\n\nstruct symbol {\n\tenum symbol_name name;\n\tchar *str;\n};\n\nextern const struct symbol operators[];\nextern const size_t operators_len;\nextern const size_t operators_max_str_len;\n\nextern const char *keywords[];\nextern const size_t keywords_len;\n\nstruct mrsh_parser {\n\tint fd; // can be -1\n\tstruct mrsh_buffer *in_buf; // can be NULL\n\tbool eof;\n\n\tstruct mrsh_buffer buf; // internal read buffer\n\tstruct mrsh_position pos;\n\n\tstruct {\n\t\tchar *msg;\n\t\tstruct mrsh_position pos;\n\t} error;\n\n\tbool has_sym;\n\tenum symbol_name sym;\n\n\tstruct mrsh_array here_documents;\n\tbool continuation_line;\n\n\tmrsh_parser_alias_func alias;\n\tvoid *alias_user_data;\n\n\tint arith_nested_parens;\n};\n\ntypedef struct mrsh_word *(*word_func)(struct mrsh_parser *parser, char end);\n\nsize_t parser_peek(struct mrsh_parser *parser, char *buf, size_t size);\nchar parser_peek_char(struct mrsh_parser *parser);\nsize_t parser_read(struct mrsh_parser *parser, char *buf, size_t size);\nchar parser_read_char(struct mrsh_parser *parser);\nbool token(struct mrsh_parser *parser, const char *str,\n\tstruct mrsh_range *range);\nbool expect_token(struct mrsh_parser *parser, const char *str,\n\tstruct mrsh_range *range);\nchar *read_token(struct mrsh_parser *parser, size_t len,\n\tstruct mrsh_range *range);\nvoid read_continuation_line(struct mrsh_parser *parser);\nvoid parser_set_error(struct mrsh_parser *parser, const char *msg);\nvoid parser_begin(struct mrsh_parser *parser);\nbool is_operator_start(char c);\nenum symbol_name get_symbol(struct mrsh_parser *parser);\n/**\n * Invalidates the current symbol. Should be used each time manual\n * parser_read calls are performed.\n */\nvoid consume_symbol(struct mrsh_parser *parser);\nbool symbol(struct mrsh_parser *parser, enum symbol_name sym);\nbool eof(struct mrsh_parser *parser);\nbool newline(struct mrsh_parser *parser);\nvoid linebreak(struct mrsh_parser *parser);\nbool newline_list(struct mrsh_parser *parser);\n\nsize_t peek_name(struct mrsh_parser *parser, bool in_braces);\nsize_t peek_word(struct mrsh_parser *parser, char end);\nstruct mrsh_word *expect_dollar(struct mrsh_parser *parser);\nstruct mrsh_word *back_quotes(struct mrsh_parser *parser);\nstruct mrsh_word *word(struct mrsh_parser *parser, char end);\nstruct mrsh_word *arithmetic_word(struct mrsh_parser *parser, char end);\nstruct mrsh_word *parameter_expansion_word(struct mrsh_parser *parser);\n\n#endif\n"
  },
  {
    "path": "include/shell/job.h",
    "content": "#ifndef SHELL_JOB_H\n#define SHELL_JOB_H\n\n#include <mrsh/array.h>\n#include <stdbool.h>\n#include <sys/types.h>\n#include <termios.h>\n\nstruct mrsh_node;\nstruct mrsh_state;\nstruct mrsh_process;\n\n/**\n * A job is a set of processes, comprising a shell pipeline, and any processes\n * descended from it, that are all in the same process group.\n *\n * In practice, a single job is also created when executing an asynchronous\n * command list.\n *\n * This object is guaranteed to be valid until either:\n * - The job terminates\n * - The shell is destroyed\n */\nstruct mrsh_job {\n\tstruct mrsh_node *node;\n\tpid_t pgid;\n\tint job_id;\n\tstruct termios term_modes; // only valid if stopped\n\tstruct mrsh_state *state;\n\tstruct mrsh_array processes; // struct mrsh_process *\n\n\tbool pending_notification; // need to print a job status notification\n\tint last_status;\n};\n\n/**\n * Create a new job. It will start in the background by default.\n */\nstruct mrsh_job *job_create(struct mrsh_state *state,\n\tconst struct mrsh_node *node);\nvoid job_destroy(struct mrsh_job *job);\n/**\n * Add a process to the job. This puts the process into the job's process\n * group. This has to be done both in the parent and in the child to prevent\n * race conditions.\n *\n * If the job doesn't have a process group (because it's empty), then a new\n * process group is created.\n */\nvoid job_add_process(struct mrsh_job *job, struct mrsh_process *proc);\n/**\n * Polls the job's current status without blocking. Returns:\n * - TASK_STATUS_WAIT if the job is running (ie. one or more processes are\n *   running)\n * - TASK_STATUS_STOPPED if the job is stopped (ie. one or more processes are\n *   stopped, all the others are terminated)\n * - An integer >= 0 if the job has terminated (ie. all processes have\n *   terminated)\n */\nint job_poll(struct mrsh_job *job);\n/**\n * Wait for the completion of the job.\n */\nint job_wait(struct mrsh_job *job);\n/**\n * Wait for the completion of the process.\n */\nint job_wait_process(struct mrsh_process *proc);\n/**\n * Put the job in the foreground or in the background. If the job is stopped and\n * cont is set to true, it will be continued.\n *\n * It is illegal to put a job in the foreground if another job is already in the\n * foreground.\n */\nbool job_set_foreground(struct mrsh_job *job, bool foreground, bool cont);\n\n/**\n * Initialize a child process state.\n */\nbool init_job_child_process(struct mrsh_state *state);\n/**\n * Refreshes status for all jobs.\n */\nbool refresh_jobs_status(struct mrsh_state *state);\n/**\n * Look up a job by its XBD Job Control Job ID.\n *\n * When using this to look up jobs internally, set interactive to false. This\n * suppresses error reporting.\n */\nstruct mrsh_job *job_by_id(struct mrsh_state *state,\n\t\tconst char *id, bool interactive);\n/**\n * Return a string describing the process' state. `r` is a random boolean.\n */\nconst char *job_state_str(struct mrsh_job *job, bool r);\n/**\n * Send SIGHUP to all running jobs.\n */\nvoid broadcast_sighup_to_jobs(struct mrsh_state *state);\n\n#endif\n"
  },
  {
    "path": "include/shell/path.h",
    "content": "#ifndef SHELL_PATH_H\n#define SHELL_PATH_H\n\n#include <mrsh/shell.h>\n#include <stdbool.h>\n\n/* Searches $PATH for the requested file and returns it if found. If exec is\n * true, it will require the file to be executable in order to be considered a\n * match. If default_path is true, the system's default search path will be\n * used instead of the $PATH variable. Fully qualified paths are returned\n * as-is.\n */\nchar *expand_path(struct mrsh_state *state, const char *file, bool exec,\n\tbool default_path);\n/* Like getcwd, but returns allocated memory */\nchar *current_working_dir(void);\n\n#endif\n"
  },
  {
    "path": "include/shell/process.h",
    "content": "#ifndef SHELL_PROCESS_H\n#define SHELL_PROCESS_H\n\n#include <mrsh/shell.h>\n#include <stdbool.h>\n#include <sys/types.h>\n\n/**\n * This struct is used to track child processes.\n *\n * This object is guaranteed to be valid until either:\n * - The process terminates\n * - The shell is destroyed\n */\nstruct mrsh_process {\n\tpid_t pid;\n\tstruct mrsh_state *state;\n\tbool stopped;\n\tbool terminated;\n\tint stat; // only valid if terminated\n\tint signal; // only valid if stopped is true\n};\n\n/**\n * Register a new process.\n */\nstruct mrsh_process *process_create(struct mrsh_state *state, pid_t pid);\nvoid process_destroy(struct mrsh_process *process);\n/**\n * Polls the process' current status without blocking. Returns:\n * - An integer >= 0 if the process has terminated\n * - TASK_STATUS_STOPPED if the process is stopped\n * - TASK_STATUS_WAIT if the process is running\n */\nint process_poll(struct mrsh_process *process);\n\n/**\n * Update the shell's state with a child process status.\n */\nvoid update_process(struct mrsh_state *state, pid_t pid, int stat);\n\n#endif\n"
  },
  {
    "path": "include/shell/redir.h",
    "content": "#ifndef SHELL_REDIR_H\n#define SHELL_REDIR_H\n\n#include <mrsh/ast.h>\n\nint process_redir(const struct mrsh_io_redirect *redir, int *redir_fd);\n\n#endif\n"
  },
  {
    "path": "include/shell/shell.h",
    "content": "#ifndef SHELL_SHELL_H\n#define SHELL_SHELL_H\n\n#include <mrsh/shell.h>\n#include <termios.h>\n#include \"job.h\"\n#include \"process.h\"\n#include \"shell/trap.h\"\n\nstruct mrsh_variable {\n\tchar *value;\n\tuint32_t attribs; // enum mrsh_variable_attrib\n};\n\nstruct mrsh_function {\n\tstruct mrsh_command *body;\n};\n\nenum mrsh_branch_control {\n\tMRSH_BRANCH_BREAK,\n\tMRSH_BRANCH_CONTINUE,\n\tMRSH_BRANCH_RETURN,\n\tMRSH_BRANCH_EXIT,\n};\n\nstruct mrsh_call_frame_priv {\n\tstruct mrsh_call_frame pub;\n\n\tenum mrsh_branch_control branch_control;\n\tint nloops;\n};\n\nstruct mrsh_trap {\n\tbool set;\n\tenum mrsh_trap_action action;\n\tstruct mrsh_program *program;\n};\n\nstruct mrsh_state_priv {\n\tstruct mrsh_state pub;\n\n\tint term_fd;\n\tstruct mrsh_array processes;\n\tstruct mrsh_hashtable aliases; // char *\n\tstruct mrsh_hashtable variables; // struct mrsh_variable *\n\tstruct mrsh_hashtable functions; // struct mrsh_function *\n\n\tbool job_control;\n\tpid_t pgid;\n\tstruct termios term_modes;\n\tstruct mrsh_array jobs; // struct mrsh_job *\n\tstruct mrsh_job *foreground_job;\n\n\tstruct mrsh_trap traps[MRSH_NSIG];\n\n\t// TODO: move this to context\n\tbool child; // true if we're not the main shell process\n};\n\n/**\n * A context holds state information and per-job information. A context is\n * guaranteed to be shared between all members of a job.\n */\nstruct mrsh_context {\n\tstruct mrsh_state *state;\n\t// When executing a pipeline, this is set to the job created for the\n\t// pipeline\n\tstruct mrsh_job *job;\n\t// When executing an asynchronous list, this is set to true\n\tbool background;\n};\n\nvoid function_destroy(struct mrsh_function *fn);\n\nstruct mrsh_call_frame_priv *call_frame_get_priv(struct mrsh_call_frame *frame);\n\nstruct mrsh_state_priv *state_get_priv(struct mrsh_state *state);\nvoid push_frame(struct mrsh_state *state, int argc, const char *argv[]);\nvoid pop_frame(struct mrsh_state *state);\n\n#endif\n"
  },
  {
    "path": "include/shell/task.h",
    "content": "#ifndef SHELL_TASK_H\n#define SHELL_TASK_H\n\n#include \"shell/shell.h\"\n#include \"shell/word.h\"\n\n/**\n * The task is waiting for child processes to finish.\n */\n#define TASK_STATUS_WAIT -1\n/**\n * A fatal error occured, the task should be destroyed.\n */\n#define TASK_STATUS_ERROR -2\n/**\n * The task has been stopped and the job has been put in the background.\n */\n#define TASK_STATUS_STOPPED -3\n/**\n * The task has been interrupted for some reason.\n */\n#define TASK_STATUS_INTERRUPTED -4\n\nstruct mrsh_context;\n\n/* Perform parameter expansion, command substitution and arithmetic expansion. */\nint run_word(struct mrsh_context *ctx, struct mrsh_word **word_ptr);\n/* Perform all word expansions, as specified in section 2.6. Fills `fields`\n * with `char *` elements. Not suitable for assignments. */\nint expand_word(struct mrsh_context *ctx, const struct mrsh_word *word,\n\tstruct mrsh_array *fields);\nint run_simple_command(struct mrsh_context *ctx, struct mrsh_simple_command *sc);\nint run_command(struct mrsh_context *ctx, struct mrsh_command *cmd);\nint run_and_or_list(struct mrsh_context *ctx, struct mrsh_and_or_list *and_or_list);\nint run_pipeline(struct mrsh_context *ctx, struct mrsh_pipeline *pipeline);\nint run_command_list_array(struct mrsh_context *ctx, struct mrsh_array *array);\n\n#endif\n"
  },
  {
    "path": "include/shell/trap.h",
    "content": "#ifndef SHELL_TRAP_H\n#define SHELL_TRAP_H\n\n#include <stdbool.h>\n\nstruct mrsh_state;\nstruct mrsh_program;\n\n// TODO: find a POSIX-compatible way to get the max signal value\n#define MRSH_NSIG 64\n\nenum mrsh_trap_action {\n\tMRSH_TRAP_DEFAULT, // SIG_DFL\n\tMRSH_TRAP_IGNORE, // SIG_IGN\n\tMRSH_TRAP_CATCH,\n};\n\nbool set_trap(struct mrsh_state *state, int sig, enum mrsh_trap_action action,\n\tstruct mrsh_program *program);\nbool set_job_control_traps(struct mrsh_state *state, bool enabled);\nbool reset_caught_traps(struct mrsh_state *state);\nbool run_pending_traps(struct mrsh_state *state);\nbool run_exit_trap(struct mrsh_state *state);\n\n#endif\n"
  },
  {
    "path": "include/shell/word.h",
    "content": "#ifndef SHELL_WORD_H\n#define SHELL_WORD_H\n\n#include <mrsh/shell.h>\n\n/**\n * Performs tilde expansion. It leaves the word as-is in case of error.\n */\nvoid expand_tilde(struct mrsh_state *state, struct mrsh_word **word_ptr,\n\tbool assignment);\n/**\n * Performs field splitting on `word`, writing fields to `fields`. This should\n * be done after expansions/substitutions.\n */\nvoid split_fields(struct mrsh_array *fields, const struct mrsh_word *word,\n\tconst char *ifs);\nvoid get_fields_str(struct mrsh_array *strs, const struct mrsh_array *fields);\n/**\n * Convert a word to a pattern. Returns NULL if word doesn't contain any\n * special pattern character (ie. requires an exact match).\n */\nchar *word_to_pattern(const struct mrsh_word *word);\n/**\n * Performs pathname expansion on each item in `fields`.\n */\nbool expand_pathnames(struct mrsh_array *expanded,\n\tconst struct mrsh_array *fields);\n\n\n#endif\n"
  },
  {
    "path": "libmrsh.darwin.sym",
    "content": "# On Darwin, symbols are prefixed with an underscore\n_mrsh_*\n"
  },
  {
    "path": "libmrsh.gnu.sym",
    "content": "{\n\tglobal:\n\t\tmrsh_*;\n\tlocal:\n\t\t*;\n};\n"
  },
  {
    "path": "main.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <errno.h>\n#include <fcntl.h>\n#include <limits.h>\n#include <mrsh/ast.h>\n#include <mrsh/buffer.h>\n#include <mrsh/builtin.h>\n#include <mrsh/entry.h>\n#include <mrsh/parser.h>\n#include <mrsh/shell.h>\n#include <stdbool.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include \"frontend.h\"\n\nextern char **environ;\n\nint main(int argc, char *argv[]) {\n\tstruct mrsh_state *state = mrsh_state_create();\n\n\tstruct mrsh_init_args init_args = {0};\n\tif (mrsh_process_args(state, &init_args, argc, argv) != 0) {\n\t\tmrsh_state_destroy(state);\n\t\treturn 1;\n\t}\n\n\tif (!mrsh_populate_env(state, environ)) {\n\t\treturn 1;\n\t}\n\n\tstruct mrsh_buffer parser_buffer = {0};\n\tstruct mrsh_parser *parser;\n\tint fd = -1;\n\tif (state->interactive) {\n\t\tinteractive_init(state);\n\t\tparser = mrsh_parser_with_buffer(&parser_buffer);\n\t} else {\n\t\tif (init_args.command_str) {\n\t\t\tparser = mrsh_parser_with_data(init_args.command_str,\n\t\t\t\tstrlen(init_args.command_str));\n\t\t} else {\n\t\t\tif (init_args.command_file) {\n\t\t\t\tfd = open(init_args.command_file, O_RDONLY | O_CLOEXEC);\n\t\t\t\tif (fd < 0) {\n\t\t\t\t\tfprintf(stderr, \"failed to open %s for reading: %s\\n\",\n\t\t\t\t\t\tinit_args.command_file, strerror(errno));\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfd = STDIN_FILENO;\n\t\t\t}\n\n\t\t\tparser = mrsh_parser_with_fd(fd);\n\t\t}\n\t}\n\tmrsh_state_set_parser_alias_func(state, parser);\n\n\tif (state->options & MRSH_OPT_MONITOR) {\n\t\tif (!mrsh_set_job_control(state, true)) {\n\t\t\tfprintf(stderr, \"failed to enable job control\\n\");\n\t\t}\n\t}\n\n\tif (!(state->options & MRSH_OPT_NOEXEC)) {\n\t\t// If argv[0] begins with `-`, it's a login shell\n\t\tif (state->frame->argv[0][0] == '-') {\n\t\t\tmrsh_source_profile(state);\n\t\t}\n\t\tif (state->interactive) {\n\t\t\tmrsh_source_env(state);\n\t\t}\n\t}\n\n\tstruct mrsh_buffer read_buffer = {0};\n\twhile (state->exit == -1) {\n\t\tif (state->interactive) {\n\t\t\tchar *prompt;\n\t\t\tif (read_buffer.len > 0) {\n\t\t\t\tprompt = mrsh_get_ps2(state);\n\t\t\t} else {\n\t\t\t\t// TODO: next_history_id\n\t\t\t\tprompt = mrsh_get_ps1(state, 0);\n\t\t\t}\n\t\t\tchar *line = NULL;\n\t\t\tsize_t n = interactive_next(state, &line, prompt);\n\t\t\tfree(prompt);\n\t\t\tif (!line) {\n\t\t\t\tstate->exit = state->last_status;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tmrsh_buffer_append(&read_buffer, line, n);\n\t\t\tfree(line);\n\n\t\t\tparser_buffer.len = 0;\n\t\t\tmrsh_buffer_append(&parser_buffer,\n\t\t\t\tread_buffer.data, read_buffer.len);\n\n\t\t\tmrsh_parser_reset(parser);\n\t\t}\n\n\t\tstruct mrsh_program *prog = mrsh_parse_line(parser);\n\t\tif (state->interactive && mrsh_parser_continuation_line(parser)) {\n\t\t\t// Nothing to see here\n\t\t} else if (prog == NULL) {\n\t\t\tstruct mrsh_position err_pos;\n\t\t\tconst char *err_msg = mrsh_parser_error(parser, &err_pos);\n\t\t\tif (err_msg != NULL) {\n\t\t\t\tmrsh_buffer_finish(&read_buffer);\n\t\t\t\tfprintf(stderr, \"%s:%d:%d: syntax error: %s\\n\",\n\t\t\t\t\tstate->frame->argv[0], err_pos.line, err_pos.column,\n\t\t\t\t\terr_msg);\n\t\t\t\tif (state->interactive) {\n\t\t\t\t\tcontinue;\n\t\t\t\t} else {\n\t\t\t\t\tstate->exit = 1;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t} else if (mrsh_parser_eof(parser)) {\n\t\t\t\tstate->exit = state->last_status;\n\t\t\t\tbreak;\n\t\t\t} else {\n\t\t\t\tfprintf(stderr, \"unknown error\\n\");\n\t\t\t\tstate->exit = 1;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t} else {\n\t\t\tif ((state->options & MRSH_OPT_NOEXEC)) {\n\t\t\t\tmrsh_program_print(prog);\n\t\t\t} else {\n\t\t\t\tmrsh_run_program(state, prog);\n\t\t\t\tmrsh_destroy_terminated_jobs(state);\n\t\t\t}\n\t\t\tmrsh_buffer_finish(&read_buffer);\n\t\t}\n\t\tmrsh_program_destroy(prog);\n\t}\n\n\tif (state->interactive) {\n\t\tprintf(\"\\n\");\n\t}\n\n\tint exit = state->exit;\n\n\tmrsh_run_exit_trap(state);\n\n\tmrsh_buffer_finish(&read_buffer);\n\tmrsh_parser_destroy(parser);\n\tmrsh_buffer_finish(&parser_buffer);\n\tmrsh_state_destroy(state);\n\tif (fd >= 0) {\n\t\tclose(fd);\n\t}\n\n\treturn exit;\n}\n"
  },
  {
    "path": "meson.build",
    "content": "project(\n\t'mrsh',\n\t'c',\n\tversion: '0.0.0',\n\tlicense: 'MIT',\n\tmeson_version: '>=0.47.0',\n\tdefault_options: [\n\t\t'c_std=c99',\n\t\t'warning_level=3',\n\t\t'werror=true',\n\t],\n)\n\ncc = meson.get_compiler('c')\n\nadd_project_arguments(cc.get_supported_arguments([\n\t'-Wundef',\n\t'-Wlogical-op',\n\t'-Wmissing-include-dirs',\n\t'-Wold-style-definition',\n\t'-Wpointer-arith',\n\t'-Winit-self',\n\t'-Wfloat-equal',\n\t'-Wstrict-prototypes',\n\t'-Wredundant-decls',\n\t'-Wimplicit-fallthrough=2',\n\t'-Wendif-labels',\n\t'-Wstrict-aliasing=2',\n\t'-Woverflow',\n\t'-Wformat=2',\n\t'-Wmissing-prototypes',\n\n\t'-Wno-missing-braces',\n\t'-Wno-missing-field-initializers',\n\t'-Wno-unused-parameter',\n\t'-Wno-unused-result', # fuck you glibc, and gcc, and the little dog, too\n\t'-Wno-format-overflow', # causes false positives with gcc\n]), language: 'c')\n\nif get_option('readline-provider') == 'readline'\n\treadline = cc.find_library('readline', required: get_option('readline'))\n\tif readline.found()\n\t\tadd_project_arguments('-DHAVE_READLINE', language: 'c')\n\tendif\n\n\tif cc.has_function('rl_replace_line', prefix: '#include <stdio.h>\\n #include <readline/readline.h>', dependencies: [readline])\n\t\tadd_project_arguments('-DHAVE_READLINE_REPLACE_LINE', language: 'c')\n\tendif\nelse # editline\n\treadline = dependency('libedit', required: get_option('readline'))\n\tif readline.found()\n\t\tadd_project_arguments('-DHAVE_EDITLINE', language: 'c')\n\tendif\nendif\n\nmrsh_inc = include_directories('include')\n\ninstall_subdir('include/mrsh', install_dir: get_option('includedir'))\n\nlibmrsh_gnu_sym_path = join_paths(meson.current_source_dir(), 'libmrsh.gnu.sym')\nlibmrsh_gnu_sym_ldflag = '-Wl,--version-script=' + libmrsh_gnu_sym_path\nlibmrsh_darwin_sym_path = join_paths(meson.current_source_dir(), 'libmrsh.darwin.sym')\n# On FreeBSD, -Wl,--version-script only works with -shared\nif cc.links('', name: '-Wl,--version-script', args: ['-shared', libmrsh_gnu_sym_ldflag])\n\t# GNU ld\n\tlink_args = [libmrsh_gnu_sym_ldflag]\nelif host_machine.system() == 'darwin' and cc.has_multi_link_arguments('-Wl,-exported_symbols_list', libmrsh_darwin_sym_path)\n\t# Clang on Darwin\n\tlink_args = ['-Wl,-exported_symbols_list', libmrsh_darwin_sym_path]\nelse\n\terror('Linker doesn\\'t support --version-script or -exported_symbols_list')\nendif\n\nlib_mrsh = library(\n\tmeson.project_name(),\n\tfiles(\n\t\t'arithm.c',\n\t\t'array.c',\n\t\t'ast_print.c',\n\t\t'ast.c',\n\t\t'buffer.c',\n\t\t'builtin/alias.c',\n\t\t'builtin/bg.c',\n\t\t'builtin/break.c',\n\t\t'builtin/builtin.c',\n\t\t'builtin/cd.c',\n\t\t'builtin/colon.c',\n\t\t'builtin/command.c',\n\t\t'builtin/dot.c',\n\t\t'builtin/eval.c',\n\t\t'builtin/exec.c',\n\t\t'builtin/exit.c',\n\t\t'builtin/export.c',\n\t\t'builtin/false.c',\n\t\t'builtin/fg.c',\n\t\t'builtin/getopts.c',\n\t\t'builtin/hash.c',\n\t\t'builtin/jobs.c',\n\t\t'builtin/pwd.c',\n\t\t'builtin/read.c',\n\t\t'builtin/return.c',\n\t\t'builtin/set.c',\n\t\t'builtin/shift.c',\n\t\t'builtin/times.c',\n\t\t'builtin/trap.c',\n\t\t'builtin/true.c',\n\t\t'builtin/type.c',\n\t\t'builtin/ulimit.c',\n\t\t'builtin/umask.c',\n\t\t'builtin/unalias.c',\n\t\t'builtin/unset.c',\n\t\t'builtin/unspecified.c',\n\t\t'builtin/wait.c',\n\t\t'getopt.c',\n\t\t'hashtable.c',\n\t\t'parser/arithm.c',\n\t\t'parser/parser.c',\n\t\t'parser/program.c',\n\t\t'parser/word.c',\n\t\t'shell/arithm.c',\n\t\t'shell/entry.c',\n\t\t'shell/job.c',\n\t\t'shell/path.c',\n\t\t'shell/process.c',\n\t\t'shell/redir.c',\n\t\t'shell/shell.c',\n\t\t'shell/task/pipeline.c',\n\t\t'shell/task/simple_command.c',\n\t\t'shell/task/task.c',\n\t\t'shell/task/word.c',\n\t\t'shell/trap.c',\n\t\t'shell/word.c',\n\t),\n\tinclude_directories: mrsh_inc,\n\tversion: meson.project_version(),\n\tlink_args: link_args,\n\tinstall: true,\n)\n\nmrsh = declare_dependency(\n\tlink_with: lib_mrsh,\n\tinclude_directories: mrsh_inc,\n)\n\nshell_deps = [mrsh]\nshell_files = [\n\t'main.c'\n]\nif readline.found()\n\tshell_deps += [readline]\n\tshell_files += ['frontend/readline.c']\nelse\n\tshell_files += ['frontend/basic.c']\nendif\n\nmrsh_exe = executable(\n\t'mrsh',\n\tfiles(shell_files),\n\tdependencies: shell_deps,\n\tinstall: true,\n)\n\nsubdir('example')\nsubdir('test')\n\npkgconfig = import('pkgconfig')\npkgconfig.generate(\n\tlibraries: lib_mrsh,\n\tversion: meson.project_version(),\n\tfilebase: meson.project_name(),\n\tname: meson.project_name(),\n\tdescription: 'POSIX shell library',\n)\n\nstatus = [\n\t'',\n\t'Features:',\n\t'  readline: @0@'.format(readline.found()),\n\t'  examples: @0@'.format(get_option('examples')),\n]\nmessage('\\n'.join(status))\n"
  },
  {
    "path": "meson_options.txt",
    "content": "option(\n\t'readline',\n\ttype: 'feature',\n\tvalue: 'auto',\n\tdescription: 'Enable improved interactive interface via readline',\n)\n\noption(\n\t'readline-provider',\n\ttype: 'combo',\n\tchoices: ['readline', 'editline'],\n\tvalue: 'readline',\n\tdescription: 'Provider of the readline library',\n)\n\noption(\n\t'examples',\n\ttype: 'boolean',\n\tvalue: true,\n\tdescription: 'Build example programs',\n)\n\noption(\n\t'reference-shell',\n\ttype: 'string',\n\tvalue: 'sh',\n\tdescription: 'Reference shell used in tests',\n)\n\noption(\n\t'test-undefined-behavior',\n\ttype: 'boolean',\n\tvalue: true,\n\tdescription: 'Run tests that assert undefined behavior fails',\n)\n"
  },
  {
    "path": "mkpc",
    "content": "#!/bin/sh\ncat <<EOF > $1\nprefix=$PREFIX\nlibdir=\\${prefix}/lib\nincludedir=\\${prefix}/include\n\nName: mrsh\nDescription: POSIX shell library\nVersion: 0.0.0\nLibs: -L\\${libdir} -lmrsh\nCflags: -I\\${includedir}\nEOF\n"
  },
  {
    "path": "parser/arithm.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <ctype.h>\n#include <errno.h>\n#include <mrsh/arithm.h>\n#include <stdlib.h>\n#include <string.h>\n#include \"parser.h\"\n\nstatic bool parse_char(struct mrsh_parser *parser, char c) {\n\tif (parser_peek_char(parser) != c) {\n\t\treturn false;\n\t}\n\tparser_read_char(parser);\n\treturn true;\n}\n\nstatic bool parse_whitespace(struct mrsh_parser *parser) {\n\tif (!isspace(parser_peek_char(parser))) {\n\t\treturn false;\n\t}\n\tparser_read_char(parser);\n\treturn true;\n}\n\nstatic inline void consume_whitespace(struct mrsh_parser *parser) {\n\twhile (parse_whitespace(parser)) {\n\t\t// This space is intentionally left blank\n\t}\n}\n\nstatic bool expect_char(struct mrsh_parser *parser, char c) {\n\tif (parse_char(parser, c)) {\n\t\treturn true;\n\t}\n\tchar msg[128];\n\tsnprintf(msg, sizeof(msg), \"expected '%c'\", c);\n\tparser_set_error(parser, msg);\n\treturn false;\n}\n\nstatic bool parse_str(struct mrsh_parser *parser, const char *str) {\n\tsize_t len = strlen(str);\n\n\tfor (size_t i = 0; i < len; ++i) {\n\t\tparser_peek(parser, NULL, i + 1);\n\n\t\tif (parser->buf.data[i] != str[i]) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tparser_read(parser, NULL, len);\n\treturn true;\n}\n\nstatic size_t peek_literal(struct mrsh_parser *parser) {\n\tsize_t i = 0;\n\twhile (true) {\n\t\tparser_peek(parser, NULL, i + 1);\n\n\t\tchar c = parser->buf.data[i];\n\t\t// TODO: 0x, 0b prefixes\n\t\tif (!isdigit(c)) {\n\t\t\tbreak;\n\t\t}\n\n\t\t++i;\n\t}\n\n\treturn i;\n}\n\nstatic struct mrsh_arithm_literal *literal(struct mrsh_parser *parser) {\n\tsize_t len = peek_literal(parser);\n\tif (len == 0) {\n\t\treturn NULL;\n\t}\n\n\tchar *str = strndup(parser->buf.data, len);\n\tparser_read(parser, NULL, len);\n\n\tchar *end;\n\terrno = 0;\n\tlong value = strtol(str, &end, 0);\n\tif (end[0] != '\\0' || errno != 0) {\n\t\tfree(str);\n\t\tparser_set_error(parser, \"failed to parse literal\");\n\t\treturn NULL;\n\t}\n\tfree(str);\n\n\treturn mrsh_arithm_literal_create(value);\n}\n\nstatic struct mrsh_arithm_variable *variable(struct mrsh_parser *parser) {\n\tsize_t name_len = peek_name(parser, false);\n\tif (name_len == 0) {\n\t\treturn NULL;\n\t}\n\n\tchar *name = malloc(name_len + 1);\n\tparser_read(parser, name, name_len);\n\tname[name_len] = '\\0';\n\n\treturn mrsh_arithm_variable_create(name);\n}\n\nstatic struct mrsh_arithm_expr *arithm_expr(struct mrsh_parser *parser);\n\nstatic struct mrsh_arithm_unop *unop(struct mrsh_parser *parser) {\n\tenum mrsh_arithm_unop_type type;\n\tswitch (parser_peek_char(parser)) {\n\tcase '+':\n\t\ttype = MRSH_ARITHM_UNOP_PLUS;\n\t\tbreak;\n\tcase '-':\n\t\ttype = MRSH_ARITHM_UNOP_MINUS;\n\t\tbreak;\n\tcase '~':\n\t\ttype = MRSH_ARITHM_UNOP_TILDE;\n\t\tbreak;\n\tcase '!':\n\t\ttype = MRSH_ARITHM_UNOP_BANG;\n\t\tbreak;\n\tdefault:\n\t\treturn NULL;\n\t}\n\tparser_read_char(parser);\n\n\tstruct mrsh_arithm_expr *body = arithm_expr(parser);\n\tif (body == NULL) {\n\t\tparser_set_error(parser,\n\t\t\t\"expected an arithmetic expression after unary operator\");\n\t\treturn NULL;\n\t}\n\n\treturn mrsh_arithm_unop_create(type, body);\n}\n\nstatic struct mrsh_arithm_expr *paren(struct mrsh_parser *parser) {\n\tif (!parse_char(parser, '(')) {\n\t\treturn NULL;\n\t}\n\n\tconsume_whitespace(parser);\n\tstruct mrsh_arithm_expr *expr = arithm_expr(parser);\n\t// consume_whitespace() is not needed here, since the call to arithm_expr()\n\t// consumes the trailing whitespace.\n\n\tif (!expect_char(parser, ')')) {\n\t\tmrsh_arithm_expr_destroy(expr);\n\t\treturn NULL;\n\t}\n\n\treturn expr;\n}\n\nstatic struct mrsh_arithm_expr *term(struct mrsh_parser *parser) {\n\tstruct mrsh_arithm_expr *expr = paren(parser);\n\tif (expr != NULL) {\n\t\treturn expr;\n\t}\n\n\tstruct mrsh_arithm_unop *au = unop(parser);\n\tif (au != NULL) {\n\t\treturn &au->expr;\n\t}\n\n\tstruct mrsh_arithm_literal *al = literal(parser);\n\tif (al != NULL) {\n\t\treturn &al->expr;\n\t}\n\n\tstruct mrsh_arithm_variable *av = variable(parser);\n\tif (av != NULL) {\n\t\treturn &av->expr;\n\t}\n\n\treturn NULL;\n}\n\nstatic struct mrsh_arithm_expr *factor(struct mrsh_parser *parser) {\n\tstruct mrsh_arithm_expr *expr = term(parser);\n\tif (expr == NULL) {\n\t\treturn NULL;\n\t}\n\n\t/* This loop ensures we parse factors as left-assossiative */\n\twhile (true) {\n\t\tconsume_whitespace(parser);\n\t\tenum mrsh_arithm_binop_type type;\n\t\tif (parse_char(parser, '*')) {\n\t\t\ttype = MRSH_ARITHM_BINOP_ASTERISK;\n\t\t} else if (parse_char(parser, '/')) {\n\t\t\ttype = MRSH_ARITHM_BINOP_SLASH;\n\t\t} else if (parse_char(parser, '%')) {\n\t\t\ttype = MRSH_ARITHM_BINOP_PERCENT;\n\t\t} else {\n\t\t\treturn expr;\n\t\t}\n\t\tconsume_whitespace(parser);\n\n\t\t/* Instead of calling ourselves recursively, we call term for\n\t\t * left-associativity */\n\t\tstruct mrsh_arithm_expr *right = term(parser);\n\t\tif (right == NULL) {\n\t\t\tparser_set_error(parser, \"expected a term after *, / or % operator\");\n\t\t\treturn NULL;\n\t\t}\n\n\t\tstruct mrsh_arithm_binop *bo =\n\t\t\tmrsh_arithm_binop_create(type, expr, right);\n\t\texpr = &bo->expr;\n\t}\n}\n\nstatic struct mrsh_arithm_expr *addend(struct mrsh_parser *parser) {\n\tstruct mrsh_arithm_expr *expr = factor(parser);\n\tif (expr == NULL) {\n\t\treturn NULL;\n\t}\n\n\t/* This loop ensures we parse addends as left-assossiative */\n\twhile (true) {\n\t\t// consume_whitespace() is not needed here, since the call to factor()\n\t\t// consumes trailing whitespace.\n\t\tenum mrsh_arithm_binop_type type;\n\t\tif (parse_char(parser, '+')) {\n\t\t\ttype = MRSH_ARITHM_BINOP_PLUS;\n\t\t} else if (parse_char(parser, '-')) {\n\t\t\ttype = MRSH_ARITHM_BINOP_MINUS;\n\t\t} else {\n\t\t\treturn expr;\n\t\t}\n\t\tconsume_whitespace(parser);\n\n\t\t/* Instead of calling ourselves recursively, we call factor for\n\t\t * left-associativity */\n\t\tstruct mrsh_arithm_expr *right = factor(parser);\n\t\tif (right == NULL) {\n\t\t\tparser_set_error(parser, \"expected a factor after + or - operator\");\n\t\t\treturn NULL;\n\t\t}\n\n\t\tstruct mrsh_arithm_binop *bo =\n\t\t\tmrsh_arithm_binop_create(type, expr, right);\n\t\texpr = &bo->expr;\n\t}\n}\n\nstatic struct mrsh_arithm_expr *shift(struct mrsh_parser *parser) {\n\tstruct mrsh_arithm_expr *left = addend(parser);\n\tif (left == NULL) {\n\t\treturn NULL;\n\t}\n\n\t// consume_whitespace() is not needed here, since the call to addend()\n\t// consumes the trailing whitespace.\n\tenum mrsh_arithm_binop_type type;\n\tif (parse_str(parser, \"<<\")) {\n\t\ttype = MRSH_ARITHM_BINOP_DLESS;\n\t} else if (parse_str(parser, \">>\")) {\n\t\ttype = MRSH_ARITHM_BINOP_DGREAT;\n\t} else {\n\t\treturn left;\n\t}\n\tconsume_whitespace(parser);\n\n\tstruct mrsh_arithm_expr *right = shift(parser);\n\tif (right == NULL) {\n\t\tmrsh_arithm_expr_destroy(left);\n\t\tparser_set_error(parser, \"expected a term\");\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_arithm_binop *bo = mrsh_arithm_binop_create(type, left, right);\n\treturn &bo->expr;\n}\n\nstatic struct mrsh_arithm_expr *comp(struct mrsh_parser *parser) {\n\tstruct mrsh_arithm_expr *left = shift(parser);\n\tif (left == NULL) {\n\t\treturn NULL;\n\t}\n\n\tenum mrsh_arithm_binop_type type;\n\tif (parse_str(parser, \"<=\")) {\n\t\ttype = MRSH_ARITHM_BINOP_LESSEQ;\n\t} else if (parse_char(parser, '<')) {\n\t\ttype = MRSH_ARITHM_BINOP_LESS;\n\t} else if (parse_str(parser, \">=\")) {\n\t\ttype = MRSH_ARITHM_BINOP_GREATEQ;\n\t} else if (parse_char(parser, '>')) {\n\t\ttype = MRSH_ARITHM_BINOP_GREAT;\n\t} else {\n\t\treturn left;\n\t}\n\tconsume_whitespace(parser);\n\n\tstruct mrsh_arithm_expr *right = comp(parser);\n\tif (right == NULL) {\n\t\tmrsh_arithm_expr_destroy(left);\n\t\tparser_set_error(parser, \"expected a term\");\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_arithm_binop *bo = mrsh_arithm_binop_create(type, left, right);\n\treturn &bo->expr;\n}\n\nstatic struct mrsh_arithm_expr *equal(struct mrsh_parser *parser) {\n\tstruct mrsh_arithm_expr *left = comp(parser);\n\tif (left == NULL) {\n\t\treturn NULL;\n\t}\n\n\tenum mrsh_arithm_binop_type type;\n\tif (parse_str(parser, \"==\")) {\n\t\ttype = MRSH_ARITHM_BINOP_DEQ;\n\t} else if (parse_str(parser, \"!=\")) {\n\t\ttype = MRSH_ARITHM_BINOP_BANGEQ;\n\t} else {\n\t\treturn left;\n\t}\n\n\tstruct mrsh_arithm_expr *right = equal(parser);\n\tif (right == NULL) {\n\t\tmrsh_arithm_expr_destroy(left);\n\t\tparser_set_error(parser, \"expected a term\");\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_arithm_binop *bo = mrsh_arithm_binop_create(type, left, right);\n\treturn &bo->expr;\n}\n\nstatic bool parse_binop(struct mrsh_parser *parser, const char *str) {\n\tsize_t len = strlen(str);\n\n\tfor (size_t i = 0; i < len; ++i) {\n\t\tparser_peek(parser, NULL, i + 1);\n\n\t\tif (parser->buf.data[i] != str[i]) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// Make sure we don't parse \"&&\" as \"&\"\n\tparser_peek(parser, NULL, len + 1);\n\tswitch (parser->buf.data[len]) {\n\tcase '|':\n\tcase '&':\n\t\treturn false;\n\t}\n\n\tparser_read(parser, NULL, len);\n\treturn true;\n}\n\nstatic struct mrsh_arithm_expr *binop(struct mrsh_parser *parser,\n\t\tenum mrsh_arithm_binop_type type, const char *str,\n\t\tstruct mrsh_arithm_expr *(*term)(struct mrsh_parser *parser)) {\n\tstruct mrsh_arithm_expr *left = term(parser);\n\tif (left == NULL) {\n\t\treturn NULL;\n\t}\n\tif (!parse_binop(parser, str)) {\n\t\treturn left;\n\t}\n\tconsume_whitespace(parser);\n\n\tstruct mrsh_arithm_expr *right = binop(parser, type, str, term);\n\tif (right == NULL) {\n\t\tmrsh_arithm_expr_destroy(left);\n\t\tparser_set_error(parser, \"expected a term\");\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_arithm_binop *bo = mrsh_arithm_binop_create(type, left, right);\n\treturn &bo->expr;\n}\n\nstatic struct mrsh_arithm_expr *bitwise_and(struct mrsh_parser *parser) {\n\treturn binop(parser, MRSH_ARITHM_BINOP_AND, \"&\", equal);\n}\n\nstatic struct mrsh_arithm_expr *bitwise_xor(struct mrsh_parser *parser) {\n\treturn binop(parser, MRSH_ARITHM_BINOP_CIRC, \"^\", bitwise_and);\n}\n\nstatic struct mrsh_arithm_expr *bitwise_or(struct mrsh_parser *parser) {\n\treturn binop(parser, MRSH_ARITHM_BINOP_OR, \"|\", bitwise_xor);\n}\n\nstatic struct mrsh_arithm_expr *logical_and(struct mrsh_parser *parser) {\n\treturn binop(parser, MRSH_ARITHM_BINOP_DAND, \"&&\", bitwise_or);\n}\n\nstatic struct mrsh_arithm_expr *logical_or(struct mrsh_parser *parser) {\n\treturn binop(parser, MRSH_ARITHM_BINOP_DOR, \"||\", logical_and);\n}\n\nstatic struct mrsh_arithm_expr *ternary(struct mrsh_parser *parser) {\n\tstruct mrsh_arithm_expr *expr = logical_or(parser);\n\tif (expr == NULL) {\n\t\treturn NULL;\n\t}\n\tif (!parse_char(parser, '?')) {\n\t\treturn expr;\n\t}\n\tstruct mrsh_arithm_expr *condition = expr;\n\n\tstruct mrsh_arithm_expr *body = ternary(parser);\n\tif (body == NULL) {\n\t\tparser_set_error(parser, \"expected a logical or term\");\n\t\tgoto error_body;\n\t}\n\n\tif (!expect_char(parser, ':')) {\n\t\tgoto error_semi;\n\t}\n\n\tstruct mrsh_arithm_expr *else_part = ternary(parser);\n\tif (else_part == NULL) {\n\t\tparser_set_error(parser, \"expected an or term\");\n\t\tgoto error_else_part;\n\t}\n\n\tstruct mrsh_arithm_cond *c =\n\t\tmrsh_arithm_cond_create(condition, body, else_part);\n\treturn &c->expr;\n\nerror_else_part:\nerror_semi:\n\tmrsh_arithm_expr_destroy(body);\nerror_body:\n\tmrsh_arithm_expr_destroy(condition);\n\treturn NULL;\n}\n\nstatic bool peek_assign_op(struct mrsh_parser *parser, size_t *offset,\n\t\tconst char *str) {\n\tsize_t len = strlen(str);\n\n\tfor (size_t i = 0; i < len; ++i) {\n\t\tparser_peek(parser, NULL, *offset + i + 1);\n\n\t\tif (parser->buf.data[*offset + i] != str[i]) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t*offset += len;\n\treturn true;\n}\n\nstatic struct mrsh_arithm_expr *assignment(struct mrsh_parser *parser) {\n\tsize_t name_len = peek_name(parser, false);\n\tif (name_len == 0) {\n\t\treturn NULL;\n\t}\n\n\tenum mrsh_arithm_assign_op op;\n\tsize_t offset = name_len;\n\tif (peek_assign_op(parser, &offset, \"=\")) {\n\t\top = MRSH_ARITHM_ASSIGN_NONE;\n\t} else if (peek_assign_op(parser, &offset, \"*=\")) {\n\t\top = MRSH_ARITHM_ASSIGN_ASTERISK;\n\t} else if (peek_assign_op(parser, &offset, \"/=\")) {\n\t\top = MRSH_ARITHM_ASSIGN_SLASH;\n\t} else if (peek_assign_op(parser, &offset, \"%=\")) {\n\t\top = MRSH_ARITHM_ASSIGN_PERCENT;\n\t} else if (peek_assign_op(parser, &offset, \"+=\")) {\n\t\top = MRSH_ARITHM_ASSIGN_PLUS;\n\t} else if (peek_assign_op(parser, &offset, \"-=\")) {\n\t\top = MRSH_ARITHM_ASSIGN_MINUS;\n\t} else if (peek_assign_op(parser, &offset, \"<<=\")) {\n\t\top = MRSH_ARITHM_ASSIGN_DLESS;\n\t} else if (peek_assign_op(parser, &offset, \">>=\")) {\n\t\top = MRSH_ARITHM_ASSIGN_DGREAT;\n\t} else if (peek_assign_op(parser, &offset, \"&=\")) {\n\t\top = MRSH_ARITHM_ASSIGN_AND;\n\t} else if (peek_assign_op(parser, &offset, \"^=\")) {\n\t\top = MRSH_ARITHM_ASSIGN_CIRC;\n\t} else if (peek_assign_op(parser, &offset, \"|=\")) {\n\t\top = MRSH_ARITHM_ASSIGN_OR;\n\t} else {\n\t\treturn NULL;\n\t}\n\t// offset is now the offset till the end of the operator\n\n\tchar *name = malloc(name_len + 1);\n\tparser_read(parser, name, name_len);\n\tname[name_len] = '\\0';\n\n\tparser_read(parser, NULL, offset - name_len); // operator\n\n\tstruct mrsh_arithm_expr *value = arithm_expr(parser);\n\tif (value == NULL) {\n\t\tfree(name);\n\t\tparser_set_error(parser, \"expected an assignment value\");\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_arithm_assign *a = mrsh_arithm_assign_create(op, name, value);\n\treturn &a->expr;\n}\n\nstatic struct mrsh_arithm_expr *arithm_expr(struct mrsh_parser *parser) {\n\tstruct mrsh_arithm_expr *expr = assignment(parser);\n\tif (expr != NULL) {\n\t\treturn expr;\n\t}\n\n\treturn ternary(parser);\n}\n\nstruct mrsh_arithm_expr *mrsh_parse_arithm_expr(struct mrsh_parser *parser) {\n\tconsume_whitespace(parser);\n\n\tstruct mrsh_arithm_expr *expr = arithm_expr(parser);\n\tif (expr == NULL) {\n\t\treturn NULL;\n\t}\n\n\tif (parser_peek_char(parser) != '\\0') {\n\t\tparser_set_error(parser,\n\t\t\t\"garbage at the end of the arithmetic expression\");\n\t\tmrsh_arithm_expr_destroy(expr);\n\t\treturn NULL;\n\t}\n\n\treturn expr;\n}\n"
  },
  {
    "path": "parser/parser.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <assert.h>\n#include <ctype.h>\n#include <errno.h>\n#include <mrsh/buffer.h>\n#include <stdbool.h>\n#include <stddef.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include \"ast.h\"\n#include \"parser.h\"\n\n#define READ_SIZE 4096\n\n// Keep sorted from the longest to the shortest\nconst struct symbol operators[] = {\n\t{ DLESSDASH, \"<<-\" },\n\t{ AND_IF, \"&&\" },\n\t{ OR_IF, \"||\" },\n\t{ DSEMI, \";;\" },\n\t{ DLESS, \"<<\" },\n\t{ DGREAT, \">>\" },\n\t{ LESSAND, \"<&\" },\n\t{ GREATAND, \">&\" },\n\t{ LESSGREAT, \"<>\" },\n\t{ CLOBBER, \">|\" },\n};\n\nconst size_t operators_len = sizeof(operators) / sizeof(operators[0]);\nconst size_t operators_max_str_len = 3;\n\nconst char *keywords[] = {\n\t\"if\",\n\t\"then\",\n\t\"else\",\n\t\"elif\",\n\t\"fi\",\n\t\"do\",\n\t\"done\",\n\n\t\"case\",\n\t\"esac\",\n\t\"while\",\n\t\"until\",\n\t\"for\",\n\n\t\"{\",\n\t\"}\",\n\t\"!\",\n\t\"in\",\n};\n\nconst size_t keywords_len = sizeof(keywords) / sizeof(keywords[0]);\n\nstatic struct mrsh_parser *parser_create(void) {\n\tstruct mrsh_parser *parser = calloc(1, sizeof(struct mrsh_parser));\n\tparser->fd = -1;\n\tparser->pos.line = parser->pos.column = 1;\n\treturn parser;\n}\n\nstruct mrsh_parser *mrsh_parser_with_fd(int fd) {\n\tstruct mrsh_parser *parser = parser_create();\n\tparser->fd = fd;\n\treturn parser;\n}\n\nstruct mrsh_parser *mrsh_parser_with_data(const char *buf, size_t len) {\n\tstruct mrsh_parser *parser = parser_create();\n\tmrsh_buffer_append(&parser->buf, buf, len);\n\tmrsh_buffer_append_char(&parser->buf, '\\0');\n\treturn parser;\n}\n\nstruct mrsh_parser *mrsh_parser_with_buffer(struct mrsh_buffer *buf) {\n\tstruct mrsh_parser *parser = parser_create();\n\tparser->in_buf = buf;\n\treturn parser;\n}\n\nvoid mrsh_parser_destroy(struct mrsh_parser *parser) {\n\tif (parser == NULL) {\n\t\treturn;\n\t}\n\tmrsh_buffer_finish(&parser->buf);\n\tmrsh_array_finish(&parser->here_documents);\n\tfree(parser->error.msg);\n\tfree(parser);\n}\n\nstatic ssize_t parser_peek_fd(struct mrsh_parser *parser, size_t size) {\n\tassert(parser->fd >= 0);\n\n\tsize_t n_read = 0;\n\twhile (n_read < size) {\n\t\tchar *dst = mrsh_buffer_reserve(&parser->buf, READ_SIZE);\n\n\t\terrno = 0;\n\t\tssize_t n = read(parser->fd, dst, READ_SIZE);\n\t\tif (n < 0 && errno == EINTR) {\n\t\t\tcontinue;\n\t\t} else if (n < 0) {\n\t\t\treturn -1;\n\t\t} else if (n == 0) {\n\t\t\tbreak;\n\t\t}\n\n\t\tparser->buf.len += n;\n\t\tn_read += n;\n\t}\n\n\treturn n_read;\n}\n\nstatic ssize_t parser_peek_buffer(struct mrsh_parser *parser, size_t size) {\n\tassert(parser->in_buf != NULL);\n\n\tsize_t n_read = parser->in_buf->len;\n\tif (n_read == 0) {\n\t\treturn 0;\n\t}\n\n\tif (parser->buf.len == 0) {\n\t\t// Move data from one buffer to the other\n\t\tmrsh_buffer_finish(&parser->buf);\n\t\tmemcpy(&parser->buf, parser->in_buf, sizeof(struct mrsh_buffer));\n\t\tmemset(parser->in_buf, 0, sizeof(struct mrsh_buffer));\n\t} else {\n\t\tmrsh_buffer_append(&parser->buf, parser->in_buf->data, n_read);\n\t\tparser->in_buf->len = 0;\n\t}\n\n\treturn n_read;\n}\n\nsize_t parser_peek(struct mrsh_parser *parser, char *buf, size_t size) {\n\tif (size > parser->buf.len) {\n\t\tsize_t n_more = size - parser->buf.len;\n\n\t\tssize_t n_read;\n\t\tif (parser->fd >= 0) {\n\t\t\tn_read = parser_peek_fd(parser, n_more);\n\t\t} else if (parser->in_buf != NULL) {\n\t\t\tn_read = parser_peek_buffer(parser, n_more);\n\t\t} else {\n\t\t\tn_read = 0;\n\t\t}\n\n\t\tif (n_read < 0) {\n\t\t\tparser_set_error(parser, \"failed to read\");\n\t\t\treturn 0; // TODO: better error handling\n\t\t}\n\n\t\tif ((size_t)n_read < n_more) {\n\t\t\tif (!parser->eof) {\n\t\t\t\tmrsh_buffer_append_char(&parser->buf, '\\0');\n\t\t\t\tparser->eof = true;\n\t\t\t}\n\t\t\tsize = parser->buf.len;\n\t\t}\n\t}\n\n\tif (buf != NULL) {\n\t\tmemcpy(buf, parser->buf.data, size);\n\t}\n\treturn size;\n}\n\nchar parser_peek_char(struct mrsh_parser *parser) {\n\tchar c = '\\0';\n\tparser_peek(parser, &c, sizeof(char));\n\treturn c;\n}\n\nsize_t parser_read(struct mrsh_parser *parser, char *buf, size_t size) {\n\tsize_t n = parser_peek(parser, buf, size);\n\tif (n > 0) {\n\t\tfor (size_t i = 0; i < n; ++i) {\n\t\t\tassert(parser->buf.data[i] != '\\0');\n\t\t\t++parser->pos.offset;\n\t\t\tif (parser->buf.data[i] == '\\n') {\n\t\t\t\t++parser->pos.line;\n\t\t\t\tparser->pos.column = 1;\n\t\t\t} else {\n\t\t\t\t++parser->pos.column;\n\t\t\t}\n\t\t}\n\t\tmemmove(parser->buf.data, parser->buf.data + n, parser->buf.len - n);\n\t\tparser->buf.len -= n;\n\n\t\tparser->continuation_line = false;\n\t}\n\treturn n;\n}\n\nchar parser_read_char(struct mrsh_parser *parser) {\n\tchar c = '\\0';\n\tparser_read(parser, &c, sizeof(char));\n\treturn c;\n}\n\nvoid read_continuation_line(struct mrsh_parser *parser) {\n\tchar c = parser_read_char(parser);\n\tassert(c == '\\n');\n\tparser->continuation_line = true;\n}\n\nbool is_operator_start(char c) {\n\tswitch (c) {\n\tcase '&':\n\tcase '|':\n\tcase ';':\n\tcase '<':\n\tcase '>':\n\t\treturn true;\n\tdefault:\n\t\treturn false;\n\t}\n}\n\nvoid parser_set_error(struct mrsh_parser *parser, const char *msg) {\n\tif (msg != NULL) {\n\t\tif (parser->error.msg != NULL) {\n\t\t\treturn;\n\t\t}\n\n\t\tparser->here_documents.len = 0;\n\t\tparser->error.pos = parser->pos;\n\t\tparser->error.msg = strdup(msg);\n\t} else {\n\t\tfree(parser->error.msg);\n\n\t\tparser->error.pos = (struct mrsh_position){0};\n\t\tparser->error.msg = NULL;\n\t}\n}\n\nconst char *mrsh_parser_error(struct mrsh_parser *parser,\n\t\tstruct mrsh_position *pos) {\n\tif (pos != NULL) {\n\t\t*pos = parser->error.pos;\n\t}\n\treturn parser->error.msg;\n}\n\nvoid parser_begin(struct mrsh_parser *parser) {\n\tparser_set_error(parser, NULL);\n\tparser->continuation_line = false;\n}\n\n// See section 2.3 Token Recognition\nstatic void next_symbol(struct mrsh_parser *parser) {\n\tparser->has_sym = true;\n\n\tchar c = parser_peek_char(parser);\n\n\tif (c == '\\0') {\n\t\tparser->sym = EOF_TOKEN;\n\t\treturn;\n\t}\n\tif (c == '\\n') {\n\t\tparser->sym = NEWLINE;\n\t\treturn;\n\t}\n\n\tif (is_operator_start(c)) {\n\t\tfor (size_t i = 0; i < operators_len; ++i) {\n\t\t\tconst char *str = operators[i].str;\n\n\t\t\tsize_t j;\n\t\t\tfor (j = 0; str[j] != '\\0'; ++j) {\n\t\t\t\tsize_t n = j + 1;\n\t\t\t\tsize_t n_read = parser_peek(parser, NULL, n);\n\t\t\t\tif (n != n_read || parser->buf.data[j] != str[j]) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (str[j] == '\\0') {\n\t\t\t\tparser->sym = operators[i].name;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (isblank(c)) {\n\t\tparser_read_char(parser);\n\t\tnext_symbol(parser);\n\t\treturn;\n\t}\n\n\tif (c == '#') {\n\t\twhile (true) {\n\t\t\tchar c = parser_peek_char(parser);\n\t\t\tif (c == '\\0' || c == '\\n') {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tparser_read_char(parser);\n\t\t}\n\t\tnext_symbol(parser);\n\t\treturn;\n\t}\n\n\tparser->sym = TOKEN;\n}\n\nenum symbol_name get_symbol(struct mrsh_parser *parser) {\n\tif (!parser->has_sym) {\n\t\tnext_symbol(parser);\n\t}\n\treturn parser->sym;\n}\n\nvoid consume_symbol(struct mrsh_parser *parser) {\n\tparser->has_sym = false;\n}\n\nbool symbol(struct mrsh_parser *parser, enum symbol_name sym) {\n\treturn get_symbol(parser) == sym;\n}\n\nbool eof(struct mrsh_parser *parser) {\n\treturn symbol(parser, EOF_TOKEN);\n}\n\nbool newline(struct mrsh_parser *parser) {\n\tif (!symbol(parser, NEWLINE)) {\n\t\treturn false;\n\t}\n\tchar c = parser_read_char(parser);\n\tassert(c == '\\n');\n\tconsume_symbol(parser);\n\treturn true;\n}\n\nvoid linebreak(struct mrsh_parser *parser) {\n\twhile (newline(parser)) {\n\t\t// This space is intentionally left blank\n\t}\n}\n\nbool newline_list(struct mrsh_parser *parser) {\n\tif (!newline(parser)) {\n\t\treturn false;\n\t}\n\n\tlinebreak(parser);\n\treturn true;\n}\n\nbool mrsh_parser_eof(struct mrsh_parser *parser) {\n\treturn parser->has_sym && parser->sym == EOF_TOKEN;\n}\n\nvoid mrsh_parser_set_alias_func(struct mrsh_parser *parser,\n\t\tmrsh_parser_alias_func alias, void *user_data) {\n\tparser->alias = alias;\n\tparser->alias_user_data = user_data;\n}\n\nbool mrsh_parser_continuation_line(struct mrsh_parser *parser) {\n\treturn parser->continuation_line;\n}\n\nvoid mrsh_parser_reset(struct mrsh_parser *parser) {\n\tparser->buf.len = 0;\n\tparser->has_sym = false;\n\tparser->pos = (struct mrsh_position){0};\n}\n"
  },
  {
    "path": "parser/program.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <assert.h>\n#include <ctype.h>\n#include <mrsh/buffer.h>\n#include <stdbool.h>\n#include <stddef.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"ast.h\"\n#include \"parser.h\"\n\nstatic const char *operator_str(enum symbol_name sym) {\n\tfor (size_t i = 0; i < operators_len; ++i) {\n\t\tif (operators[i].name == sym) {\n\t\t\treturn operators[i].str;\n\t\t}\n\t}\n\treturn NULL;\n}\n\nstatic bool operator(struct mrsh_parser *parser, enum symbol_name sym,\n\t\tstruct mrsh_range *range) {\n\tif (!symbol(parser, sym)) {\n\t\treturn false;\n\t}\n\n\tstruct mrsh_position begin = parser->pos;\n\n\tconst char *str = operator_str(sym);\n\tassert(str != NULL);\n\n\tchar buf[operators_max_str_len];\n\tparser_read(parser, buf, strlen(str));\n\tassert(strncmp(str, buf, strlen(str)) == 0);\n\n\tif (range != NULL) {\n\t\trange->begin = begin;\n\t\trange->end = parser->pos;\n\t}\n\n\tconsume_symbol(parser);\n\treturn true;\n}\n\nstatic int separator_op(struct mrsh_parser *parser) {\n\tif (token(parser, \"&\", NULL)) {\n\t\treturn '&';\n\t}\n\tif (token(parser, \";\", NULL)) {\n\t\treturn ';';\n\t}\n\treturn -1;\n}\n\nstatic size_t peek_alias(struct mrsh_parser *parser) {\n\tsize_t n = peek_word(parser, 0);\n\n\tfor (size_t i = 0; i < n; ++i) {\n\t\tchar c = parser->buf.data[i];\n\t\tswitch (c) {\n\t\tcase '_':\n\t\tcase '!':\n\t\tcase '%':\n\t\tcase ',':\n\t\tcase '@':\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tif (!isalnum(c)) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn n;\n}\n\nstatic void apply_aliases(struct mrsh_parser *parser) {\n\tif (parser->alias == NULL) {\n\t\treturn;\n\t}\n\n\tconst char *last_repl = NULL;\n\twhile (true) {\n\t\tif (!symbol(parser, TOKEN)) {\n\t\t\treturn;\n\t\t}\n\n\t\tsize_t alias_len = peek_alias(parser);\n\t\tif (alias_len == 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tchar *name = strndup(parser->buf.data, alias_len);\n\t\tconst char *repl = parser->alias(name, parser->alias_user_data);\n\t\tfree(name);\n\t\tif (repl == NULL || last_repl == repl) {\n\t\t\tbreak;\n\t\t}\n\n\t\tsize_t trailing_len = parser->buf.len - alias_len;\n\t\tsize_t repl_len = strlen(repl);\n\t\tif (repl_len > alias_len) {\n\t\t\tmrsh_buffer_reserve(&parser->buf, repl_len - alias_len);\n\t\t}\n\t\tmemmove(&parser->buf.data[repl_len], &parser->buf.data[alias_len],\n\t\t\tparser->buf.len - alias_len);\n\t\tmemcpy(parser->buf.data, repl, repl_len);\n\t\tparser->buf.len = repl_len + trailing_len;\n\n\t\t// TODO: fixup parser->pos\n\t\t// TODO: if repl's last char is blank, replace next alias too\n\n\t\tconsume_symbol(parser);\n\t\tlast_repl = repl;\n\t}\n}\n\nstatic bool io_here(struct mrsh_parser *parser, struct mrsh_io_redirect *redir) {\n\tif (operator(parser, DLESS, &redir->op_range)) {\n\t\tredir->op = MRSH_IO_DLESS;\n\t} else if (operator(parser, DLESSDASH, &redir->op_range)) {\n\t\tredir->op = MRSH_IO_DLESSDASH;\n\t} else {\n\t\treturn false;\n\t}\n\n\tredir->name = word(parser, 0);\n\tif (redir->name == NULL) {\n\t\tparser_set_error(parser,\n\t\t\t\"expected a name after IO here-document redirection operator\");\n\t\treturn false;\n\t}\n\t// TODO: check redir->name only contains word strings and lists\n\n\treturn true;\n}\n\nstatic struct mrsh_word *filename(struct mrsh_parser *parser) {\n\t// TODO: Apply rule 2\n\treturn word(parser, 0);\n}\n\nstatic int io_redirect_op(struct mrsh_parser *parser, struct mrsh_range *range) {\n\tif (token(parser, \"<\", range)) {\n\t\treturn MRSH_IO_LESS;\n\t} else if (token(parser, \">\", range)) {\n\t\treturn MRSH_IO_GREAT;\n\t} else if (operator(parser, LESSAND, range)) {\n\t\treturn MRSH_IO_LESSAND;\n\t} else if (operator(parser, GREATAND, range)) {\n\t\treturn MRSH_IO_GREATAND;\n\t} else if (operator(parser, DGREAT, range)) {\n\t\treturn MRSH_IO_DGREAT;\n\t} else if (operator(parser, CLOBBER, range)) {\n\t\treturn MRSH_IO_CLOBBER;\n\t} else if (operator(parser, LESSGREAT, range)) {\n\t\treturn MRSH_IO_LESSGREAT;\n\t} else {\n\t\treturn -1;\n\t}\n}\n\nstatic bool io_file(struct mrsh_parser *parser,\n\t\tstruct mrsh_io_redirect *redir) {\n\tint op = io_redirect_op(parser, &redir->op_range);\n\tif (op < 0) {\n\t\treturn false;\n\t}\n\tredir->op = op;\n\n\tredir->name = filename(parser);\n\tif (redir->name == NULL) {\n\t\tparser_set_error(parser,\n\t\t\t\"expected a filename after IO file redirection operator\");\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nstatic int io_number(struct mrsh_parser *parser) {\n\tif (!symbol(parser, TOKEN)) {\n\t\treturn -1;\n\t}\n\n\tchar c = parser_peek_char(parser);\n\tif (!isdigit(c)) {\n\t\treturn -1;\n\t}\n\n\tchar buf[2];\n\tparser_peek(parser, buf, sizeof(buf));\n\tif (buf[1] != '<' && buf[1] != '>') {\n\t\treturn -1;\n\t}\n\n\tparser_read_char(parser);\n\tconsume_symbol(parser);\n\treturn strtol(buf, NULL, 10);\n}\n\nstatic struct mrsh_io_redirect *io_redirect(struct mrsh_parser *parser) {\n\tstruct mrsh_io_redirect redir = {0};\n\n\tstruct mrsh_position io_number_pos = parser->pos;\n\tredir.io_number = io_number(parser);\n\tif (redir.io_number >= 0) {\n\t\tredir.io_number_pos = io_number_pos;\n\t}\n\n\tredir.op_range.begin = parser->pos;\n\tif (io_file(parser, &redir)) {\n\t\tstruct mrsh_io_redirect *redir_ptr =\n\t\t\tcalloc(1, sizeof(struct mrsh_io_redirect));\n\t\tmemcpy(redir_ptr, &redir, sizeof(struct mrsh_io_redirect));\n\t\tredir.op_range.end = parser->pos;\n\t\treturn redir_ptr;\n\t}\n\tif (io_here(parser, &redir)) {\n\t\tstruct mrsh_io_redirect *redir_ptr =\n\t\t\tcalloc(1, sizeof(struct mrsh_io_redirect));\n\t\tmemcpy(redir_ptr, &redir, sizeof(struct mrsh_io_redirect));\n\t\tredir.op_range.end = parser->pos;\n\t\tmrsh_array_add(&parser->here_documents, redir_ptr);\n\t\treturn redir_ptr;\n\t}\n\n\tif (redir.io_number >= 0) {\n\t\tparser_set_error(parser, \"expected an IO redirect after IO number\");\n\t}\n\treturn NULL;\n}\n\nstatic struct mrsh_assignment *assignment_word(struct mrsh_parser *parser) {\n\tif (!symbol(parser, TOKEN)) {\n\t\treturn NULL;\n\t}\n\n\tsize_t name_len = peek_name(parser, false);\n\tif (name_len == 0) {\n\t\treturn NULL;\n\t}\n\n\tparser_peek(parser, NULL, name_len + 1);\n\tif (parser->buf.data[name_len] != '=') {\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_range name_range;\n\tchar *name = read_token(parser, name_len, &name_range);\n\n\tstruct mrsh_position equal_pos = parser->pos;\n\tparser_read(parser, NULL, 1);\n\n\tstruct mrsh_word *value = word(parser, 0);\n\tif (value == NULL) {\n\t\tvalue = &mrsh_word_string_create(strdup(\"\"), false)->word;\n\t}\n\n\tstruct mrsh_assignment *assign = calloc(1, sizeof(struct mrsh_assignment));\n\tassign->name = name;\n\tassign->value = value;\n\tassign->name_range = name_range;\n\tassign->equal_pos = equal_pos;\n\treturn assign;\n}\n\nstatic bool cmd_prefix(struct mrsh_parser *parser,\n\t\tstruct mrsh_simple_command *cmd) {\n\tstruct mrsh_io_redirect *redir = io_redirect(parser);\n\tif (redir != NULL) {\n\t\tmrsh_array_add(&cmd->io_redirects, redir);\n\t\treturn true;\n\t}\n\n\tstruct mrsh_assignment *assign = assignment_word(parser);\n\tif (assign != NULL) {\n\t\tmrsh_array_add(&cmd->assignments, assign);\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nstatic struct mrsh_word *cmd_name(struct mrsh_parser *parser) {\n\tapply_aliases(parser);\n\n\tsize_t word_len = peek_word(parser, 0);\n\tif (word_len == 0) {\n\t\treturn word(parser, 0);\n\t}\n\n\t// TODO: optimize this\n\tfor (size_t i = 0; i < keywords_len; ++i) {\n\t\tif (strlen(keywords[i]) == word_len &&\n\t\t\t\tstrncmp(parser->buf.data, keywords[i], word_len) == 0) {\n\t\t\treturn NULL;\n\t\t}\n\t}\n\n\tstruct mrsh_range range;\n\tchar *str = read_token(parser, word_len, &range);\n\n\tstruct mrsh_word_string *ws = mrsh_word_string_create(str, false);\n\tws->range = range;\n\treturn &ws->word;\n}\n\nstatic bool cmd_suffix(struct mrsh_parser *parser,\n\t\tstruct mrsh_simple_command *cmd) {\n\tstruct mrsh_io_redirect *redir = io_redirect(parser);\n\tif (redir != NULL) {\n\t\tmrsh_array_add(&cmd->io_redirects, redir);\n\t\treturn true;\n\t}\n\n\tstruct mrsh_word *arg = word(parser, 0);\n\tif (arg != NULL) {\n\t\tmrsh_array_add(&cmd->arguments, arg);\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nstatic struct mrsh_simple_command *simple_command(struct mrsh_parser *parser) {\n\tstruct mrsh_simple_command cmd = {0};\n\n\tbool has_prefix = false;\n\twhile (cmd_prefix(parser, &cmd)) {\n\t\thas_prefix = true;\n\t}\n\n\tcmd.name = cmd_name(parser);\n\tif (cmd.name == NULL && !has_prefix) {\n\t\treturn NULL;\n\t} else if (cmd.name != NULL) {\n\t\twhile (cmd_suffix(parser, &cmd)) {\n\t\t\t// This space is intentionally left blank\n\t\t}\n\t}\n\n\treturn mrsh_simple_command_create(cmd.name, &cmd.arguments,\n\t\t&cmd.io_redirects, &cmd.assignments);\n}\n\nstatic int separator(struct mrsh_parser *parser) {\n\tint sep = separator_op(parser);\n\tif (sep != -1) {\n\t\tlinebreak(parser);\n\t\treturn sep;\n\t}\n\n\tif (newline_list(parser)) {\n\t\treturn '\\n';\n\t}\n\n\treturn -1;\n}\n\nstatic struct mrsh_and_or_list *and_or(struct mrsh_parser *parser);\n\nstatic bool expect_here_document(struct mrsh_parser *parser,\n\tstruct mrsh_io_redirect *redir, const char *delim);\n\nstatic struct mrsh_command_list *term(struct mrsh_parser *parser) {\n\tstruct mrsh_and_or_list *and_or_list = and_or(parser);\n\tif (and_or_list == NULL) {\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_command_list *cmd = mrsh_command_list_create();\n\tcmd->and_or_list = and_or_list;\n\n\tstruct mrsh_position separator_pos = parser->pos;\n\tint sep = separator(parser);\n\tif (sep == '&') {\n\t\tcmd->ampersand = true;\n\t}\n\tif (sep >= 0) {\n\t\tcmd->separator_pos = separator_pos;\n\t}\n\n\tif (sep == '\\n' && parser->here_documents.len > 0) {\n\t\tfor (size_t i = 0; i < parser->here_documents.len; ++i) {\n\t\t\tstruct mrsh_io_redirect *redir = parser->here_documents.data[i];\n\n\t\t\tchar *delim = mrsh_word_str(redir->name);\n\t\t\tbool ok = expect_here_document(parser, redir, delim);\n\t\t\tfree(delim);\n\t\t\tif (!ok) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\tparser->here_documents.len = 0;\n\t}\n\n\treturn cmd;\n}\n\nstatic bool compound_list(struct mrsh_parser *parser, struct mrsh_array *cmds) {\n\tlinebreak(parser);\n\n\tstruct mrsh_command_list *l = term(parser);\n\tif (l == NULL) {\n\t\treturn false;\n\t}\n\tmrsh_array_add(cmds, l);\n\n\twhile (true) {\n\t\tl = term(parser);\n\t\tif (l == NULL) {\n\t\t\tbreak;\n\t\t}\n\t\tmrsh_array_add(cmds, l);\n\t}\n\n\treturn true;\n}\n\nstatic bool expect_compound_list(struct mrsh_parser *parser,\n\t\tstruct mrsh_array *cmds) {\n\tif (!compound_list(parser, cmds)) {\n\t\tparser_set_error(parser, \"expected a compound list\");\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nstatic struct mrsh_brace_group *brace_group(struct mrsh_parser *parser) {\n\tstruct mrsh_position lbrace_pos = parser->pos;\n\tif (!token(parser, \"{\", NULL)) {\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_array body = {0};\n\tif (!expect_compound_list(parser, &body)) {\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_position rbrace_pos = parser->pos;\n\tif (!expect_token(parser, \"}\", NULL)) {\n\t\tcommand_list_array_finish(&body);\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_brace_group *bg = mrsh_brace_group_create(&body);\n\tbg->lbrace_pos = lbrace_pos;\n\tbg->rbrace_pos = rbrace_pos;\n\treturn bg;\n}\n\nstatic struct mrsh_subshell *subshell(struct mrsh_parser *parser) {\n\tstruct mrsh_position lparen_pos = parser->pos;\n\tif (!token(parser, \"(\", NULL)) {\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_array body = {0};\n\tif (!expect_compound_list(parser, &body)) {\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_position rparen_pos = parser->pos;\n\tif (!expect_token(parser, \")\", NULL)) {\n\t\tcommand_list_array_finish(&body);\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_subshell *s = mrsh_subshell_create(&body);\n\ts->lparen_pos = lparen_pos;\n\ts->rparen_pos = rparen_pos;\n\treturn s;\n}\n\nstatic struct mrsh_command *else_part(struct mrsh_parser *parser) {\n\tstruct mrsh_range if_range = {0};\n\tif (token(parser, \"elif\", &if_range)) {\n\t\tstruct mrsh_array cond = {0};\n\t\tif (!expect_compound_list(parser, &cond)) {\n\t\t\treturn NULL;\n\t\t}\n\n\t\tstruct mrsh_range then_range;\n\t\tif (!expect_token(parser, \"then\", &then_range)) {\n\t\t\tcommand_list_array_finish(&cond);\n\t\t\treturn NULL;\n\t\t}\n\n\t\tstruct mrsh_array body = {0};\n\t\tif (!expect_compound_list(parser, &body)) {\n\t\t\tcommand_list_array_finish(&cond);\n\t\t\treturn NULL;\n\t\t}\n\n\t\tstruct mrsh_command *ep = else_part(parser);\n\n\t\tstruct mrsh_if_clause *ic = mrsh_if_clause_create(&cond, &body, ep);\n\t\tic->if_range = if_range;\n\t\tic->then_range = then_range;\n\t\treturn &ic->command;\n\t}\n\n\tif (token(parser, \"else\", NULL)) {\n\t\tstruct mrsh_array body = {0};\n\t\tif (!expect_compound_list(parser, &body)) {\n\t\t\treturn NULL;\n\t\t}\n\n\t\t// TODO: position information is missing\n\t\tstruct mrsh_brace_group *bg = mrsh_brace_group_create(&body);\n\t\treturn &bg->command;\n\t}\n\n\treturn NULL;\n}\n\nstatic struct mrsh_if_clause *if_clause(struct mrsh_parser *parser) {\n\tstruct mrsh_range if_range;\n\tif (!token(parser, \"if\", &if_range)) {\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_array cond = {0};\n\tif (!expect_compound_list(parser, &cond)) {\n\t\tgoto error_cond;\n\t}\n\n\tstruct mrsh_range then_range;\n\tif (!expect_token(parser, \"then\", &then_range)) {\n\t\tgoto error_cond;\n\t}\n\n\tstruct mrsh_array body = {0};\n\tif (!expect_compound_list(parser, &body)) {\n\t\tgoto error_body;\n\t}\n\n\tstruct mrsh_command *ep = else_part(parser);\n\n\tstruct mrsh_range fi_range;\n\tif (!expect_token(parser, \"fi\", &fi_range)) {\n\t\tgoto error_else_part;\n\t}\n\n\tstruct mrsh_if_clause *ic = mrsh_if_clause_create(&cond, &body, ep);\n\tic->if_range = if_range;\n\tic->then_range = then_range;\n\tic->fi_range = fi_range;\n\treturn ic;\n\nerror_else_part:\n\tmrsh_command_destroy(ep);\nerror_body:\n\tcommand_list_array_finish(&body);\nerror_cond:\n\tcommand_list_array_finish(&cond);\n\treturn NULL;\n}\n\nstatic bool sequential_sep(struct mrsh_parser *parser) {\n\tif (token(parser, \";\", NULL)) {\n\t\tlinebreak(parser);\n\t\treturn true;\n\t}\n\treturn newline_list(parser);\n}\n\nstatic void wordlist(struct mrsh_parser *parser,\n\t\tstruct mrsh_array *words) {\n\twhile (true) {\n\t\tstruct mrsh_word *w = word(parser, 0);\n\t\tif (w == NULL) {\n\t\t\tbreak;\n\t\t}\n\t\tmrsh_array_add(words, w);\n\t}\n}\n\nstatic bool expect_do_group(struct mrsh_parser *parser,\n\t\tstruct mrsh_array *body, struct mrsh_range *do_range,\n\t\tstruct mrsh_range *done_range) {\n\tif (!token(parser, \"do\", do_range)) {\n\t\tparser_set_error(parser, \"expected 'do'\");\n\t\treturn false;\n\t}\n\n\tif (!expect_compound_list(parser, body)) {\n\t\treturn false;\n\t}\n\n\tif (!token(parser, \"done\", done_range)) {\n\t\tparser_set_error(parser, \"expected 'done'\");\n\t\tcommand_list_array_finish(body);\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nstatic struct mrsh_for_clause *for_clause(struct mrsh_parser *parser) {\n\tstruct mrsh_range for_range;\n\tif (!token(parser, \"for\", &for_range)) {\n\t\treturn NULL;\n\t}\n\n\tsize_t name_len = peek_name(parser, false);\n\tif (name_len == 0) {\n\t\tparser_set_error(parser, \"expected name\");\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_range name_range;\n\tchar *name = read_token(parser, name_len, &name_range);\n\n\tlinebreak(parser);\n\n\tstruct mrsh_range in_range = {0};\n\tbool in = token(parser, \"in\", &in_range);\n\n\t// TODO: save sequential_sep position, if any\n\tstruct mrsh_array words = {0};\n\tif (in) {\n\t\twordlist(parser, &words);\n\n\t\tif (!sequential_sep(parser)) {\n\t\t\tparser_set_error(parser, \"expected sequential separator\");\n\t\t\tgoto error_words;\n\t\t}\n\t} else {\n\t\tsequential_sep(parser);\n\t}\n\n\tstruct mrsh_array body = {0};\n\tstruct mrsh_range do_range, done_range;\n\tif (!expect_do_group(parser, &body, &do_range, &done_range)) {\n\t\tgoto error_words;\n\t}\n\n\tstruct mrsh_for_clause *fc =\n\t\tmrsh_for_clause_create(name, in, &words, &body);\n\tfc->for_range = for_range;\n\tfc->name_range = name_range;\n\tfc->in_range = in_range;\n\tfc->do_range = do_range;\n\tfc->done_range = done_range;\n\treturn fc;\n\nerror_words:\n\tfor (size_t i = 0; i < words.len; ++i) {\n\t\tstruct mrsh_word *word = words.data[i];\n\t\tmrsh_word_destroy(word);\n\t}\n\tmrsh_array_finish(&words);\n\tfree(name);\n\treturn NULL;\n}\n\nstatic struct mrsh_loop_clause *loop_clause(struct mrsh_parser *parser) {\n\tenum mrsh_loop_type type;\n\tstruct mrsh_range while_until_range;\n\tif (token(parser, \"while\", &while_until_range)) {\n\t\ttype = MRSH_LOOP_WHILE;\n\t} else if (token(parser, \"until\", &while_until_range)) {\n\t\ttype = MRSH_LOOP_UNTIL;\n\t} else {\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_array condition = {0};\n\tif (!expect_compound_list(parser, &condition)) {\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_array body = {0};\n\tstruct mrsh_range do_range, done_range;\n\tif (!expect_do_group(parser, &body, &do_range, &done_range)) {\n\t\tcommand_list_array_finish(&condition);\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_loop_clause *fc =\n\t\tmrsh_loop_clause_create(type, &condition, &body);\n\tfc->while_until_range = while_until_range;\n\tfc->do_range = do_range;\n\tfc->done_range = done_range;\n\treturn fc;\n}\n\nstatic struct mrsh_case_item *expect_case_item(struct mrsh_parser *parser,\n\t\tbool *dsemi) {\n\tstruct mrsh_position lparen_pos = parser->pos;\n\tif (!token(parser, \"(\", NULL)) {\n\t\tlparen_pos = (struct mrsh_position){0};\n\t}\n\n\tstruct mrsh_word *w = word(parser, 0);\n\tif (w == NULL) {\n\t\tparser_set_error(parser, \"expected a word\");\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_array patterns = {0};\n\tmrsh_array_add(&patterns, w);\n\n\twhile (token(parser, \"|\", NULL)) {\n\t\tstruct mrsh_word *w = word(parser, 0);\n\t\tif (w == NULL) {\n\t\t\tparser_set_error(parser, \"expected a word\");\n\t\t\treturn NULL;\n\t\t}\n\t\tmrsh_array_add(&patterns, w);\n\t}\n\n\tstruct mrsh_position rparen_pos = parser->pos;\n\tif (!expect_token(parser, \")\", NULL)) {\n\t\tgoto error_patterns;\n\t}\n\n\t// It's okay if there's no body\n\tstruct mrsh_array body = {0};\n\tcompound_list(parser, &body);\n\tif (mrsh_parser_error(parser, NULL)) {\n\t\tgoto error_patterns;\n\t}\n\n\tstruct mrsh_range dsemi_range = {0};\n\t*dsemi = operator(parser, DSEMI, &dsemi_range);\n\tif (*dsemi) {\n\t\tlinebreak(parser);\n\t}\n\n\tstruct mrsh_case_item *item = calloc(1, sizeof(struct mrsh_case_item));\n\tif (item == NULL) {\n\t\tgoto error_body;\n\t}\n\titem->patterns = patterns;\n\titem->body = body;\n\titem->lparen_pos = lparen_pos;\n\titem->rparen_pos = rparen_pos;\n\titem->dsemi_range = dsemi_range;\n\treturn item;\n\nerror_body:\n\tcommand_list_array_finish(&body);\nerror_patterns:\n\tfor (size_t i = 0; i < patterns.len; ++i) {\n\t\tstruct mrsh_word *w = patterns.data[i];\n\t\tmrsh_word_destroy(w);\n\t}\n\tmrsh_array_finish(&patterns);\n\treturn NULL;\n}\n\nstatic struct mrsh_case_clause *case_clause(struct mrsh_parser *parser) {\n\tstruct mrsh_range case_range;\n\tif (!token(parser, \"case\", &case_range)) {\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_word *w = word(parser, 0);\n\tif (w == NULL) {\n\t\tparser_set_error(parser, \"expected a word\");\n\t\treturn NULL;\n\t}\n\n\tlinebreak(parser);\n\n\tstruct mrsh_range in_range;\n\tif (!expect_token(parser, \"in\", &in_range)) {\n\t\tgoto error_word;\n\t}\n\n\tlinebreak(parser);\n\n\tbool dsemi = false;\n\tstruct mrsh_array items = {0};\n\tstruct mrsh_range esac_range;\n\twhile (!token(parser, \"esac\", &esac_range)) {\n\t\tstruct mrsh_case_item *item = expect_case_item(parser, &dsemi);\n\t\tif (item == NULL) {\n\t\t\tgoto error_items;\n\t\t}\n\t\tmrsh_array_add(&items, item);\n\n\t\tif (!dsemi) {\n\t\t\t// Only the last case can omit `;;`\n\t\t\tif (!expect_token(parser, \"esac\", &esac_range)) {\n\t\t\t\tgoto error_items;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tstruct mrsh_case_clause *cc = mrsh_case_clause_create(w, &items);\n\tcc->case_range = case_range;\n\tcc->in_range = in_range;\n\tcc->esac_range = esac_range;\n\treturn cc;\n\nerror_items:\n\tfor (size_t i = 0; i < items.len; ++i) {\n\t\tstruct mrsh_case_item *item = items.data[i];\n\t\tcase_item_destroy(item);\n\t}\n\tmrsh_array_finish(&items);\nerror_word:\n\tmrsh_word_destroy(w);\n\treturn NULL;\n}\n\nstatic struct mrsh_command *compound_command(struct mrsh_parser *parser);\n\nstatic struct mrsh_function_definition *function_definition(\n\t\tstruct mrsh_parser *parser) {\n\tsize_t name_len = peek_name(parser, false);\n\tif (name_len == 0) {\n\t\treturn NULL;\n\t}\n\n\tsize_t i = name_len;\n\twhile (true) {\n\t\tparser_peek(parser, NULL, i + 1);\n\n\t\tchar c = parser->buf.data[i];\n\t\tif (c == '(') {\n\t\t\tbreak;\n\t\t} else if (!isblank(c)) {\n\t\t\treturn NULL;\n\t\t}\n\n\t\t++i;\n\t}\n\n\tstruct mrsh_range name_range;\n\tchar *name = read_token(parser, name_len, &name_range);\n\n\tstruct mrsh_position lparen_pos = parser->pos;\n\tif (!expect_token(parser, \"(\", NULL)) {\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_position rparen_pos = parser->pos;\n\tif (!expect_token(parser, \")\", NULL)) {\n\t\treturn NULL;\n\t}\n\n\tlinebreak(parser);\n\n\tstruct mrsh_command *cmd = compound_command(parser);\n\tif (cmd == NULL) {\n\t\tparser_set_error(parser, \"expected a compound command\");\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_array io_redirects = {0};\n\twhile (true) {\n\t\tstruct mrsh_io_redirect *redir = io_redirect(parser);\n\t\tif (redir == NULL) {\n\t\t\tbreak;\n\t\t}\n\t\tmrsh_array_add(&io_redirects, redir);\n\t}\n\n\tstruct mrsh_function_definition *fd =\n\t\tmrsh_function_definition_create(name, cmd, &io_redirects);\n\tfd->name_range = name_range;\n\tfd->lparen_pos = lparen_pos;\n\tfd->rparen_pos = rparen_pos;\n\treturn fd;\n}\n\nstatic bool unspecified_word(struct mrsh_parser *parser) {\n\tconst char *const reserved[] = {\n\t\t\"[[\",\n\t\t\"]]\",\n\t\t\"function\",\n\t\t\"select\",\n\t};\n\n\tsize_t word_len = peek_word(parser, 0);\n\tif (word_len == 0) {\n\t\treturn false;\n\t}\n\n\tfor (size_t i = 0; i < sizeof(reserved) / sizeof(reserved[0]); i++) {\n\t\tif (strncmp(parser->buf.data, reserved[i], word_len) == 0 &&\n\t\t\t\tword_len == strlen(reserved[i])) {\n\t\t\tchar err_msg[256];\n\t\t\tsnprintf(err_msg, sizeof(err_msg),\n\t\t\t\t\"keyword is reserved and causes unspecified results: %s\",\n\t\t\t\treserved[i]);\n\t\t\tparser_set_error(parser, err_msg);\n\t\t\treturn true;\n\t\t}\n\t}\n\n\tsize_t name_len = peek_name(parser, false);\n\tif (name_len == 0) {\n\t\treturn false;\n\t}\n\n\tparser_peek(parser, NULL, name_len + 1);\n\tif (parser->buf.data[name_len] == ':') {\n\t\tparser_set_error(parser, \"words that are the concatenation of a name \"\n\t\t\t\"and a colon produce unspecified results\");\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nstatic struct mrsh_command *compound_command(struct mrsh_parser *parser) {\n\tstruct mrsh_brace_group *bg = brace_group(parser);\n\tif (bg != NULL) {\n\t\treturn &bg->command;\n\t} else if (mrsh_parser_error(parser, NULL)) {\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_subshell *s = subshell(parser);\n\tif (s != NULL) {\n\t\treturn &s->command;\n\t} else if (mrsh_parser_error(parser, NULL)) {\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_if_clause *ic = if_clause(parser);\n\tif (ic != NULL) {\n\t\treturn &ic->command;\n\t} else if (mrsh_parser_error(parser, NULL)) {\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_for_clause *fc = for_clause(parser);\n\tif (fc != NULL) {\n\t\treturn &fc->command;\n\t} else if (mrsh_parser_error(parser, NULL)) {\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_loop_clause *lc = loop_clause(parser);\n\tif (lc != NULL) {\n\t\treturn &lc->command;\n\t} else if (mrsh_parser_error(parser, NULL)) {\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_case_clause *cc = case_clause(parser);\n\tif (cc != NULL) {\n\t\treturn &cc->command;\n\t} else if (mrsh_parser_error(parser, NULL)) {\n\t\treturn NULL;\n\t}\n\n\tif (unspecified_word(parser)) {\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_function_definition *fd = function_definition(parser);\n\tif (fd != NULL) {\n\t\treturn &fd->command;\n\t} else if (mrsh_parser_error(parser, NULL)) {\n\t\treturn NULL;\n\t}\n\n\treturn NULL;\n}\n\nstatic struct mrsh_command *command(struct mrsh_parser *parser) {\n\tapply_aliases(parser);\n\n\tstruct mrsh_command *cmd = compound_command(parser);\n\tif (cmd != NULL || mrsh_parser_error(parser, NULL)) {\n\t\treturn cmd;\n\t}\n\n\t// TODO: compound_command redirect_list\n\n\tstruct mrsh_simple_command *sc = simple_command(parser);\n\tif (sc != NULL) {\n\t\treturn &sc->command;\n\t}\n\n\treturn NULL;\n}\n\nstatic struct mrsh_pipeline *pipeline(struct mrsh_parser *parser) {\n\tstruct mrsh_range bang_range = {0};\n\tbool bang = token(parser, \"!\", &bang_range);\n\tstruct mrsh_position bang_pos = bang_range.begin; // can be invalid\n\n\tstruct mrsh_command *cmd = command(parser);\n\tif (cmd == NULL) {\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_array commands = {0};\n\tmrsh_array_add(&commands, cmd);\n\n\twhile (token(parser, \"|\", NULL)) {\n\t\tlinebreak(parser);\n\t\tstruct mrsh_command *cmd = command(parser);\n\t\tif (cmd == NULL) {\n\t\t\tparser_set_error(parser, \"expected a command\");\n\t\t\tgoto error_commands;\n\t\t}\n\t\tmrsh_array_add(&commands, cmd);\n\t}\n\n\tstruct mrsh_pipeline *p = mrsh_pipeline_create(&commands, bang);\n\tp->bang_pos = bang_pos;\n\treturn p;\n\nerror_commands:\n\tfor (size_t i = 0; i < commands.len; ++i) {\n\t\tmrsh_command_destroy((struct mrsh_command *)commands.data[i]);\n\t}\n\tmrsh_array_finish(&commands);\n\treturn NULL;\n}\n\nstatic struct mrsh_and_or_list *and_or(struct mrsh_parser *parser) {\n\tstruct mrsh_pipeline *pl = pipeline(parser);\n\tif (pl == NULL) {\n\t\treturn NULL;\n\t}\n\n\tenum mrsh_binop_type binop_type;\n\tstruct mrsh_range op_range;\n\tif (operator(parser, AND_IF, &op_range)) {\n\t\tbinop_type = MRSH_BINOP_AND;\n\t} else if (operator(parser, OR_IF, &op_range)) {\n\t\tbinop_type = MRSH_BINOP_OR;\n\t} else {\n\t\treturn &pl->and_or_list;\n\t}\n\n\tlinebreak(parser);\n\tstruct mrsh_and_or_list *and_or_list = and_or(parser);\n\tif (and_or_list == NULL) {\n\t\tmrsh_and_or_list_destroy(&pl->and_or_list);\n\t\tparser_set_error(parser, \"expected an AND-OR list\");\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_binop *binop = mrsh_binop_create(binop_type, &pl->and_or_list, and_or_list);\n\tbinop->op_range = op_range;\n\treturn &binop->and_or_list;\n}\n\nstatic struct mrsh_command_list *list(struct mrsh_parser *parser) {\n\tstruct mrsh_and_or_list *and_or_list = and_or(parser);\n\tif (and_or_list == NULL) {\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_command_list *cmd = mrsh_command_list_create();\n\tcmd->and_or_list = and_or_list;\n\n\tstruct mrsh_position separator_pos = parser->pos;\n\tint sep = separator_op(parser);\n\tif (sep == '&') {\n\t\tcmd->ampersand = true;\n\t}\n\tif (sep >= 0) {\n\t\tcmd->separator_pos = separator_pos;\n\t}\n\n\treturn cmd;\n}\n\n/**\n * Append a new string word to `children` with the contents of `buf`, and reset\n * `buf`.\n */\nstatic void push_buffer_word_string(struct mrsh_array *children,\n\t\tstruct mrsh_buffer *buf) {\n\tif (buf->len == 0) {\n\t\treturn;\n\t}\n\n\tmrsh_buffer_append_char(buf, '\\0');\n\n\tchar *data = mrsh_buffer_steal(buf);\n\tstruct mrsh_word_string *ws = mrsh_word_string_create(data, false);\n\tmrsh_array_add(children, &ws->word);\n}\n\nstatic struct mrsh_word *here_document_line(struct mrsh_parser *parser) {\n\tstruct mrsh_array children = {0};\n\tstruct mrsh_buffer buf = {0};\n\n\twhile (true) {\n\t\tchar c = parser_peek_char(parser);\n\t\tif (c == '\\0') {\n\t\t\tbreak;\n\t\t}\n\n\t\tif (c == '$') {\n\t\t\tpush_buffer_word_string(&children, &buf);\n\t\t\tstruct mrsh_word *t = expect_dollar(parser);\n\t\t\tif (t == NULL) {\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t\tmrsh_array_add(&children, t);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (c == '`') {\n\t\t\tpush_buffer_word_string(&children, &buf);\n\t\t\tstruct mrsh_word *t = back_quotes(parser);\n\t\t\tmrsh_array_add(&children, t);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (c == '\\\\') {\n\t\t\t// Here-document backslash, same semantics as quoted backslash\n\t\t\t// except double-quotes are not special\n\t\t\tchar next[2];\n\t\t\tparser_peek(parser, next, sizeof(next));\n\t\t\tswitch (next[1]) {\n\t\t\tcase '$':\n\t\t\tcase '`':\n\t\t\tcase '\\\\':\n\t\t\t\tparser_read_char(parser);\n\t\t\t\tc = next[1];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tparser_read_char(parser);\n\t\tmrsh_buffer_append_char(&buf, c);\n\t}\n\n\tpush_buffer_word_string(&children, &buf);\n\tmrsh_buffer_finish(&buf);\n\n\tif (children.len == 1) {\n\t\tstruct mrsh_word *word = children.data[0];\n\t\tmrsh_array_finish(&children); // TODO: don't allocate this array\n\t\treturn word;\n\t} else {\n\t\tstruct mrsh_word_list *wl = mrsh_word_list_create(&children, false);\n\t\treturn &wl->word;\n\t}\n}\n\nstatic bool is_word_quoted(struct mrsh_word *word) {\n\tswitch (word->type) {\n\tcase MRSH_WORD_STRING:;\n\t\tstruct mrsh_word_string *ws = mrsh_word_get_string(word);\n\t\treturn ws->single_quoted;\n\tcase MRSH_WORD_LIST:;\n\t\tstruct mrsh_word_list *wl = mrsh_word_get_list(word);\n\t\tif (wl->double_quoted) {\n\t\t\treturn true;\n\t\t}\n\t\tfor (size_t i = 0; i < wl->children.len; ++i) {\n\t\t\tstruct mrsh_word *child = wl->children.data[i];\n\t\t\tif (is_word_quoted(child)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\tdefault:\n\t\tabort();\n\t}\n}\n\nstatic bool expect_here_document(struct mrsh_parser *parser,\n\t\tstruct mrsh_io_redirect *redir, const char *delim) {\n\tbool trim_tabs = redir->op == MRSH_IO_DLESSDASH;\n\tbool expand_lines = !is_word_quoted(redir->name);\n\n\tparser->continuation_line = true;\n\n\tstruct mrsh_buffer buf = {0};\n\twhile (true) {\n\t\tbuf.len = 0;\n\t\twhile (true) {\n\t\t\tchar c = parser_peek_char(parser);\n\t\t\tif (c == '\\0' || c == '\\n') {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tmrsh_buffer_append_char(&buf, parser_read_char(parser));\n\t\t}\n\t\tmrsh_buffer_append_char(&buf, '\\0');\n\n\t\tconst char *line = buf.data;\n\t\tif (trim_tabs) {\n\t\t\twhile (line[0] == '\\t') {\n\t\t\t\t++line;\n\t\t\t}\n\t\t}\n\n\t\tif (strcmp(line, delim) == 0) {\n\t\t\tif (parser_peek_char(parser) == '\\n') {\n\t\t\t\tparser_read_char(parser);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tif (parser_peek_char(parser) == '\\0') {\n\t\t\tparser_set_error(parser, \"unterminated here-document\");\n\t\t\treturn false;\n\t\t}\n\t\tread_continuation_line(parser);\n\n\t\tstruct mrsh_word *word;\n\t\tif (expand_lines) {\n\t\t\tstruct mrsh_parser *subparser =\n\t\t\t\tmrsh_parser_with_data(line, strlen(line));\n\t\t\tword = here_document_line(subparser);\n\t\t\tmrsh_parser_destroy(subparser);\n\t\t} else {\n\t\t\tstruct mrsh_word_string *ws =\n\t\t\t\tmrsh_word_string_create(strdup(line), true);\n\t\t\tword = &ws->word;\n\t\t}\n\n\t\tmrsh_array_add(&redir->here_document, word);\n\t}\n\tmrsh_buffer_finish(&buf);\n\n\tconsume_symbol(parser);\n\treturn true;\n}\n\nstatic bool complete_command(struct mrsh_parser *parser,\n\t\tstruct mrsh_array *cmds) {\n\tstruct mrsh_command_list *l = list(parser);\n\tif (l == NULL) {\n\t\treturn false;\n\t}\n\tmrsh_array_add(cmds, l);\n\n\twhile (true) {\n\t\tl = list(parser);\n\t\tif (l == NULL) {\n\t\t\tbreak;\n\t\t}\n\t\tmrsh_array_add(cmds, l);\n\t}\n\n\tif (parser->here_documents.len > 0) {\n\t\tfor (size_t i = 0; i < parser->here_documents.len; ++i) {\n\t\t\tstruct mrsh_io_redirect *redir = parser->here_documents.data[i];\n\n\t\t\tif (!newline(parser)) {\n\t\t\t\tparser_set_error(parser,\n\t\t\t\t\t\"expected a newline followed by a here-document\");\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tchar *delim = mrsh_word_str(redir->name);\n\t\t\tbool ok = expect_here_document(parser, redir, delim);\n\t\t\tfree(delim);\n\t\t\tif (!ok) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\tparser->here_documents.len = 0;\n\t}\n\n\treturn true;\n}\n\nstatic bool expect_complete_command(struct mrsh_parser *parser,\n\t\tstruct mrsh_array *cmds) {\n\tif (!complete_command(parser, cmds)) {\n\t\tparser_set_error(parser, \"expected a complete command\");\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nstatic struct mrsh_program *program(struct mrsh_parser *parser) {\n\tstruct mrsh_program *prog = mrsh_program_create();\n\tif (prog == NULL) {\n\t\treturn NULL;\n\t}\n\n\tlinebreak(parser);\n\tif (eof(parser)) {\n\t\treturn prog;\n\t}\n\n\tif (!expect_complete_command(parser, &prog->body)) {\n\t\tmrsh_program_destroy(prog);\n\t\treturn NULL;\n\t}\n\n\twhile (newline_list(parser)) {\n\t\tif (eof(parser)) {\n\t\t\treturn prog;\n\t\t}\n\n\t\tif (!complete_command(parser, &prog->body)) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tlinebreak(parser);\n\treturn prog;\n}\n\nstruct mrsh_program *mrsh_parse_line(struct mrsh_parser *parser) {\n\tparser_begin(parser);\n\n\tif (eof(parser)) {\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_program *prog = mrsh_program_create();\n\tif (prog == NULL) {\n\t\treturn NULL;\n\t}\n\n\tif (newline(parser)) {\n\t\treturn prog;\n\t}\n\n\tif (!expect_complete_command(parser, &prog->body)) {\n\t\tgoto error;\n\t}\n\tif (!eof(parser) && !newline(parser)) {\n\t\tparser_set_error(parser, \"expected a newline\");\n\t\tgoto error;\n\t}\n\n\treturn prog;\n\nerror:\n\tmrsh_program_destroy(prog);\n\n\t// Consume the whole line\n\twhile (true) {\n\t\tchar c = parser_peek_char(parser);\n\t\tif (c == '\\0') {\n\t\t\tbreak;\n\t\t}\n\n\t\tparser_read_char(parser);\n\t\tif (c == '\\n') {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tparser->has_sym = false;\n\n\treturn NULL;\n}\n\nstruct mrsh_program *mrsh_parse_program(struct mrsh_parser *parser) {\n\tparser_begin(parser);\n\treturn program(parser);\n}\n"
  },
  {
    "path": "parser/word.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <assert.h>\n#include <ctype.h>\n#include <mrsh/buffer.h>\n#include <stdbool.h>\n#include <stddef.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"ast.h\"\n#include \"parser.h\"\n\nstatic struct mrsh_word *single_quotes(struct mrsh_parser *parser) {\n\tstruct mrsh_position begin = parser->pos;\n\n\tchar c = parser_read_char(parser);\n\tassert(c == '\\'');\n\n\tstruct mrsh_buffer buf = {0};\n\n\twhile (true) {\n\t\tchar c = parser_peek_char(parser);\n\t\tif (c == '\\0') {\n\t\t\tparser_set_error(parser, \"single quotes not terminated\");\n\t\t\treturn NULL;\n\t\t}\n\t\tif (c == '\\'') {\n\t\t\tparser_read_char(parser);\n\t\t\tbreak;\n\t\t}\n\n\t\tif (c == '\\n') {\n\t\t\tread_continuation_line(parser);\n\t\t} else {\n\t\t\tparser_read_char(parser);\n\t\t}\n\n\t\tmrsh_buffer_append_char(&buf, c);\n\t}\n\n\tmrsh_buffer_append_char(&buf, '\\0');\n\tchar *data = mrsh_buffer_steal(&buf);\n\tstruct mrsh_word_string *ws = mrsh_word_string_create(data, true);\n\tws->range.begin = begin;\n\tws->range.end = parser->pos;\n\treturn &ws->word;\n}\n\nsize_t peek_name(struct mrsh_parser *parser, bool in_braces) {\n\t// In the shell command language, a word consisting solely of underscores,\n\t// digits, and alphabetics from the portable character set. The first\n\t// character of a name is not a digit.\n\n\tif (!symbol(parser, TOKEN)) {\n\t\treturn false;\n\t}\n\n\tsize_t i = 0;\n\twhile (true) {\n\t\tparser_peek(parser, NULL, i + 1);\n\n\t\tchar c = parser->buf.data[i];\n\t\tif (c != '_' && !isalnum(c)) {\n\t\t\tbreak;\n\t\t} else if (i == 0 && isdigit(c) && !in_braces) {\n\t\t\tbreak;\n\t\t}\n\n\t\t++i;\n\t}\n\n\treturn i;\n}\n\nsize_t peek_word(struct mrsh_parser *parser, char end) {\n\tif (!symbol(parser, TOKEN)) {\n\t\treturn false;\n\t}\n\n\tsize_t i = 0;\n\twhile (true) {\n\t\tparser_peek(parser, NULL, i + 1);\n\n\t\tchar c = parser->buf.data[i];\n\n\t\tswitch (c) {\n\t\tcase '\\0':\n\t\tcase '\\n':\n\t\tcase ')':\n\t\t\treturn i;\n\t\tcase '$':\n\t\tcase '`':\n\t\tcase '\\'':\n\t\tcase '\"':\n\t\tcase '\\\\': // TODO: allow backslash in words\n\t\t\treturn 0;\n\t\t}\n\n\t\tif (is_operator_start(c) || isblank(c) || c == end) {\n\t\t\treturn i;\n\t\t}\n\n\t\t++i;\n\t}\n}\n\nbool token(struct mrsh_parser *parser, const char *str,\n\t\tstruct mrsh_range *range) {\n\tif (!symbol(parser, TOKEN)) {\n\t\treturn false;\n\t}\n\n\tstruct mrsh_position begin = parser->pos;\n\n\tsize_t len = strlen(str);\n\tassert(len > 0);\n\n\tif (len == 1 && !isalpha(str[0])) {\n\t\tif (parser_peek_char(parser) != str[0]) {\n\t\t\treturn false;\n\t\t}\n\t\tparser_read_char(parser);\n\t} else {\n\t\tsize_t word_len = peek_word(parser, 0);\n\t\tif (len != word_len || strncmp(parser->buf.data, str, word_len) != 0) {\n\t\t\treturn false;\n\t\t}\n\t\t// assert(isalpha(str[i]));\n\n\t\tparser_read(parser, NULL, len);\n\t}\n\n\tif (range != NULL) {\n\t\trange->begin = begin;\n\t\trange->end = parser->pos;\n\t}\n\n\tconsume_symbol(parser);\n\treturn true;\n}\n\nbool expect_token(struct mrsh_parser *parser, const char *str,\n\t\tstruct mrsh_range *range) {\n\tif (token(parser, str, range)) {\n\t\treturn true;\n\t}\n\tchar msg[128];\n\tsnprintf(msg, sizeof(msg), \"expected '%s'\", str);\n\tparser_set_error(parser, msg);\n\treturn false;\n}\n\nchar *read_token(struct mrsh_parser *parser, size_t len,\n\t\tstruct mrsh_range *range) {\n\tif (!symbol(parser, TOKEN)) {\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_position begin = parser->pos;\n\n\tchar *tok = malloc(len + 1);\n\tparser_read(parser, tok, len);\n\ttok[len] = '\\0';\n\n\tif (range != NULL) {\n\t\trange->begin = begin;\n\t\trange->end = parser->pos;\n\t}\n\n\tconsume_symbol(parser);\n\treturn tok;\n}\n\nstatic struct mrsh_word *word_list(struct mrsh_parser *parser, char end,\n\t\tword_func f) {\n\tstruct mrsh_array children = {0};\n\n\twhile (true) {\n\t\tif (parser_peek_char(parser) == end) {\n\t\t\tbreak;\n\t\t}\n\n\t\tstruct mrsh_word *child = f(parser, end);\n\t\tif (child == NULL) {\n\t\t\tbreak;\n\t\t}\n\t\tmrsh_array_add(&children, child);\n\n\t\tstruct mrsh_position begin = parser->pos;\n\t\tstruct mrsh_buffer buf = {0};\n\t\twhile (true) {\n\t\t\tchar c = parser_peek_char(parser);\n\t\t\tif (!isblank(c)) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tmrsh_buffer_append_char(&buf, parser_read_char(parser));\n\t\t}\n\t\tif (buf.len == 0) {\n\t\t\tbreak; // word() ended on a non-blank char, stop here\n\t\t}\n\t\tmrsh_buffer_append_char(&buf, '\\0');\n\t\tstruct mrsh_word_string *ws =\n\t\t\tmrsh_word_string_create(mrsh_buffer_steal(&buf), false);\n\t\tws->range.begin = begin;\n\t\tws->range.end = parser->pos;\n\t\tmrsh_array_add(&children, &ws->word);\n\t\tmrsh_buffer_finish(&buf);\n\t}\n\n\tif (children.len == 0) {\n\t\treturn NULL;\n\t} else if (children.len == 1) {\n\t\tstruct mrsh_word *child = children.data[0];\n\t\tmrsh_array_finish(&children);\n\t\treturn child;\n\t} else {\n\t\tstruct mrsh_word_list *wl = mrsh_word_list_create(&children, false);\n\t\treturn &wl->word;\n\t}\n}\n\nstatic enum mrsh_word_parameter_op char_to_parameter_op_val(char c) {\n\tswitch (c) {\n\tcase '-':\n\t\treturn MRSH_PARAM_MINUS;\n\tcase '=':\n\t\treturn MRSH_PARAM_EQUAL;\n\tcase '?':\n\t\treturn MRSH_PARAM_QMARK;\n\tcase '+':\n\t\treturn MRSH_PARAM_PLUS;\n\tdefault:\n\t\treturn MRSH_PARAM_NONE;\n\t}\n}\n\nstatic bool expect_parameter_op(struct mrsh_parser *parser,\n\t\tenum mrsh_word_parameter_op *op, bool *colon) {\n\tchar c = parser_read_char(parser);\n\n\t*colon = c == ':';\n\tif (*colon) {\n\t\tc = parser_read_char(parser);\n\t}\n\n\t*op = char_to_parameter_op_val(c);\n\tif (*op != MRSH_PARAM_NONE) {\n\t\treturn true;\n\t}\n\n\t// Colon can only be used with value operations\n\tif (*colon) {\n\t\tparser_set_error(parser, \"expected a parameter operation\");\n\t\treturn false;\n\t}\n\n\t// Substring processing operations\n\tchar c_next = parser_peek_char(parser);\n\tbool is_double = c == c_next;\n\tswitch (c) {\n\tcase '%':\n\t\t*op = is_double ? MRSH_PARAM_DPERCENT : MRSH_PARAM_PERCENT;\n\t\tbreak;\n\tcase '#':\n\t\t*op = is_double ? MRSH_PARAM_DHASH : MRSH_PARAM_HASH;\n\t\tbreak;\n\tdefault:\n\t\tparser_set_error(parser, \"expected a parameter operation\");\n\t\treturn false;\n\t}\n\n\tif (is_double) {\n\t\tparser_read_char(parser);\n\t}\n\treturn true;\n}\n\nstatic struct mrsh_word_parameter *expect_parameter_expression(\n\t\tstruct mrsh_parser *parser) {\n\tstruct mrsh_position lbrace_pos = parser->pos;\n\n\tchar c = parser_read_char(parser);\n\tassert(c == '{');\n\n\tenum mrsh_word_parameter_op op = MRSH_PARAM_NONE;\n\tstruct mrsh_range op_range = {0};\n\tif (parser_peek_char(parser) == '#') {\n\t\top_range.begin = parser->pos;\n\t\tparser_read_char(parser);\n\t\top_range.end = parser->pos;\n\t\top = MRSH_PARAM_LEADING_HASH;\n\t}\n\n\tsize_t name_len = peek_name(parser, true);\n\tif (name_len == 0) {\n\t\tparser_set_error(parser, \"expected a parameter\");\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_range name_range;\n\tchar *name = read_token(parser, name_len, &name_range);\n\tif (name == NULL) {\n\t\treturn NULL;\n\t}\n\n\tbool colon = false;\n\tstruct mrsh_word *arg = NULL;\n\tif (op == MRSH_PARAM_NONE && parser_peek_char(parser) != '}') {\n\t\top_range.begin = parser->pos;\n\t\tif (!expect_parameter_op(parser, &op, &colon)) {\n\t\t\treturn NULL;\n\t\t}\n\t\top_range.end = parser->pos;\n\t\targ = word_list(parser, '}', word);\n\t}\n\n\tstruct mrsh_position rbrace_pos = parser->pos;\n\tif (parser_read_char(parser) != '}') {\n\t\tparser_set_error(parser, \"expected end of parameter\");\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_word_parameter *wp =\n\t\tmrsh_word_parameter_create(name, op, colon, arg);\n\twp->name_range = name_range;\n\twp->op_range = op_range;\n\twp->lbrace_pos = lbrace_pos;\n\twp->rbrace_pos = rbrace_pos;\n\treturn wp;\n}\n\nstatic struct mrsh_word_command *expect_word_command(\n\t\tstruct mrsh_parser *parser) {\n\tchar c = parser_read_char(parser);\n\tassert(c == '(');\n\tassert(symbol(parser, TOKEN));\n\tconsume_symbol(parser);\n\n\t// Alias substitution is not allowed inside command substitution, see\n\t// section 2.2.3\n\tmrsh_parser_alias_func alias = parser->alias;\n\tparser->alias = NULL;\n\n\tstruct mrsh_program *prog = mrsh_parse_program(parser);\n\tparser->alias = alias;\n\tif (mrsh_parser_error(parser, NULL) != NULL) {\n\t\tmrsh_program_destroy(prog);\n\t\treturn NULL;\n\t} else if (prog == NULL) {\n\t\tparser_set_error(parser, \"expected a program\");\n\t\treturn NULL;\n\t}\n\n\tif (!expect_token(parser, \")\", NULL)) {\n\t\tmrsh_program_destroy(prog);\n\t\treturn NULL;\n\t}\n\n\treturn mrsh_word_command_create(prog, false);\n}\n\nstatic struct mrsh_word_arithmetic *expect_word_arithmetic(\n\t\tstruct mrsh_parser *parser) {\n\tchar c = parser_read_char(parser);\n\tassert(c == '(');\n\tc = parser_read_char(parser);\n\tassert(c == '(');\n\n\tparser->arith_nested_parens = 0;\n\tstruct mrsh_word *body = word_list(parser, 0, arithmetic_word);\n\tif (body == NULL) {\n\t\tif (!mrsh_parser_error(parser, NULL)) {\n\t\t\tparser_set_error(parser, \"expected an arithmetic expression\");\n\t\t}\n\t\treturn NULL;\n\t}\n\n\tif (!expect_token(parser, \")\", NULL)) {\n\t\tmrsh_word_destroy(body);\n\t\treturn NULL;\n\t}\n\tif (!expect_token(parser, \")\", NULL)) {\n\t\tmrsh_word_destroy(body);\n\t\treturn NULL;\n\t}\n\n\treturn mrsh_word_arithmetic_create(body);\n}\n\n// Expect parameter expansion or command substitution\nstruct mrsh_word *expect_dollar(struct mrsh_parser *parser) {\n\tstruct mrsh_position dollar_pos = parser->pos;\n\tchar c = parser_read_char(parser);\n\tassert(c == '$');\n\n\tstruct mrsh_word_parameter *wp;\n\tc = parser_peek_char(parser);\n\tswitch (c) {\n\tcase '{': // Parameter expansion in the form `${expression}`\n\t\twp = expect_parameter_expression(parser);\n\t\tif (wp == NULL) {\n\t\t\treturn NULL;\n\t\t}\n\t\twp->dollar_pos = dollar_pos;\n\t\treturn &wp->word;\n\t// Command substitution in the form `$(command)` or arithmetic expansion in\n\t// the form `$((expression))`\n\tcase '(':;\n\t\tchar next[2];\n\t\tparser_peek(parser, next, sizeof(next));\n\t\tif (next[1] == '(') {\n\t\t\tstruct mrsh_word_arithmetic *wa = expect_word_arithmetic(parser);\n\t\t\tif (wa == NULL) {\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t\t// TODO: store dollar_pos in wa\n\t\t\treturn &wa->word;\n\t\t} else {\n\t\t\tstruct mrsh_word_command *wc = expect_word_command(parser);\n\t\t\tif (wc == NULL) {\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t\t// TODO: store dollar_pos in wc\n\t\t\treturn &wc->word;\n\t\t}\n\tdefault:; // Parameter expansion in the form `$parameter`\n\t\tsize_t name_len = peek_name(parser, false);\n\t\tif (name_len == 0) {\n\t\t\tbool ok = false;\n\t\t\tswitch (c) {\n\t\t\tcase '@':\n\t\t\tcase '*':\n\t\t\tcase '#':\n\t\t\tcase '?':\n\t\t\tcase '-':\n\t\t\tcase '$':\n\t\t\tcase '!':\n\t\t\t\tok = true;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tok = isdigit(c);\n\t\t\t}\n\t\t\tif (ok) {\n\t\t\t\tname_len = 1;\n\t\t\t} else {\n\t\t\t\t// 2.6. If an unquoted '$' is followed by a character that is\n\t\t\t\t// not one of the following […] the result is unspecified.\n\t\t\t\tparser_set_error(parser, \"invalid parameter name\");\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t}\n\n\t\tstruct mrsh_range name_range;\n\t\tchar *name = read_token(parser, name_len, &name_range);\n\t\tif (name == NULL) {\n\t\t\treturn NULL;\n\t\t}\n\n\t\twp = mrsh_word_parameter_create(name, MRSH_PARAM_NONE, false, NULL);\n\t\twp->dollar_pos = dollar_pos;\n\t\twp->name_range = name_range;\n\t\treturn &wp->word;\n\t}\n}\n\nstruct mrsh_word *back_quotes(struct mrsh_parser *parser) {\n\tstruct mrsh_position begin = parser->pos;\n\n\tchar c = parser_read_char(parser);\n\tassert(c == '`');\n\n\tstruct mrsh_buffer buf = {0};\n\n\twhile (true) {\n\t\tchar c = parser_peek_char(parser);\n\t\tif (c == '\\0') {\n\t\t\tparser_set_error(parser, \"back quotes not terminated\");\n\t\t\treturn NULL;\n\t\t}\n\t\tif (c == '`') {\n\t\t\tparser_read_char(parser);\n\t\t\tbreak;\n\t\t}\n\t\tif (c == '\\\\') {\n\t\t\t// Quoted backslash\n\t\t\tchar next[2];\n\t\t\tparser_peek(parser, next, sizeof(next));\n\t\t\tswitch (next[1]) {\n\t\t\tcase '$':\n\t\t\tcase '`':\n\t\t\tcase '\\\\':\n\t\t\t\tparser_read_char(parser);\n\t\t\t\tc = next[1];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (c == '\\n') {\n\t\t\tread_continuation_line(parser);\n\t\t} else {\n\t\t\tparser_read_char(parser);\n\t\t}\n\n\t\tmrsh_buffer_append_char(&buf, c);\n\t}\n\n\tstruct mrsh_parser *subparser = mrsh_parser_with_data(buf.data, buf.len);\n\tif (subparser == NULL) {\n\t\tgoto error;\n\t}\n\tstruct mrsh_program *prog = mrsh_parse_program(subparser);\n\tconst char *err_msg = mrsh_parser_error(subparser, NULL);\n\tif (err_msg != NULL) {\n\t\t// TODO: how should we handle subparser error position?\n\t\tparser_set_error(parser, err_msg);\n\t\tmrsh_program_destroy(prog);\n\t\tgoto error;\n\t}\n\tmrsh_parser_destroy(subparser);\n\n\tmrsh_buffer_finish(&buf);\n\n\tstruct mrsh_word_command *wc = mrsh_word_command_create(prog, true);\n\twc->range.begin = begin;\n\twc->range.end = parser->pos;\n\treturn &wc->word;\n\nerror:\n\tmrsh_parser_destroy(subparser);\n\tmrsh_buffer_finish(&buf);\n\treturn NULL;\n}\n\n/**\n * Append a new string word to `children` with the contents of `buf`, and reset\n * `buf`.\n */\nstatic void push_buffer_word_string(struct mrsh_parser *parser,\n\t\tstruct mrsh_array *children, struct mrsh_buffer *buf,\n\t\tstruct mrsh_position *child_begin) {\n\tif (buf->len == 0) {\n\t\t*child_begin = (struct mrsh_position){0};\n\t\treturn;\n\t}\n\n\tmrsh_buffer_append_char(buf, '\\0');\n\n\tchar *data = mrsh_buffer_steal(buf);\n\tstruct mrsh_word_string *ws = mrsh_word_string_create(data, false);\n\tws->range.begin = *child_begin;\n\tws->range.end = parser->pos;\n\tmrsh_array_add(children, &ws->word);\n\n\t*child_begin = (struct mrsh_position){0};\n}\n\nstatic struct mrsh_word *double_quotes(struct mrsh_parser *parser) {\n\tstruct mrsh_position lquote_pos = parser->pos;\n\n\tchar c = parser_read_char(parser);\n\tassert(c == '\"');\n\n\tstruct mrsh_array children = {0};\n\tstruct mrsh_buffer buf = {0};\n\tstruct mrsh_position child_begin = {0};\n\tstruct mrsh_position rquote_pos = {0};\n\twhile (true) {\n\t\tif (!mrsh_position_valid(&child_begin)) {\n\t\t\tchild_begin = parser->pos;\n\t\t}\n\n\t\tchar c = parser_peek_char(parser);\n\t\tif (c == '\\0') {\n\t\t\tparser_set_error(parser, \"double quotes not terminated\");\n\t\t\treturn NULL;\n\t\t}\n\t\tif (c == '\"') {\n\t\t\tpush_buffer_word_string(parser, &children, &buf, &child_begin);\n\t\t\trquote_pos = parser->pos;\n\t\t\tparser_read_char(parser);\n\t\t\tbreak;\n\t\t}\n\n\t\tif (c == '$') {\n\t\t\tpush_buffer_word_string(parser, &children, &buf, &child_begin);\n\t\t\tstruct mrsh_word *t = expect_dollar(parser);\n\t\t\tif (t == NULL) {\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t\tmrsh_array_add(&children, t);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (c == '`') {\n\t\t\tpush_buffer_word_string(parser, &children, &buf, &child_begin);\n\t\t\tstruct mrsh_word *t = back_quotes(parser);\n\t\t\tif (t == NULL) {\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t\tmrsh_array_add(&children, t);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (c == '\\\\') {\n\t\t\t// Quoted backslash\n\t\t\tchar next[2];\n\t\t\tparser_peek(parser, next, sizeof(next));\n\t\t\tswitch (next[1]) {\n\t\t\tcase '$':\n\t\t\tcase '`':\n\t\t\tcase '\"':\n\t\t\tcase '\\\\':\n\t\t\t\tparser_read_char(parser);\n\t\t\t\tc = next[1];\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (next[1] == '\\n') {\n\t\t\t\tparser_read_char(parser); // read backslash\n\t\t\t\tread_continuation_line(parser);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tparser_read_char(parser);\n\t\tmrsh_buffer_append_char(&buf, c);\n\t}\n\n\tmrsh_buffer_finish(&buf);\n\n\tstruct mrsh_word_list *wl = mrsh_word_list_create(&children, true);\n\twl->lquote_pos = lquote_pos;\n\twl->rquote_pos = rquote_pos;\n\treturn &wl->word;\n}\n\nstruct mrsh_word *word(struct mrsh_parser *parser, char end) {\n\tif (!symbol(parser, TOKEN)) {\n\t\treturn NULL;\n\t}\n\n\tif (is_operator_start(parser_peek_char(parser))\n\t\t\t|| parser_peek_char(parser) == ')'\n\t\t\t|| parser_peek_char(parser) == end) {\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_array children = {0};\n\tstruct mrsh_buffer buf = {0};\n\tstruct mrsh_position child_begin = {0};\n\n\twhile (true) {\n\t\tif (!mrsh_position_valid(&child_begin)) {\n\t\t\tchild_begin = parser->pos;\n\t\t}\n\n\t\tchar c = parser_peek_char(parser);\n\t\tif (c == '\\0' || c == '\\n' || c == ')' || c == end) {\n\t\t\tbreak;\n\t\t}\n\n\t\tif (c == '$') {\n\t\t\tpush_buffer_word_string(parser, &children, &buf, &child_begin);\n\t\t\tstruct mrsh_word *t = expect_dollar(parser);\n\t\t\tif (t == NULL) {\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t\tmrsh_array_add(&children, t);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (c == '`') {\n\t\t\tpush_buffer_word_string(parser, &children, &buf, &child_begin);\n\t\t\tstruct mrsh_word *t = back_quotes(parser);\n\t\t\tif (t == NULL) {\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t\tmrsh_array_add(&children, t);\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Quoting\n\t\tif (c == '\\'') {\n\t\t\tpush_buffer_word_string(parser, &children, &buf, &child_begin);\n\t\t\tstruct mrsh_word *t = single_quotes(parser);\n\t\t\tif (t == NULL) {\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t\tmrsh_array_add(&children, t);\n\t\t\tcontinue;\n\t\t}\n\t\tif (c == '\"') {\n\t\t\tpush_buffer_word_string(parser, &children, &buf, &child_begin);\n\t\t\tstruct mrsh_word *t = double_quotes(parser);\n\t\t\tif (t == NULL) {\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t\tmrsh_array_add(&children, t);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (c == '\\\\') {\n\t\t\t// Unquoted backslash\n\t\t\tparser_read_char(parser);\n\t\t\tc = parser_peek_char(parser);\n\t\t\tif (c == '\\n') {\n\t\t\t\t// Continuation line\n\t\t\t\tread_continuation_line(parser);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t} else if (is_operator_start(c) || isblank(c)) {\n\t\t\tbreak;\n\t\t}\n\n\t\tparser_read_char(parser);\n\t\tmrsh_buffer_append_char(&buf, c);\n\t}\n\n\tpush_buffer_word_string(parser, &children, &buf, &child_begin);\n\tmrsh_buffer_finish(&buf);\n\n\tconsume_symbol(parser);\n\n\tif (children.len == 1) {\n\t\tstruct mrsh_word *word = children.data[0];\n\t\tmrsh_array_finish(&children); // TODO: don't allocate this array\n\t\treturn word;\n\t} else {\n\t\tstruct mrsh_word_list *wl = mrsh_word_list_create(&children, false);\n\t\treturn &wl->word;\n\t}\n}\n\n/* TODO remove end parameter when no *_word function takes it */\nstruct mrsh_word *arithmetic_word(struct mrsh_parser *parser, char end) {\n\tchar next[3] = {0};\n\tchar c = parser_peek_char(parser);\n\tif (c == ')') {\n\t\tparser_peek(parser, next, sizeof(*next) * 2);\n\t\t// If arith_nested_parens != 0, we might be closing an expr.\n\t\t// E.g. $(((1+1 )))\n\t\t//              ^\n\t\tif (!strcmp(next, \"))\") && parser->arith_nested_parens == 0) {\n\t\t\treturn NULL;\n\t\t}\n\t}\n\n\tstruct mrsh_array children = {0};\n\tstruct mrsh_buffer buf = {0};\n\tstruct mrsh_position child_begin = {0};\n\n\twhile (true) {\n\t\tif (!mrsh_position_valid(&child_begin)) {\n\t\t\tchild_begin = parser->pos;\n\t\t}\n\n\t\tparser_peek(parser, next, sizeof(*next) * 2);\n\t\tc = next[0];\n\t\tif (c == '\\0' || c == '\\n' || c == ';'\n\t\t\t\t|| isblank(c)\n\t\t\t\t|| (strcmp(next, \"))\") == 0 && parser->arith_nested_parens == 0)) {\n\t\t\tbreak;\n\t\t}\n\n\t\tif (c == '$') {\n\t\t\tpush_buffer_word_string(parser, &children, &buf, &child_begin);\n\t\t\tstruct mrsh_word *t = expect_dollar(parser);\n\t\t\tif (t == NULL) {\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t\tmrsh_array_add(&children, t);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (c == '`') {\n\t\t\tpush_buffer_word_string(parser, &children, &buf, &child_begin);\n\t\t\tstruct mrsh_word *t = back_quotes(parser);\n\t\t\tif (t == NULL) {\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t\tmrsh_array_add(&children, t);\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Quoting\n\t\tif (c == '\\'') {\n\t\t\tpush_buffer_word_string(parser, &children, &buf, &child_begin);\n\t\t\tstruct mrsh_word *t = single_quotes(parser);\n\t\t\tif (t == NULL) {\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t\tmrsh_array_add(&children, t);\n\t\t\tcontinue;\n\t\t}\n\t\tif (c == '\"') {\n\t\t\tpush_buffer_word_string(parser, &children, &buf, &child_begin);\n\t\t\tstruct mrsh_word *t = double_quotes(parser);\n\t\t\tif (t == NULL) {\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t\tmrsh_array_add(&children, t);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (c == '\\\\') {\n\t\t\t// Unquoted backslash\n\t\t\tparser_read_char(parser);\n\t\t\tc = parser_peek_char(parser);\n\t\t\tif (c == '\\n') {\n\t\t\t\t// Continuation line\n\t\t\t\tread_continuation_line(parser);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t\tif (!strcmp(next, \"<<\") || !strcmp(next, \">>\")) {\n\t\t\tparser_read_char(parser);\n\t\t\tmrsh_buffer_append_char(&buf, c);\n\t\t}\n\n\t\tif (c == '(') {\n\t\t\tparser->arith_nested_parens++;\n\t\t} else if (c == ')') {\n\t\t\tif (parser->arith_nested_parens == 0) {\n\t\t\t\tparser_set_error(parser, \"unmatched closing parenthesis \"\n\t\t\t\t\t\"in arithmetic expression\");\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t\tparser->arith_nested_parens--;\n\t\t}\n\n\t\tparser_read_char(parser);\n\t\tmrsh_buffer_append_char(&buf, c);\n\t}\n\n\tpush_buffer_word_string(parser, &children, &buf, &child_begin);\n\tmrsh_buffer_finish(&buf);\n\n\tconsume_symbol(parser);\n\n\tif (children.len == 1) {\n\t\tstruct mrsh_word *word = children.data[0];\n\t\tmrsh_array_finish(&children); // TODO: don't allocate this array\n\t\treturn word;\n\t} else {\n\t\tstruct mrsh_word_list *wl = mrsh_word_list_create(&children, false);\n\t\treturn &wl->word;\n\t}\n}\n\n/**\n * Parses a word, only recognizing parameter expansion. Quoting and operators\n * are ignored. */\nstruct mrsh_word *parameter_expansion_word(struct mrsh_parser *parser) {\n\tstruct mrsh_array children = {0};\n\tstruct mrsh_buffer buf = {0};\n\tstruct mrsh_position child_begin = {0};\n\n\twhile (true) {\n\t\tif (!mrsh_position_valid(&child_begin)) {\n\t\t\tchild_begin = parser->pos;\n\t\t}\n\n\t\tchar c = parser_peek_char(parser);\n\t\tif (c == '\\0') {\n\t\t\tbreak;\n\t\t}\n\n\t\tif (c == '$') {\n\t\t\tpush_buffer_word_string(parser, &children, &buf, &child_begin);\n\t\t\tstruct mrsh_word *t = expect_dollar(parser);\n\t\t\tif (t == NULL) {\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t\tmrsh_array_add(&children, t);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (c == '`') {\n\t\t\tpush_buffer_word_string(parser, &children, &buf, &child_begin);\n\t\t\tstruct mrsh_word *t = back_quotes(parser);\n\t\t\tif (t == NULL) {\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t\tmrsh_array_add(&children, t);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (c == '\\\\') {\n\t\t\t// Unquoted backslash\n\t\t\tparser_read_char(parser);\n\t\t\tc = parser_peek_char(parser);\n\t\t\tif (c == '\\n') {\n\t\t\t\t// Continuation line\n\t\t\t\tread_continuation_line(parser);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tparser_read_char(parser);\n\t\tmrsh_buffer_append_char(&buf, c);\n\t}\n\n\tpush_buffer_word_string(parser, &children, &buf, &child_begin);\n\tmrsh_buffer_finish(&buf);\n\n\tconsume_symbol(parser);\n\n\tif (children.len == 1) {\n\t\tstruct mrsh_word *word = children.data[0];\n\t\tmrsh_array_finish(&children); // TODO: don't allocate this array\n\t\treturn word;\n\t} else {\n\t\tstruct mrsh_word_list *wl = mrsh_word_list_create(&children, false);\n\t\treturn &wl->word;\n\t}\n}\n"
  },
  {
    "path": "shell/arithm.c",
    "content": "#include <assert.h>\n#include <mrsh/shell.h>\n#include <stdlib.h>\n\nstatic bool run_variable(struct mrsh_state *state, const char *name, long *val,\n\t\tuint32_t *attribs) {\n\tconst char *str = mrsh_env_get(state, name, attribs);\n\tif (str == NULL) {\n\t\tif ((state->options & MRSH_OPT_NOUNSET)) {\n\t\t\tfprintf(stderr, \"%s: %s: unbound variable\\n\",\n\t\t\t\t\tstate->frame->argv[0], name);\n\t\t\treturn false;\n\t\t}\n\t\t*val = 0; // POSIX is not clear what to do in this case\n\t} else {\n\t\tchar *end;\n\t\t*val = strtod(str, &end);\n\t\tif (end == str || end[0] != '\\0') {\n\t\t\tfprintf(stderr, \"%s: %s: not a number: %s\\n\",\n\t\t\t\t\tstate->frame->argv[0], name, str);\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n}\n\nstatic bool run_arithm_binop(struct mrsh_state *state,\n\t\tstruct mrsh_arithm_binop *binop, long *result) {\n\tlong left, right;\n\tif (!mrsh_run_arithm_expr(state, binop->left, &left)) {\n\t\treturn false;\n\t}\n\tif (!mrsh_run_arithm_expr(state, binop->right, &right)) {\n\t\treturn false;\n\t}\n\tswitch (binop->type) {\n\tcase MRSH_ARITHM_BINOP_ASTERISK:\n\t\t*result = left * right;\n\t\treturn true;\n\tcase MRSH_ARITHM_BINOP_SLASH:\n\t\tif (right == 0) {\n\t\t\tfprintf(stderr, \"%s: division by zero: %ld/%ld\\n\",\n\t\t\t\tstate->frame->argv[0], left, right);\n\t\t\treturn false;\n\t\t}\n\t\t*result = left / right;\n\t\treturn true;\n\tcase MRSH_ARITHM_BINOP_PERCENT:\n\t\tif (right == 0) {\n\t\t\tfprintf(stderr, \"%s: division by zero: %ld%%%ld\\n\",\n\t\t\t\tstate->frame->argv[0], left, right);\n\t\t\treturn false;\n\t\t}\n\t\t*result = left % right;\n\t\treturn true;\n\tcase MRSH_ARITHM_BINOP_PLUS:\n\t\t*result = left + right;\n\t\treturn true;\n\tcase MRSH_ARITHM_BINOP_MINUS:\n\t\t*result = left - right;\n\t\treturn true;\n\tcase MRSH_ARITHM_BINOP_DLESS:\n\t\t*result = left << right;\n\t\treturn true;\n\tcase MRSH_ARITHM_BINOP_DGREAT:\n\t\t*result = left >> right;\n\t\treturn true;\n\tcase MRSH_ARITHM_BINOP_LESS:\n\t\t*result = left < right;\n\t\treturn true;\n\tcase MRSH_ARITHM_BINOP_LESSEQ:\n\t\t*result = left <= right;\n\t\treturn true;\n\tcase MRSH_ARITHM_BINOP_GREAT:\n\t\t*result = left > right;\n\t\treturn true;\n\tcase MRSH_ARITHM_BINOP_GREATEQ:\n\t\t*result = left >= right;\n\t\treturn true;\n\tcase MRSH_ARITHM_BINOP_DEQ:\n\t\t*result = left == right;\n\t\treturn true;\n\tcase MRSH_ARITHM_BINOP_BANGEQ:\n\t\t*result = left != right;\n\t\treturn true;\n\tcase MRSH_ARITHM_BINOP_AND:\n\t\t*result = left & right;\n\t\treturn true;\n\tcase MRSH_ARITHM_BINOP_CIRC:\n\t\t*result = left ^ right;\n\t\treturn true;\n\tcase MRSH_ARITHM_BINOP_OR:\n\t\t*result = left | right;\n\t\treturn true;\n\tcase MRSH_ARITHM_BINOP_DAND:\n\t\t*result = left && right;\n\t\treturn true;\n\tcase MRSH_ARITHM_BINOP_DOR:\n\t\t*result = left || right;\n\t\treturn true;\n\t}\n\tabort(); // Unknown binary arithmetic operation\n}\n\nstatic bool run_arithm_unop(struct mrsh_state *state,\n\t\tstruct mrsh_arithm_unop *unop, long *result) {\n\tlong val;\n\tif (!mrsh_run_arithm_expr(state, unop->body, &val)) {\n\t\treturn false;\n\t}\n\tswitch (unop->type) {\n\tcase MRSH_ARITHM_UNOP_PLUS:;\n\t\t/* no-op */\n\t\treturn true;\n\tcase MRSH_ARITHM_UNOP_MINUS:;\n\t\t*result = -val;\n\t\treturn true;\n\tcase MRSH_ARITHM_UNOP_TILDE:;\n\t\t*result = ~val;\n\t\treturn true;\n\tcase MRSH_ARITHM_UNOP_BANG:;\n\t\t*result = !val;\n\t\treturn true;\n\t}\n\tabort(); // Unknown unary arithmetic operation\n}\n\nstatic bool run_arithm_cond(struct mrsh_state *state,\n\t\tstruct mrsh_arithm_cond *cond, long *result) {\n\tlong condition;\n\tif (!mrsh_run_arithm_expr(state, cond->condition, &condition)) {\n\t\treturn false;\n\t}\n\tif (condition) {\n\t\tif (!mrsh_run_arithm_expr(state, cond->body, result)) {\n\t\t\treturn false;\n\t\t}\n\t} else {\n\t\tif (!mrsh_run_arithm_expr(state, cond->else_part, result)) {\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n}\n\nstatic long run_arithm_assign_op(enum mrsh_arithm_assign_op op,\n\t\tlong cur, long val, long *result) {\n\tswitch (op) {\n\tcase MRSH_ARITHM_ASSIGN_NONE:\n\t\t*result = val;\n\t\treturn true;\n\tcase MRSH_ARITHM_ASSIGN_ASTERISK:\n\t\t*result = cur * val;\n\t\treturn true;\n\tcase MRSH_ARITHM_ASSIGN_SLASH:\n\t\tif (val == 0) {\n\t\t\tfprintf(stderr, \"division by zero: %ld/%ld\\n\", cur, val);\n\t\t\treturn false;\n\t\t}\n\t\t*result = cur / val;\n\t\treturn true;\n\tcase MRSH_ARITHM_ASSIGN_PERCENT:\n\t\tif (val == 0) {\n\t\t\tfprintf(stderr, \"division by zero: %ld%%%ld\\n\", cur, val);\n\t\t\treturn false;\n\t\t}\n\t\t*result = cur % val;\n\t\treturn true;\n\tcase MRSH_ARITHM_ASSIGN_PLUS:\n\t\t*result = cur + val;\n\t\treturn true;\n\tcase MRSH_ARITHM_ASSIGN_MINUS:\n\t\t*result = cur - val;\n\t\treturn true;\n\tcase MRSH_ARITHM_ASSIGN_DLESS:\n\t\t*result = cur << val;\n\t\treturn true;\n\tcase MRSH_ARITHM_ASSIGN_DGREAT:\n\t\t*result = cur >> val;\n\t\treturn true;\n\tcase MRSH_ARITHM_ASSIGN_AND:\n\t\t*result = cur & val;\n\t\treturn true;\n\tcase MRSH_ARITHM_ASSIGN_CIRC:\n\t\t*result = cur ^ val;\n\t\treturn true;\n\tcase MRSH_ARITHM_ASSIGN_OR:\n\t\t*result = cur | val;\n\t\treturn true;\n\t}\n\tabort();\n}\n\nstatic bool run_arithm_assign(struct mrsh_state *state,\n\t\tstruct mrsh_arithm_assign *assign, long *result) {\n\tlong val;\n\tif (!mrsh_run_arithm_expr(state, assign->value, &val)) {\n\t\treturn false;\n\t}\n\tlong cur = 0;\n\tuint32_t attribs = MRSH_VAR_ATTRIB_NONE;\n\tif (assign->op != MRSH_ARITHM_ASSIGN_NONE) {\n\t\tif (!run_variable(state, assign->name, &cur, &attribs)) {\n\t\t\treturn false;\n\t\t}\n\t}\n\tif (!run_arithm_assign_op(assign->op, cur, val, result)) {\n\t\treturn false;\n\t}\n\n\tchar buf[32];\n\tsnprintf(buf, sizeof(buf), \"%ld\", *result);\n\tmrsh_env_set(state, assign->name, buf, attribs);\n\n\treturn true;\n}\n\nbool mrsh_run_arithm_expr(struct mrsh_state *state,\n\t\tstruct mrsh_arithm_expr *expr, long *result) {\n\tswitch (expr->type) {\n\tcase MRSH_ARITHM_LITERAL:;\n\t\tstruct mrsh_arithm_literal *literal =\n\t\t\t(struct mrsh_arithm_literal *)expr;\n\t\t*result = literal->value;\n\t\treturn true;\n\tcase MRSH_ARITHM_VARIABLE:;\n\t\tstruct mrsh_arithm_variable *variable =\n\t\t\t(struct mrsh_arithm_variable *)expr;\n\t\treturn run_variable(state, variable->name, result, NULL);\n\tcase MRSH_ARITHM_BINOP:;\n\t\tstruct mrsh_arithm_binop *binop =\n\t\t\t(struct mrsh_arithm_binop *)expr;\n\t\treturn run_arithm_binop(state, binop, result);\n\tcase MRSH_ARITHM_UNOP:;\n\t\tstruct mrsh_arithm_unop *unop =\n\t\t\t(struct mrsh_arithm_unop *)expr;\n\t\treturn run_arithm_unop(state, unop, result);\n\tcase MRSH_ARITHM_COND:;\n\t\tstruct mrsh_arithm_cond *cond =\n\t\t\t(struct mrsh_arithm_cond *)expr;\n\t\treturn run_arithm_cond(state, cond, result);\n\tcase MRSH_ARITHM_ASSIGN:;\n\t\tstruct mrsh_arithm_assign *assign =\n\t\t\t(struct mrsh_arithm_assign *)expr;\n\t\treturn run_arithm_assign(state, assign, result);\n\t}\n\tabort();\n}\n"
  },
  {
    "path": "shell/entry.c",
    "content": "#define _XOPEN_SOURCE 700\n#include <mrsh/builtin.h>\n#include <mrsh/entry.h>\n#include <mrsh/shell.h>\n#include <mrsh/parser.h>\n#include <errno.h>\n#include <string.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include \"builtin.h\"\n#include \"parser.h\"\n#include \"shell/path.h\"\n#include \"shell/trap.h\"\n\nstatic char *expand_str(struct mrsh_state *state, const char *src) {\n\tstruct mrsh_parser *parser = mrsh_parser_with_data(src, strlen(src));\n\tif (parser == NULL) {\n\t\treturn NULL;\n\t}\n\tstruct mrsh_word *word = parameter_expansion_word(parser);\n\tif (word == NULL) {\n\t\tstruct mrsh_position err_pos;\n\t\tconst char *err_msg = mrsh_parser_error(parser, &err_pos);\n\t\tif (err_msg != NULL) {\n\t\t\tfprintf(stderr, \"%d:%d: syntax error: %s\\n\",\n\t\t\t\terr_pos.line, err_pos.column, err_msg);\n\t\t} else {\n\t\t\tfprintf(stderr, \"expand_str: unknown error\\n\");\n\t\t}\n\t\tmrsh_parser_destroy(parser);\n\t\treturn NULL;\n\t}\n\tmrsh_parser_destroy(parser);\n\tmrsh_run_word(state, &word);\n\tchar *str = mrsh_word_str(word);\n\tmrsh_word_destroy(word);\n\treturn str;\n}\n\nstatic char *expand_ps(struct mrsh_state *state, const char *name) {\n\tconst char *ps = mrsh_env_get(state, name, NULL);\n\tif (ps == NULL) {\n\t\treturn NULL;\n\t}\n\tchar *str = expand_str(state, ps);\n\tif (str == NULL) {\n\t\tfprintf(stderr, \"failed to expand '%s'\\n\", name);\n\t\t// On error, fallback to the default PSn value\n\t}\n\treturn str;\n}\n\nchar *mrsh_get_ps1(struct mrsh_state *state, int next_history_id) {\n\t// TODO: Replace ! with next history ID\n\tchar *str = expand_ps(state, \"PS1\");\n\tif (str != NULL) {\n\t\treturn str;\n\t}\n\tchar *p = malloc(3);\n\tsprintf(p, \"%s\", getuid() ? \"$ \" : \"# \");\n\treturn p;\n}\n\nchar *mrsh_get_ps2(struct mrsh_state *state) {\n\t// TODO: Replace ! with next history ID\n\tchar *str = expand_ps(state, \"PS2\");\n\tif (str != NULL) {\n\t\treturn str;\n\t}\n\treturn strdup(\"> \");\n}\n\nchar *mrsh_get_ps4(struct mrsh_state *state) {\n\tchar *str = expand_ps(state, \"PS4\");\n\tif (str != NULL) {\n\t\treturn str;\n\t}\n\treturn strdup(\"+ \");\n}\n\nbool mrsh_populate_env(struct mrsh_state *state, char **environ) {\n\tfor (size_t i = 0; environ[i] != NULL; ++i) {\n\t\tchar *eql = strchr(environ[i], '=');\n\t\tsize_t klen = eql - environ[i];\n\t\tchar *key = strndup(environ[i], klen);\n\t\tchar *val = &eql[1];\n\t\tmrsh_env_set(state, key, val, MRSH_VAR_ATTRIB_EXPORT);\n\t\tfree(key);\n\t}\n\n\tmrsh_env_set(state, \"IFS\", \" \\t\\n\", MRSH_VAR_ATTRIB_NONE);\n\n\tpid_t ppid = getppid();\n\tchar ppid_str[24];\n\tsnprintf(ppid_str, sizeof(ppid_str), \"%d\", ppid);\n\tmrsh_env_set(state, \"PPID\", ppid_str, MRSH_VAR_ATTRIB_NONE);\n\n\t// TODO check if path is well-formed, has . or .., and handle symbolic links\n\tconst char *pwd = mrsh_env_get(state, \"PWD\", NULL);\n\tif (pwd == NULL) {\n\t\tchar *cwd = current_working_dir();\n\t\tif (cwd == NULL) {\n\t\t\tperror(\"current_working_dir failed\");\n\t\t\treturn false;\n\t\t}\n\t\tmrsh_env_set(state, \"PWD\", cwd,\n\t\t\t\tMRSH_VAR_ATTRIB_EXPORT | MRSH_VAR_ATTRIB_READONLY);\n\t\tfree(cwd);\n\t} else {\n\t\tmrsh_env_set(state, \"PWD\", pwd,\n\t\t\t\tMRSH_VAR_ATTRIB_EXPORT | MRSH_VAR_ATTRIB_READONLY);\n\t}\n\n\tmrsh_env_set(state, \"OPTIND\", \"1\", MRSH_VAR_ATTRIB_NONE);\n\treturn true;\n}\n\nstatic void source_file(struct mrsh_state *state, char *path) {\n\tif (access(path, F_OK) == -1) {\n\t\treturn;\n\t}\n\tchar *env_argv[] = { \".\", path };\n\tmrsh_run_builtin(state, sizeof(env_argv) / sizeof(env_argv[0]), env_argv);\n}\n\nvoid mrsh_source_profile(struct mrsh_state *state) {\n\tsource_file(state, \"/etc/profile\");\n\n\tconst char *home = getenv(\"HOME\");\n\tint n = snprintf(NULL, 0, \"%s/.profile\", home);\n\tif (n < 0) {\n\t\tperror(\"snprintf failed\");\n\t\treturn;\n\t}\n\tchar *path = malloc(n + 1);\n\tif (path == NULL) {\n\t\tperror(\"malloc failed\");\n\t\treturn;\n\t}\n\tsnprintf(path, n + 1, \"%s/.profile\", home);\n\n\tsource_file(state, path);\n\n\tfree(path);\n}\n\nvoid mrsh_source_env(struct mrsh_state *state) {\n\tchar *path = getenv(\"ENV\");\n\tif (path == NULL) {\n\t\treturn;\n\t}\n\tif (getuid() != geteuid() || getgid() != getegid()) {\n\t\treturn;\n\t}\n\tpath = expand_str(state, path);\n\tif (path[0] != '/') {\n\t\tfprintf(stderr, \"Error: $ENV is not an absolute path; \"\n\t\t\t\t\"this is undefined behavior.\\n\");\n\t\tfprintf(stderr, \"Continuing without sourcing it.\\n\");\n\t} else {\n\t\tsource_file(state, path);\n\t}\n\tfree(path);\n}\n\nbool mrsh_run_exit_trap(struct mrsh_state *state) {\n\treturn run_exit_trap(state);\n}\n"
  },
  {
    "path": "shell/job.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <assert.h>\n#include <errno.h>\n#include <mrsh/array.h>\n#include <signal.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/types.h>\n#include <sys/wait.h>\n#include <unistd.h>\n#include \"shell/job.h\"\n#include \"shell/process.h\"\n#include \"shell/shell.h\"\n#include \"shell/task.h\"\n\nbool mrsh_set_job_control(struct mrsh_state *state, bool enabled) {\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\n\tassert(priv->term_fd >= 0);\n\n\tif (priv->job_control == enabled) {\n\t\treturn true;\n\t}\n\n\tif (enabled) {\n\t\t// Loop until we are in the foreground\n\t\twhile (true) {\n\t\t\tpid_t pgid = getpgrp();\n\t\t\tif (tcgetpgrp(priv->term_fd) == pgid) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tkill(-pgid, SIGTTIN);\n\t\t}\n\n\t\t// Ignore interactive and job-control signals\n\t\tset_job_control_traps(state, true);\n\n\t\t// Put ourselves in our own process group, if we aren't the session\n\t\t// leader\n\t\tpriv->pgid = getpid();\n\t\tif (getsid(0) != priv->pgid) {\n\t\t\tif (setpgid(priv->pgid, priv->pgid) != 0) {\n\t\t\t\tperror(\"setpgid\");\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\t// Grab control of the terminal\n\t\tif (tcsetpgrp(priv->term_fd, priv->pgid) != 0) {\n\t\t\tperror(\"tcsetpgrp\");\n\t\t\treturn false;\n\t\t}\n\t\t// Save default terminal attributes for the shell\n\t\tif (tcgetattr(priv->term_fd, &priv->term_modes) != 0) {\n\t\t\tperror(\"tcgetattr\");\n\t\t\treturn false;\n\t\t}\n\t} else {\n\t\tset_job_control_traps(state, false);\n\t}\n\n\tpriv->job_control = enabled;\n\treturn true;\n}\n\nstatic void array_remove(struct mrsh_array *array, size_t i) {\n\tmemmove(&array->data[i], &array->data[i + 1],\n\t\t(array->len - i - 1) * sizeof(void *));\n\t--array->len;\n}\n\nstruct mrsh_job *job_create(struct mrsh_state *state,\n\t\tconst struct mrsh_node *node) {\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\n\tint id = 1;\n\tfor (size_t i = 0; i < priv->jobs.len; ++i) {\n\t\tstruct mrsh_job *job = priv->jobs.data[i];\n\t\tif (id < job->job_id + 1) {\n\t\t\tid = job->job_id + 1;\n\t\t}\n\t}\n\n\tstruct mrsh_job *job = calloc(1, sizeof(struct mrsh_job));\n\tjob->state = state;\n\tjob->node = mrsh_node_copy(node);\n\tjob->pgid = -1;\n\tjob->job_id = id;\n\tjob->last_status = TASK_STATUS_WAIT;\n\tmrsh_array_add(&priv->jobs, job);\n\treturn job;\n}\n\nvoid job_destroy(struct mrsh_job *job) {\n\tif (job == NULL) {\n\t\treturn;\n\t}\n\n\tstruct mrsh_state_priv *priv = state_get_priv(job->state);\n\n\tif (priv->foreground_job == job) {\n\t\tjob_set_foreground(job, false, false);\n\t}\n\n\tfor (size_t i = 0; i < priv->jobs.len; ++i) {\n\t\tif (priv->jobs.data[i] == job) {\n\t\t\tarray_remove(&priv->jobs, i);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tfor (size_t j = 0; j < job->processes.len; ++j) {\n\t\tprocess_destroy(job->processes.data[j]);\n\t}\n\tmrsh_array_finish(&job->processes);\n\tmrsh_node_destroy(job->node);\n\tfree(job);\n}\n\nvoid job_add_process(struct mrsh_job *job, struct mrsh_process *proc) {\n\tif (job->pgid <= 0) {\n\t\tjob->pgid = proc->pid;\n\t}\n\t// This can fail because we do it both in the parent and the child\n\tif (setpgid(proc->pid, job->pgid) != 0 && errno != EPERM) {\n\t\tperror(\"setpgid\");\n\t\treturn;\n\t}\n\tmrsh_array_add(&job->processes, proc);\n}\n\nstatic void job_queue_notification(struct mrsh_job *job) {\n\tstruct mrsh_state_priv *priv = state_get_priv(job->state);\n\n\tint status = job_poll(job);\n\tif (status != job->last_status && job->pgid > 0 &&\n\t\t\tpriv->foreground_job != job) {\n\t\tjob->pending_notification = true;\n\t}\n\tjob->last_status = status;\n}\n\nbool job_set_foreground(struct mrsh_job *job, bool foreground, bool cont) {\n\tstruct mrsh_state *state = job->state;\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\n\tassert(job->pgid > 0);\n\n\tif (!priv->job_control) {\n\t\treturn false;\n\t}\n\n\t// Don't try to continue the job if it's not stopped\n\tif (job_poll(job) != TASK_STATUS_STOPPED) {\n\t\tcont = false;\n\t}\n\n\tif (foreground && priv->foreground_job != job) {\n\t\tassert(priv->foreground_job == NULL);\n\t\t// Put the job in the foreground\n\t\ttcsetpgrp(priv->term_fd, job->pgid);\n\t\tif (cont) {\n\t\t\t// Restore the job's terminal modes\n\t\t\ttcsetattr(priv->term_fd, TCSADRAIN, &job->term_modes);\n\t\t}\n\t\tpriv->foreground_job = job;\n\t}\n\n\tif (!foreground && priv->foreground_job == job) {\n\t\t// Put the shell back in the foreground\n\t\ttcsetpgrp(priv->term_fd, priv->pgid);\n\t\t// Save the job's terminal modes, to restore them if it's put in the\n\t\t// foreground again\n\t\ttcgetattr(priv->term_fd, &job->term_modes);\n\t\t// Restore the shell’s terminal modes\n\t\ttcsetattr(priv->term_fd, TCSADRAIN, &priv->term_modes);\n\t\tpriv->foreground_job = NULL;\n\t}\n\n\tif (cont) {\n\t\tif (kill(-job->pgid, SIGCONT) != 0) {\n\t\t\tperror(\"kill\");\n\t\t\treturn false;\n\t\t}\n\n\t\tfor (size_t j = 0; j < job->processes.len; ++j) {\n\t\t\tstruct mrsh_process *proc = job->processes.data[j];\n\t\t\tproc->stopped = false;\n\t\t}\n\t}\n\n\tjob_queue_notification(job);\n\n\treturn true;\n}\n\nint job_poll(struct mrsh_job *job) {\n\tint proc_status = 0;\n\tbool stopped = false;\n\tfor (size_t j = 0; j < job->processes.len; ++j) {\n\t\tstruct mrsh_process *proc = job->processes.data[j];\n\t\tproc_status = process_poll(proc);\n\t\tif (proc_status == TASK_STATUS_WAIT) {\n\t\t\treturn TASK_STATUS_WAIT;\n\t\t}\n\t\tif (proc_status == TASK_STATUS_STOPPED) {\n\t\t\tstopped = true;\n\t\t}\n\t}\n\n\tif (stopped) {\n\t\treturn TASK_STATUS_STOPPED;\n\t}\n\t// All processes have terminated, return the last one's status\n\treturn proc_status;\n}\n\nstatic void update_job(struct mrsh_state *state, pid_t pid, int stat);\n\nstatic bool _job_wait(struct mrsh_state *state, pid_t pid, int options) {\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\n\tassert(pid > 0 && pid != getpid());\n\n\t// We only want to be notified about stopped processes in the main\n\t// shell. Child processes want to block until their own children have\n\t// terminated.\n\tif (!priv->child) {\n\t\toptions |= WUNTRACED;\n\t}\n\n\twhile (true) {\n\t\t// Here it's important to wait for a specific process: we don't want to\n\t\t// steal one of our grandchildren's status for one of our children.\n\t\tint stat;\n\t\tpid_t ret = waitpid(pid, &stat, options);\n\t\tif (ret == 0) { // no status available\n\t\t\tassert(options & WNOHANG);\n\t\t\treturn true;\n\t\t} else if (ret < 0) {\n\t\t\tif (errno == EINTR) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tfprintf(stderr, \"waitpid(%d): %s\\n\", pid, strerror(errno));\n\t\t\treturn false;\n\t\t}\n\t\tassert(ret == pid);\n\n\t\tupdate_job(state, ret, stat);\n\t\treturn true;\n\t}\n}\n\nstatic struct mrsh_process *job_get_running_process(struct mrsh_job *job) {\n\tfor (size_t j = 0; j < job->processes.len; ++j) {\n\t\tstruct mrsh_process *proc = job->processes.data[j];\n\t\tif (process_poll(proc) == TASK_STATUS_WAIT) {\n\t\t\treturn proc;\n\t\t}\n\t}\n\treturn NULL;\n}\n\nint job_wait(struct mrsh_job *job) {\n\twhile (true) {\n\t\tint status = job_poll(job);\n\t\tif (status != TASK_STATUS_WAIT) {\n\t\t\treturn status;\n\t\t}\n\n\t\tstruct mrsh_process *wait_proc = job_get_running_process(job);\n\t\tassert(wait_proc != NULL);\n\t\tif (!_job_wait(job->state, wait_proc->pid, 0)) {\n\t\t\treturn TASK_STATUS_ERROR;\n\t\t}\n\t}\n}\n\nint job_wait_process(struct mrsh_process *proc) {\n\twhile (true) {\n\t\tint status = process_poll(proc);\n\t\tif (status != TASK_STATUS_WAIT) {\n\t\t\treturn status;\n\t\t}\n\n\t\tif (!_job_wait(proc->state, proc->pid, 0)) {\n\t\t\treturn TASK_STATUS_ERROR;\n\t\t}\n\t}\n}\n\nbool refresh_jobs_status(struct mrsh_state *state) {\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\n\tfor (size_t i = 0; i < priv->jobs.len; ++i) {\n\t\tstruct mrsh_job *job = priv->jobs.data[i];\n\t\tstruct mrsh_process *proc = job_get_running_process(job);\n\t\tif (proc == NULL) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (!_job_wait(job->state, proc->pid, WNOHANG)) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\nbool init_job_child_process(struct mrsh_state *state) {\n\treturn mrsh_set_job_control(state, false);\n}\n\nstatic void update_job(struct mrsh_state *state, pid_t pid, int stat) {\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\n\tupdate_process(state, pid, stat);\n\n\tif (!priv->job_control) {\n\t\treturn;\n\t}\n\n\t// Put stopped and terminated jobs in the background. We don't want to do so\n\t// if we're not the main shell, because we only have a partial view of the\n\t// jobs (we only know about our own child processes).\n\tfor (size_t i = 0; i < priv->jobs.len; ++i) {\n\t\tstruct mrsh_job *job = priv->jobs.data[i];\n\n\t\tint status = job_poll(job);\n\t\tif (status >= 0) {\n\t\t\tjob_queue_notification(job);\n\t\t}\n\t\tif (status != TASK_STATUS_WAIT && job->pgid > 0) {\n\t\t\tjob_set_foreground(job, false, false);\n\t\t}\n\t}\n}\n\n// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_204\nstruct mrsh_job *job_by_id(struct mrsh_state *state,\n\t\tconst char *id, bool interactive) {\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\n\tif (id[0] != '%' || id[1] == '\\0') {\n\t\tif (interactive) {\n\t\t\tfprintf(stderr, \"Invalid job ID specifier\\n\");\n\t\t}\n\t\treturn NULL;\n\t}\n\n\tif (id[2] == '\\0') {\n\t\tswitch (id[1]) {\n\t\tcase '%':\n\t\tcase '+':\n\t\t\t// Current job\n\t\t\tfor (ssize_t i = priv->jobs.len - 1; i >= 0; --i) {\n\t\t\t\tstruct mrsh_job *job = priv->jobs.data[i];\n\t\t\t\tif (job_poll(job) == TASK_STATUS_STOPPED) {\n\t\t\t\t\treturn job;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (ssize_t i = priv->jobs.len - 1; i >= 0; --i) {\n\t\t\t\tstruct mrsh_job *job = priv->jobs.data[i];\n\t\t\t\tif (job_poll(job) == TASK_STATUS_WAIT) {\n\t\t\t\t\treturn job;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (interactive) {\n\t\t\t\tfprintf(stderr, \"No current job\\n\");\n\t\t\t}\n\t\t\treturn NULL;\n\t\tcase '-':\n\t\t\t// Previous job\n\t\t\tfor (ssize_t i = priv->jobs.len - 1, n = 0; i >= 0; --i) {\n\t\t\t\tstruct mrsh_job *job = priv->jobs.data[i];\n\t\t\t\tif (job_poll(job) == TASK_STATUS_STOPPED) {\n\t\t\t\t\tif (++n == 2) {\n\t\t\t\t\t\treturn job;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tbool first = true;\n\t\t\tfor (ssize_t i = priv->jobs.len - 1; i >= 0; --i) {\n\t\t\t\tstruct mrsh_job *job = priv->jobs.data[i];\n\t\t\t\tif (job_poll(job) == TASK_STATUS_WAIT) {\n\t\t\t\t\tif (first) {\n\t\t\t\t\t\tfirst = false;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\treturn job;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (interactive) {\n\t\t\t\tfprintf(stderr, \"No previous job\\n\");\n\t\t\t}\n\t\t\treturn NULL;\n\t\t}\n\t}\n\n\tif (id[1] >= '0' && id[1] <= '9') {\n\t\tchar *endptr;\n\t\tint n = strtol(&id[1], &endptr, 10);\n\t\tif (endptr[0] != '\\0') {\n\t\t\tif (interactive) {\n\t\t\t\tfprintf(stderr, \"Invalid job number '%s'\\n\", id);\n\t\t\t}\n\t\t\treturn NULL;\n\t\t}\n\t\tfor (size_t i = 0; i < priv->jobs.len; ++i) {\n\t\t\tstruct mrsh_job *job = priv->jobs.data[i];\n\t\t\tif (job->job_id == n) {\n\t\t\t\treturn job;\n\t\t\t}\n\t\t}\n\t\tif (interactive) {\n\t\t\tfprintf(stderr, \"No such job '%s' (%d)\\n\", id, n);\n\t\t}\n\t\treturn NULL;\n\t}\n\n\tfor (size_t i = 0; i < priv->jobs.len; i++) {\n\t\tstruct mrsh_job *job = priv->jobs.data[i];\n\t\tchar *cmd = mrsh_node_format(job->node);\n\t\tbool match = false;\n\t\tswitch (id[1]) {\n\t\tcase '?':\n\t\t\tmatch = strstr(cmd, &id[2]) != NULL;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tmatch = strstr(cmd, &id[1]) == cmd;\n\t\t\tbreak;\n\t\t}\n\t\tfree(cmd);\n\t\tif (match) {\n\t\t\treturn job;\n\t\t}\n\t}\n\n\tif (interactive) {\n\t\tfprintf(stderr, \"No such job '%s'\\n\", id);\n\t}\n\treturn NULL;\n}\n\nconst char *job_state_str(struct mrsh_job *job, bool r) {\n\tint status = job_poll(job);\n\tswitch (status) {\n\tcase TASK_STATUS_WAIT:\n\t\treturn \"Running\";\n\tcase TASK_STATUS_ERROR:\n\t\treturn \"Error\";\n\tcase TASK_STATUS_STOPPED:\n\t\tif (job->processes.len > 0) {\n\t\t\tstruct mrsh_process *proc = job->processes.data[0];\n\t\t\tswitch (proc->signal) {\n\t\t\tcase SIGSTOP:\n\t\t\t\treturn r ? \"Stopped (SIGSTOP)\" : \"Suspended (SIGSTOP)\";\n\t\t\tcase SIGTTIN:\n\t\t\t\treturn r ? \"Stopped (SIGTTIN)\" : \"Suspended (SIGTTIN)\";\n\t\t\tcase SIGTTOU:\n\t\t\t\treturn r ? \"Stopped (SIGTTOU)\" : \"Suspended (SIGTTOU)\";\n\t\t\t}\n\t\t}\n\t\treturn r ? \"Stopped\" : \"Suspended\";\n\tdefault:\n\t\tif (job->processes.len > 0) {\n\t\t\tstruct mrsh_process *proc = job->processes.data[0];\n\t\t\tif (proc->stat != 0) {\n\t\t\t\tstatic char stat[128];\n\t\t\t\tsnprintf(stat, sizeof(stat), \"Done(%d)\", proc->stat);\n\t\t\t\treturn stat;\n\t\t\t}\n\t\t}\n\t\tassert(status >= 0);\n\t\treturn \"Done\";\n\t}\n}\n\nvoid broadcast_sighup_to_jobs(struct mrsh_state *state) {\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\tassert(priv->job_control);\n\n\tfor (size_t i = 0; i < priv->jobs.len; ++i) {\n\t\tstruct mrsh_job *job = priv->jobs.data[i];\n\t\tif (job_poll(job) >= 0) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (kill(-job->pgid, SIGHUP) != 0) {\n\t\t\tperror(\"kill\");\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "shell/path.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <errno.h>\n#include <mrsh/buffer.h>\n#include <mrsh/shell.h>\n#include <string.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include \"shell/path.h\"\n\nchar *expand_path(struct mrsh_state *state, const char *file, bool exec,\n\t\tbool default_path) {\n\tif (strchr(file, '/')) {\n\t\treturn strdup(file);\n\t}\n\n\tchar *pathe;\n\tif (!default_path) {\n\t\tconst char *_pathe = mrsh_env_get(state, \"PATH\", NULL);\n\t\tif (!_pathe) {\n\t\t\treturn NULL;\n\t\t}\n\t\tpathe = strdup(_pathe);\n\t\tif (!pathe) {\n\t\t\treturn NULL;\n\t\t}\n\t} else {\n\t\tsize_t pathe_size = confstr(_CS_PATH, NULL, 0);\n\t\tif (pathe_size == 0) {\n\t\t\treturn NULL;\n\t\t}\n\t\tpathe = malloc(pathe_size);\n\t\tif (pathe == NULL) {\n\t\t\treturn NULL;\n\t\t}\n\t\tif (confstr(_CS_PATH, pathe, pathe_size) != pathe_size) {\n\t\t\tfree(pathe);\n\t\t\treturn NULL;\n\t\t}\n\t}\n\n\tchar *path = NULL;\n\tchar *basedir = strtok(pathe, \":\");\n\twhile (basedir != NULL) {\n\t\tsize_t blen = strlen(basedir);\n\t\tif (blen == 0) {\n\t\t\tgoto next;\n\t\t}\n\t\tbool slash = basedir[blen - 1] == '/';\n\t\tsize_t n = snprintf(NULL, 0, \"%s%s%s\", basedir, slash ? \"\" : \"/\", file);\n\t\tpath = realloc(path, n + 1);\n\t\tif (path == NULL) {\n\t\t\tgoto next;\n\t\t}\n\t\tsnprintf(path, n + 1, \"%s%s%s\", basedir, slash ? \"\" : \"/\", file);\n\t\tif (access(path, exec ? X_OK : R_OK) != -1) {\n\t\t\tfree(pathe);\n\t\t\treturn path;\n\t\t}\nnext:\n\t\tbasedir = strtok(NULL, \":\");\n\t}\n\tfree(path);\n\tfree(pathe);\n\treturn NULL;\n}\n\nchar *current_working_dir(void) {\n\t// POSIX doesn't provide a way to query the CWD size\n\tstruct mrsh_buffer buf = {0};\n\tif (mrsh_buffer_reserve(&buf, 256) == NULL) {\n\t\treturn NULL;\n\t}\n\twhile (getcwd(buf.data, buf.cap) == NULL) {\n\t\tif (errno != ERANGE) {\n\t\t\treturn NULL;\n\t\t}\n\t\tif (mrsh_buffer_reserve(&buf, buf.cap * 2) == NULL) {\n\t\t\treturn NULL;\n\t\t}\n\t}\n\treturn mrsh_buffer_steal(&buf);\n}\n"
  },
  {
    "path": "shell/process.c",
    "content": "#define _POSIX_C_SOURCE 1\n#include <assert.h>\n#include <mrsh/array.h>\n#include <signal.h>\n#include <stdbool.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/wait.h>\n#include \"shell/process.h\"\n#include \"shell/task.h\"\n\nstruct mrsh_process *process_create(struct mrsh_state *state, pid_t pid) {\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\n\tstruct mrsh_process *proc = calloc(1, sizeof(struct mrsh_process));\n\tproc->pid = pid;\n\tproc->state = state;\n\tmrsh_array_add(&priv->processes, proc);\n\treturn proc;\n}\n\nstatic void array_remove(struct mrsh_array *array, size_t i) {\n\tmemmove(&array->data[i], &array->data[i + 1],\n\t\t(array->len - i - 1) * sizeof(void *));\n\t--array->len;\n}\n\nvoid process_destroy(struct mrsh_process *proc) {\n\tstruct mrsh_state_priv *priv = state_get_priv(proc->state);\n\n\tfor (size_t i = 0; i < priv->processes.len; ++i) {\n\t\tif (priv->processes.data[i] == proc) {\n\t\t\tarray_remove(&priv->processes, i);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tfree(proc);\n}\n\nint process_poll(struct mrsh_process *proc) {\n\tif (proc->stopped) {\n\t\treturn TASK_STATUS_STOPPED;\n\t} else if (!proc->terminated) {\n\t\treturn TASK_STATUS_WAIT;\n\t}\n\n\tif (WIFEXITED(proc->stat)) {\n\t\treturn WEXITSTATUS(proc->stat);\n\t} else if (WIFSIGNALED(proc->stat)) {\n\t\treturn 129; // POSIX requires >128\n\t} else {\n\t\tabort();\n\t}\n}\n\nvoid update_process(struct mrsh_state *state, pid_t pid, int stat) {\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\n\tstruct mrsh_process *proc = NULL;\n\tbool found = false;\n\tfor (size_t i = 0; i < priv->processes.len; ++i) {\n\t\tproc = priv->processes.data[i];\n\t\tif (proc->pid == pid) {\n\t\t\tfound = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (!found) {\n\t\treturn;\n\t}\n\n\tif (WIFEXITED(stat) || WIFSIGNALED(stat)) {\n\t\tproc->terminated = true;\n\t\tproc->stat = stat;\n\t} else if (WIFSTOPPED(stat)) {\n\t\tproc->stopped = true;\n\t\tproc->signal = WSTOPSIG(stat);\n\t} else {\n\t\tabort();\n\t}\n}\n"
  },
  {
    "path": "shell/redir.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <errno.h>\n#include <fcntl.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <sys/param.h>\n#include \"shell/redir.h\"\n\nstatic ssize_t write_here_document_line(int fd, struct mrsh_word *line,\n\t\tssize_t max_size) {\n\tchar *line_str = mrsh_word_str(line);\n\tsize_t line_len = strlen(line_str);\n\tsize_t write_len = line_len + 1; // line + terminating \\n\n\tif (max_size >= 0 && write_len > (size_t)max_size) {\n\t\tfree(line_str);\n\t\treturn 0;\n\t}\n\n\terrno = 0;\n\tssize_t n = write(fd, line_str, line_len);\n\tfree(line_str);\n\tif (n < 0 || (size_t)n != line_len) {\n\t\tgoto err_write;\n\t}\n\n\tif (write(fd, \"\\n\", sizeof(char)) != 1) {\n\t\tgoto err_write;\n\t}\n\n\treturn write_len;\n\nerr_write:\n\tfprintf(stderr, \"write() failed: %s\\n\",\n\t\terrno ? strerror(errno) : \"short write\");\n\treturn -1;\n}\n\nstatic int create_here_document_fd(const struct mrsh_array *lines) {\n\tint fds[2];\n\tif (pipe(fds) != 0) {\n\t\tperror(\"pipe\");\n\t\treturn -1;\n\t}\n\n\t// We can write at most PIPE_BUF bytes without blocking. If we want to write\n\t// more, we need to fork and continue writing in another process.\n\tsize_t remaining = PIPE_BUF;\n\tbool more = false;\n\tsize_t i;\n\tfor (i = 0; i < lines->len; ++i) {\n\t\tstruct mrsh_word *line = lines->data[i];\n\t\tssize_t n = write_here_document_line(fds[1], line, remaining);\n\t\tif (n < 0) {\n\t\t\tclose(fds[0]);\n\t\t\tclose(fds[1]);\n\t\t\treturn -1;\n\t\t} else if (n == 0) {\n\t\t\tmore = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (!more) {\n\t\t// We could write everything into the pipe buffer\n\t\tclose(fds[1]);\n\t\treturn fds[0];\n\t}\n\n\tpid_t pid = fork();\n\tif (pid < 0) {\n\t\tperror(\"fork\");\n\t\tclose(fds[0]);\n\t\tclose(fds[1]);\n\t\treturn -1;\n\t} else if (pid == 0) {\n\t\tclose(fds[0]);\n\n\t\tfor (; i < lines->len; ++i) {\n\t\t\tstruct mrsh_word *line = lines->data[i];\n\t\t\tssize_t n = write_here_document_line(fds[1], line, -1);\n\t\t\tif (n < 0) {\n\t\t\t\tclose(fds[1]);\n\t\t\t\texit(1);\n\t\t\t}\n\t\t}\n\t\tclose(fds[1]);\n\t\texit(0);\n\t}\n\n\tclose(fds[1]);\n\treturn fds[0];\n}\n\nstatic int parse_fd(const char *str) {\n\tchar *endptr;\n\terrno = 0;\n\tint fd = strtol(str, &endptr, 10);\n\tif (errno != 0) {\n\t\treturn -1;\n\t}\n\tif (endptr[0] != '\\0') {\n\t\terrno = EINVAL;\n\t\treturn -1;\n\t}\n\n\treturn fd;\n}\n\nint process_redir(const struct mrsh_io_redirect *redir, int *redir_fd) {\n\t// TODO: filename expansions\n\tchar *filename = mrsh_word_str(redir->name);\n\n\tint fd = -1, default_redir_fd = -1;\n\terrno = 0;\n\tswitch (redir->op) {\n\tcase MRSH_IO_LESS: // <\n\t\tfd = open(filename, O_CLOEXEC | O_RDONLY);\n\t\tdefault_redir_fd = STDIN_FILENO;\n\t\tbreak;\n\tcase MRSH_IO_GREAT: // >\n\tcase MRSH_IO_CLOBBER: // >|\n\t\tfd = open(filename,\n\t\t\tO_CLOEXEC | O_WRONLY | O_CREAT | O_TRUNC, 0644);\n\t\tdefault_redir_fd = STDOUT_FILENO;\n\t\tbreak;\n\tcase MRSH_IO_DGREAT: // >>\n\t\tfd = open(filename,\n\t\t\tO_CLOEXEC | O_WRONLY | O_CREAT | O_APPEND, 0644);\n\t\tdefault_redir_fd = STDOUT_FILENO;\n\t\tbreak;\n\tcase MRSH_IO_LESSAND: // <&\n\t\t// TODO: parse \"-\"\n\t\tfd = parse_fd(filename);\n\t\tdefault_redir_fd = STDIN_FILENO;\n\t\tbreak;\n\tcase MRSH_IO_GREATAND: // >&\n\t\t// TODO: parse \"-\"\n\t\tfd = parse_fd(filename);\n\t\tdefault_redir_fd = STDOUT_FILENO;\n\t\tbreak;\n\tcase MRSH_IO_LESSGREAT: // <>\n\t\tfd = open(filename, O_CLOEXEC | O_RDWR | O_CREAT, 0644);\n\t\tdefault_redir_fd = STDIN_FILENO;\n\t\tbreak;\n\tcase MRSH_IO_DLESS: // <<\n\tcase MRSH_IO_DLESSDASH: // <<-\n\t\tfd = create_here_document_fd(&redir->here_document);\n\t\tdefault_redir_fd = STDIN_FILENO;\n\t\tbreak;\n\t}\n\tif (fd < 0) {\n\t\tfprintf(stderr, \"cannot open %s: %s\\n\", filename,\n\t\t\tstrerror(errno));\n\t\treturn -1;\n\t}\n\n\tfree(filename);\n\n\t*redir_fd = redir->io_number;\n\tif (*redir_fd < 0) {\n\t\t*redir_fd = default_redir_fd;\n\t}\n\n\treturn fd;\n}\n"
  },
  {
    "path": "shell/shell.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <assert.h>\n#include <errno.h>\n#include <mrsh/hashtable.h>\n#include <mrsh/parser.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include \"shell/job.h\"\n#include \"shell/shell.h\"\n#include \"shell/process.h\"\n\nvoid function_destroy(struct mrsh_function *fn) {\n\tif (!fn) {\n\t\treturn;\n\t}\n\tmrsh_command_destroy(fn->body);\n\tfree(fn);\n}\n\nstruct mrsh_state *mrsh_state_create(void) {\n\tstruct mrsh_state_priv *priv = calloc(1, sizeof(*priv));\n\tif (priv == NULL) {\n\t\treturn NULL;\n\t}\n\n\tpriv->term_fd = STDIN_FILENO;\n\n\tstruct mrsh_state *state = &priv->pub;\n\tstate->exit = -1;\n\n\tstruct mrsh_call_frame_priv *frame_priv =\n\t\tcalloc(1, sizeof(struct mrsh_call_frame_priv));\n\tif (frame_priv == NULL) {\n\t\tfree(priv);\n\t\treturn NULL;\n\t}\n\tstate->frame = &frame_priv->pub;\n\n\treturn state;\n}\n\nstatic const char *get_alias_func(const char *name, void *data) {\n\tstruct mrsh_state *state = data;\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\treturn mrsh_hashtable_get(&priv->aliases, name);\n}\n\nvoid mrsh_state_set_parser_alias_func(\n\t\tstruct mrsh_state *state, struct mrsh_parser *parser) {\n\tmrsh_parser_set_alias_func(parser, get_alias_func, state);\n}\n\nstatic void state_string_finish_iterator(const char *key, void *value,\n\t\tvoid *user_data) {\n\tfree(value);\n}\n\nstatic void variable_destroy(struct mrsh_variable *var) {\n\tif (!var) {\n\t\treturn;\n\t}\n\tfree(var->value);\n\tfree(var);\n}\n\nstatic void state_var_finish_iterator(const char *key, void *value,\n\t\tvoid *user_data) {\n\tvariable_destroy((struct mrsh_variable *)value);\n}\n\nstatic void state_fn_finish_iterator(const char *key, void *value, void *_) {\n\tfunction_destroy((struct mrsh_function *)value);\n}\n\nstatic void call_frame_destroy(struct mrsh_call_frame *frame) {\n\tfor (int i = 0; i < frame->argc; ++i) {\n\t\tfree(frame->argv[i]);\n\t}\n\tfree(frame->argv);\n\tfree(frame);\n}\n\nvoid mrsh_state_destroy(struct mrsh_state *state) {\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\tif (priv->job_control) {\n\t\tbroadcast_sighup_to_jobs(state);\n\t}\n\tmrsh_hashtable_for_each(&priv->variables, state_var_finish_iterator, NULL);\n\tmrsh_hashtable_finish(&priv->variables);\n\tmrsh_hashtable_for_each(&priv->functions, state_fn_finish_iterator, NULL);\n\tmrsh_hashtable_finish(&priv->functions);\n\tmrsh_hashtable_for_each(&priv->aliases,\n\t\tstate_string_finish_iterator, NULL);\n\tmrsh_hashtable_finish(&priv->aliases);\n\twhile (priv->jobs.len > 0) {\n\t\tjob_destroy(priv->jobs.data[priv->jobs.len - 1]);\n\t}\n\tmrsh_array_finish(&priv->jobs);\n\twhile (priv->processes.len > 0) {\n\t\tprocess_destroy(priv->processes.data[priv->processes.len - 1]);\n\t}\n\tmrsh_array_finish(&priv->processes);\n\tstruct mrsh_call_frame *frame = state->frame;\n\twhile (frame) {\n\t\tstruct mrsh_call_frame *prev = frame->prev;\n\t\tcall_frame_destroy(frame);\n\t\tframe = prev;\n\t}\n\tfor (size_t i = 0; i < MRSH_NSIG; i++) {\n\t\tmrsh_program_destroy(priv->traps[i].program);\n\t}\n\tfree(state);\n}\n\nstruct mrsh_state_priv *state_get_priv(struct mrsh_state *state) {\n\treturn (struct mrsh_state_priv *)state;\n}\n\nvoid mrsh_env_set(struct mrsh_state *state,\n\t\tconst char *key, const char *value, uint32_t attribs) {\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\n\tstruct mrsh_variable *var = calloc(1, sizeof(struct mrsh_variable));\n\tif (!var) {\n\t\treturn;\n\t}\n\tvar->value = strdup(value);\n\tvar->attribs = attribs;\n\tstruct mrsh_variable *old = mrsh_hashtable_set(&priv->variables, key, var);\n\tvariable_destroy(old);\n}\n\nvoid mrsh_env_unset(struct mrsh_state *state, const char *key) {\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\n\tvariable_destroy(mrsh_hashtable_del(&priv->variables, key));\n}\n\nconst char *mrsh_env_get(struct mrsh_state *state,\n\t\tconst char *key, uint32_t *attribs) {\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\n\tstruct mrsh_variable *var = mrsh_hashtable_get(&priv->variables, key);\n\tif (var && attribs) {\n\t\t*attribs = var->attribs;\n\t}\n\treturn var ? var->value : NULL;\n}\n\nstruct mrsh_call_frame_priv *call_frame_get_priv(struct mrsh_call_frame *frame) {\n\treturn (struct mrsh_call_frame_priv *)frame;\n}\n\nvoid push_frame(struct mrsh_state *state, int argc, const char *argv[]) {\n\tstruct mrsh_call_frame_priv *next = calloc(1, sizeof(*next));\n\tnext->pub.argc = argc;\n\tnext->pub.argv = calloc(argc + 1, sizeof(char *));\n\tfor (int i = 0; i < argc; ++i) {\n\t\tnext->pub.argv[i] = strdup(argv[i]);\n\t}\n\tnext->pub.prev = state->frame;\n\tstate->frame = &next->pub;\n}\n\nvoid pop_frame(struct mrsh_state *state) {\n\tstruct mrsh_call_frame *frame = state->frame;\n\tassert(frame->prev != NULL);\n\tstate->frame = frame->prev;\n\tcall_frame_destroy(frame);\n}\n"
  },
  {
    "path": "shell/task/pipeline.c",
    "content": "#include <assert.h>\n#include <errno.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include \"shell/task.h\"\n\n/**\n * Put the process into its job's process group. This has to be done both in the\n * parent and the child because of potential race conditions.\n */\nstatic struct mrsh_process *init_child(struct mrsh_context *ctx, pid_t pid) {\n\tstruct mrsh_process *proc = process_create(ctx->state, pid);\n\tif (ctx->state->options & MRSH_OPT_MONITOR) {\n\t\tjob_add_process(ctx->job, proc);\n\n\t\tif (ctx->state->interactive && !ctx->background) {\n\t\t\tjob_set_foreground(ctx->job, true, false);\n\t\t}\n\t}\n\treturn proc;\n}\n\nint run_pipeline(struct mrsh_context *ctx, struct mrsh_pipeline *pl) {\n\tstruct mrsh_state_priv *priv = state_get_priv(ctx->state);\n\n\t// Create a new sub-context, because we want one job per pipeline.\n\tstruct mrsh_context child_ctx = *ctx;\n\tif (child_ctx.job == NULL) {\n\t\tchild_ctx.job = job_create(ctx->state, &pl->and_or_list.node);\n\t}\n\n\tassert(pl->commands.len > 0);\n\tif (pl->commands.len == 1) {\n\t\tint ret = run_command(&child_ctx, pl->commands.data[0]);\n\t\tif (pl->bang && ret >= 0) {\n\t\t\tret = !ret;\n\t\t}\n\t\treturn ret;\n\t}\n\n\tstruct mrsh_array procs = {0};\n\tmrsh_array_reserve(&procs, pl->commands.len);\n\tint next_stdin = -1, cur_stdin = -1, cur_stdout = -1;\n\tfor (size_t i = 0; i < pl->commands.len; ++i) {\n\t\tstruct mrsh_command *cmd = pl->commands.data[i];\n\n\t\tif (i < pl->commands.len - 1) {\n\t\t\tint fds[2];\n\t\t\tif (pipe(fds) != 0) {\n\t\t\t\tperror(\"pipe\");\n\t\t\t\treturn TASK_STATUS_ERROR;\n\t\t\t}\n\n\t\t\t// We'll use the write end of the pipe as stdout, the read end will\n\t\t\t// be used as stdin by the next command\n\t\t\tassert(next_stdin == -1 && cur_stdout == -1);\n\t\t\tnext_stdin = fds[0];\n\t\t\tcur_stdout = fds[1];\n\t\t}\n\n\t\tpid_t pid = fork();\n\t\tif (pid < 0) {\n\t\t\treturn TASK_STATUS_ERROR;\n\t\t} else if (pid == 0) {\n\t\t\tpriv->child = true;\n\n\t\t\tinit_child(&child_ctx, getpid());\n\t\t\tif (ctx->state->options & MRSH_OPT_MONITOR) {\n\t\t\t\tinit_job_child_process(ctx->state);\n\t\t\t}\n\n\t\t\tif (next_stdin >= 0) {\n\t\t\t\tclose(next_stdin);\n\t\t\t}\n\n\t\t\tif (i > 0 && cur_stdin != STDIN_FILENO) {\n\t\t\t\tif (dup2(cur_stdin, STDIN_FILENO) < 0) {\n\t\t\t\t\tfprintf(stderr, \"failed to duplicate stdin: %s\\n\",\n\t\t\t\t\t\tstrerror(errno));\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tclose(cur_stdin);\n\t\t\t}\n\n\t\t\tif (i < pl->commands.len - 1 && cur_stdout != STDOUT_FILENO) {\n\t\t\t\tif (dup2(cur_stdout, STDOUT_FILENO) < 0) {\n\t\t\t\t\tfprintf(stderr, \"failed to duplicate stdout: %s\\n\",\n\t\t\t\t\t\tstrerror(errno));\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tclose(cur_stdout);\n\t\t\t}\n\n\t\t\tint ret = run_command(&child_ctx, cmd);\n\t\t\tif (ret < 0) {\n\t\t\t\texit(127);\n\t\t\t}\n\n\t\t\texit(ret);\n\t\t}\n\n\t\tstruct mrsh_process *proc = init_child(&child_ctx, pid);\n\t\tmrsh_array_add(&procs, proc);\n\n\t\tif (cur_stdin >= 0) {\n\t\t\tclose(cur_stdin);\n\t\t\tcur_stdin = -1;\n\t\t}\n\t\tif (cur_stdout >= 0) {\n\t\t\tclose(cur_stdout);\n\t\t\tcur_stdout = -1;\n\t\t}\n\n\t\tcur_stdin = next_stdin;\n\t\tnext_stdin = -1;\n\t}\n\n\tassert(next_stdin == -1 && cur_stdout == -1 && cur_stdin == -1);\n\n\tint ret = 0;\n\tfor (size_t i = 0; i < procs.len; ++i) {\n\t\tstruct mrsh_process *proc = procs.data[i];\n\t\tret = job_wait_process(proc);\n\t\tif (ret < 0) {\n\t\t\tbreak;\n\t\t}\n\t}\n\tmrsh_array_finish(&procs);\n\tif (pl->bang && ret >= 0) {\n\t\tret = !ret;\n\t}\n\treturn ret;\n}\n"
  },
  {
    "path": "shell/task/simple_command.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <assert.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <mrsh/ast.h>\n#include <mrsh/builtin.h>\n#include <mrsh/entry.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include \"shell/shell.h\"\n#include \"shell/path.h\"\n#include \"shell/redir.h\"\n#include \"shell/word.h\"\n#include \"shell/task.h\"\n\nstatic void populate_env_iterator(const char *key, void *_var, void *_) {\n\tstruct mrsh_variable *var = _var;\n\tif ((var->attribs & MRSH_VAR_ATTRIB_EXPORT)) {\n\t\tsetenv(key, var->value, 1);\n\t}\n}\n\n/**\n * Put the process into its job's process group. This has to be done both in the\n * parent and the child because of potential race conditions.\n */\nstatic struct mrsh_process *init_child(struct mrsh_context *ctx, pid_t pid) {\n\tstruct mrsh_process *proc = process_create(ctx->state, pid);\n\tif (ctx->state->options & MRSH_OPT_MONITOR) {\n\t\tjob_add_process(ctx->job, proc);\n\n\t\tif (ctx->state->interactive && !ctx->background) {\n\t\t\tjob_set_foreground(ctx->job, true, false);\n\t\t}\n\t}\n\treturn proc;\n}\n\nstatic int run_process(struct mrsh_context *ctx, struct mrsh_simple_command *sc,\n\t\tchar **argv) {\n\tstruct mrsh_state *state = ctx->state;\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\n\t// The pipeline is responsible for creating the job\n\tassert(ctx->job != NULL);\n\n\tchar *path = expand_path(ctx->state, argv[0], true, false);\n\tif (!path) {\n\t\tfprintf(stderr, \"%s: not found\\n\", argv[0]);\n\t\treturn 127;\n\t}\n\n\tpid_t pid = fork();\n\tif (pid < 0) {\n\t\tperror(\"fork\");\n\t\treturn TASK_STATUS_ERROR;\n\t} else if (pid == 0) {\n\t\tinit_child(ctx, getpid());\n\t\tif (state->options & MRSH_OPT_MONITOR) {\n\t\t\tinit_job_child_process(state);\n\t\t}\n\n\t\tfor (size_t i = 0; i < sc->assignments.len; ++i) {\n\t\t\tstruct mrsh_assignment *assign = sc->assignments.data[i];\n\t\t\tuint32_t prev_attribs;\n\t\t\tif (mrsh_env_get(state, assign->name, &prev_attribs)\n\t\t\t\t\t&& (prev_attribs & MRSH_VAR_ATTRIB_READONLY)) {\n\t\t\t\tfprintf(stderr, \"cannot modify readonly variable %s\\n\",\n\t\t\t\t\t\tassign->name);\n\t\t\t\texit(1);\n\t\t\t}\n\t\t\tchar *value = mrsh_word_str(assign->value);\n\t\t\tsetenv(assign->name, value, true);\n\t\t\tfree(value);\n\t\t}\n\n\t\tmrsh_hashtable_for_each(&priv->variables,\n\t\t\tpopulate_env_iterator, NULL);\n\n\t\tfor (size_t i = 0; i < sc->io_redirects.len; ++i) {\n\t\t\tstruct mrsh_io_redirect *redir = sc->io_redirects.data[i];\n\n\t\t\tint redir_fd;\n\t\t\tint fd = process_redir(redir, &redir_fd);\n\t\t\tif (fd < 0) {\n\t\t\t\texit(1);\n\t\t\t}\n\n\t\t\tif (fd == redir_fd) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tint ret = dup2(fd, redir_fd);\n\t\t\tif (ret < 0) {\n\t\t\t\tfprintf(stderr, \"cannot duplicate file descriptor: %s\\n\",\n\t\t\t\t\tstrerror(errno));\n\t\t\t\texit(1);\n\t\t\t}\n\t\t}\n\n\t\texecv(path, argv);\n\n\t\t// Something went wrong\n\t\tfprintf(stderr, \"%s: %s\\n\", argv[0], strerror(errno));\n\t\texit(127);\n\t}\n\n\tfree(path);\n\n\tstruct mrsh_process *process = init_child(ctx, pid);\n\treturn job_wait_process(process);\n}\n\nstruct saved_fd {\n\tint dup_fd;\n\tint redir_fd;\n};\n\nstatic bool dup_and_save_fd(int fd, int redir_fd, struct saved_fd *saved) {\n\tsaved->redir_fd = redir_fd;\n\tsaved->dup_fd = -1;\n\n\tif (fd == redir_fd) {\n\t\treturn true;\n\t}\n\n\tsaved->dup_fd = dup(redir_fd);\n\tif (saved->dup_fd < 0) {\n\t\tfprintf(stderr, \"failed to duplicate file descriptor: %s\\n\",\n\t\t\tstrerror(errno));\n\t\treturn false;\n\t}\n\n\tif (dup2(fd, redir_fd) < 0) {\n\t\tfprintf(stderr, \"failed to duplicate file descriptor: %s\\n\",\n\t\t\tstrerror(errno));\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nstatic int run_builtin(struct mrsh_context *ctx, struct mrsh_simple_command *sc,\n\t\tint argc, char **argv) {\n\t// Duplicate old FDs to be able to restore them later\n\t// Zero-length VLAs are undefined behaviour\n\tstruct saved_fd fds[sc->io_redirects.len + 1];\n\tfor (size_t i = 0; i < sizeof(fds) / sizeof(fds[0]); ++i) {\n\t\tfds[i].dup_fd = fds[i].redir_fd = -1;\n\t}\n\n\tfor (size_t i = 0; i < sc->io_redirects.len; ++i) {\n\t\tstruct mrsh_io_redirect *redir = sc->io_redirects.data[i];\n\t\tstruct saved_fd *saved = &fds[i];\n\n\t\tint redir_fd;\n\t\tint fd = process_redir(redir, &redir_fd);\n\t\tif (fd < 0) {\n\t\t\treturn TASK_STATUS_ERROR;\n\t\t}\n\n\t\tif (!dup_and_save_fd(fd, redir_fd, saved)) {\n\t\t\treturn TASK_STATUS_ERROR;\n\t\t}\n\t}\n\n\t// TODO: environment from assignements\n\n\tint ret = mrsh_run_builtin(ctx->state, argc, argv);\n\n\t// In case stdout/stderr are pipes, we need to flush to ensure output lines\n\t// aren't out-of-order\n\tfflush(stdout);\n\tfflush(stderr);\n\n\t// Restore old FDs\n\tfor (size_t i = 0; i < sizeof(fds) / sizeof(fds[0]); ++i) {\n\t\tif (fds[i].dup_fd < 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (dup2(fds[i].dup_fd, fds[i].redir_fd) < 0) {\n\t\t\tfprintf(stderr, \"failed to duplicate file descriptor: %s\\n\",\n\t\t\t\tstrerror(errno));\n\t\t\treturn TASK_STATUS_ERROR;\n\t\t}\n\t\tclose(fds[i].dup_fd);\n\t}\n\n\treturn ret;\n}\n\nstatic int run_assignments(struct mrsh_context *ctx, struct mrsh_array *assignments) {\n\tfor (size_t i = 0; i < assignments->len; ++i) {\n\t\tstruct mrsh_assignment *assign = assignments->data[i];\n\t\tchar *new_value = mrsh_word_str(assign->value);\n\t\tuint32_t attribs = MRSH_VAR_ATTRIB_NONE;\n\t\tif ((ctx->state->options & MRSH_OPT_ALLEXPORT)) {\n\t\t\tattribs = MRSH_VAR_ATTRIB_EXPORT;\n\t\t}\n\t\tuint32_t prev_attribs = 0;\n\t\tif (mrsh_env_get(ctx->state, assign->name, &prev_attribs) != NULL\n\t\t\t\t&& (prev_attribs & MRSH_VAR_ATTRIB_READONLY)) {\n\t\t\tfree(new_value);\n\t\t\tfprintf(stderr, \"cannot modify readonly variable %s\\n\",\n\t\t\t\tassign->name);\n\t\t\treturn TASK_STATUS_ERROR;\n\t\t}\n\t\tmrsh_env_set(ctx->state, assign->name, new_value, attribs);\n\t\tfree(new_value);\n\t}\n\n\treturn 0;\n}\n\nstatic int expand_assignments(struct mrsh_context *ctx,\n\t\tstruct mrsh_array *assignments) {\n\tfor (size_t i = 0; i < assignments->len; ++i) {\n\t\tstruct mrsh_assignment *assign = assignments->data[i];\n\t\texpand_tilde(ctx->state, &assign->value, true);\n\t\tint ret = run_word(ctx, &assign->value);\n\t\tif (ret < 0) {\n\t\t\treturn ret;\n\t\t}\n\t}\n\treturn 0;\n}\n\nstatic struct mrsh_simple_command *copy_simple_command(\n\t\tconst struct mrsh_simple_command *sc) {\n\tstruct mrsh_command *cmd = mrsh_command_copy(&sc->command);\n\treturn mrsh_command_get_simple_command(cmd);\n}\n\nint run_simple_command(struct mrsh_context *ctx, struct mrsh_simple_command *sc) {\n\tstruct mrsh_state *state = ctx->state;\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\n\tif (sc->name == NULL) {\n\t\t// Copy each assignment from the AST, because during expansion and\n\t\t// substitution we'll mutate the tree\n\t\tstruct mrsh_array assignments = {0};\n\t\tmrsh_array_reserve(&assignments, sc->assignments.len);\n\t\tfor (size_t i = 0; i < sc->assignments.len; ++i) {\n\t\t\tstruct mrsh_assignment *assign = sc->assignments.data[i];\n\t\t\tmrsh_array_add(&assignments, mrsh_assignment_copy(assign));\n\t\t}\n\n\t\tint ret = expand_assignments(ctx, &assignments);\n\t\tif (ret < 0) {\n\t\t\treturn ret;\n\t\t}\n\n\t\tret = run_assignments(ctx, &assignments);\n\t\tif (ret < 0) {\n\t\t\treturn ret;\n\t\t}\n\n\t\tfor (size_t i = 0; i < assignments.len; ++i) {\n\t\t\tstruct mrsh_assignment *assign = assignments.data[i];\n\t\t\tmrsh_assignment_destroy(assign);\n\t\t}\n\t\tmrsh_array_finish(&assignments);\n\n\t\treturn 0;\n\t}\n\n\t// Copy the command from the AST, because during expansion and substitution\n\t// we'll mutate the tree\n\tsc = copy_simple_command(sc);\n\n\tstruct mrsh_array args = {0};\n\tint ret = expand_word(ctx, sc->name, &args);\n\tif (ret < 0) {\n\t\treturn ret;\n\t}\n\tfor (size_t i = 0; i < sc->arguments.len; ++i) {\n\t\tstruct mrsh_word *arg = sc->arguments.data[i];\n\t\tret = expand_word(ctx, arg, &args);\n\t\tif (ret < 0) {\n\t\t\treturn ret;\n\t\t}\n\t}\n\tassert(args.len > 0);\n\tmrsh_array_add(&args, NULL);\n\n\tret = expand_assignments(ctx, &sc->assignments);\n\tif (ret < 0) {\n\t\treturn ret;\n\t}\n\n\tfor (size_t i = 0; i < sc->io_redirects.len; ++i) {\n\t\tstruct mrsh_io_redirect *redir = sc->io_redirects.data[i];\n\t\texpand_tilde(state, &redir->name, false);\n\t\tret = run_word(ctx, &redir->name);\n\t\tif (ret < 0) {\n\t\t\treturn ret;\n\t\t}\n\t\tfor (size_t j = 0; j < redir->here_document.len; ++j) {\n\t\t\tstruct mrsh_word **line_word_ptr =\n\t\t\t\t(struct mrsh_word **)&redir->here_document.data[j];\n\t\t\texpand_tilde(state, line_word_ptr, false);\n\t\t\tret = run_word(ctx, line_word_ptr);\n\t\t\tif (ret < 0) {\n\t\t\t\treturn ret;\n\t\t\t}\n\t\t}\n\t}\n\n\tchar **argv = (char **)args.data;\n\tint argc = args.len - 1; // argv is NULL-terminated\n\tconst char *argv_0 = argv[0];\n\n\tif ((state->options & MRSH_OPT_XTRACE)) {\n\t\tchar *ps4 = mrsh_get_ps4(state);\n\t\tfprintf(stderr, \"%s\", ps4);\n\t\tfor (int i = 0; i < argc; ++i) {\n\t\t\tfprintf(stderr, \"%s%s\", i > 0 ? \" \" : \"\", argv[i]);\n\t\t}\n\t\tfprintf(stderr, \"\\n\");\n\t\tfree(ps4);\n\t}\n\n\tret = -1;\n\tconst struct mrsh_function *fn_def =\n\t\tmrsh_hashtable_get(&priv->functions, argv_0);\n\tif (fn_def != NULL) {\n\t\tpush_frame(state, argc, (const char **)argv);\n\t\t// fn_def may be free'd during run_command when overwritten with another\n\t\t// function, so we need to copy it.\n\t\tstruct mrsh_command *body = mrsh_command_copy(fn_def->body);\n\t\tret = run_command(ctx, body);\n\t\tmrsh_command_destroy(body);\n\t\tpop_frame(state);\n\t} else if (mrsh_has_builtin(argv_0)) {\n\t\tret = run_builtin(ctx, sc, argc, argv);\n\t} else {\n\t\tret = run_process(ctx, sc, argv);\n\t}\n\n\tmrsh_command_destroy(&sc->command);\n\tfor (size_t i = 0; i < args.len; ++i) {\n\t\tfree(args.data[i]);\n\t}\n\tmrsh_array_finish(&args);\n\treturn ret;\n}\n"
  },
  {
    "path": "shell/task/task.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <assert.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <fnmatch.h>\n#include <mrsh/ast.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include \"shell/shell.h\"\n#include \"shell/task.h\"\n#include \"shell/trap.h\"\n\nstatic int run_subshell(struct mrsh_context *ctx, struct mrsh_array *array) {\n\tstruct mrsh_state_priv *priv = state_get_priv(ctx->state);\n\n\tpid_t pid = fork();\n\tif (pid < 0) {\n\t\tperror(\"fork\");\n\t\treturn TASK_STATUS_ERROR;\n\t} else if (pid == 0) {\n\t\tpriv->child = true;\n\n\t\treset_caught_traps(ctx->state);\n\n\t\tif (!(ctx->state->options & MRSH_OPT_MONITOR)) {\n\t\t\t// If job control is disabled, stdin is /dev/null\n\t\t\tint fd = open(\"/dev/null\", O_CLOEXEC | O_RDONLY);\n\t\t\tif (fd < 0) {\n\t\t\t\tfprintf(stderr, \"failed to open /dev/null: %s\\n\",\n\t\t\t\t\tstrerror(errno));\n\t\t\t\texit(1);\n\t\t\t}\n\t\t\tif (fd != STDIN_FILENO) {\n\t\t\t\tdup2(fd, STDIN_FILENO);\n\t\t\t\tclose(fd);\n\t\t\t}\n\t\t}\n\n\t\tint ret = run_command_list_array(ctx, array);\n\t\tif (ret < 0) {\n\t\t\texit(127);\n\t\t}\n\n\t\tif (ctx->state->exit >= 0) {\n\t\t\texit(ctx->state->exit);\n\t\t}\n\t\texit(ret);\n\t}\n\n\tstruct mrsh_process *proc = process_create(ctx->state, pid);\n\treturn job_wait_process(proc);\n}\n\nstatic int run_if_clause(struct mrsh_context *ctx, struct mrsh_if_clause *ic) {\n\tint ret = run_command_list_array(ctx, &ic->condition);\n\tif (ret < 0) {\n\t\treturn ret;\n\t}\n\n\tif (ret == 0) {\n\t\treturn run_command_list_array(ctx, &ic->body);\n\t} else {\n\t\tif (ic->else_part) {\n\t\t\treturn run_command(ctx, ic->else_part);\n\t\t}\n\t\treturn 0;\n\t}\n}\n\nstatic int run_loop_clause(struct mrsh_context *ctx, struct mrsh_loop_clause *lc) {\n\tstruct mrsh_call_frame_priv *frame_priv =\n\t\tcall_frame_get_priv(ctx->state->frame);\n\tint loop_num = ++frame_priv->nloops;\n\n\tint loop_ret = 0;\n\twhile (ctx->state->exit == -1) {\n\t\tint ret = run_command_list_array(ctx, &lc->condition);\n\t\tif (ret == TASK_STATUS_INTERRUPTED) {\n\t\t\tgoto interrupt;\n\t\t} else if (ret < 0) {\n\t\t\treturn ret;\n\t\t}\n\n\t\tbool break_loop;\n\t\tswitch (lc->type) {\n\t\tcase MRSH_LOOP_WHILE:\n\t\t\tbreak_loop = ret > 0;\n\t\t\tbreak;\n\t\tcase MRSH_LOOP_UNTIL:\n\t\t\tbreak_loop = ret == 0;\n\t\t\tbreak;\n\t\t}\n\t\tif (break_loop) {\n\t\t\tbreak;\n\t\t}\n\n\t\tloop_ret = run_command_list_array(ctx, &lc->body);\n\t\tif (loop_ret == TASK_STATUS_INTERRUPTED) {\n\t\t\tgoto interrupt;\n\t\t} else if (loop_ret < 0) {\n\t\t\treturn loop_ret;\n\t\t}\n\n\t\tcontinue;\n\ninterrupt:\n\t\tif (frame_priv->nloops < loop_num) {\n\t\t\tloop_ret = TASK_STATUS_INTERRUPTED; // break to parent loop\n\t\t\tbreak;\n\t\t}\n\t\tswitch (frame_priv->branch_control) {\n\t\tcase MRSH_BRANCH_BREAK:\n\t\tcase MRSH_BRANCH_RETURN:\n\t\tcase MRSH_BRANCH_EXIT:\n\t\t\tbreak_loop = true;\n\t\t\tloop_ret = 0;\n\t\t\tbreak;\n\t\tcase MRSH_BRANCH_CONTINUE:\n\t\t\tbreak;\n\t\t}\n\t\tif (break_loop) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t--frame_priv->nloops;\n\treturn loop_ret;\n}\n\nstatic int run_for_clause(struct mrsh_context *ctx, struct mrsh_for_clause *fc) {\n\tstruct mrsh_call_frame_priv *frame_priv =\n\t\tcall_frame_get_priv(ctx->state->frame);\n\tint loop_num = ++frame_priv->nloops;\n\n\tstruct mrsh_array fields = {0};\n\tfor (size_t i = 0; i < fc->word_list.len; i++) {\n\t\tstruct mrsh_word *word = fc->word_list.data[i];\n\t\tint ret = expand_word(ctx, word, &fields);\n\t\tif (ret < 0) {\n\t\t\treturn ret;\n\t\t}\n\t}\n\n\tint loop_ret = 0;\n\tsize_t word_index = 0;\n\twhile (ctx->state->exit == -1) {\n\t\tif (word_index == fields.len) {\n\t\t\tbreak;\n\t\t}\n\n\t\tmrsh_env_set(ctx->state, fc->name, fields.data[word_index],\n\t\t\tMRSH_VAR_ATTRIB_NONE);\n\t\tword_index++;\n\n\t\tloop_ret = run_command_list_array(ctx, &fc->body);\n\t\tif (loop_ret == TASK_STATUS_INTERRUPTED) {\n\t\t\tgoto interrupt;\n\t\t} else if (loop_ret < 0) {\n\t\t\treturn loop_ret;\n\t\t}\n\n\t\tcontinue;\n\ninterrupt:\n\t\tif (frame_priv->nloops < loop_num) {\n\t\t\tloop_ret = TASK_STATUS_INTERRUPTED; // break to parent loop\n\t\t\tbreak;\n\t\t}\n\t\tbool break_loop = false;\n\t\tswitch (frame_priv->branch_control) {\n\t\tcase MRSH_BRANCH_BREAK:\n\t\tcase MRSH_BRANCH_RETURN:\n\t\tcase MRSH_BRANCH_EXIT:\n\t\t\tbreak_loop = true;\n\t\t\tloop_ret = 0;\n\t\t\tbreak;\n\t\tcase MRSH_BRANCH_CONTINUE:\n\t\t\tbreak;\n\t\t}\n\t\tif (break_loop) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tfor (size_t i = 0; i < fields.len; i++) {\n\t\tfree(fields.data[i]);\n\t}\n\tmrsh_array_finish(&fields);\n\n\t--frame_priv->nloops;\n\treturn loop_ret;\n}\n\nstatic int run_case_clause(struct mrsh_context *ctx, struct mrsh_case_clause *cc) {\n\tstruct mrsh_word *word = mrsh_word_copy(cc->word);\n\texpand_tilde(ctx->state, &word, false);\n\tint ret = run_word(ctx, &word);\n\tif (ret < 0) {\n\t\tmrsh_word_destroy(word);\n\t\treturn ret;\n\t}\n\tchar *word_str = mrsh_word_str(word);\n\tmrsh_word_destroy(word);\n\n\tint case_ret = 0;\n\tfor (size_t i = 0; i < cc->items.len; ++i) {\n\t\tstruct mrsh_case_item *ci = cc->items.data[i];\n\n\t\tbool selected = false;\n\t\tfor (size_t j = 0; j < ci->patterns.len; ++j) {\n\t\t\t// TODO: this mutates the AST\n\t\t\tstruct mrsh_word **word_ptr =\n\t\t\t\t(struct mrsh_word **)&ci->patterns.data[j];\n\t\t\texpand_tilde(ctx->state, word_ptr, false);\n\t\t\tint ret = run_word(ctx, word_ptr);\n\t\t\tif (ret < 0) {\n\t\t\t\treturn ret;\n\t\t\t}\n\t\t\tchar *pattern = word_to_pattern(*word_ptr);\n\t\t\tif (pattern != NULL) {\n\t\t\t\tselected = fnmatch(pattern, word_str, 0) == 0;\n\t\t\t\tfree(pattern);\n\t\t\t} else {\n\t\t\t\tchar *str = mrsh_word_str(*word_ptr);\n\t\t\t\tselected = strcmp(str, word_str) == 0;\n\t\t\t\tfree(str);\n\t\t\t}\n\t\t\tif (selected) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (selected) {\n\t\t\tcase_ret = run_command_list_array(ctx, &ci->body);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tfree(word_str);\n\treturn case_ret;\n}\n\nstatic int run_function_definition(struct mrsh_context *ctx,\n\t\tstruct mrsh_function_definition *fnd) {\n\tstruct mrsh_state_priv *priv = state_get_priv(ctx->state);\n\n\tstruct mrsh_function *fn = calloc(1, sizeof(struct mrsh_function));\n\tfn->body = mrsh_command_copy(fnd->body);\n\tstruct mrsh_function *old_fn =\n\t\tmrsh_hashtable_set(&priv->functions, fnd->name, fn);\n\tfunction_destroy(old_fn);\n\treturn 0;\n}\n\nint run_command(struct mrsh_context *ctx, struct mrsh_command *cmd) {\n\tswitch (cmd->type) {\n\tcase MRSH_SIMPLE_COMMAND:;\n\t\tstruct mrsh_simple_command *sc = mrsh_command_get_simple_command(cmd);\n\t\treturn run_simple_command(ctx, sc);\n\tcase MRSH_BRACE_GROUP:;\n\t\tstruct mrsh_brace_group *bg = mrsh_command_get_brace_group(cmd);\n\t\treturn run_command_list_array(ctx, &bg->body);\n\tcase MRSH_SUBSHELL:;\n\t\tstruct mrsh_subshell *s = mrsh_command_get_subshell(cmd);\n\t\treturn run_subshell(ctx, &s->body);\n\tcase MRSH_IF_CLAUSE:;\n\t\tstruct mrsh_if_clause *ic = mrsh_command_get_if_clause(cmd);\n\t\treturn run_if_clause(ctx, ic);\n\tcase MRSH_LOOP_CLAUSE:;\n\t\tstruct mrsh_loop_clause *lc = mrsh_command_get_loop_clause(cmd);\n\t\treturn run_loop_clause(ctx, lc);\n\tcase MRSH_FOR_CLAUSE:;\n\t\tstruct mrsh_for_clause *fc = mrsh_command_get_for_clause(cmd);\n\t\treturn run_for_clause(ctx, fc);\n\tcase MRSH_CASE_CLAUSE:;\n\t\tstruct mrsh_case_clause *cc =\n\t\t\tmrsh_command_get_case_clause(cmd);\n\t\treturn run_case_clause(ctx, cc);\n\tcase MRSH_FUNCTION_DEFINITION:;\n\t\tstruct mrsh_function_definition *fnd =\n\t\t\tmrsh_command_get_function_definition(cmd);\n\t\treturn run_function_definition(ctx, fnd);\n\t}\n\tabort();\n}\n\nint run_and_or_list(struct mrsh_context *ctx, struct mrsh_and_or_list *and_or_list) {\n\tswitch (and_or_list->type) {\n\tcase MRSH_AND_OR_LIST_PIPELINE:;\n\t\tstruct mrsh_pipeline *pl = mrsh_and_or_list_get_pipeline(and_or_list);\n\t\treturn run_pipeline(ctx, pl);\n\tcase MRSH_AND_OR_LIST_BINOP:;\n\t\tstruct mrsh_binop *binop = mrsh_and_or_list_get_binop(and_or_list);\n\t\tint left_status = run_and_or_list(ctx, binop->left);\n\t\tswitch (binop->type) {\n\t\tcase MRSH_BINOP_AND:\n\t\t\tif (left_status != 0) {\n\t\t\t\treturn left_status;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase MRSH_BINOP_OR:\n\t\t\tif (left_status == 0) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\treturn run_and_or_list(ctx, binop->right);\n\t}\n\tabort();\n}\n\n/**\n * Put the process into its job's process group. This has to be done both in the\n * parent and the child because of potential race conditions.\n */\nstatic struct mrsh_process *init_async_child(struct mrsh_context *ctx, pid_t pid) {\n\tstruct mrsh_process *proc = process_create(ctx->state, pid);\n\n\tif (ctx->state->options & MRSH_OPT_MONITOR) {\n\t\tjob_add_process(ctx->job, proc);\n\t}\n\n\treturn proc;\n}\n\nint run_command_list_array(struct mrsh_context *ctx, struct mrsh_array *array) {\n\tstruct mrsh_state *state = ctx->state;\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\n\tint ret = 0;\n\tfor (size_t i = 0; i < array->len; ++i) {\n\t\tstruct mrsh_command_list *list = array->data[i];\n\t\tif (list->ampersand) {\n\t\t\tstruct mrsh_context child_ctx = *ctx;\n\t\t\tchild_ctx.background = true;\n\t\t\tif (child_ctx.job == NULL) {\n\t\t\t\tchild_ctx.job = job_create(state, &list->node);\n\t\t\t}\n\n\t\t\tpid_t pid = fork();\n\t\t\tif (pid < 0) {\n\t\t\t\tperror(\"fork\");\n\t\t\t\treturn TASK_STATUS_ERROR;\n\t\t\t} else if (pid == 0) {\n\t\t\t\tctx = NULL; // Use child_ctx instead\n\t\t\t\tpriv->child = true;\n\n\t\t\t\tinit_async_child(&child_ctx, getpid());\n\t\t\t\tif (state->options & MRSH_OPT_MONITOR) {\n\t\t\t\t\tinit_job_child_process(state);\n\t\t\t\t}\n\n\t\t\t\tif (!(state->options & MRSH_OPT_MONITOR)) {\n\t\t\t\t\t// If job control is disabled, stdin is /dev/null\n\t\t\t\t\tint fd = open(\"/dev/null\", O_CLOEXEC | O_RDONLY);\n\t\t\t\t\tif (fd < 0) {\n\t\t\t\t\t\tfprintf(stderr, \"failed to open /dev/null: %s\\n\",\n\t\t\t\t\t\t\tstrerror(errno));\n\t\t\t\t\t\texit(1);\n\t\t\t\t\t}\n\t\t\t\t\tif (fd != STDIN_FILENO) {\n\t\t\t\t\t\tdup2(fd, STDIN_FILENO);\n\t\t\t\t\t\tclose(fd);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tint ret = run_and_or_list(&child_ctx, list->and_or_list);\n\t\t\t\tif (ret < 0) {\n\t\t\t\t\texit(127);\n\t\t\t\t}\n\t\t\t\texit(ret);\n\t\t\t}\n\n\t\t\tret = 0;\n\t\t\tinit_async_child(&child_ctx, pid);\n\t\t} else {\n\t\t\tret = run_and_or_list(ctx, list->and_or_list);\n\t\t\tif (ret < 0) {\n\t\t\t\treturn ret;\n\t\t\t}\n\t\t}\n\n\t\tif (ret >= 0) {\n\t\t\tstate->last_status = ret;\n\t\t}\n\t}\n\treturn ret;\n}\n\nstatic void show_job(struct mrsh_job *job, struct mrsh_job *current,\n\t\tstruct mrsh_job *previous, bool r) {\n\tchar curprev = ' ';\n\tif (job == current) {\n\t\tcurprev = '+';\n\t} else if (job == previous) {\n\t\tcurprev = '-';\n\t}\n\tchar *cmd = mrsh_node_format(job->node);\n\tfprintf(stderr, \"[%d] %c %s %s\\n\", job->job_id, curprev,\n\t\tjob_state_str(job, r), cmd);\n\tfree(cmd);\n}\n\nvoid mrsh_destroy_terminated_jobs(struct mrsh_state *state) {\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\n\tstruct mrsh_job *current = job_by_id(state, \"%+\", false),\n\t\t*previous = job_by_id(state, \"%-\", false);\n\tbool r = rand() % 2 == 0;\n\n\trefresh_jobs_status(state);\n\n\tfor (size_t i = 0; i < priv->jobs.len; ++i) {\n\t\tstruct mrsh_job *job = priv->jobs.data[i];\n\n\t\tint status = job_poll(job);\n\n\t\tif (state->options & MRSH_OPT_NOTIFY && job->pending_notification) {\n\t\t\tshow_job(job, current, previous, r);\n\t\t\tjob->pending_notification = false;\n\t\t}\n\n\t\tif (status >= 0) {\n\t\t\tjob_destroy(job);\n\t\t\t--i;\n\t\t}\n\t}\n\n\tfflush(stderr);\n}\n\nint mrsh_run_program(struct mrsh_state *state, struct mrsh_program *prog) {\n\tstruct mrsh_context ctx = { .state = state };\n\tint ret = run_command_list_array(&ctx, &prog->body);\n\trun_pending_traps(state);\n\treturn ret;\n}\n\nint mrsh_run_word(struct mrsh_state *state, struct mrsh_word **word) {\n\texpand_tilde(state, word, false);\n\n\tstruct mrsh_context ctx = { .state = state };\n\tint last_status = state->last_status;\n\tint ret = run_word(&ctx, word);\n\tstate->last_status = last_status;\n\treturn ret;\n}\n"
  },
  {
    "path": "shell/task/word.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <assert.h>\n#include <errno.h>\n#include <fnmatch.h>\n#include <mrsh/buffer.h>\n#include <mrsh/parser.h>\n#include <stdio.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include \"builtin.h\"\n#include \"shell/process.h\"\n#include \"shell/task.h\"\n#include \"shell/word.h\"\n\n#define READ_SIZE 1024\n\nstatic bool buffer_read_from(struct mrsh_buffer *buf, int fd) {\n\twhile (true) {\n\t\tchar *dst = mrsh_buffer_reserve(buf, READ_SIZE);\n\n\t\tssize_t n = read(fd, dst, READ_SIZE);\n\t\tif (n < 0 && errno == EINTR) {\n\t\t\tcontinue;\n\t\t} else if (n < 0) {\n\t\t\tperror(\"read\");\n\t\t\treturn false;\n\t\t} else if (n == 0) {\n\t\t\tbreak;\n\t\t}\n\n\t\tbuf->len += n;\n\t}\n\n\treturn true;\n}\n\nstatic bool naive_word_streq(struct mrsh_word *word, const char *str) {\n\twhile (word->type == MRSH_WORD_LIST) {\n\t\tstruct mrsh_word_list *wl = mrsh_word_get_list(word);\n\t\tif (wl->children.len != 1) {\n\t\t\treturn false;\n\t\t}\n\t\tword = wl->children.data[0];\n\t}\n\tif (word->type != MRSH_WORD_STRING) {\n\t\treturn false;\n\t}\n\tstruct mrsh_word_string *ws = mrsh_word_get_string(word);\n\treturn strcmp(ws->str, \"trap\") == 0;\n}\n\nstatic bool is_print_traps(struct mrsh_program *program) {\n\tif (program->body.len != 1) {\n\t\treturn false;\n\t}\n\tstruct mrsh_command_list *cl = program->body.data[0];\n\tif (cl->ampersand || cl->and_or_list->type != MRSH_AND_OR_LIST_PIPELINE) {\n\t\treturn false;\n\t}\n\tstruct mrsh_pipeline *pipeline =\n\t\tmrsh_and_or_list_get_pipeline(cl->and_or_list);\n\tif (pipeline->bang || pipeline->commands.len != 1) {\n\t\treturn false;\n\t}\n\tstruct mrsh_command *cmd = pipeline->commands.data[0];\n\tif (cmd->type != MRSH_SIMPLE_COMMAND) {\n\t\treturn false;\n\t}\n\tstruct mrsh_simple_command *sc = mrsh_command_get_simple_command(cmd);\n\tif (sc->name == NULL || !naive_word_streq(sc->name, \"trap\")) {\n\t\treturn false;\n\t}\n\tif (sc->arguments.len == 1) {\n\t\tstruct mrsh_word *arg = sc->arguments.data[0];\n\t\treturn naive_word_streq(arg, \"--\");\n\t} else {\n\t\treturn sc->arguments.len == 0;\n\t}\n}\n\nstatic void swap_words(struct mrsh_word **word_ptr, struct mrsh_word *new_word) {\n\tmrsh_word_destroy(*word_ptr);\n\t*word_ptr = new_word;\n}\n\nstatic int run_word_command(struct mrsh_context *ctx, struct mrsh_word **word_ptr) {\n\tstruct mrsh_word_command *wc = mrsh_word_get_command(*word_ptr);\n\n\tint fds[2];\n\tif (pipe(fds) != 0) {\n\t\tperror(\"pipe\");\n\t\treturn TASK_STATUS_ERROR;\n\t}\n\n\tpid_t pid = fork();\n\tif (pid < 0) {\n\t\tperror(\"fork\");\n\t\tclose(fds[0]);\n\t\tclose(fds[1]);\n\t\treturn TASK_STATUS_ERROR;\n\t} else if (pid == 0) {\n\t\tclose(fds[0]);\n\n\t\tif (fds[1] != STDOUT_FILENO) {\n\t\t\tdup2(fds[1], STDOUT_FILENO);\n\t\t\tclose(fds[1]);\n\t\t}\n\n\t\tinit_job_child_process(ctx->state);\n\n\t\t// When a subshell is entered, traps that are not being ignored shall\n\t\t// be set to the default actions, except in the case of a command\n\t\t// substitution containing only a single trap command, when the traps\n\t\t// need not be altered.\n\t\tif (!wc->program || !is_print_traps(wc->program)) {\n\t\t\treset_caught_traps(ctx->state);\n\t\t}\n\n\t\tif (wc->program != NULL) {\n\t\t\tmrsh_run_program(ctx->state, wc->program);\n\t\t}\n\n\t\texit(ctx->state->exit >= 0 ? ctx->state->exit : 0);\n\t}\n\n\tstruct mrsh_process *process = process_create(ctx->state, pid);\n\n\tclose(fds[1]);\n\tint child_fd = fds[0];\n\n\tstruct mrsh_buffer buf = {0};\n\tif (!buffer_read_from(&buf, child_fd)) {\n\t\tmrsh_buffer_finish(&buf);\n\t\tclose(child_fd);\n\t\treturn TASK_STATUS_ERROR;\n\t}\n\tmrsh_buffer_append_char(&buf, '\\0');\n\tclose(child_fd);\n\n\t// Trim newlines at the end\n\tssize_t i = buf.len - 2;\n\twhile (i >= 0 && buf.data[i] == '\\n') {\n\t\tbuf.data[i] = '\\0';\n\t\t--i;\n\t}\n\n\tstruct mrsh_word_string *ws =\n\t\tmrsh_word_string_create(mrsh_buffer_steal(&buf), false);\n\tws->split_fields = true;\n\tswap_words(word_ptr, &ws->word);\n\treturn job_wait_process(process);\n}\n\nstatic const char *parameter_get_value(struct mrsh_state *state,\n\t\tconst char *name) {\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\n\tstatic char value[16];\n\tchar *end;\n\tlong lvalue = strtol(name, &end, 10);\n\t// Special cases\n\tif (strcmp(name, \"@\") == 0 || strcmp(name, \"*\") == 0) {\n\t\t// These are handled separately, because they evaluate to a word, not\n\t\t// a raw string.\n\t\treturn NULL;\n\t} else if (strcmp(name, \"#\") == 0) {\n\t\tsprintf(value, \"%d\", state->frame->argc - 1);\n\t\treturn value;\n\t} else if (strcmp(name, \"?\") == 0) {\n\t\tsprintf(value, \"%d\", state->last_status);\n\t\treturn value;\n\t} else if (strcmp(name, \"-\") == 0) {\n\t\treturn state_get_options(state);\n\t} else if (strcmp(name, \"$\") == 0) {\n\t\tsprintf(value, \"%d\", (int)getpid());\n\t\treturn value;\n\t} else if (strcmp(name, \"!\") == 0) {\n\t\tfor (ssize_t i = priv->jobs.len - 1; i >= 0; i--) {\n\t\t\tstruct mrsh_job *job = priv->jobs.data[i];\n\t\t\tif (job->processes.len == 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tstruct mrsh_process *process =\n\t\t\t\tjob->processes.data[job->processes.len - 1];\n\t\t\tsprintf(value, \"%d\", process->pid);\n\t\t\treturn value;\n\t\t}\n\t\t/* Standard is unclear on what to do in this case, mimic dash */\n\t\treturn \"\";\n\t} else if (end[0] == '\\0' && end != name) {\n\t\tif (lvalue >= state->frame->argc) {\n\t\t\treturn NULL;\n\t\t}\n\t\treturn state->frame->argv[lvalue];\n\t}\n\t// User-set cases\n\treturn mrsh_env_get(state, name, NULL);\n}\n\nstatic struct mrsh_word *expand_positional_params(struct mrsh_state *state,\n\t\tbool quote_args) {\n\tconst char *ifs = mrsh_env_get(state, \"IFS\", NULL);\n\tchar sep[2] = {0};\n\tif (ifs == NULL) {\n\t\tsep[0] = ' ';\n\t} else {\n\t\tsep[0] = ifs[0];\n\t}\n\n\tstruct mrsh_array words = {0};\n\tfor (int i = 1; i < state->frame->argc; i++) {\n\t\tconst char *arg = state->frame->argv[i];\n\t\tif (i > 1 && sep[0] != '\\0') {\n\t\t\tstruct mrsh_word_string *ws =\n\t\t\t\tmrsh_word_string_create(strdup(sep), false);\n\t\t\tws->split_fields = true;\n\t\t\tmrsh_array_add(&words, &ws->word);\n\t\t}\n\t\tstruct mrsh_word_string *ws =\n\t\t\tmrsh_word_string_create(strdup(arg), quote_args);\n\t\tws->split_fields = true;\n\t\tmrsh_array_add(&words, &ws->word);\n\t}\n\n\tstruct mrsh_word_list *wl = mrsh_word_list_create(&words, false);\n\treturn &wl->word;\n}\n\nstatic void mark_word_split_fields(struct mrsh_word *word) {\n\tswitch (word->type) {\n\tcase MRSH_WORD_STRING:;\n\t\tstruct mrsh_word_string *ws = mrsh_word_get_string(word);\n\t\tws->split_fields = true;\n\t\tbreak;\n\tcase MRSH_WORD_LIST:;\n\t\tstruct mrsh_word_list *wl = mrsh_word_get_list(word);\n\t\tfor (size_t i = 0; i < wl->children.len; i++) {\n\t\t\tmark_word_split_fields(wl->children.data[i]);\n\t\t}\n\t\tbreak;\n\tdefault:\n\t\tbreak;\n\t}\n}\n\nstatic struct mrsh_word *create_word_string(const char *str) {\n\tstruct mrsh_word_string *ws = mrsh_word_string_create(strdup(str), false);\n\treturn &ws->word;\n}\n\nstatic struct mrsh_word *copy_word_or_null(struct mrsh_word *word) {\n\tif (word != NULL) {\n\t\treturn mrsh_word_copy(word);\n\t} else {\n\t\treturn create_word_string(\"\");\n\t}\n}\n\nstatic bool is_null_word(const struct mrsh_word *word) {\n\tswitch (word->type) {\n\tcase MRSH_WORD_STRING:;\n\t\tconst struct mrsh_word_string *ws = mrsh_word_get_string(word);\n\t\treturn ws->str[0] == '\\0';\n\tcase MRSH_WORD_LIST:;\n\t\tconst struct mrsh_word_list *wl = mrsh_word_get_list(word);\n\t\tfor (size_t i = 0; i < wl->children.len; i++) {\n\t\t\tconst struct mrsh_word *child = wl->children.data[i];\n\t\t\tif (!is_null_word(child)) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\treturn true;\n\tdefault:\n\t\tabort();\n\t}\n}\n\nstatic int apply_parameter_cond_op(struct mrsh_context *ctx,\n\t\tstruct mrsh_word_parameter *wp, struct mrsh_word *value,\n\t\tstruct mrsh_word **result) {\n\tswitch (wp->op) {\n\tcase MRSH_PARAM_NONE:\n\t\t*result = value;\n\t\treturn 0;\n\tcase MRSH_PARAM_MINUS: // Use Default Values\n\t\tif (value == NULL || (wp->colon && is_null_word(value))) {\n\t\t\tmrsh_word_destroy(value);\n\t\t\t*result = copy_word_or_null(wp->arg);\n\t\t} else {\n\t\t\t*result = value;\n\t\t}\n\t\treturn 0;\n\tcase MRSH_PARAM_EQUAL: // Assign Default Values\n\t\tif (value == NULL || (wp->colon && is_null_word(value))) {\n\t\t\tmrsh_word_destroy(value);\n\t\t\t*result = copy_word_or_null(wp->arg);\n\t\t\tint ret = run_word(ctx, result);\n\t\t\tif (ret < 0) {\n\t\t\t\treturn ret;\n\t\t\t}\n\t\t\tchar *str = mrsh_word_str(*result);\n\t\t\tmrsh_env_set(ctx->state, wp->name, str, 0);\n\t\t\tfree(str);\n\t\t} else {\n\t\t\t*result = value;\n\t\t}\n\t\treturn 0;\n\tcase MRSH_PARAM_QMARK: // Indicate Error if Null or Unset\n\t\tif (value == NULL || (wp->colon && is_null_word(value))) {\n\t\t\tmrsh_word_destroy(value);\n\t\t\tchar *err_msg;\n\t\t\tif (wp->arg != NULL) {\n\t\t\t\tstruct mrsh_word *err_msg_word = mrsh_word_copy(wp->arg);\n\t\t\t\tint ret = run_word(ctx, &err_msg_word);\n\t\t\t\tif (ret < 0) {\n\t\t\t\t\treturn ret;\n\t\t\t\t}\n\t\t\t\terr_msg = mrsh_word_str(err_msg_word);\n\t\t\t\tmrsh_word_destroy(err_msg_word);\n\t\t\t} else {\n\t\t\t\terr_msg = strdup(\"parameter not set or null\");\n\t\t\t}\n\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", ctx->state->frame->argv[0],\n\t\t\t\twp->name, err_msg);\n\t\t\tfree(err_msg);\n\t\t\t// TODO: make the shell exit if non-interactive\n\t\t\treturn TASK_STATUS_ERROR;\n\t\t} else {\n\t\t\t*result = value;\n\t\t}\n\t\treturn 0;\n\tcase MRSH_PARAM_PLUS: // Use Alternative Value\n\t\tif (value == NULL || (wp->colon && is_null_word(value))) {\n\t\t\t*result = create_word_string(\"\");\n\t\t} else {\n\t\t\t*result = copy_word_or_null(wp->arg);\n\t\t}\n\t\tmrsh_word_destroy(value);\n\t\treturn 0;\n\tdefault:\n\t\tabort(); // unreachable\n\t}\n}\n\nstatic char *trim_str(const char *str, const char *cut, bool suffix) {\n\tsize_t len = strlen(str);\n\tsize_t cut_len = strlen(cut);\n\tif (cut_len <= len) {\n\t\tif (!suffix && memcmp(str, cut, cut_len) == 0) {\n\t\t\treturn strdup(str + cut_len);\n\t\t}\n\t\tif (suffix && memcmp(str + len - cut_len, cut, cut_len) == 0) {\n\t\t\treturn strndup(str, len - cut_len);\n\t\t}\n\t}\n\treturn strdup(str);\n}\n\nstatic char *trim_pattern(const char *str, const char *pattern, bool suffix,\n\t\tbool largest) {\n\tsize_t len = strlen(str);\n\tssize_t begin, end, delta;\n\tif ((!suffix && !largest) || (suffix && largest)) {\n\t\tbegin = 0;\n\t\tend = len;\n\t\tdelta = 1;\n\t} else {\n\t\tbegin = len - 1;\n\t\tend = -1;\n\t\tdelta = -1;\n\t}\n\n\tchar *buf = strdup(str);\n\tfor (ssize_t i = begin; i != end; i += delta) {\n\t\tchar ch = buf[i];\n\t\tbuf[i] = '\\0';\n\n\t\tconst char *match, *trimmed;\n\t\tif (!suffix) {\n\t\t\tmatch = buf;\n\t\t\ttrimmed = str + i;\n\t\t} else {\n\t\t\tmatch = str + i;\n\t\t\ttrimmed = buf;\n\t\t}\n\n\t\tif (fnmatch(pattern, match, 0) == 0) {\n\t\t\tchar *result = strdup(trimmed);\n\t\t\tfree(buf);\n\t\t\treturn result;\n\t\t}\n\n\t\tbuf[i] = ch;\n\t}\n\tfree(buf);\n\n\treturn strdup(str);\n}\n\nstatic int apply_parameter_str_op(struct mrsh_context *ctx,\n\t\tstruct mrsh_word_parameter *wp, const char *str,\n\t\tstruct mrsh_word **result) {\n\tswitch (wp->op) {\n\tcase MRSH_PARAM_LEADING_HASH: // String Length\n\t\tif (str == NULL && (ctx->state->options & MRSH_OPT_NOUNSET)) {\n\t\t\t*result = NULL;\n\t\t\treturn 0;\n\t\t}\n\n\t\tint len = 0;\n\t\tif (str != NULL) {\n\t\t\tlen = strlen(str);\n\t\t}\n\t\tchar len_str[32];\n\t\tsnprintf(len_str, sizeof(len_str), \"%d\", len);\n\t\t*result = create_word_string(len_str);\n\t\treturn 0;\n\tcase MRSH_PARAM_PERCENT: // Remove Smallest Suffix Pattern\n\tcase MRSH_PARAM_DPERCENT: // Remove Largest Suffix Pattern\n\tcase MRSH_PARAM_HASH: // Remove Smallest Prefix Pattern\n\tcase MRSH_PARAM_DHASH: // Remove Largest Prefix Pattern\n\t\tif (str == NULL) {\n\t\t\t*result = NULL;\n\t\t\treturn 0;\n\t\t} else if (wp->arg == NULL) {\n\t\t\t*result = create_word_string(str);\n\t\t\treturn 0;\n\t\t}\n\n\t\tbool suffix = wp->op == MRSH_PARAM_PERCENT ||\n\t\t\twp->op == MRSH_PARAM_DPERCENT;\n\t\tbool largest = wp->op == MRSH_PARAM_DPERCENT ||\n\t\t\twp->op == MRSH_PARAM_DHASH;\n\n\t\tstruct mrsh_word *pattern = mrsh_word_copy(wp->arg);\n\t\tint ret = run_word(ctx, &pattern);\n\t\tif (ret < 0) {\n\t\t\treturn ret;\n\t\t}\n\n\t\tchar *result_str;\n\t\tchar *pattern_str = word_to_pattern(pattern);\n\t\tif (pattern_str == NULL) {\n\t\t\tchar *arg = mrsh_word_str(pattern);\n\t\t\tmrsh_word_destroy(pattern);\n\t\t\tresult_str = trim_str(str, arg, suffix);\n\t\t\tfree(arg);\n\t\t} else {\n\t\t\tmrsh_word_destroy(pattern);\n\t\t\tresult_str = trim_pattern(str, pattern_str, suffix, largest);\n\t\t\tfree(pattern_str);\n\t\t}\n\n\t\tstruct mrsh_word_string *result_ws =\n\t\t\tmrsh_word_string_create(result_str, false);\n\t\t*result = &result_ws->word;\n\t\treturn 0;\n\tdefault:\n\t\tabort();\n\t}\n}\n\nstatic int _run_word(struct mrsh_context *ctx, struct mrsh_word **word_ptr,\n\t\tbool double_quoted) {\n\tstruct mrsh_word *word = *word_ptr;\n\n\tint ret;\n\tswitch (word->type) {\n\tcase MRSH_WORD_STRING:;\n\t\treturn 0;\n\tcase MRSH_WORD_PARAMETER:;\n\t\tstruct mrsh_word_parameter *wp = mrsh_word_get_parameter(word);\n\n\t\tconst char *value = parameter_get_value(ctx->state, wp->name);\n\t\tchar lineno[16];\n\t\tif (value == NULL && strcmp(wp->name, \"LINENO\") == 0) {\n\t\t\tstruct mrsh_position pos;\n\t\t\tmrsh_word_range(word, &pos, NULL);\n\n\t\t\tsnprintf(lineno, sizeof(lineno), \"%d\", pos.line);\n\n\t\t\tvalue = lineno;\n\t\t}\n\n\t\tstruct mrsh_word *result = NULL;\n\t\tswitch (wp->op) {\n\t\tcase MRSH_PARAM_NONE:\n\t\tcase MRSH_PARAM_MINUS:\n\t\tcase MRSH_PARAM_EQUAL:\n\t\tcase MRSH_PARAM_QMARK:\n\t\tcase MRSH_PARAM_PLUS:;\n\t\t\tstruct mrsh_word *value_word;\n\t\t\tif (strcmp(wp->name, \"@\") == 0) {\n\t\t\t\t// $@ expands to quoted fields only if it's inside double quotes\n\t\t\t\t// TODO: error out if expansion is unspecified\n\t\t\t\tvalue_word = expand_positional_params(ctx->state,\n\t\t\t\t\tdouble_quoted);\n\t\t\t} else if (strcmp(wp->name, \"*\") == 0) {\n\t\t\t\tvalue_word = expand_positional_params(ctx->state, false);\n\t\t\t} else if (value != NULL) {\n\t\t\t\tvalue_word = create_word_string(value);\n\t\t\t} else {\n\t\t\t\tvalue_word = NULL;\n\t\t\t}\n\n\t\t\tret = apply_parameter_cond_op(ctx, wp, value_word, &result);\n\t\t\tif (ret < 0) {\n\t\t\t\treturn ret;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase MRSH_PARAM_LEADING_HASH:\n\t\tcase MRSH_PARAM_PERCENT:\n\t\tcase MRSH_PARAM_DPERCENT:\n\t\tcase MRSH_PARAM_HASH:\n\t\tcase MRSH_PARAM_DHASH:\n\t\t\tif (strcmp(wp->name, \"@\") == 0 || strcmp(wp->name, \"*\") == 0 ||\n\t\t\t\t\t(wp->op != MRSH_PARAM_LEADING_HASH &&\n\t\t\t\t\tstrcmp(wp->name, \"#\") == 0)) {\n\t\t\t\tfprintf(stderr, \"%s: using this parameter operator on $%s \"\n\t\t\t\t\t\"is undefined behaviour\\n\",\n\t\t\t\t\tctx->state->frame->argv[0], wp->name);\n\t\t\t\treturn TASK_STATUS_ERROR;\n\t\t\t}\n\n\t\t\tret = apply_parameter_str_op(ctx, wp, value, &result);\n\t\t\tif (ret < 0) {\n\t\t\t\treturn ret;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\tif (result == NULL) {\n\t\t\tif ((ctx->state->options & MRSH_OPT_NOUNSET)) {\n\t\t\t\tfprintf(stderr, \"%s: %s: unbound variable\\n\",\n\t\t\t\t\t\tctx->state->frame->argv[0], wp->name);\n\t\t\t\treturn TASK_STATUS_ERROR;\n\t\t\t}\n\t\t\tresult = create_word_string(\"\");\n\t\t}\n\t\tmark_word_split_fields(result);\n\t\tif (result->type != MRSH_WORD_STRING) {\n\t\t\tret = run_word(ctx, &result);\n\t\t\tif (ret < 0) {\n\t\t\t\treturn ret;\n\t\t\t}\n\t\t}\n\t\tswap_words(word_ptr, result);\n\t\treturn 0;\n\tcase MRSH_WORD_COMMAND:\n\t\treturn run_word_command(ctx, word_ptr);\n\tcase MRSH_WORD_ARITHMETIC:;\n\t\t// For arithmetic words, we need to expand the arithmetic expression\n\t\t// before parsing and evaluating it\n\t\tstruct mrsh_word_arithmetic *wa = mrsh_word_get_arithmetic(word);\n\t\tret = run_word(ctx, &wa->body);\n\t\tif (ret < 0) {\n\t\t\treturn ret;\n\t\t}\n\n\t\tchar *body_str = mrsh_word_str(wa->body);\n\t\tstruct mrsh_parser *parser =\n\t\t\tmrsh_parser_with_data(body_str, strlen(body_str));\n\t\tfree(body_str);\n\t\tstruct mrsh_arithm_expr *expr = mrsh_parse_arithm_expr(parser);\n\t\tif (expr == NULL) {\n\t\t\tstruct mrsh_position err_pos;\n\t\t\tconst char *err_msg = mrsh_parser_error(parser, &err_pos);\n\t\t\tif (err_msg != NULL) {\n\t\t\t\t// TODO: improve error line/column\n\t\t\t\tfprintf(stderr, \"%s (arithmetic %d:%d): %s\\n\",\n\t\t\t\t\tctx->state->frame->argv[0], err_pos.line,\n\t\t\t\t\terr_pos.column, err_msg);\n\t\t\t} else {\n\t\t\t\tfprintf(stderr, \"expected an arithmetic expression\\n\");\n\t\t\t}\n\t\t\tret = TASK_STATUS_ERROR;\n\t\t} else {\n\t\t\tlong result;\n\t\t\tif (!mrsh_run_arithm_expr(ctx->state, expr, &result)) {\n\t\t\t\tret = TASK_STATUS_ERROR;\n\t\t\t} else {\n\t\t\t\tchar buf[32];\n\t\t\t\tsnprintf(buf, sizeof(buf), \"%ld\", result);\n\n\t\t\t\tstruct mrsh_word_string *ws =\n\t\t\t\t\tmrsh_word_string_create(strdup(buf), false);\n\t\t\t\tws->split_fields = true;\n\t\t\t\tswap_words(word_ptr, &ws->word);\n\t\t\t\tret = 0;\n\t\t\t}\n\t\t}\n\t\tmrsh_arithm_expr_destroy(expr);\n\t\tmrsh_parser_destroy(parser);\n\t\treturn ret;\n\tcase MRSH_WORD_LIST:;\n\t\tstruct mrsh_word_list *wl = mrsh_word_get_list(word);\n\n\t\tstruct mrsh_array at_sign_words = {0};\n\t\tfor (size_t i = 0; i < wl->children.len; ++i) {\n\t\t\tstruct mrsh_word **child_ptr =\n\t\t\t\t(struct mrsh_word **)&wl->children.data[i];\n\n\t\t\tbool is_at_sign = false;\n\t\t\tif ((*child_ptr)->type == MRSH_WORD_PARAMETER) {\n\t\t\t\tstruct mrsh_word_parameter *wp =\n\t\t\t\t\tmrsh_word_get_parameter(*child_ptr);\n\t\t\t\tis_at_sign = strcmp(wp->name, \"@\") == 0;\n\t\t\t}\n\n\t\t\tret = _run_word(ctx, child_ptr,\n\t\t\t\tdouble_quoted || wl->double_quoted);\n\t\t\tif (ret < 0) {\n\t\t\t\treturn ret;\n\t\t\t}\n\n\t\t\tif (wl->double_quoted && is_at_sign) {\n\t\t\t\t// Fucking $@ needs special handling: we need to extract the\n\t\t\t\t// fields it expands to outside of the double quotes\n\t\t\t\tmrsh_array_add(&at_sign_words, *child_ptr);\n\t\t\t}\n\t\t}\n\n\t\tif (at_sign_words.len > 0) {\n\t\t\t// We need to put $@ expansions outside of the double quotes.\n\t\t\t// Disclaimer: this is a PITA.\n\t\t\tstruct mrsh_array quoted = {0};\n\t\t\tstruct mrsh_array unquoted = {0};\n\t\t\tsize_t at_sign_idx = 0;\n\t\t\tfor (size_t i = 0; i < wl->children.len; i++) {\n\t\t\t\tstruct mrsh_word *child = wl->children.data[i];\n\t\t\t\twl->children.data[i] = NULL; // steal the child\n\t\t\t\tif (at_sign_idx >= at_sign_words.len ||\n\t\t\t\t\t\tchild != at_sign_words.data[at_sign_idx]) {\n\t\t\t\t\tmrsh_array_add(&quoted, child);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (quoted.len > 0) {\n\t\t\t\t\tstruct mrsh_word_list *quoted_wl =\n\t\t\t\t\t\tmrsh_word_list_create(&quoted, true);\n\t\t\t\t\tmrsh_array_add(&unquoted, &quoted_wl->word);\n\t\t\t\t\t// `unquoted` has been stolen by mrsh_word_list_create\n\t\t\t\t\tquoted = (struct mrsh_array){0};\n\t\t\t\t}\n\n\t\t\t\tmrsh_array_add(&unquoted, at_sign_words.data[at_sign_idx]);\n\n\t\t\t\tat_sign_idx++;\n\t\t\t}\n\t\t\tif (quoted.len > 0) {\n\t\t\t\tstruct mrsh_word_list *quoted_wl =\n\t\t\t\t\tmrsh_word_list_create(&quoted, true);\n\t\t\t\tmrsh_array_add(&unquoted, &quoted_wl->word);\n\t\t\t}\n\n\t\t\tstruct mrsh_word_list *unquoted_wl =\n\t\t\t\tmrsh_word_list_create(&unquoted, false);\n\t\t\tswap_words(word_ptr, &unquoted_wl->word);\n\t\t}\n\t\tmrsh_array_finish(&at_sign_words);\n\n\t\treturn 0;\n\t}\n\tabort();\n}\n\nint run_word(struct mrsh_context *ctx, struct mrsh_word **word_ptr) {\n\treturn _run_word(ctx, word_ptr, false);\n}\n\nint expand_word(struct mrsh_context *ctx, const struct mrsh_word *_word,\n\t\tstruct mrsh_array *expanded_fields) {\n\tstruct mrsh_word *word = mrsh_word_copy(_word);\n\texpand_tilde(ctx->state, &word, false);\n\n\tint ret = run_word(ctx, &word);\n\tif (ret < 0) {\n\t\treturn ret;\n\t}\n\n\tstruct mrsh_array fields = {0};\n\tconst char *ifs = mrsh_env_get(ctx->state, \"IFS\", NULL);\n\tsplit_fields(&fields, word, ifs);\n\tmrsh_word_destroy(word);\n\n\tif (ctx->state->options & MRSH_OPT_NOGLOB) {\n\t\tget_fields_str(expanded_fields, &fields);\n\t} else {\n\t\tif (!expand_pathnames(expanded_fields, &fields)) {\n\t\t\treturn TASK_STATUS_ERROR;\n\t\t}\n\t}\n\n\tfor (size_t i = 0; i < fields.len; ++i) {\n\t\tmrsh_word_destroy(fields.data[i]);\n\t}\n\tmrsh_array_finish(&fields);\n\n\treturn ret;\n}\n"
  },
  {
    "path": "shell/trap.c",
    "content": "#define _POSIX_C_SOURCE 1\n#include <assert.h>\n#include <signal.h>\n#include \"shell/shell.h\"\n#include \"shell/trap.h\"\n\nstatic const int ignored_job_control_sigs[] = {\n\tSIGINT,\n\tSIGQUIT,\n\tSIGTSTP,\n\tSIGTTIN,\n\tSIGTTOU,\n};\nstatic const size_t ignored_job_control_sigs_len =\n\tsizeof(ignored_job_control_sigs) / sizeof(ignored_job_control_sigs[0]);\n\nstatic int pending_sigs[MRSH_NSIG] = {0};\n\nstatic void handle_signal(int sig) {\n\tassert(sig < MRSH_NSIG);\n\tpending_sigs[sig]++;\n}\n\nbool set_trap(struct mrsh_state *state, int sig, enum mrsh_trap_action action,\n\t\tstruct mrsh_program *program) {\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\n\tassert(action == MRSH_TRAP_CATCH || program == NULL);\n\n\tstruct mrsh_trap *trap = &priv->traps[sig];\n\n\tif (sig != 0) {\n\t\tif (!trap->set && !state->interactive) {\n\t\t\t// Signals that were ignored on entry to a non-interactive shell\n\t\t\t// cannot be trapped or reset\n\t\t\tstruct sigaction sa;\n\t\t\tif (sigaction(sig, NULL, &sa) != 0) {\n\t\t\t\tperror(\"failed to get current signal action: sigaction\");\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (sa.sa_handler == SIG_IGN) {\n\t\t\t\tfprintf(stderr, \"cannot trap signal %d: \"\n\t\t\t\t\t\"ignored on non-interactive shell entry\\n\", sig);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\tif (action == MRSH_TRAP_DEFAULT && priv->job_control) {\n\t\t\t// When job control is enabled, some signals are ignored by default\n\t\t\tfor (size_t i = 0; i < ignored_job_control_sigs_len; i++) {\n\t\t\t\tif (sig == ignored_job_control_sigs[i]) {\n\t\t\t\t\taction = MRSH_TRAP_IGNORE;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tstruct sigaction sa = {0};\n\t\tswitch (action) {\n\t\tcase MRSH_TRAP_DEFAULT:\n\t\t\tsa.sa_handler = SIG_DFL;\n\t\t\tbreak;\n\t\tcase MRSH_TRAP_IGNORE:\n\t\t\tsa.sa_handler = SIG_IGN;\n\t\t\tbreak;\n\t\tcase MRSH_TRAP_CATCH:\n\t\t\tsa.sa_handler = handle_signal;\n\t\t\tbreak;\n\t\t}\n\n\t\tif (sigaction(sig, &sa, NULL) < 0) {\n\t\t\tperror(\"failed to set signal action: sigaction\");\n\t\t\treturn false;\n\t\t}\n\t}\n\n\ttrap->set = true;\n\ttrap->action = action;\n\tmrsh_program_destroy(trap->program);\n\ttrap->program = program;\n\n\treturn true;\n}\n\nbool set_job_control_traps(struct mrsh_state *state, bool enabled) {\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\n\tfor (size_t i = 0; i < ignored_job_control_sigs_len; i++) {\n\t\tint sig = ignored_job_control_sigs[i];\n\t\tstruct mrsh_trap *trap = &priv->traps[i];\n\n\t\tstruct sigaction sa = {0};\n\t\tif (enabled) {\n\t\t\tsa.sa_handler = SIG_IGN;\n\t\t} else {\n\t\t\tsa.sa_handler = SIG_DFL;\n\t\t}\n\t\tif (sigaction(sig, &sa, NULL) < 0) {\n\t\t\tperror(\"sigaction\");\n\t\t\treturn false;\n\t\t}\n\n\t\ttrap->set = false;\n\t\ttrap->action = MRSH_TRAP_DEFAULT;\n\t\tmrsh_program_destroy(trap->program);\n\t\ttrap->program = NULL;\n\t}\n\treturn true;\n}\n\nbool reset_caught_traps(struct mrsh_state *state) {\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\n\tfor (size_t i = 0; i < MRSH_NSIG; i++) {\n\t\tstruct mrsh_trap *trap = &priv->traps[i];\n\t\tif (trap->set && trap->action != MRSH_TRAP_IGNORE) {\n\t\t\tif (!set_trap(state, i, MRSH_TRAP_DEFAULT, NULL)) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn true;\n}\n\nbool run_pending_traps(struct mrsh_state *state) {\n\tstruct mrsh_state_priv *priv = state_get_priv(state);\n\tstatic bool in_trap = false;\n\n\tif (in_trap) {\n\t\treturn true;\n\t}\n\tin_trap = true;\n\n\tint last_status = state->last_status;\n\n\tfor (size_t i = 0; i < MRSH_NSIG; i++) {\n\t\tstruct mrsh_trap *trap = &priv->traps[i];\n\t\twhile (pending_sigs[i] > 0) {\n\t\t\tif (!trap->set || trap->action != MRSH_TRAP_CATCH ||\n\t\t\t\t\ttrap->program == NULL) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tint ret = mrsh_run_program(state, trap->program);\n\t\t\tif (ret < 0) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tpending_sigs[i]--;\n\t\t\tstate->last_status = last_status; // Restore \"$?\"\n\t\t}\n\n\t\tpending_sigs[i] = 0;\n\t}\n\n\tin_trap = false;\n\treturn true;\n}\n\nbool run_exit_trap(struct mrsh_state *state) {\n\tpending_sigs[0]++;\n\treturn run_pending_traps(state);\n}\n"
  },
  {
    "path": "shell/word.c",
    "content": "#define _POSIX_C_SOURCE 200809L\n#include <assert.h>\n#include <ctype.h>\n#include <glob.h>\n#include <mrsh/buffer.h>\n#include <pwd.h>\n#include <stdbool.h>\n#include <stdlib.h>\n#include <string.h>\n#include \"shell/shell.h\"\n#include \"shell/word.h\"\n\nstatic bool is_logname_char(char c) {\n\t// See https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_282\n\treturn (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||\n\t\t(c >= '0' && c <= '9') || c == '.' || c == '_' || c == '-';\n}\n\nstatic ssize_t expand_tilde_at(struct mrsh_state *state, const char *str,\n\t\tbool last, char **expanded_ptr) {\n\tif (str[0] != '~') {\n\t\treturn -1;\n\t}\n\n\tconst char *cur;\n\tfor (cur = str + 1; cur[0] != '\\0' && cur[0] != '/'; cur++) {\n\t\tif (!is_logname_char(cur[0])) {\n\t\t\treturn -1;\n\t\t}\n\t}\n\tif (cur[0] == '\\0' && !last) {\n\t\treturn -1;\n\t}\n\tconst char *slash = cur;\n\n\tchar *name = NULL;\n\tif (slash > str + 1) {\n\t\tname = strndup(str + 1, slash - str - 1);\n\t}\n\n\tconst char *dir = NULL;\n\tif (name == NULL) {\n\t\tdir = mrsh_env_get(state, \"HOME\", NULL);\n\t} else {\n\t\tstruct passwd *pw = getpwnam(name);\n\t\tif (pw != NULL) {\n\t\t\tdir = pw->pw_dir;\n\t\t}\n\t}\n\tfree(name);\n\n\tif (dir == NULL) {\n\t\treturn -1;\n\t}\n\n\t*expanded_ptr = strdup(dir);\n\treturn slash - str;\n}\n\nstatic void _expand_tilde(struct mrsh_state *state, struct mrsh_word **word_ptr,\n\t\tbool assignment, bool first, bool last) {\n\tstruct mrsh_word *word = *word_ptr;\n\tswitch (word->type) {\n\tcase MRSH_WORD_STRING:;\n\t\tstruct mrsh_word_string *ws = mrsh_word_get_string(word);\n\t\tif (ws->single_quoted) {\n\t\t\tbreak;\n\t\t}\n\n\t\tstruct mrsh_array words = {0};\n\n\t\tconst char *str = ws->str;\n\t\tif (first) {\n\t\t\tchar *expanded;\n\t\t\tssize_t offset = expand_tilde_at(state, str, last, &expanded);\n\t\t\tif (offset >= 0) {\n\t\t\t\tmrsh_array_add(&words,\n\t\t\t\t\tmrsh_word_string_create(expanded, true));\n\t\t\t\tstr += offset;\n\t\t\t}\n\t\t}\n\n\t\tif (assignment) {\n\t\t\twhile (true) {\n\t\t\t\tconst char *colon = strchr(str, ':');\n\t\t\t\tif (colon == NULL) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tchar *slice = strndup(str, colon - str + 1);\n\t\t\t\tmrsh_array_add(&words,\n\t\t\t\t\tmrsh_word_string_create(slice, false));\n\n\t\t\t\tstr = colon + 1;\n\n\t\t\t\tchar *expanded;\n\t\t\t\tssize_t offset = expand_tilde_at(state, str, last, &expanded);\n\t\t\t\tif (offset >= 0) {\n\t\t\t\t\tmrsh_array_add(&words,\n\t\t\t\t\t\tmrsh_word_string_create(expanded, true));\n\t\t\t\t\tstr += offset;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (words.len > 0) {\n\t\t\tchar *trailing = strdup(str);\n\t\t\tmrsh_array_add(&words,\n\t\t\t\tmrsh_word_string_create(trailing, false));\n\n\t\t\tstruct mrsh_word_list *wl = mrsh_word_list_create(&words, false);\n\t\t\t*word_ptr = &wl->word;\n\t\t\tmrsh_word_destroy(word);\n\t\t}\n\t\tbreak;\n\tcase MRSH_WORD_LIST:;\n\t\tstruct mrsh_word_list *wl = mrsh_word_get_list(word);\n\t\tif (wl->double_quoted) {\n\t\t\tbreak;\n\t\t}\n\t\tfor (size_t i = 0; i < wl->children.len; ++i) {\n\t\t\tstruct mrsh_word **child_ptr =\n\t\t\t\t(struct mrsh_word **)&wl->children.data[i];\n\t\t\t_expand_tilde(state, child_ptr, assignment, first && i == 0,\n\t\t\t\tlast && i == wl->children.len - 1);\n\t\t}\n\t\tbreak;\n\tdefault:\n\t\tbreak;\n\t}\n}\n\nvoid expand_tilde(struct mrsh_state *state, struct mrsh_word **word_ptr,\n\t\tbool assignment) {\n\t_expand_tilde(state, word_ptr, assignment, true, true);\n}\n\nstruct split_fields_data {\n\tstruct mrsh_array *fields;\n\tstruct mrsh_word_list *cur_field;\n\tconst char *ifs, *ifs_non_space;\n\tbool in_ifs, in_ifs_non_space;\n};\n\nstatic void add_to_cur_field(struct split_fields_data *data,\n\t\tstruct mrsh_word *word) {\n\tif (data->cur_field == NULL) {\n\t\tdata->cur_field = mrsh_word_list_create(NULL, false);\n\t\tmrsh_array_add(data->fields, data->cur_field);\n\t}\n\tmrsh_array_add(&data->cur_field->children, word);\n}\n\nstatic void _split_fields(struct split_fields_data *data,\n\t\tconst struct mrsh_word *word) {\n\tswitch (word->type) {\n\tcase MRSH_WORD_STRING:;\n\t\tconst struct mrsh_word_string *ws = mrsh_word_get_string(word);\n\n\t\tif (ws->single_quoted || !ws->split_fields) {\n\t\t\tadd_to_cur_field(data, mrsh_word_copy(word));\n\t\t\tdata->in_ifs = data->in_ifs_non_space = false;\n\t\t\treturn;\n\t\t}\n\n\t\tstruct mrsh_buffer buf = {0};\n\t\tsize_t len = strlen(ws->str);\n\t\tfor (size_t i = 0; i < len; ++i) {\n\t\t\tchar c = ws->str[i];\n\t\t\tif (strchr(data->ifs, c) == NULL) {\n\t\t\t\tmrsh_buffer_append_char(&buf, c);\n\t\t\t\tdata->in_ifs = data->in_ifs_non_space = false;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tbool is_ifs_non_space = strchr(data->ifs_non_space, c) != NULL;\n\t\t\tif (!data->in_ifs || (is_ifs_non_space && data->in_ifs_non_space)) {\n\t\t\t\tmrsh_buffer_append_char(&buf, '\\0');\n\t\t\t\tchar *str = mrsh_buffer_steal(&buf);\n\t\t\t\tadd_to_cur_field(data,\n\t\t\t\t\t&mrsh_word_string_create(str, false)->word);\n\t\t\t\tdata->cur_field = NULL;\n\t\t\t\tdata->in_ifs = true;\n\t\t\t} else if (is_ifs_non_space) {\n\t\t\t\tdata->in_ifs_non_space = true;\n\t\t\t}\n\t\t}\n\n\t\tif (!data->in_ifs) {\n\t\t\tmrsh_buffer_append_char(&buf, '\\0');\n\t\t\tchar *str = mrsh_buffer_steal(&buf);\n\t\t\tadd_to_cur_field(data,\n\t\t\t\t&mrsh_word_string_create(str, false)->word);\n\t\t}\n\n\t\tmrsh_buffer_finish(&buf);\n\t\tbreak;\n\tcase MRSH_WORD_LIST:;\n\t\tconst struct mrsh_word_list *wl = mrsh_word_get_list(word);\n\n\t\tif (wl->double_quoted) {\n\t\t\tadd_to_cur_field(data, mrsh_word_copy(word));\n\t\t\treturn;\n\t\t}\n\n\t\tfor (size_t i = 0; i < wl->children.len; ++i) {\n\t\t\tconst struct mrsh_word *child = wl->children.data[i];\n\t\t\t_split_fields(data, child);\n\t\t}\n\t\tbreak;\n\tdefault:\n\t\tabort();\n\t}\n}\n\nvoid split_fields(struct mrsh_array *fields, const struct mrsh_word *word,\n\t\tconst char *ifs) {\n\tif (ifs == NULL) {\n\t\tifs = \" \\t\\n\";\n\t} else if (ifs[0] == '\\0') {\n\t\tmrsh_array_add(fields, mrsh_word_copy(word));\n\t\treturn;\n\t}\n\n\tsize_t ifs_len = strlen(ifs);\n\tchar *ifs_non_space = calloc(ifs_len, sizeof(char));\n\tsize_t ifs_non_space_len = 0;\n\tfor (size_t i = 0; i < ifs_len; ++i) {\n\t\tif (!isspace(ifs[i])) {\n\t\t\tifs_non_space[ifs_non_space_len++] = ifs[i];\n\t\t}\n\t}\n\n\tstruct split_fields_data data = {\n\t\t.fields = fields,\n\t\t.ifs = ifs,\n\t\t.ifs_non_space = ifs_non_space,\n\t\t.in_ifs = true,\n\t};\n\t_split_fields(&data, word);\n\n\tfree(ifs_non_space);\n}\n\nvoid get_fields_str(struct mrsh_array *strs, const struct mrsh_array *fields) {\n\tfor (size_t i = 0; i < fields->len; i++) {\n\t\tstruct mrsh_word *word = fields->data[i];\n\t\tmrsh_array_add(strs, mrsh_word_str(word));\n\t}\n}\n\nstatic bool is_pathname_metachar(char c) {\n\tswitch (c) {\n\tcase '*':\n\tcase '?':\n\tcase '[':\n\tcase ']':\n\t\treturn true;\n\tdefault:\n\t\treturn false;\n\t}\n}\n\nstatic bool needs_pathname_expansion(const struct mrsh_word *word) {\n\tswitch (word->type) {\n\tcase MRSH_WORD_STRING:;\n\t\tconst struct mrsh_word_string *ws = mrsh_word_get_string(word);\n\t\tif (ws->single_quoted) {\n\t\t\treturn false;\n\t\t}\n\n\t\tsize_t len = strlen(ws->str);\n\t\tfor (size_t i = 0; i < len; i++) {\n\t\t\tif (is_pathname_metachar(ws->str[i])) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\tcase MRSH_WORD_LIST:;\n\t\tconst struct mrsh_word_list *wl = mrsh_word_get_list(word);\n\t\tif (wl->double_quoted) {\n\t\t\treturn false;\n\t\t}\n\n\t\tfor (size_t i = 0; i < wl->children.len; i++) {\n\t\t\tconst struct mrsh_word *child = wl->children.data[i];\n\t\t\tif (needs_pathname_expansion(child)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\tdefault:\n\t\tabort();\n\t}\n\n}\n\nstatic void _word_to_pattern(struct mrsh_buffer *buf,\n\t\tconst struct mrsh_word *word, bool quoted) {\n\tswitch (word->type) {\n\tcase MRSH_WORD_STRING:;\n\t\tconst struct mrsh_word_string *ws = mrsh_word_get_string(word);\n\n\t\tsize_t len = strlen(ws->str);\n\t\tfor (size_t i = 0; i < len; i++) {\n\t\t\tchar c = ws->str[i];\n\t\t\tif (is_pathname_metachar(c) && (quoted || ws->single_quoted)) {\n\t\t\t\tmrsh_buffer_append_char(buf, '\\\\');\n\t\t\t}\n\t\t\tmrsh_buffer_append_char(buf, c);\n\t\t}\n\t\tbreak;\n\tcase MRSH_WORD_LIST:;\n\t\tconst struct mrsh_word_list *wl = mrsh_word_get_list(word);\n\n\t\tfor (size_t i = 0; i < wl->children.len; i++) {\n\t\t\tconst struct mrsh_word *child = wl->children.data[i];\n\t\t\t_word_to_pattern(buf, child, quoted || wl->double_quoted);\n\t\t}\n\t\tbreak;\n\tdefault:\n\t\tabort();\n\t}\n}\n\nchar *word_to_pattern(const struct mrsh_word *word) {\n\tif (!needs_pathname_expansion(word)) {\n\t\treturn NULL;\n\t}\n\n\tstruct mrsh_buffer buf = {0};\n\t_word_to_pattern(&buf, word, false);\n\tmrsh_buffer_append_char(&buf, '\\0');\n\treturn mrsh_buffer_steal(&buf);\n}\n\nbool expand_pathnames(struct mrsh_array *expanded,\n\t\tconst struct mrsh_array *fields) {\n\tfor (size_t i = 0; i < fields->len; ++i) {\n\t\tconst struct mrsh_word *field = fields->data[i];\n\n\t\tchar *pattern = word_to_pattern(field);\n\t\tif (pattern == NULL) {\n\t\t\tmrsh_array_add(expanded, mrsh_word_str(field));\n\t\t\tcontinue;\n\t\t}\n\n\t\tglob_t glob_buf;\n\t\tint ret = glob(pattern, GLOB_NOSORT, NULL, &glob_buf);\n\t\tif (ret == 0) {\n\t\t\tfor (size_t i = 0; i < glob_buf.gl_pathc; ++i) {\n\t\t\t\tmrsh_array_add(expanded, strdup(glob_buf.gl_pathv[i]));\n\t\t\t}\n\t\t\tglobfree(&glob_buf);\n\t\t} else if (ret == GLOB_NOMATCH) {\n\t\t\tmrsh_array_add(expanded, mrsh_word_str(field));\n\t\t} else {\n\t\t\tfprintf(stderr, \"glob failed: %d\\n\", ret);\n\t\t\tfree(pattern);\n\t\t\treturn false;\n\t\t}\n\n\t\tfree(pattern);\n\t}\n\n\treturn true;\n}\n"
  },
  {
    "path": "test/args.sh",
    "content": "func() (\n\tgetopts \"abcd\" opt\n)\n\nfunc\nfunc -a\nfunc -ab\nfunc -abc\n"
  },
  {
    "path": "test/arithm.sh",
    "content": "#!/bin/sh -eu\n\necho \"1 =\" $((1))\necho \"2*5 =\" $((2*5))\necho \"2/5 =\" $((2/5))\necho \"2%5 =\" $((2%5))\necho \"2+5 =\" $((2+5))\necho \"2-5 =\" $((2-5))\necho \"2<<5 =\" $((2<<5))\necho \"2>>5 =\" $((2>>5))\necho \"2<5 =\" $((2<5))\necho \"2<=5 =\" $((2<=5))\necho \"2>5 =\" $((2>5))\necho \"2>=5 =\" $((2>=5))\necho \"2==5 =\" $((2==5))\necho \"2!=5 =\" $((2!=5))\necho \"2&5 =\" $((2&5))\necho \"2^5 =\" $((2^5))\necho \"2|5 =\" $((2|5))\necho \"2&&5 =\" $((2&&5))\necho \"2||5 =\" $((2||5))\n\n# Associativity\necho \"1+2+3 =\" $((1+2+3))\necho \"5-1-2 =\" $((5-1-2))\necho \"1+2*3 =\" $((1+2*3))\necho \"2*3+1 =\" $((2*3+1))\necho \"6/3/2 =\" $((6/3/2))\necho \"2*(3+1) =\" $((2*(3+1)))\necho \"2*(3+1)+1 =\" $((2*(3+1)+1))\necho \"1+(1+(1+1))+1 =\" $((1+(1+(1+1))+1))\necho \"2|(1||1)\" $((2|(1||1))) # 3\necho \"(2|1)||1\" $(((2|1)||1)) # 1\necho \"2|1||1\" $((2|1||1)) # 1\necho \"1||1|2\" $((1||1|2)) # 1\n\n# Variables\na=42\necho \"a =\" $((a))\necho \"a+2 =\" $((a+2))\necho \"2*a-10 =\" $((2*a-10))\necho \"\\$a =\" $(($a))\necho \"\\$a+2 =\" $(($a+2))\n\n# Assignments\na=0\necho \"(a=42) =\" $((a=42)) \"->\" $a\necho \"(a+=1) =\" $((a+=1)) \"->\" $a\necho \"(a-=4) =\" $((a-=4)) \"->\" $a\necho \"(a*=9) =\" $((a*=9)) \"->\" $a\necho \"(a/=3) =\" $((a/=3)) \"->\" $a\n"
  },
  {
    "path": "test/async.sh",
    "content": "#!/bin/sh\n\necho >&2 \"Returns immediately\"\n(wait)\n\necho >&2 \"Run asynchronous list and wait\"\necho a &\nwait $!\n\necho >&2 \"Run two asynchronous lists in parallel and wait\"\necho a &\np1=$!\necho a &\nwait $p1\ns1=$?\nwait $!\ns2=$?\necho Job 1 exited with status $s1\necho Job 2 exited with status $s2\n\n#echo >&2 \"Run asynchronous list, kill it and wait\"\n#sleep 1000 &\n#pid=$!\n#kill -kill $pid\n#wait $pid\n#echo $pid was terminated by a SIG$(kill -l $?) signal.\n"
  },
  {
    "path": "test/case.sh",
    "content": "#!/bin/sh\n\nx=hello\n\necho \"exact matching with variable expansion\"\ncase \"$x\" in\n\thello)\n\t\techo pass\n\t\t;;\n\tworld)\n\t\techo fail\n\t\t;;\nesac\n\necho \"* pattern\"\ncase \"$x\" in\n\the*)\n\t\techo pass\n\t\t;;\n\t*)\n\t\techo fail\n\t\t;;\nesac\n\necho \"? pattern\"\ncase \"$x\" in\n\tfoo)\n\t\techo fail\n\t\t;;\n\the??o)\n\t\techo pass\n\t\t;;\nesac\n\necho \"default pattern\"\ncase \"$x\" in\n\tfoo)\n\t\techo fail\n\t\t;;\n\t*)\n\t\techo pass\n\t\t;;\nesac\n\necho \"| pattern\"\ncase \"$x\" in\n\tworld|hello)\n\t\techo pass\n\t\t;;\n\t*)\n\t\techo fail\n\t\t;;\nesac\n\necho \"[] pattern\"\ncase \"$x\" in\n\thell[a-z])\n\t\techo pass\n\t\t;;\n\t*)\n\t\techo fail\n\t\t;;\nesac\n\ny=hello\n\necho \"expanding patterns\"\ncase \"$x\" in\n\t$y)\n\t\techo pass\n\t\t;;\n\t*)\n\t\techo fail\n\t\t;;\nesac\n\necho \"quoted strings in patterns\"\ncase \"$x\" in\n\t\"$y\"'')\n\t\techo pass\n\t\t;;\n\t*)\n\t\techo fail\n\t\t;;\nesac\n\necho \";; optional for last item\"\ncase hello in\n\t*)\n\t\techo pass\nesac\n"
  },
  {
    "path": "test/command.sh",
    "content": "#!/bin/sh\n\nlla () {\n\tls -la\n}\n\nalias ll=\"ls -l\"\n\ncommand -v if\necho \"exists if $?\"\ncommand -v cd\necho \"exits cd $?\"\ncommand -v ls\necho \"exists ls $?\"\ncommand -v ll\necho \"exists ll $?\"\ncommand -v lla\necho \"exists lla $?\"\ncommand -v idontexists\nif [ $? -ne 0 ]\nthen\n\techo \"ok\"\nelse\n\techo \"ko\"\nfi\n"
  },
  {
    "path": "test/conformance/2.2-quoted-characters.sh",
    "content": "# 2.2.1 Escape Character (Backslash)\n# The following must be quoted:\nprintf '%s\\n' \\|\\&\\;\\<\\>\\(\\)\\$\\`\\\\\\\"\\'\n# The following must be quoted only under certain conditions:\nprintf '%s\\n' *?[#˜=%\n# Escaping whitespace:\nprintf 'arg one: %s; arg two: %s\\n' with\\ space with\\\ttab\nprintf 'arg one: %s; arg two: %s\\n' without \\\n\tnewline\nprintf 'arg one: %s; arg two: %s\\n' without\\\nwhitespace 'arg two'\n\n# 2.2.2 Single quotes\nprintf '%s\\n' '|&;<>()$`\\\"'\n\n# 2.2.3 Double quotes\nprintf '%s\\n' \"|&;<>()'\\\"\"\n\n# Parameter & arithmetic expansion should work:\nlval=2\nrval=3\nprintf '%s\\n' \"$lval + ${rval} = $((lval+rval))\"\n# Command expansion should work\nprintf '%s\\n' \"cmd 1: $(echo \"cmd 1\")\"\nprintf '%s\\n' \"cmd 2: `echo \"cmd 2\"`\"\n# Command expansion should work, recursively\nprintf '%s\\n' \"cmd 3: $(echo $(echo \"cmd 3\"))\"\n\n# Backquotes should work\nprintf '%s\\n' \"cmd 4: `echo \"cmd 4\"`\"\n\n# Backslashes should escape the following:\nprintf '%s\\n' \"\\$\\`\\\"\\\\\\\ntest\"\n# But not the following:\nprintf '%s\\n' \"\\|\\&\\;\\<\\>\\(\\)\\'\"\n"
  },
  {
    "path": "test/conformance/2.2-quoted-characters.stdout",
    "content": "|&;<>()$`\\\"'\n*?[#˜=%\narg one: with space; arg two: with\ttab\narg one: without; arg two: newline\narg one: withoutwhitespace; arg two: arg two\n|&;<>()$`\\\"\n|&;<>()'\"\n2 + 3 = 5\ncmd 1: cmd 1\ncmd 2: cmd 2\ncmd 3: cmd 3\ncmd 4: cmd 4\n$`\"\\test\n\\|\\&\\;\\<\\>\\(\\)\\'\n"
  },
  {
    "path": "test/conformance/2.2.2-nested-single-quotes.fail.sh",
    "content": "# 2.2.2 Single quotes\n# Single quotes cannot contain single quotes\nprintf '%s\\n' '''\n"
  },
  {
    "path": "test/conformance/2.2.3-alias-expansion.fail.sh",
    "content": "# 2.2.3 Double quotes\n# Should NOT apply alias expansion rules when finding the )\nalias myalias=\"echo )\"\nvar=\"$(myalias arg-two)\"\n"
  },
  {
    "path": "test/conformance/2.2.3-backquote-nonterminated-dquote.undefined.sh",
    "content": "printf '%s\\n' \"cmd: `echo \"`\"\n"
  },
  {
    "path": "test/conformance/2.2.3-backquote-nonterminated-squote.undefined.sh",
    "content": "printf '%s\\n' \"cmd: `echo '`\"\n"
  },
  {
    "path": "test/conformance/2.2.3-dquote-nonterminated-backquote.undefined.sh",
    "content": "printf '%s\\n' \"`echo\"\n"
  },
  {
    "path": "test/conformance/README",
    "content": "Tests in this directory test each specified behavior of a feature in the POSIX\nspecification. If mrsh passes such a test, it is considered compliant with the\nappropriate part of the spec.\n"
  },
  {
    "path": "test/conformance/harness.sh",
    "content": "#!/bin/sh\ndir=$(dirname \"$0\")\ntestcase=\"$1\"\nstdout=\"${testcase%%.sh}.stdout\"\n\n# Set TEST_SHELL to quickly compare the conformance of different shells\nmrsh=${TEST_SHELL:-$MRSH}\n\nactual_out=$(\"$mrsh\" \"$testcase\")\nactual_ret=$?\n\nif [ -f \"$stdout\" ]\nthen\n\tstdout=\"$(cat \"$stdout\")\"\n\tif [ \"$stdout\" != \"$actual_out\" ]\n\tthen\n\t\texit 1\n\tfi\nfi\n\nexit $actual_ret\n"
  },
  {
    "path": "test/conformance/meson.build",
    "content": "harness = find_program('./harness.sh')\n\ntest_files = [\n\t'2.2-quoted-characters.sh',\n]\n\nfailures = [\n\t'2.2.2-nested-single-quotes.fail.sh',\n\t# TODO: https://github.com/emersion/mrsh/issues/145\n\t#'2.2.3-alias-expansion.fail.sh',\n]\n\nundefined = [\n\t'2.2.3-backquote-nonterminated-squote.undefined.sh',\n\t'2.2.3-backquote-nonterminated-dquote.undefined.sh',\n\t'2.2.3-dquote-nonterminated-backquote.undefined.sh',\n]\n\ntestenv = [\n\t'MRSH=@0@'.format(mrsh_exe.full_path()),\n]\n\nforeach test_file : test_files\n\ttest(\n\t\t'conformance/' + test_file,\n\t\tharness,\n\t\tenv: testenv,\n\t\targs: [join_paths(meson.current_source_dir(), test_file)],\n\t)\nendforeach\n\nforeach test_file : failures\n\ttest(\n\t\t'conformance/' + test_file,\n\t\tharness,\n\t\tenv: testenv,\n\t\targs: [join_paths(meson.current_source_dir(), test_file)],\n\t\tshould_fail: true,\n\t)\nendforeach\n\nif get_option('test-undefined-behavior')\n\tforeach test_file : undefined\n\t\ttest(\n\t\t\t'conformance/' + test_file,\n\t\t\tharness,\n\t\t\tenv: testenv,\n\t\t\targs: [join_paths(meson.current_source_dir(), test_file)],\n\t\t\tshould_fail: true,\n\t\t)\n\tendforeach\nendif\n"
  },
  {
    "path": "test/for.sh",
    "content": "#!/bin/sh\n\necho \"Simple for loop\"\nfor i in 1 2 3; do\n\techo $i\ndone\n\necho \"Word expansion in for loop\"\ntwo=2\nfor i in 1 $two $(echo 3); do\n\techo $i\ndone\necho $i\n\necho \"No-op for loop\"\nfor i in; do\n\techo invalid\ndone\necho $i\n\necho \"Field splitting in for loop, expanded from parameter\"\nasdf='a s d f'\nfor c in $asdf; do\n\techo $c\ndone\n\necho \"Field splitting in for loop, expanded from command substitution\"\nfor c in $(echo a s d f); do\n\techo $c\ndone\n\necho \"Field splitting in for loop, with IFS set\"\n(\n\tIFS=':'\n\tasdf='a:s:d:f'\n\tfor c in $asdf; do\n\t\techo $c\n\tdone\n)\n"
  },
  {
    "path": "test/function.sh",
    "content": "#!/bin/sh -e\nfunc_a() {\n\techo func_a\n}\n\nfunc_b() {\n\techo func_b\n}\n\nfunc_b() {\n\techo func_b revised\n}\n\nfunc_c() {\n\techo func_c\n\n\tfunc_c() {\n\t\techo func_c revised\n\t}\n}\n\nfunc_d() if true; then echo func_d; fi\n\nfunc_e() {\n\techo $1\n}\n\nfunc_a\nfunc_b\nfunc_a\nfunc_c\nfunc_c\nfunc_d\nfunc_e hello\n\noutput=$(func_a)\necho \"output is $output\"\n"
  },
  {
    "path": "test/harness.sh",
    "content": "#!/bin/sh\ndir=$(dirname \"$0\")\ntestcase=\"$1\"\n\necho \"Running with mrsh\"\nmrsh_out=$(\"$MRSH\" \"$testcase\")\nmrsh_ret=$?\necho \"Running with reference shell ($REF_SH)\"\nref_out=$(\"$REF_SH\" \"$testcase\")\nref_ret=$?\nif [ $mrsh_ret -ne $ref_ret ] || [ \"$mrsh_out\" != \"$ref_out\" ]\nthen\n\techo >&2 \"$testcase: mismatch\"\n\techo >&2 \"\"\n\techo >&2 \"mrsh: $mrsh_ret\"\n\techo >&2 \"$mrsh_out\"\n\techo >&2 \"\"\n\techo >&2 \"ref ($REF_SH): $ref_ret\"\n\techo >&2 \"$ref_out\"\n\techo >&2 \"\"\n\texit 1\nfi\n"
  },
  {
    "path": "test/if.sh",
    "content": "#!/bin/sh\n# Reference stdout:\n# pass\n# pass\n# pass\n# pass\n# pass\n# pass\n\necho \"-> if..fi with true condition\"\nif true\nthen\n\techo pass\nfi\n\necho \"-> if..fi with false condition\"\nif false\nthen\n\techo >&2 \"fail: This branch should not have run\" && exit 1\nfi\necho pass\n\necho \"-> if..else..fi with true condition\"\nif true\nthen\n\techo pass\nelse\n\techo >&2 \"fail: This branch should not have run\" && exit 1\nfi\n\necho \"-> if..else..fi with false condition\"\nif false\nthen\n\techo >&2 \"fail: This branch should not have run\" && exit 1\nelse\n\techo pass\nfi\n\necho \"-> if..else..fi with true condition\"\nif true\nthen\n\techo pass\nelse\n\techo >&2 \"fail: This branch should not have run\" && exit 1\nfi\n\necho \"-> if..else..elif..fi with false condition\"\nif false\nthen\n\techo >&2 \"fail: This branch should not have run\" && exit 1\nelif true\nthen\n\techo pass\nelse\n\techo >&2 \"fail: This branch should not have run\" && exit 1\nfi\n\necho \"-> test exit status\"\nif true\nthen\n\t( exit 10 )\nelse\n\t( exit 20 )\nfi\n[ $# -eq 10 ] || { echo >&2 \"fail: Expected status code = 10\" && exit 1; }\nif false\nthen\n\t( exit 10 )\nelse\n\t( exit 20 )\nfi\n[ $# -eq 20 ] || { echo >&2 \"fail: Expected status code = 20\" && exit 1; }\n\necho \"-> test alternate syntax\"\n# These tests are only expected to parse, they do not make assertions\nif true; then true; fi\nif true; then true; else true; fi\nif true; then true; elif true; then true; else true; fi\n"
  },
  {
    "path": "test/loop.sh",
    "content": "#!/bin/sh\n\necho \"basic while loop\"\nn=asdf\necho start\nwhile [ \"$n\" != \"fdsa\" ]; do\n\techo \"n: $n\"\n\tn=\"fdsa\"\n\techo \"n: $n\"\ndone\necho stop\n\necho \"continue in while loop should skip the iteration\"\nn=asdf\necho start\nwhile [ \"$n\" != fdsa ]; do\n\tn=fdsa\n\tcontinue\n\techo \"this shouldn't be printed\"\ndone\necho stop\n\necho \"break in while loop should stop the loop\"\nn=asdf\necho start\nwhile true; do\n\tif [ \"$n\" = fdsa ]; then\n\t\tbreak\n\tfi\n\tn=fdsa\ndone\necho stop\n\necho \"exit in infinite loop should exit immediately\"\nwhile true\ndo\n\texit\n\t# https://github.com/emersion/mrsh/issues/37\n\techo bad\ndone\n"
  },
  {
    "path": "test/meson.build",
    "content": "harness = find_program('./harness.sh')\nref_sh = find_program(get_option('reference-shell'), required: false)\n\ntest_files = [\n\t'args.sh',\n\t'arithm.sh',\n\t'async.sh',\n\t'case.sh',\n\t'command.sh',\n\t'for.sh',\n\t'function.sh',\n\t'if.sh',\n\t'loop.sh',\n\t'pipeline.sh',\n\t'read.sh',\n\t'readonly.sh',\n\t'redir.sh',\n\t'return.sh',\n\t'subshell.sh',\n\t'syntax.sh',\n\t'ulimit.sh',\n\t'word.sh',\n]\n\nforeach test_file : test_files\n\ttest(\n\t\ttest_file,\n\t\tharness,\n\t\tenv: [\n\t\t\t'MRSH=@0@'.format(mrsh_exe.full_path()),\n\t\t\t'REF_SH=@0@'.format(ref_sh.path()),\n\t\t],\n\t\targs: [join_paths(meson.current_source_dir(), test_file)],\n\t)\nendforeach\n\nsubdir('conformance')\n"
  },
  {
    "path": "test/pipeline.sh",
    "content": "#!/bin/sh\n\necho \"Pipeline with 1 command\"\necho \"a b c d\"\n\necho \"Pipeline with 2 commands\"\necho \"a b c d\" | sed s/b/B/\n\necho \"Pipeline with 3 commands\"\necho \"a b c d\" | sed s/b/B/ | sed s/c/C/\n\necho \"Pipeline with bang\"\n! false\necho $?\n\necho \"Pipeline with unknown command\"\nidontexist\necho $? # 127\n\n# https://github.com/emersion/mrsh/issues/100\necho \"Pipeline with subshell\"\n(echo \"a b\"; echo \"c d\") | sed s/c/C/\n\n# https://github.com/emersion/mrsh/issues/96\necho \"Pipeline with brace group\"\n{ echo \"a b\"; echo \"c d\"; } | sed s/c/C/\n\n# https://github.com/emersion/mrsh/issues/95\n#echo \"Pipeline with early close\"\n#(\n#\ti=0\n#\twhile [ $i -lt 8096 ]\n#\tdo\n#\t\techo \"Line $i\"\n#\t\ti=$((i+1))\n#\tdone\n#) | head -n 1\n"
  },
  {
    "path": "test/read.sh",
    "content": "#!/bin/sh -eu\n\n\nprintf | read a\necho $?\n\ni=0\n\nprintf \"a\\nb\\nc\\n\" | while read line; do\n  printf \"%s\\n\" \"${line:-blank}\"\n\n  i=$((i+1))\n\n  [ $i -gt 10 ] && break\ndone\n\n[ $i = 3 ] && echo \"correct!\"\n"
  },
  {
    "path": "test/readonly.sh",
    "content": "#!/bin/sh\n\necho \"Print read-only parameters after setting one\"\nreadonly mrsh_readonly_param=b\nreadonly -p | grep mrsh_readonly_param | wc -l\n\n#echo \"Try setting a read-only parameter\"\n#(mrsh_readonly_param=c) 2>/dev/null\n#echo $?\n"
  },
  {
    "path": "test/redir.sh",
    "content": "#!/bin/sh\n\necho \"to stdout\"\nuname\n\necho >&2 \"stdout to stderr\"\nuname >&2\n\necho 2>&1 \"stderr to stdout\"\nuname 2>&1\n#(echo >&2 asdf) 2>&1\n"
  },
  {
    "path": "test/return.sh",
    "content": "#!/bin/sh -e\nfunc_a() {\n\techo func a\n\treturn\n\techo func a post-return\n}\n\nfunc_b() {\n\techo func b\n\treturn 1\n\techo func b post-return\n}\n\nfunc_c() {\n\techo func c\n\twhile :\n\tdo\n\t\techo func c loop\n\t\treturn\n\t\techo func c loop post-return\n\tdone\n\techo func c post loop\n}\n\nfunc_a\n\nif func_b\nthen\n\texit 1\nfi\n\nfunc_c\n"
  },
  {
    "path": "test/subshell.sh",
    "content": "#!/bin/sh\n\necho \"Simple subshell\"\n(echo hi)\n\necho \"Subshell assignment\"\na=a\n(a=b)\necho $a\n\necho \"Subshell status\"\n(exit 1) && echo \"shouldn't happen\"\ntrue # to clear the last status\n\necho \"Multi-line subshell\"\n(\n\techo a\n\techo b\n)\n"
  },
  {
    "path": "test/syntax.sh",
    "content": "#!/bin/sh\n\n# This is a comment\n\necho a  b \t c\necho d # e f\necho 'a\tb  c'\necho \"a\tb  c\"\n\necho a\"*\"b'c\"'\necho \"a\\\"b\\\\\" \"'hey'\" '\"hey\"' '#' \\''a'\n"
  },
  {
    "path": "test/ulimit.sh",
    "content": "#!/bin/sh -e\n\nmrsh_limits=`ulimit`\n[ \"$mrsh_limits\" = \"unlimited\" ]\nif [ -e /proc/self/limits ]\nthen\n\tgrep \"Max file size\" /proc/self/limits | grep \"unlimited\"\nfi\n\nulimit -f 100\n\nmrsh_limits=`ulimit`\n[ \"$mrsh_limits\" -eq 100 ]\nif [ -e /proc/self/limits ]\nthen\n\tgrep \"Max file size\" /proc/self/limits | grep 51200\nfi\n"
  },
  {
    "path": "test/word.sh",
    "content": "#!/bin/sh\n\necho \"\"\necho \"Tilde Expansion\"\necho ~ ~/stuff ~/\"stuff\" \"~/stuff\"\necho '~/stuff' ~\"/stuff\" \"/\"~ \"a\"~\"a\"\necho ~root\na=~/stuff\necho $a\na=~/foo:~/bar:~/baz\necho $a\n\necho \"\"\necho \"Parameter Expansion\"\na=a\nb=B\nhello=hello\nnull=\"\"\necho $a ${b} \">$a<\"\necho \\$a '$a'\necho ${a:-BAD} ${idontexist:-GOOD} ${null:-GOOD} ${idontexist:-}\necho ${a-BAD} ${idontexist-GOOD} ${null-BAD} ${null-}\necho ${c:=GOOD} $c; echo ${c:=BAD} $c; c=\"\"; echo ${c:=GOOD} $c; unset c\necho ${c=GOOD} $c; echo ${c=BAD} $c; c=\"\"; echo ${c=BAD} $c; unset c\necho ${a:+GOOD} ${idontexist:+BAD} ${null:+BAD} ${idontexist:+}\necho ${a+GOOD} ${idontexist+BAD} ${null+GOOD} ${null+}\necho ${#hello} ${#null} ${#idontexist}\n\nnargs() {\n\techo \"$#\"\n}\n\nset a 'b   c' d '\te'\necho $*\nnargs $*\necho \"$*\"\nnargs \"$*\"\necho $@\nnargs $@\necho \"$@\"\nnargs \"$@\"\necho \"1  $@  2\"\nnargs \"1  $@  2\"\necho ${null:-\"$@\"}\nnargs ${null:-\"$@\"}\n(\n\tIFS=':'\n\tasdf='a:s:d:f'\n\techo $asdf\n\tnargs $asdf\n)\n\necho \"a\nb\"\necho 'a\nb'\n\n# Examples from the spec\n# ${parameter}: dash and busybox choke on this\n#a=1\n#set 2\n#echo ${a}b-$ab-${1}0-${10}-$10\n# ${parameter-word}\nfoo=asdf\necho ${foo-bar}xyz}\nfoo=\necho ${foo-bar}xyz}\nunset foo\necho ${foo-bar}xyz}\n# ${parameter:-word}\n#unset x\n#echo ${x:-$(echo >&2 GOOD)} 2>&1\n#x=x\n#echo ${x:-$(echo >&2 BAD)} 2>&1\n# ${parameter:=word}\nunset X\necho ${X:=abc}\n# ${parameter:?word}\n#unset posix\n#echo ${posix:?}\n# ${parameter:+word}\nset a b c\necho ${3:+posix}\n# ${#parameter}\nposix=/usr/posix\necho ${#posix}\n# ${parameter%word}\nx=file.c\necho ${x%.c}.o\n# ${parameter%%word}\nx=posix/src/std\necho ${x%%/*}\n# ${parameter#word}\nx=$HOME/src/cmd\necho ${x#$HOME}\n# ${parameter##word}\nx=/one/two/three\necho ${x##*/}\n\necho \"\"\necho \"Command Substitution\"\necho $(echo asdf)\necho `echo asdf`\necho $(\n\techo a\n\techo b\n)\necho `\n\techo asdf\n`\n\n# Field Splitting\n# Pathname Expansion\n# Quote Removal\n"
  }
]