hush: explain run_list() in detail; small optimizations
authorDenis Vlasenko <vda.linux@googlemail.com>
Mon, 28 Jul 2008 15:15:09 +0000 (15:15 -0000)
committerDenis Vlasenko <vda.linux@googlemail.com>
Mon, 28 Jul 2008 15:15:09 +0000 (15:15 -0000)
function                                             old     new   delta
hush_main                                            785     786      +1
expand_variables                                    1447    1448      +1
builtin_exit                                          48      49      +1
builtin_eval                                          54      55      +1
run_list                                            2075    2055     -20
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 4/1 up/down: 4/-20)             Total: -16 bytes

shell/hush.c

index 5a565b392b62b0b78c689e30b74c3809d1dc18f4..56374fa2a6ebce219030cb4c163c6bf7d9abbb38 100644 (file)
@@ -431,12 +431,12 @@ struct globals {
 #endif
        smallint fake_mode;
        /* these three support $?, $#, and $1 */
+       smalluint last_return_code;
        char **global_argv;
        int global_argc;
-       int last_return_code;
+       pid_t last_bg_pid;
        const char *ifs;
        const char *cwd;
-       unsigned last_bg_pid;
        struct variable *top_var; /* = &shell_ver (set in main()) */
        struct variable shell_ver;
 #if ENABLE_FEATURE_SH_STANDALONE
@@ -2012,54 +2012,53 @@ static void debug_print_tree(struct pipe *pi, int lvl)
  * global data until exec/_exit (we can be a child after vfork!) */
 static int run_list(struct pipe *pi)
 {
-       struct pipe *rpipe;
+#if ENABLE_HUSH_CASE
+       char *case_word = NULL;
+#endif
 #if ENABLE_HUSH_LOOPS
+       struct pipe *loop_top;
        char *for_varname = NULL;
        char **for_lcur = NULL;
        char **for_list = NULL;
-       int flag_rep = 0;
-#endif
-#if ENABLE_HUSH_CASE
-       char *case_word = NULL;
+       smallint flag_run_loop = 0;
+       smallint flag_goto_looptop = 0;
 #endif
-       int flag_skip = 1;
-       int rcode = 0; /* probably for gcc only */
-       int flag_restore = 0;
+       smallint flag_skip = 1;
+       smalluint rcode = 0; /* probably for gcc only */
 #if ENABLE_HUSH_IF
-       int if_code = 0, next_if_code = 0;  /* need double-buffer to handle elif */
+       smalluint cond_code = 0;
+       smalluint last_cond_code = 0; /* need double-buffer to handle "elif" */
 #else
-       enum { if_code = 0, next_if_code = 0 };
+       enum { cond_code = 0, last_cond_code = 0 };
 #endif
-       reserved_style rword IF_HAS_NO_KEYWORDS(= RES_NONE);
-       reserved_style skip_more_for_this_rword = RES_XXXX;
+       /*reserved_style*/ smallint rword IF_HAS_NO_KEYWORDS(= RES_NONE);
+       /*reserved_style*/ smallint skip_more_for_this_rword = RES_XXXX;
 
        debug_printf_exec("run_list start lvl %d\n", run_list_level + 1);
 
 #if ENABLE_HUSH_LOOPS
        /* check syntax for "for" */
-       for (rpipe = pi; rpipe; rpipe = rpipe->next) {
-               if (rpipe->res_word != RES_FOR && rpipe->res_word != RES_IN)
+       for (loop_top = pi; loop_top; loop_top = loop_top->next) {
+               if (loop_top->res_word != RES_FOR && loop_top->res_word != RES_IN)
                        continue;
                /* current word is FOR or IN (BOLD in comments below) */
-               if (rpipe->next == NULL) {
+               if (loop_top->next == NULL) {
                        syntax("malformed for");
                        debug_printf_exec("run_list lvl %d return 1\n", run_list_level);
                        return 1;
                }
                /* "FOR v; do ..." and "for v IN a b; do..." are ok */
-               if (rpipe->next->res_word == RES_DO)
+               if (loop_top->next->res_word == RES_DO)
                        continue;
                /* next word is not "do". It must be "in" then ("FOR v in ...") */
-               if (rpipe->res_word == RES_IN /* "for v IN a b; not_do..."? */
-                || rpipe->next->res_word != RES_IN /* FOR v not_do_and_not_in..."? */
+               if (loop_top->res_word == RES_IN /* "for v IN a b; not_do..."? */
+                || loop_top->next->res_word != RES_IN /* FOR v not_do_and_not_in..."? */
                ) {
                        syntax("malformed for");
                        debug_printf_exec("run_list lvl %d return 1\n", run_list_level);
                        return 1;
                }
        }
-#else
-       rpipe = NULL;
 #endif
 
        /* Past this point, all code paths should jump to ret: label
@@ -2108,21 +2107,24 @@ static int run_list(struct pipe *pi)
        }
 #endif /* JOB */
 
-       for (; pi; pi = flag_restore ? rpipe : pi->next) {
+       /* Go through list of pipes, (maybe) executing them */
+       for (; pi; pi = USE_HUSH_LOOPS( flag_goto_looptop ? loop_top : ) pi->next) {
                IF_HAS_KEYWORDS(rword = pi->res_word;)
                IF_HAS_NO_KEYWORDS(rword = RES_NONE;)
+               debug_printf_exec(": rword=%d cond_code=%d last_cond_code=%d skip_more=%d flag_run_loop=%d\n",
+                               rword, cond_code, last_cond_code, skip_more_for_this_rword, flag_run_loop);
 #if ENABLE_HUSH_LOOPS
                if (rword == RES_WHILE || rword == RES_UNTIL || rword == RES_FOR) {
-                       flag_restore = 0;
-                       if (!rpipe) {
-                               flag_rep = 0;
-                               rpipe = pi;
+                       /* start of a loop: remember it */
+                       flag_goto_looptop = 0; /* not yet reached final "done" */
+                       if (!loop_top) { /* hmm why this check is needed? */
+                               flag_run_loop = 0; /* suppose loop condition is false (for now) */
+                               loop_top = pi; /* remember where loop starts */
                        }
                }
 #endif
-               debug_printf_exec(": rword=%d if_code=%d next_if_code=%d skip_more=%d\n",
-                               rword, if_code, next_if_code, skip_more_for_this_rword);
                if (rword == skip_more_for_this_rword && flag_skip) {
+                       /* it is "<false> && CMD ... */
                        if (pi->followup == PIPE_SEQ)
                                flag_skip = 0;
                        continue;
@@ -2131,13 +2133,14 @@ static int run_list(struct pipe *pi)
                skip_more_for_this_rword = RES_XXXX;
 #if ENABLE_HUSH_IF
                if (rword == RES_THEN || rword == RES_ELSE)
-                       if_code = next_if_code;
-               if (rword == RES_THEN && if_code)
-                       continue;
-               if (rword == RES_ELSE && !if_code)
-                       continue;
-               if (rword == RES_ELIF && !if_code)
-                       break;
+                       cond_code = last_cond_code;
+               if (rword == RES_THEN && cond_code)
+                       continue; /* "if <false> THEN cmd": skip cmd */
+               if (rword == RES_ELSE && !cond_code)
+                       continue; /* "if <true> then ... ELSE cmd": skip cmd */
+// TODO: break;?
+               if (rword == RES_ELIF && !cond_code)
+                       break; /* "if <true> then ... ELIF cmd": skip cmd and all following ones */
 #endif
 #if ENABLE_HUSH_LOOPS
                if (rword == RES_FOR && pi->num_progs) {
@@ -2153,7 +2156,7 @@ static int run_list(struct pipe *pi)
                                char **vals;
 
                                vals = (char**)encoded_dollar_at_argv;
-                               if (rpipe->next->res_word == RES_IN) {
+                               if (loop_top->next->res_word == RES_IN) {
                                        /* if no variable values after "in" we skip "for" */
                                        if (!pi->next->progs->argv)
                                                continue;
@@ -2166,7 +2169,7 @@ static int run_list(struct pipe *pi)
                                debug_print_strings("for_list", for_list);
                                for_varname = pi->progs->argv[0];
                                pi->progs->argv[0] = NULL;
-                               flag_rep = 1;
+                               flag_run_loop = 1; /* "for" has no loop condition, loop... */
                        }
                        free(pi->progs->argv[0]);
                        if (!*for_lcur) {
@@ -2174,26 +2177,27 @@ static int run_list(struct pipe *pi)
                                free(for_list);
                                for_list = NULL;
                                for_lcur = NULL;
-                               flag_rep = 0;
+                               flag_run_loop = 0; /* ... until end of value list */
                                pi->progs->argv[0] = for_varname;
                                continue;
                        }
                        /* insert next value from for_lcur */
-                       /* vda: does it need escaping? */
+//TODO: does it need escaping?
                        pi->progs->argv[0] = xasprintf("%s=%s", for_varname, *for_lcur++);
                }
-               if (rword == RES_IN)
+               if (rword == RES_IN) /* "for v IN list; do ..." - no pipe to execute here */
                        continue;
-               if (rword == RES_DO) {
-                       if (!flag_rep)
-                               continue;
+               if (rword == RES_DO) { /* "...; DO cmd; cmd" - this pipe is in loop body */
+                       if (!flag_run_loop)
+                               continue; /* we are skipping this iteration */
                }
-               if (rword == RES_DONE) {
-                       if (flag_rep) {
-                               flag_restore = 1;
+               if (rword == RES_DONE) { /* end of loop? */
+                       if (flag_run_loop) {
+                               flag_goto_looptop = 1;
                        } else {
-                               rpipe = NULL;
+                               loop_top = NULL;
                        }
+//TODO: continue;? DONE has no cmd anyway
                }
 #endif
 #if ENABLE_HUSH_CASE
@@ -2203,69 +2207,82 @@ static int run_list(struct pipe *pi)
                        continue;
                }
                if (rword == RES_MATCH) {
-                       if (case_word) {
-                               char *pattern = expand_strvec_to_string(pi->progs->argv);
-                               /* TODO: which FNM_xxx flags to use? */
-                               next_if_code = fnmatch(pattern, case_word, /*flags:*/ 0);
-                               //bb_error_msg("fnmatch('%s','%s'):%d", pattern, case_word, next_if_code);
-                               free(pattern);
-                               if (next_if_code == 0) {
-                                       free(case_word);
-                                       case_word = NULL;
-                               }
-                               continue;
+                       char *pattern;
+                       if (!case_word) /* "case ... matched_word) ... WORD)": we executed selected branch, stop */
+                               break;
+                       /* all prev words didn't match, does this one match? */
+                       pattern = expand_strvec_to_string(pi->progs->argv);
+                       /* TODO: which FNM_xxx flags to use? */
+                       last_cond_code = (fnmatch(pattern, case_word, /*flags:*/ 0) != 0);
+                       //bb_error_msg("fnmatch('%s','%s'):%d", pattern, case_word, last_cond_code);
+                       free(pattern);
+                       if (last_cond_code == 0) { /* match! we will execute this branch */
+                               free(case_word); /* make future "word)" stop */
+                               case_word = NULL;
                        }
-                       break;
+                       continue;
                }
-               if (rword == RES_CASEI) {
-                       if (next_if_code != 0)
-                               continue;
+               if (rword == RES_CASEI) { /* inside of a case branch */
+                       if (last_cond_code != 0)
+                               continue; /* not matched yet, skip this pipe */
                }
 #endif
-
                if (pi->num_progs == 0)
                        continue;
+
+               /* After analyzing all keywrds and conditions, we decided
+                * to execute this pipe */
                debug_printf_exec(": run_pipe with %d members\n", pi->num_progs);
-               rcode = run_pipe(pi);
-               if (rcode != -1) {
-                       /* We only ran a builtin: rcode was set by the return value
-                        * of run_pipe(), and we don't need to wait for anything. */
-               } else if (pi->followup == PIPE_BG) {
-                       /* What does bash do with attempts to background builtins? */
-                       /* Even bash 3.2 doesn't do that well with nested bg:
-                        * try "{ { sleep 10; echo DEEP; } & echo HERE; } &".
-                        * I'm NOT treating inner &'s as jobs */
+               {
+                       int r;
+                       rcode = r = run_pipe(pi); /* NB: rcode is a smallint */
+                       if (r != -1) {
+                               /* We only ran a builtin: rcode was set by the return value
+                                * of run_pipe(), and we don't need to wait for anything. */
+                       } else if (pi->followup == PIPE_BG) {
+                               /* What does bash do with attempts to background builtins? */
+                               /* Even bash 3.2 doesn't do that well with nested bg:
+                                * try "{ { sleep 10; echo DEEP; } & echo HERE; } &".
+                                * I'm NOT treating inner &'s as jobs */
 #if ENABLE_HUSH_JOB
-                       if (run_list_level == 1)
-                               insert_bg_job(pi);
+                               if (run_list_level == 1)
+                                       insert_bg_job(pi);
 #endif
-                       rcode = EXIT_SUCCESS;
-               } else {
+                               rcode = 0; /* EXIT_SUCCESS */
+                       } else {
 #if ENABLE_HUSH_JOB
-                       if (run_list_level == 1 && interactive_fd) {
-                               /* waits for completion, then fg's main shell */
-                               rcode = checkjobs_and_fg_shell(pi);
-                       } else
-#endif
-                       { /* this one just waits for completion */
-                               rcode = checkjobs(pi);
+                               if (run_list_level == 1 && interactive_fd) {
+                                       /* waits for completion, then fg's main shell */
+                                       rcode = checkjobs_and_fg_shell(pi);
+                                       debug_printf_exec(": checkjobs_and_fg_shell returned %d\n", rcode);
+                               } else
+#endif
+                               { /* this one just waits for completion */
+                                       rcode = checkjobs(pi);
+                                       debug_printf_exec(": checkjobs returned %d\n", rcode);
+                               }
                        }
-                       debug_printf_exec(": checkjobs returned %d\n", rcode);
                }
                debug_printf_exec(": setting last_return_code=%d\n", rcode);
                last_return_code = rcode;
+
+               /* Analyze how result affects subsequent commands */
 #if ENABLE_HUSH_IF
                if (rword == RES_IF || rword == RES_ELIF)
-                       next_if_code = rcode;  /* can be overwritten a number of times */
+                       last_cond_code = rcode;
 #endif
 #if ENABLE_HUSH_LOOPS
-               if (rword == RES_WHILE)
-                       flag_rep = !last_return_code;
-               if (rword == RES_UNTIL)
-                       flag_rep = last_return_code;
+               if (rword == RES_WHILE) {
+                       flag_run_loop = !rcode;
+                       debug_printf_exec(": setting flag_run_loop=%d\n", flag_run_loop);
+               }
+               if (rword == RES_UNTIL) {
+                       flag_run_loop = rcode;
+                       debug_printf_exec(": setting flag_run_loop=%d\n", flag_run_loop);
+               }
 #endif
-               if ((rcode == EXIT_SUCCESS && pi->followup == PIPE_OR)
-                || (rcode != EXIT_SUCCESS && pi->followup == PIPE_AND)
+               if ((rcode == 0 && pi->followup == PIPE_OR)
+                || (rcode != 0 && pi->followup == PIPE_AND)
                ) {
                        skip_more_for_this_rword = rword;
                }