HT="$(printf '\t.')"; HT="${HT%.}" LF="$(printf '\n.')"; LF="${LF%.}" RS="$(printf '\036.')"; RS="${RS%.}" US="$(printf '\037.')"; US="${US%.}" dbg() { dbg=true #dbg=false if ${dbg}; then printf 'DEBUG: %s\n' "${@}" >&2 fi } . ./tokens.sh . ./lexer.sh complete_command() { if list; then separator return 0 fi # Unexpected EOF synexp '' } list() { dbg 'list()' if and_or; then while separator_op; do if ! and_or; then return 1 fi done return 0 fi return 1 } and_or() { dbg 'and_or()' if pipeline; then while accept T_AND_IF || accept T_OR_IF; do if ! linebreak || ! pipeline; then return 1 fi done return 0 fi return 1 } pipeline() { dbg 'pipeline()' accept T_BANG if pipe_sequence; then return 0 fi return 1 } pipe_sequence() { dbg 'pipe_sequence()' if command; then while accept T_PIPE; do if ! linebreak || ! command; then return 1 fi done return 0 fi return 1 } command() { dbg 'command()' if simple_command; then return 0 elif compound_command; then redirect_list return 0 fi return 1 } compound_command() { dbg 'compound_command()' if brace_group; then return 0 elif subshell; then return 0 elif for_clause; then return 0 elif case_clause; then return 0 elif if_clause; then return 0 elif while_clause; then return 0 elif until_clause; then return 0 fi return 1 } subshell() { dbg 'subshell()' if accept T_LPAREN && compound_list && expect T_RPAREN; then return 0 fi return 1 } compound_list() { dbg 'compound_list()' newline_list if term; then dbg FOUND TERM separator return 0 fi return 1 } term() { dbg 'term()' if and_or; then while separator; do and_or done return 0 fi return 1 } for_clause() { dbg 'for_clause()' if accept T_FOR; then if expect T_NAME && linebreak; then if accept T_IN; then wordlist if ! sequential_sep; then return 1 fi fi if do_group; then return 0 fi fi fi return 1 } wordlist() { dbg 'wordlist()' if accept T_WORD; then while accept T_WORD; do :; done return 0 fi return 1 } case_clause() { if accept T_CASE; then if expect T_WORD && linebreak && expect T_IN && linebreak; then case_list || case_list_ns expect T_ESAC return 0 fi fi return 1 } case_list_ns() { if case_list && case_item_ns; then return 0 elif case_item_ns; then return 0 fi return 1 } case_list() { if case_item; then while case_item; do : done return 0 fi return 1 } case_item_ns() { accept T_LPAREN if pattern && expect RPAREN; then compound_list if linebreak; then return 0 fi fi return 1 } case_item() { accept T_LPAREN if pattern && expect T_RPAREN; then if compound_list || linebreak; then if expect T_DSEMI && linebreak; then return 0 fi fi fi return 1 } pattern() { if accept T_CMDNAME; then while accept T_PIPE; do expect T_WORD done return 0 fi return 1 } if_clause() { if accept T_IF; then if compound_list && expect T_THEN && compound_list; then else_part expect T_FI return 0 fi fi return 1 } else_part() { while accept T_ELIF; do if compound_list && expect T_THEN && compound_list; then continue fi return 1 done if accept T_ELSE; then if compound_list; then return 0 fi fi return 1 } while_clause() { if accept T_WHILE; then if compound_list && do_group; then return 0 fi fi return 1 } until_clause() { if accept T_UNTIL; then if compound_list && do_group; then return 0 fi fi return 1 } function_body() { if compound_command; then redirect_list fi return 1 } brace_group() { dbg 'brace_group()' if accept T_LBRACE && compound_list && expect T_RBRACE; then return 0 fi return 1 } do_group() { dbg 'do_group()' if accept T_DO && compound_list && expect T_DONE; then return 0 fi return 1 } simple_command() { dbg 'simple_command()' if cmd_prefix; then if cmd_word; then cmd_suffix fi return 0 elif cmd_name; then if accept T_LPAREN; then expect T_RPAREN if linebreak && function_body; then return 0 fi else cmd_suffix return 0 fi fi return 1 } cmd_name() { dbg 'cmd_name()' # TODO: Assignment if accept T_CMDNAME; then return 0 fi return 1 } cmd_word() { dbg 'cmd_word()' # TODO: Assignment if accept T_WORD; then return 0 fi return 1 } cmd_prefix() { if io_redirect || accept T_ASSIGNMENT_WORD; then while io_redirect || accept T_ASSIGNMENT_WORD; do : done return 0 fi return 1 } cmd_suffix() { if io_redirect || accept T_WORD; then while io_redirect || accept T_WORD; do : done return 0 fi return 1 } redirect_list() { if io_redirect; then while io_redirect; do : done return 0 fi return 1 } io_redirect() { if io_file || io_here; then return 0 fi return 1 } io_file() { if accept T_LESS || accept T_LESSAND || accept T_GREAT || \ accept T_GREATAND || accept T_DGREAT || \ accept T_LESSGREAT || accept T_CLOBBER; then if filename; then return 0 fi fi return 1 } filename() { if accept T_WORD; then return 0 fi return 1 } io_here() { if accept T_DLESS || accept T_DLESSDASH; then if here_end; then return 0 fi fi return 1 } here_end() { if accept T_WORD; then return 0 fi return 1 } newline_list() { if accept T_NEWLINE; then while accept T_NEWLINE; do : done return 0 fi return 1 } linebreak() { newline_list return 0 } separator_op() { if accept T_AND || accept T_SEMI; then return 0 fi return 1 } separator() { if separator_op && linebreak; then return 0 elif newline_list; then return 0 fi return 1 } sequential_sep() { dbg 'sequential_sep()' if accept T_SEMI; then if linebreak; then return 0 fi elif newline_list; then return 0 fi return 1 } parse() { local fn="${1}" shift 1 init_lexer "${fn}" # If this returns (does not exit), there are no errors. while ! accept T_EOF; do complete_command done get_tokens return 0 } try() { local tokens= local t= printf 'Trying script:\n' printf '\t%s\n' "${@}" if tokens="$(printf '%s\n' "${@}" | parse -)"; then IFS="${RS}" for t in ${tokens}; do printf 'Token: %s\n' "$(tokname "${t}")" case "${t%${US}*}" in T_WORD) printf ' "%s"\n' "${t#T_WORD${US}}" ;; esac done unset IFS else printf 'FAIL\n' fi printf '\n\n' } #try '"foo bar" && $baz || qux' '${quux%uux quuux' #try '"foo bar" && $baz || qux' '${quux%uux } quuux' #try 'foo ${bar}' #try 'foo ${#bar}' #try 'foo ${bar#baz}' #try 'foo ${#bar#}' #try 'foo ${^}' #try 'foo `bar`' #try 'foo &&' #try '{ foo; }' #try '( foo )' #try 'for i in 1 2 3; do stuff; done' #try 'if foo; then bar; fi' #try 'if foo; then bar; elif baz; then qux; else quux; fi' #try 'if ; then ; fi' #try 'while foo; do bar; done' #try 'while ; do ; done' try 'foo(){ bar; }' #try 'case foo in bar) baz;; (qux) quux;; quux);; esac'