hush: add case statement support. It is incomplete and disabled for now.
authorDenis Vlasenko <vda.linux@googlemail.com>
Mon, 14 Jul 2008 04:32:29 +0000 (04:32 -0000)
committerDenis Vlasenko <vda.linux@googlemail.com>
Mon, 14 Jul 2008 04:32:29 +0000 (04:32 -0000)
 costs ~300 bytes when enabled.

shell/hush.c

index aab53c33647004ce061d29e7619794497ca29e16..47e53f8c1a1e07dbeb7584ef5be493210add67c5 100644 (file)
@@ -73,6 +73,9 @@
 
 #include "busybox.h" /* for APPLET_IS_NOFORK/NOEXEC */
 
+// TEMP
+#define ENABLE_HUSH_CASE 0
+
 
 #if !BB_MMU && ENABLE_HUSH_TICK
 //#undef ENABLE_HUSH_TICK
@@ -262,22 +265,29 @@ typedef enum {
 typedef enum {
        RES_NONE  = 0,
 #if ENABLE_HUSH_IF
-       RES_IF    = 1,
-       RES_THEN  = 2,
-       RES_ELIF  = 3,
-       RES_ELSE  = 4,
-       RES_FI    = 5,
+       RES_IF    ,
+       RES_THEN  ,
+       RES_ELIF  ,
+       RES_ELSE  ,
+       RES_FI    ,
 #endif
 #if ENABLE_HUSH_LOOPS
-       RES_FOR   = 6,
-       RES_WHILE = 7,
-       RES_UNTIL = 8,
-       RES_DO    = 9,
-       RES_DONE  = 10,
-       RES_IN    = 11,
-#endif
-       RES_XXXX  = 12,
-       RES_SNTX  = 13
+       RES_FOR   ,
+       RES_WHILE ,
+       RES_UNTIL ,
+       RES_DO    ,
+       RES_DONE  ,
+       RES_IN    ,
+#endif
+#if ENABLE_HUSH_CASE
+       RES_CASE  ,
+       /* two pseudo-keywords support contrived "case" syntax: */
+       RES_MATCH , /* "word)" */
+       RES_CASEI , /* "this command is inside CASE" */
+       RES_ESAC  ,
+#endif
+       RES_XXXX  ,
+       RES_SNTX
 } reserved_style;
 
 /* This holds pointers to the various results of parsing */
@@ -289,6 +299,9 @@ struct p_context {
 #if HAS_KEYWORDS
        smallint ctx_res_w;
        smallint ctx_inverted; /* "! cmd | cmd" */
+#if ENABLE_HUSH_CASE
+       smallint ctx_dsemicolon; /* ";;" seen */
+#endif
        int old_flag; /* bitmask of FLAG_xxx, for figuring out valid reserved words */
        struct p_context *stack;
 #endif
@@ -350,7 +363,7 @@ struct variable {
 
 typedef struct {
        char *data;
-       int length;
+       int length; /* position where data is appended */
        int maxlen;
        /* Misnomer! it's not "quoting", it's "protection against globbing"!
         * (by prepending \ to *, ?, [ and to \ too) */
@@ -1954,6 +1967,12 @@ static void debug_print_tree(struct pipe *pi, int lvl)
                [RES_DO   ] = "DO"   ,
                [RES_DONE ] = "DONE" ,
                [RES_IN   ] = "IN"   ,
+#endif
+#if ENABLE_HUSH_CASE
+               [RES_CASE ] = "CASE" ,
+               [RES_MATCH] = "MATCH",
+               [RES_CASEI] = "CASEI",
+               [RES_ESAC ] = "ESAC" ,
 #endif
                [RES_XXXX ] = "XXXX" ,
                [RES_SNTX ] = "SNTX" ,
@@ -2002,6 +2021,9 @@ static int run_list(struct pipe *pi)
        char **for_lcur = NULL;
        char **for_list = NULL;
        int flag_rep = 0;
+#endif
+#if ENABLE_HUSH_CASE
+       char *case_word = NULL;
 #endif
        int flag_skip = 1;
        int rcode = 0; /* probably for gcc only */
@@ -2019,17 +2041,20 @@ static int run_list(struct pipe *pi)
 #if ENABLE_HUSH_LOOPS
        /* check syntax for "for" */
        for (rpipe = pi; rpipe; rpipe = rpipe->next) {
-               if ((rpipe->res_word == RES_IN || rpipe->res_word == RES_FOR)
-                && (rpipe->next == NULL)
-               ) {
-                       syntax("malformed for"); /* no IN or no commands after IN */
+               if (rpipe->res_word != RES_FOR && rpipe->res_word != RES_IN)
+                       continue;
+               /* current word is FOR or IN (BOLD in comments below) */
+               if (rpipe->next == NULL) {
+                       syntax("malformed for");
                        debug_printf_exec("run_list lvl %d return 1\n", run_list_level);
                        return 1;
                }
-               if (/* Extra statement after IN: "for a in a b; echo Hi; do ...; done" ? */
-                   (rpipe->res_word == RES_IN && rpipe->next->res_word == RES_IN && rpipe->next->progs[0].argv != NULL)
-               /* FOR not followed by IN or DO ("for var; do..." case)? */
-                || (rpipe->res_word == RES_FOR && (rpipe->next->res_word != RES_IN && rpipe->next->res_word != RES_DO))
+               /* "FOR v; do ..." and "for v IN a b; do..." are ok */
+               if (rpipe->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..."? */
                ) {
                        syntax("malformed for");
                        debug_printf_exec("run_list lvl %d return 1\n", run_list_level);
@@ -2136,8 +2161,8 @@ static int run_list(struct pipe *pi)
                                /* create list of variable values */
                                debug_print_strings("for_list made from", vals);
                                for_list = expand_strvec_to_strvec(vals);
-                               debug_print_strings("for_list", for_list);
                                for_lcur = for_list;
+                               debug_print_strings("for_list", for_list);
                                for_varname = pi->progs->argv[0];
                                pi->progs->argv[0] = NULL;
                                flag_rep = 1;
@@ -2169,6 +2194,26 @@ static int run_list(struct pipe *pi)
                        }
                }
 #endif
+#if ENABLE_HUSH_CASE
+               if (rword == RES_CASE) {
+                       case_word = pi->progs->argv[0];
+                       continue;
+               }
+               if (rword == RES_MATCH) {
+                       if (case_word) {
+                               next_if_code = strcmp(case_word, pi->progs->argv[0]);
+                               if (next_if_code == 0)
+                                       case_word = NULL;
+                               continue;
+                       }
+                       break;
+               }
+               if (rword == RES_CASEI) {
+                       if (next_if_code != 0)
+                               continue;
+               }
+#endif
+
                if (pi->num_progs == 0)
                        continue;
                debug_printf_exec(": run_pipe with %d members\n", pi->num_progs);
@@ -2193,8 +2238,7 @@ static int run_list(struct pipe *pi)
                                rcode = checkjobs_and_fg_shell(pi);
                        } else
 #endif
-                       {
-                               /* this one just waits for completion */
+                       { /* this one just waits for completion */
                                rcode = checkjobs(pi);
                        }
                        debug_printf_exec(": checkjobs returned %d\n", rcode);
@@ -2217,12 +2261,13 @@ static int run_list(struct pipe *pi)
                        skip_more_for_this_rword = rword;
                }
                checkjobs(NULL);
-       }
+       } /* for (pi) */
 
 #if ENABLE_HUSH_JOB
        if (ctrl_z_flag) {
                /* ctrl-Z forked somewhere in the past, we are the child,
                 * and now we completed running the list. Exit. */
+//TODO: _exit?
                exit(rcode);
        }
  ret:
@@ -2820,6 +2865,10 @@ static int reserved_word(const o_string *word, struct p_context *ctx)
                FLAG_DO    = (1 << RES_DO   ),
                FLAG_DONE  = (1 << RES_DONE ),
                FLAG_IN    = (1 << RES_IN   ),
+#endif
+#if ENABLE_HUSH_CASE
+               FLAG_MATCH = (1 << RES_MATCH),
+               FLAG_ESAC  = (1 << RES_ESAC ),
 #endif
                FLAG_START = (1 << RES_XXXX ),
        };
@@ -2843,16 +2892,30 @@ static int reserved_word(const o_string *word, struct p_context *ctx)
                { "until", RES_UNTIL, FLAG_DO | FLAG_START },
                { "in",    RES_IN,    FLAG_DO   },
                { "do",    RES_DO,    FLAG_DONE },
-               { "done",  RES_DONE,  FLAG_END  }
+               { "done",  RES_DONE,  FLAG_END  },
+#endif
+#if ENABLE_HUSH_CASE
+               { "case",  RES_CASE,  FLAG_MATCH | FLAG_START },
+               { "esac",  RES_ESAC,  FLAG_END  },
 #endif
        };
-
+#if ENABLE_HUSH_CASE
+       static const struct reserved_combo reserved_match = {
+               "" /* "match" */, RES_MATCH, FLAG_MATCH | FLAG_ESAC
+       };
+#endif
        const struct reserved_combo *r;
 
        for (r = reserved_list; r < reserved_list + ARRAY_SIZE(reserved_list); r++) {
                if (strcmp(word->data, r->literal) != 0)
                        continue;
                debug_printf("found reserved word %s, res %d\n", r->literal, r->res);
+#if ENABLE_HUSH_CASE
+               if (r->res == RES_IN && ctx->ctx_res_w == RES_CASE)
+                       /* "case word IN ..." - IN part starts first match part */
+                       r = &reserved_match;
+               else
+#endif
                if (r->flag == 0) { /* '!' */
                        if (ctx->ctx_inverted) { /* bash doesn't accept '! ! true' */
                                syntax(NULL);
@@ -2864,13 +2927,6 @@ static int reserved_word(const o_string *word, struct p_context *ctx)
                if (r->flag & FLAG_START) {
                        struct p_context *new;
                        debug_printf("push stack\n");
-#if ENABLE_HUSH_LOOPS
-                       if (ctx->ctx_res_w == RES_IN || ctx->ctx_res_w == RES_FOR) {
-                               syntax("malformed for"); /* example: 'for if' */
-                               ctx->ctx_res_w = RES_SNTX;
-                               return 1;
-                       }
-#endif
                        new = xmalloc(sizeof(*new));
                        *new = *ctx;   /* physical copy */
                        initialize_context(ctx);
@@ -2924,12 +2980,20 @@ static int done_word(o_string *word, struct p_context *ctx)
                word->o_assignment = NOT_ASSIGNMENT;
                debug_printf("word stored in rd_filename: '%s'\n", word->data);
        } else {
-               if (child->group) { /* TODO: example how to trigger? */
-                       syntax(NULL);
-                       debug_printf_parse("done_word return 1: syntax error, groups and arglists don't mix\n");
-                       return 1;
-               }
+//             if (child->group) { /* TODO: example how to trigger? */
+//                     syntax(NULL);
+//                     debug_printf_parse("done_word return 1: syntax error, groups and arglists don't mix\n");
+//                     return 1;
+//             }
 #if HAS_KEYWORDS
+#if ENABLE_HUSH_CASE
+               if (ctx->ctx_dsemicolon) {
+                       /* already done when ctx_dsemicolon was set to 1 */
+                       /* ctx->ctx_res_w = RES_MATCH; */
+                       ctx->ctx_dsemicolon = 0;
+               } else
+#endif
+
                if (!child->argv /* if it's the first word... */
 #if ENABLE_HUSH_LOOPS
                 && ctx->ctx_res_w != RES_FOR /* ...not after FOR or IN */
@@ -2983,6 +3047,12 @@ static int done_word(o_string *word, struct p_context *ctx)
 //TODO: check that child->argv[0] is a valid variable name!
                done_pipe(ctx, PIPE_SEQ);
        }
+#endif
+#if ENABLE_HUSH_CASE
+       /* Force CASE to have just one word */
+       if (ctx->ctx_res_w == RES_CASE) {
+               done_pipe(ctx, PIPE_SEQ);
+       }
 #endif
        debug_printf_parse("done_word return 0\n");
        return 0;
@@ -3055,6 +3125,10 @@ static void done_pipe(struct p_context *ctx, pipe_style type)
                if (ctx->ctx_res_w == RES_FOR
                 || ctx->ctx_res_w == RES_IN)
                        ctx->ctx_res_w = RES_NONE;
+#endif
+#if ENABLE_HUSH_CASE
+               if (ctx->ctx_res_w == RES_MATCH)
+                       ctx->ctx_res_w = RES_CASEI;
 #endif
                /* Create the memory for child, roughly:
                 * ctx->pipe->progs = new struct child_prog;
@@ -3458,10 +3532,9 @@ static int handle_dollar(o_string *dest, struct in_str *input)
 }
 
 /* Scan input, call done_word() whenever full IFS delimited word was seen.
- * call done_pipe if '\n' was seen (and end_trigger != NULL)
- * Return if (non-quoted) char in end_trigger was seen; or on parse error. */
-/* Return code is 0 if end_trigger char is met,
- * -1 on EOF (but if end_trigger == NULL then return 0)
+ * Call done_pipe if '\n' was seen (and end_trigger != NULL).
+ * Return code is 0 if end_trigger char is met,
+ * -1 on EOF (but if end_trigger == NULL then return 0),
  * 1 for syntax error */
 static int parse_stream(o_string *dest, struct p_context *ctx,
        struct in_str *input, const char *end_trigger)
@@ -3664,8 +3737,26 @@ static int parse_stream(o_string *dest, struct p_context *ctx,
                        setup_redirect(ctx, redir_fd, redir_style, input);
                        break;
                case ';':
+#if ENABLE_HUSH_CASE
+ case_semi:
+#endif
                        done_word(dest, ctx);
                        done_pipe(ctx, PIPE_SEQ);
+#if ENABLE_HUSH_CASE
+                       /* Eat multiple semicolons, detect
+                        * whether it means something special */
+                       while (1) {
+                               ch = i_peek(input);
+                               if (ch != ';')
+                                       break;
+                               i_getch(input);
+                               if (ctx->ctx_res_w == RES_CASEI) {
+                                       ctx->ctx_dsemicolon = 1;
+                                       ctx->ctx_res_w = RES_MATCH;
+                                       break;
+                               }
+                       }
+#endif
                        break;
                case '&':
                        done_word(dest, ctx);
@@ -3689,6 +3780,13 @@ static int parse_stream(o_string *dest, struct p_context *ctx,
                        }
                        break;
                case '(':
+#if ENABLE_HUSH_CASE
+                       if (dest->length == 0 // && argv[0] == NULL
+                        && ctx->ctx_res_w == RES_MATCH
+                       ) {
+                               continue;
+                       }
+#endif
                case '{':
                        if (parse_group(dest, ctx, input, ch) != 0) {
                                debug_printf_parse("parse_stream return 1: parse_group returned non-0\n");
@@ -3696,6 +3794,10 @@ static int parse_stream(o_string *dest, struct p_context *ctx,
                        }
                        break;
                case ')':
+#if ENABLE_HUSH_CASE
+                       if (ctx->ctx_res_w == RES_MATCH)
+                               goto case_semi;
+#endif
                case '}':
                        /* proper use of this character is caught by end_trigger */
                        syntax("unexpected } or )");