hush: support "for if in do done then; do echo $if; done" case
authorDenis Vlasenko <vda.linux@googlemail.com>
Sun, 6 Jul 2008 10:01:13 +0000 (10:01 -0000)
committerDenis Vlasenko <vda.linux@googlemail.com>
Sun, 6 Jul 2008 10:01:13 +0000 (10:01 -0000)
function                                             old     new   delta
done_pipe                                             83      95     +12
parse_stream                                        1758    1764      +6
done_word                                            674     647     -27
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 2/1 up/down: 18/-27)             Total: -9 bytes

shell/hush.c
shell/hush_test/hush-misc/for_with_keywords.right [new file with mode: 0644]
shell/hush_test/hush-misc/for_with_keywords.tests [new file with mode: 0755]

index f38f3752b364ff66e7ac2928b11a9552f501baf5..c71ddb14f23807b10fc667cc084652dd02a75bc4 100644 (file)
@@ -1344,7 +1344,7 @@ static int setup_redirects(struct child_prog *prog, int squirrel[])
                if (redir->dup == -1) {
                        char *p;
                        mode = redir_table[redir->rd_type].mode;
-//TODO: check redir to names like '\\'
+//TODO: check redir for names like '\\'
                        p = expand_string_to_string(redir->rd_filename);
                        openfd = open_or_warn(p, mode);
                        free(p);
@@ -2854,17 +2854,7 @@ static int reserved_word(const o_string *word, struct p_context *ctx)
                        continue;
                debug_printf("found reserved word %s, res %d\n", r->literal, r->res);
                if (r->flag == 0) { /* '!' */
-#if ENABLE_HUSH_LOOPS
-                       if (ctx->ctx_res_w == RES_IN) {
-                               /* 'for a in ! a b c; ...' - ! isn't a keyword here */
-                               break;
-                       }
-#endif
-                       if (ctx->ctx_inverted /* bash doesn't accept '! ! true' */
-#if ENABLE_HUSH_LOOPS
-                        || ctx->ctx_res_w == RES_FOR /* example: 'for ! a' */
-#endif
-                       ) {
+                       if (ctx->ctx_inverted) { /* bash doesn't accept '! ! true' */
                                syntax(NULL);
                                IF_HAS_KEYWORDS(ctx->ctx_res_w = RES_SNTX;)
                        }
@@ -2885,7 +2875,7 @@ static int reserved_word(const o_string *word, struct p_context *ctx)
                        *new = *ctx;   /* physical copy */
                        initialize_context(ctx);
                        ctx->stack = new;
-               } else if (ctx->ctx_res_w == RES_NONE || !(ctx->old_flag & (1 << r->res))) {
+               } else if (/*ctx->ctx_res_w == RES_NONE ||*/ !(ctx->old_flag & (1 << r->res))) {
                        syntax(NULL);
                        ctx->ctx_res_w = RES_SNTX;
                        return 1;
@@ -2940,7 +2930,10 @@ static int done_word(o_string *word, struct p_context *ctx)
                        return 1;
                }
 #if HAS_KEYWORDS
-               if (!child->argv) { /* if it's the first word... */
+               if (!child->argv /* if it's the first word... */
+                && ctx->ctx_res_w != RES_FOR /* ...not after FOR or IN */
+                && ctx->ctx_res_w != RES_IN
+               ) {
                        debug_printf_parse(": checking '%s' for reserved-ness\n", word->data);
                        if (reserved_word(word, ctx)) {
                                o_reset(word);
@@ -2953,9 +2946,9 @@ static int done_word(o_string *word, struct p_context *ctx)
                if (word->nonnull /* word had "xx" or 'xx' at least as part of it. */
                 /* optimization: and if it's ("" or '') or ($v... or `cmd`...): */
                 && (word->data[0] == '\0' || word->data[0] == SPECIAL_VAR_SYMBOL)
-                /* (otherwise it's "abc".... and is already safe) */
+                /* (otherwise it's known to be not empty and is already safe) */
                ) {
-                       /* but exclude "$@" - it expands to no word despite "" */
+                       /* exclude "$@" - it can expand to no word despite "" */
                        char *p = word->data;
                        while (p[0] == SPECIAL_VAR_SYMBOL
                            && (p[1] & 0x7f) == '@'
@@ -2964,7 +2957,9 @@ static int done_word(o_string *word, struct p_context *ctx)
                                p += 3;
                        }
                        if (p == word->data || p[0] != '\0') {
-                               /* Insert "empty variable" reference, this makes
+                               /* saw no "$@", or not only "$@" but some
+                                * real text is there too */
+                               /* insert "empty variable" reference, this makes
                                 * e.g. "", $empty"" etc to not disappear */
                                o_addchr(word, SPECIAL_VAR_SYMBOL);
                                o_addchr(word, SPECIAL_VAR_SYMBOL);
@@ -2979,8 +2974,13 @@ static int done_word(o_string *word, struct p_context *ctx)
 
 #if ENABLE_HUSH_LOOPS
        /* Force FOR to have just one word (variable name) */
-       if (ctx->ctx_res_w == RES_FOR)
+       /* NB: basically, this makes hush see "for v in ..." syntax as if
+        * as it is "for v; in ...". FOR and IN become two pipe structs
+        * in parse tree. */
+       if (ctx->ctx_res_w == RES_FOR) {
+//TODO: check that child->argv[0] is a valid variable name!
                done_pipe(ctx, PIPE_SEQ);
+       }
 #endif
        debug_printf_parse("done_word return 0\n");
        return 0;
@@ -3030,19 +3030,28 @@ static void done_pipe(struct p_context *ctx, pipe_style type)
        debug_printf_parse("done_pipe entered, followup %d\n", type);
        not_null = done_command(ctx);  /* implicit closure of previous command */
        ctx->pipe->followup = type;
-       IF_HAS_KEYWORDS(ctx->pipe->res_word = ctx->ctx_res_w;)
        IF_HAS_KEYWORDS(ctx->pipe->pi_inverted = ctx->ctx_inverted;)
        IF_HAS_KEYWORDS(ctx->ctx_inverted = 0;)
+       IF_HAS_KEYWORDS(ctx->pipe->res_word = ctx->ctx_res_w;)
        /* Without this check, even just <enter> on command line generates
         * tree of three NOPs (!). Which is harmless but annoying.
         * IOW: it is safe to do it unconditionally.
-        * RES_IN case is for "for a in; do ..." (empty IN set)
-        * to work. */
-       if (not_null USE_HUSH_LOOPS(|| ctx->pipe->res_word == RES_IN)) {
+        * RES_NONE case is for "for a in; do ..." (empty IN set)
+        * to work, possibly other cases too. */
+       if (not_null IF_HAS_KEYWORDS(|| ctx->ctx_res_w != RES_NONE)) {
                struct pipe *new_p = new_pipe();
                ctx->pipe->next = new_p;
                ctx->pipe = new_p;
                ctx->child = NULL; /* needed! */
+               /* RES_IF, RES_WHILE etc are "sticky" -
+                * they remain set for commands inside if/while.
+                * This is used to control execution.
+                * RES_FOR and RES_IN are NOT sticky (needed to support
+                * cases where variable or value happens to match a keyword):
+                */
+               if (ctx->ctx_res_w == RES_FOR
+                || ctx->ctx_res_w == RES_IN)
+                       ctx->ctx_res_w = RES_NONE;
                /* Create the memory for child, roughly:
                 * ctx->pipe->progs = new struct child_prog;
                 * ctx->pipe->progs[0].family = ctx->pipe;
@@ -3444,7 +3453,7 @@ static int handle_dollar(o_string *dest, struct in_str *input)
        return 0;
 }
 
-/* Scan input, call done_word() whenever full IFS delemited word was seen.
+/* 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,
@@ -3517,7 +3526,9 @@ static int parse_stream(o_string *dest, struct p_context *ctx,
 //err chk?
                                        done_pipe(ctx, PIPE_SEQ);
                                }
-                               if (!HAS_KEYWORDS IF_HAS_KEYWORDS(|| ctx->ctx_res_w == RES_NONE)) {
+                               if (!HAS_KEYWORDS
+                                IF_HAS_KEYWORDS(|| (ctx->ctx_res_w == RES_NONE && ctx->old_flag == 0))
+                               ) {
                                        debug_printf_parse("parse_stream return 0: end_trigger char found\n");
                                        return 0;
                                }
diff --git a/shell/hush_test/hush-misc/for_with_keywords.right b/shell/hush_test/hush-misc/for_with_keywords.right
new file mode 100644 (file)
index 0000000..eb04e9a
--- /dev/null
@@ -0,0 +1,4 @@
+do
+done
+then
+OK: 0
diff --git a/shell/hush_test/hush-misc/for_with_keywords.tests b/shell/hush_test/hush-misc/for_with_keywords.tests
new file mode 100755 (executable)
index 0000000..a8b8e42
--- /dev/null
@@ -0,0 +1,2 @@
+for if in do done then; do echo $if; done
+echo OK: $?