hush: fix "if { echo foo; } then { echo bar; } fi" parsing

function                                             old     new   delta
done_word                                            728     793     +65
parse_stream                                        2084    2098     +14
This commit is contained in:
Denis Vlasenko 2009-04-16 10:59:40 +00:00
parent 74a931ac9e
commit bb929517a8
4 changed files with 48 additions and 18 deletions

View File

@ -4154,6 +4154,8 @@ static const struct reserved_combo* match_reserved_word(o_string *word)
} }
return NULL; return NULL;
} }
/* Return 0: not a keyword, 1: keyword
*/
static int reserved_word(o_string *word, struct parse_context *ctx) static int reserved_word(o_string *word, struct parse_context *ctx)
{ {
#if ENABLE_HUSH_CASE #if ENABLE_HUSH_CASE
@ -4163,6 +4165,8 @@ static int reserved_word(o_string *word, struct parse_context *ctx)
#endif #endif
const struct reserved_combo *r; const struct reserved_combo *r;
if (word->o_quoted)
return 0;
r = match_reserved_word(word); r = match_reserved_word(word);
if (!r) if (!r)
return 0; return 0;
@ -4177,13 +4181,14 @@ static int reserved_word(o_string *word, struct parse_context *ctx)
if (r->flag == 0) { /* '!' */ if (r->flag == 0) { /* '!' */
if (ctx->ctx_inverted) { /* bash doesn't accept '! ! true' */ if (ctx->ctx_inverted) { /* bash doesn't accept '! ! true' */
syntax_error("! ! command"); syntax_error("! ! command");
IF_HAS_KEYWORDS(ctx->ctx_res_w = RES_SNTX;) ctx->ctx_res_w = RES_SNTX;
} }
ctx->ctx_inverted = 1; ctx->ctx_inverted = 1;
return 1; return 1;
} }
if (r->flag & FLAG_START) { if (r->flag & FLAG_START) {
struct parse_context *old; struct parse_context *old;
old = xmalloc(sizeof(*old)); old = xmalloc(sizeof(*old));
debug_printf_parse("push stack %p\n", old); debug_printf_parse("push stack %p\n", old);
*old = *ctx; /* physical copy */ *old = *ctx; /* physical copy */
@ -4193,11 +4198,21 @@ static int reserved_word(o_string *word, struct parse_context *ctx)
syntax_error_at(word->data); syntax_error_at(word->data);
ctx->ctx_res_w = RES_SNTX; ctx->ctx_res_w = RES_SNTX;
return 1; return 1;
} else {
/* "{...} fi" is ok. "{...} if" is not
* Example:
* if { echo foo; } then { echo bar; } fi */
if (ctx->command->group)
done_pipe(ctx, PIPE_SEQ);
} }
ctx->ctx_res_w = r->res; ctx->ctx_res_w = r->res;
ctx->old_flag = r->flag; ctx->old_flag = r->flag;
word->o_assignment = r->assignment_flag;
if (ctx->old_flag & FLAG_END) { if (ctx->old_flag & FLAG_END) {
struct parse_context *old; struct parse_context *old;
done_pipe(ctx, PIPE_SEQ); done_pipe(ctx, PIPE_SEQ);
debug_printf_parse("pop stack %p\n", ctx->stack); debug_printf_parse("pop stack %p\n", ctx->stack);
old = ctx->stack; old = ctx->stack;
@ -4213,7 +4228,6 @@ static int reserved_word(o_string *word, struct parse_context *ctx)
*ctx = *old; /* physical copy */ *ctx = *old; /* physical copy */
free(old); free(old);
} }
word->o_assignment = r->assignment_flag;
return 1; return 1;
} }
#endif #endif
@ -4273,19 +4287,6 @@ static int done_word(o_string *word, struct parse_context *ctx)
word->o_assignment = MAYBE_ASSIGNMENT; word->o_assignment = MAYBE_ASSIGNMENT;
} }
if (command->group) {
/* "{ echo foo; } echo bar" - bad */
/* NB: bash allows e.g.:
* if true; then { echo foo; } fi
* while if false; then false; fi do break; done
* and disallows:
* while if false; then false; fi; do; break; done
* TODO? */
syntax_error_at(word->data);
debug_printf_parse("done_word return 1: syntax error, "
"groups and arglists don't mix\n");
return 1;
}
#if HAS_KEYWORDS #if HAS_KEYWORDS
# if ENABLE_HUSH_CASE # if ENABLE_HUSH_CASE
if (ctx->ctx_dsemicolon if (ctx->ctx_dsemicolon
@ -4311,6 +4312,13 @@ static int done_word(o_string *word, struct parse_context *ctx)
} }
} }
#endif #endif
if (command->group) {
/* "{ echo foo; } echo bar" - bad */
syntax_error_at(word->data);
debug_printf_parse("done_word return 1: syntax error, "
"groups and arglists don't mix\n");
return 1;
}
if (word->o_quoted /* word had "xx" or 'xx' at least as part of it. */ if (word->o_quoted /* word had "xx" or 'xx' at least as part of it. */
/* optimization: and if it's ("" or '') or ($v... or `cmd`...): */ /* optimization: and if it's ("" or '') or ($v... or `cmd`...): */
&& (word->data[0] == '\0' || word->data[0] == SPECIAL_VAR_SYMBOL) && (word->data[0] == '\0' || word->data[0] == SPECIAL_VAR_SYMBOL)
@ -4720,7 +4728,8 @@ static int parse_group(o_string *dest, struct parse_context *ctx,
#if ENABLE_HUSH_FUNCTIONS #if ENABLE_HUSH_FUNCTIONS
if (ch == '(' && !dest->o_quoted) { if (ch == '(' && !dest->o_quoted) {
if (dest->length) if (dest->length)
done_word(dest, ctx); if (done_word(dest, ctx))
return 1;
if (!command->argv) if (!command->argv)
goto skip; /* (... */ goto skip; /* (... */
if (command->argv[1]) { /* word word ... (... */ if (command->argv[1]) { /* word word ... (... */
@ -4778,10 +4787,10 @@ static int parse_group(o_string *dest, struct parse_context *ctx,
#endif #endif
/* empty ()/{} or parse error? */ /* empty ()/{} or parse error? */
if (!pipe_list || pipe_list == ERR_PTR) { if (!pipe_list || pipe_list == ERR_PTR) {
/* parse_stream already emitted error msg */
#if !BB_MMU #if !BB_MMU
free(as_string); free(as_string);
#endif #endif
syntax_error(NULL);
debug_printf_parse("parse_group return 1: " debug_printf_parse("parse_group return 1: "
"parse_stream returned %p\n", pipe_list); "parse_stream returned %p\n", pipe_list);
return 1; return 1;

View File

@ -0,0 +1,11 @@
Semicolons after } can be omitted 1:
foo
bar
Semicolons after } can be omitted 2:
foo
bar
Semicolons after fi can be omitted:
foo
bar
baz
Done:0

View File

@ -0,0 +1,10 @@
echo "Semicolons after } can be omitted 1:"
if { echo foo; } then { echo bar; } fi
echo "Semicolons after } can be omitted 2:"
while { echo foo; } do { echo bar; break; } done
echo "Semicolons after fi can be omitted:"
while if echo foo; then echo bar; fi do echo baz; break; done
echo Done:$?

View File

@ -1,5 +1,5 @@
if test $# = 0; then if test $# = 0; then
#BUG in builtin_exec! will glob param! # UNFIXED BUG in builtin_exec! will glob param!
#exec "$THIS_SH" "$0" 'param_glob.t*' #exec "$THIS_SH" "$0" 'param_glob.t*'
"$THIS_SH" "$0" 'param_glob.t*' "$THIS_SH" "$0" 'param_glob.t*'
exit exit