SOH="$(printf '\001.')"; SOH="${SOH%.}" STX="$(printf '\002.')"; STX="${STX%.}" ETX="$(printf '\003.')"; ETX="${ETX%.}" HT="$(printf '\t.')"; HT="${HT%.}" LF="$(printf '\n.')"; LF="${LF%.}" RS="$(printf '\036.')"; RS="${RS%.}" US="$(printf '\037.')"; US="${US%.}" dbg=true dbg=false dbg() { if ${dbg}; then printf 'DEBUG: %s\n' "${@}" >&2 fi } ptrace=false ptrace_begn() { local fn="${1}" shift 1 if ${ptrace}; then printf 'TRACE: BEGN %s()\n' "${fn}" >&2 fi } ptrace_pass() { local fn="${1}" shift 1 if ${ptrace}; then printf 'TRACE: PASS %s()\n' "${fn}" >&2 fi } ptrace_fail() { local fn="${1}" shift 1 if ${ptrace}; then printf 'TRACE: FAIL %s()\n' "${fn}" >&2 fi } . ./tokens.sh . ./lexer.sh . ./codegen.sh complete_command() { if list; then separator return 0 fi # Unexpected EOF synexp '' } list() { ptrace_begn list if and_or; then while separator && and_or; do : done ptrace_pass list return 0 fi ptrace_fail list return 1 } and_or() { ptrace_begn and_or if pipeline; then while accept T_AND_IF || accept T_OR_IF; do if ! linebreak || ! pipeline; then ptrace_fail and_or return 1 fi done ptrace_pass and_or return 0 fi ptrace_fail and_or return 1 } pipeline() { ptrace_begn pipeline accept T_BANG if pipe_sequence; then ptrace_pass pipeline return 0 fi ptrace_fail pipeline return 1 } pipe_sequence() { ptrace_begn pipe_sequence if command; then while accept T_PIPE; do if ! linebreak || ! command; then ptrace_fail pipe_sequence return 1 fi done ptrace_pass pipe_sequence return 0 fi ptrace_fail pipe_sequence return 1 } command() { ptrace_begn command if simple_command; then ptrace_pass command return 0 elif compound_command; then redirect_list ptrace_pass command return 0 fi ptrace_fail command return 1 } compound_command() { ptrace_begn compound_command if brace_group; then ptrace_pass compound_command return 0 elif subshell; then ptrace_pass compound_command return 0 elif for_clause; then ptrace_pass compound_command return 0 elif case_clause; then ptrace_pass compound_command return 0 elif if_clause; then ptrace_pass compound_command return 0 elif while_clause; then ptrace_pass compound_command return 0 elif until_clause; then ptrace_pass compound_command return 0 fi ptrace_fail compound_command return 1 } subshell() { ptrace_begn subshell if accept T_LPAREN && compound_list && expect T_RPAREN; then ptrace_pass subshell return 0 fi ptrace_fail subshell return 1 } compound_list() { ptrace_begn compound_list newline_list if term; then separator ptrace_pass compound_list return 0 fi ptrace_fail compound_list return 1 } term() { ptrace_begn term if and_or; then while separator; do and_or done ptrace_pass term return 0 fi ptrace_fail term return 1 } for_clause() { ptrace_begn for_clause if accept T_FOR; then if expect T_NAME && linebreak; then if accept T_IN; then wordlist if ! sequential_sep; then ptrace_fail for_clause return 1 fi fi if do_group; then ptrace_pass for_clause return 0 fi fi fi ptrace_fail for_clause return 1 } wordlist() { ptrace_begn wordlist if accept T_WORD; then while accept T_WORD; do :; done ptrace_pass wordlist return 0 fi ptrace_fail wordlist 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() { ptrace_begn function_body if compound_command; then redirect_list ptrace_pass function_body return 0 fi ptrace_fail function_body return 1 } brace_group() { ptrace_begn brace_group if accept T_LBRACE && compound_list && expect T_RBRACE; then ptrace_pass brace_group return 0 fi ptrace_fail brace_group return 1 } do_group() { ptrace_begn do_group if accept T_DO && compound_list && expect T_DONE; then ptrace_pass do_group return 0 fi ptrace_fail do_group return 1 } simple_command() { ptrace_begn simple_command if cmd_prefix; then if cmd_word; then cmd_suffix fi ptrace_pass simple_command return 0 elif accept T_FNAME; then if accept T_LPAREN; then expect T_RPAREN if linebreak && function_body; then ptrace_pass simple_command return 0 fi else cmd_suffix ptrace_pass simple_command return 0 fi elif cmd_name; then cmd_suffix ptrace_pass simple_command return 0 fi ptrace_fail simple_command return 1 } cmd_name() { ptrace_begn cmd_name # TODO: Assignment if accept T_CMDNAME; then ptrace_pass cmd_name return 0 fi ptrace_fail cmd_name return 1 } cmd_word() { ptrace_begn cmd_word # TODO: Assignment if accept T_WORD; then ptrace_pass cmd_word return 0 fi ptrace_fail cmd_word return 1 } cmd_prefix() { ptrace_begn cmd_prefix if io_redirect || accept T_ASSIGNMENT_WORD; then while io_redirect || accept T_ASSIGNMENT_WORD; do : done ptrace_pass cmd_prefix return 0 fi ptrace_fail cmd_prefix return 1 } cmd_suffix() { ptrace_begn cmd_suffix if io_redirect || accept T_WORD; then while io_redirect || accept T_WORD; do : done ptrace_pass cmd_suffix return 0 fi ptrace_fail cmd_suffix return 1 } redirect_list() { ptrace_begn redirect_list if io_redirect; then while io_redirect; do : done ptrace_pass redirect_list return 0 fi ptrace_fail redirect_list return 1 } io_redirect() { ptrace_begn io_redirect if io_file || io_here; then ptrace_pass io_redirect return 0 fi ptrace_fail io_redirect 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() { ptrace_begn sequential_sep if accept T_SEMI; then if linebreak; then ptrace_pass sequential_sep return 0 fi elif newline_list; then ptrace_pass sequential_sep return 0 fi ptrace_fail sequential_sep return 1 } parse() { local fn="${1}" shift 1 if run_lexer "${fn}" complete_command; then return 0 fi return 1 } try() { local tokens= local t= printf 'Trying script:\n' printf '\t%s\n' "${@}" if tokens="$(printf '%s\n' "${@}" | parse -)"; then printf 'Tokens: %s\n' "${tokens}" | sed " s/${SOH}//g; s/${STX}//g; s/${ETX}//g; s/${RS}//g; s/${US}//g; " IFS="${RS}" for t in ${tokens}; do printf 'Token: %s\n' "$(tokname "${t}")" case "${t%${US}*}" in T_NAME|T_FNAME|T_CMDNAME|T_WORD) printf ' "%s"\n' "${t#*${US}}" ;; esac done printf 'Generated code:\n' IFS="${LF}" printf '\t%s\n' $(codegen "${tokens}") 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' #try 'foo bar ( baz )' #try 'foo $(bar)' #try 'foo $(bar); baz' #try 'foo $(bar)' 'baz' #try 'foo $(bar) baz' #try 'foo$(bar$(baz))qux' #try 'foo $((1 + 1))' #try '$((1 + 1))' #try '$((1 + (1 + 1)))' #try '$((1 + $(foo) + 1))' #try '$((1' #try 'foo <