diff options
author | Denis Vlasenko <vda.linux@googlemail.com> | 2008-07-14 04:32:29 +0000 |
---|---|---|
committer | Denis Vlasenko <vda.linux@googlemail.com> | 2008-07-14 04:32:29 +0000 |
commit | 17f02e79f407ff2311255981c191a1bf8b3a7ae0 (patch) | |
tree | 417a580a6aaf5d9844b2aaf1059fe89e6cb14751 | |
parent | 3177ba08522fd49292f37deecc59db0f75b046fd (diff) | |
download | busybox-w32-17f02e79f407ff2311255981c191a1bf8b3a7ae0.tar.gz busybox-w32-17f02e79f407ff2311255981c191a1bf8b3a7ae0.tar.bz2 busybox-w32-17f02e79f407ff2311255981c191a1bf8b3a7ae0.zip |
hush: add case statement support. It is incomplete and disabled for now.
costs ~300 bytes when enabled.
-rw-r--r-- | shell/hush.c | 192 |
1 files changed, 147 insertions, 45 deletions
diff --git a/shell/hush.c b/shell/hush.c index aab53c336..47e53f8c1 100644 --- a/shell/hush.c +++ b/shell/hush.c | |||
@@ -73,6 +73,9 @@ | |||
73 | 73 | ||
74 | #include "busybox.h" /* for APPLET_IS_NOFORK/NOEXEC */ | 74 | #include "busybox.h" /* for APPLET_IS_NOFORK/NOEXEC */ |
75 | 75 | ||
76 | // TEMP | ||
77 | #define ENABLE_HUSH_CASE 0 | ||
78 | |||
76 | 79 | ||
77 | #if !BB_MMU && ENABLE_HUSH_TICK | 80 | #if !BB_MMU && ENABLE_HUSH_TICK |
78 | //#undef ENABLE_HUSH_TICK | 81 | //#undef ENABLE_HUSH_TICK |
@@ -262,22 +265,29 @@ typedef enum { | |||
262 | typedef enum { | 265 | typedef enum { |
263 | RES_NONE = 0, | 266 | RES_NONE = 0, |
264 | #if ENABLE_HUSH_IF | 267 | #if ENABLE_HUSH_IF |
265 | RES_IF = 1, | 268 | RES_IF , |
266 | RES_THEN = 2, | 269 | RES_THEN , |
267 | RES_ELIF = 3, | 270 | RES_ELIF , |
268 | RES_ELSE = 4, | 271 | RES_ELSE , |
269 | RES_FI = 5, | 272 | RES_FI , |
270 | #endif | 273 | #endif |
271 | #if ENABLE_HUSH_LOOPS | 274 | #if ENABLE_HUSH_LOOPS |
272 | RES_FOR = 6, | 275 | RES_FOR , |
273 | RES_WHILE = 7, | 276 | RES_WHILE , |
274 | RES_UNTIL = 8, | 277 | RES_UNTIL , |
275 | RES_DO = 9, | 278 | RES_DO , |
276 | RES_DONE = 10, | 279 | RES_DONE , |
277 | RES_IN = 11, | 280 | RES_IN , |
278 | #endif | 281 | #endif |
279 | RES_XXXX = 12, | 282 | #if ENABLE_HUSH_CASE |
280 | RES_SNTX = 13 | 283 | RES_CASE , |
284 | /* two pseudo-keywords support contrived "case" syntax: */ | ||
285 | RES_MATCH , /* "word)" */ | ||
286 | RES_CASEI , /* "this command is inside CASE" */ | ||
287 | RES_ESAC , | ||
288 | #endif | ||
289 | RES_XXXX , | ||
290 | RES_SNTX | ||
281 | } reserved_style; | 291 | } reserved_style; |
282 | 292 | ||
283 | /* This holds pointers to the various results of parsing */ | 293 | /* This holds pointers to the various results of parsing */ |
@@ -289,6 +299,9 @@ struct p_context { | |||
289 | #if HAS_KEYWORDS | 299 | #if HAS_KEYWORDS |
290 | smallint ctx_res_w; | 300 | smallint ctx_res_w; |
291 | smallint ctx_inverted; /* "! cmd | cmd" */ | 301 | smallint ctx_inverted; /* "! cmd | cmd" */ |
302 | #if ENABLE_HUSH_CASE | ||
303 | smallint ctx_dsemicolon; /* ";;" seen */ | ||
304 | #endif | ||
292 | int old_flag; /* bitmask of FLAG_xxx, for figuring out valid reserved words */ | 305 | int old_flag; /* bitmask of FLAG_xxx, for figuring out valid reserved words */ |
293 | struct p_context *stack; | 306 | struct p_context *stack; |
294 | #endif | 307 | #endif |
@@ -350,7 +363,7 @@ struct variable { | |||
350 | 363 | ||
351 | typedef struct { | 364 | typedef struct { |
352 | char *data; | 365 | char *data; |
353 | int length; | 366 | int length; /* position where data is appended */ |
354 | int maxlen; | 367 | int maxlen; |
355 | /* Misnomer! it's not "quoting", it's "protection against globbing"! | 368 | /* Misnomer! it's not "quoting", it's "protection against globbing"! |
356 | * (by prepending \ to *, ?, [ and to \ too) */ | 369 | * (by prepending \ to *, ?, [ and to \ too) */ |
@@ -1955,6 +1968,12 @@ static void debug_print_tree(struct pipe *pi, int lvl) | |||
1955 | [RES_DONE ] = "DONE" , | 1968 | [RES_DONE ] = "DONE" , |
1956 | [RES_IN ] = "IN" , | 1969 | [RES_IN ] = "IN" , |
1957 | #endif | 1970 | #endif |
1971 | #if ENABLE_HUSH_CASE | ||
1972 | [RES_CASE ] = "CASE" , | ||
1973 | [RES_MATCH] = "MATCH", | ||
1974 | [RES_CASEI] = "CASEI", | ||
1975 | [RES_ESAC ] = "ESAC" , | ||
1976 | #endif | ||
1958 | [RES_XXXX ] = "XXXX" , | 1977 | [RES_XXXX ] = "XXXX" , |
1959 | [RES_SNTX ] = "SNTX" , | 1978 | [RES_SNTX ] = "SNTX" , |
1960 | }; | 1979 | }; |
@@ -2003,6 +2022,9 @@ static int run_list(struct pipe *pi) | |||
2003 | char **for_list = NULL; | 2022 | char **for_list = NULL; |
2004 | int flag_rep = 0; | 2023 | int flag_rep = 0; |
2005 | #endif | 2024 | #endif |
2025 | #if ENABLE_HUSH_CASE | ||
2026 | char *case_word = NULL; | ||
2027 | #endif | ||
2006 | int flag_skip = 1; | 2028 | int flag_skip = 1; |
2007 | int rcode = 0; /* probably for gcc only */ | 2029 | int rcode = 0; /* probably for gcc only */ |
2008 | int flag_restore = 0; | 2030 | int flag_restore = 0; |
@@ -2019,17 +2041,20 @@ static int run_list(struct pipe *pi) | |||
2019 | #if ENABLE_HUSH_LOOPS | 2041 | #if ENABLE_HUSH_LOOPS |
2020 | /* check syntax for "for" */ | 2042 | /* check syntax for "for" */ |
2021 | for (rpipe = pi; rpipe; rpipe = rpipe->next) { | 2043 | for (rpipe = pi; rpipe; rpipe = rpipe->next) { |
2022 | if ((rpipe->res_word == RES_IN || rpipe->res_word == RES_FOR) | 2044 | if (rpipe->res_word != RES_FOR && rpipe->res_word != RES_IN) |
2023 | && (rpipe->next == NULL) | 2045 | continue; |
2024 | ) { | 2046 | /* current word is FOR or IN (BOLD in comments below) */ |
2025 | syntax("malformed for"); /* no IN or no commands after IN */ | 2047 | if (rpipe->next == NULL) { |
2048 | syntax("malformed for"); | ||
2026 | debug_printf_exec("run_list lvl %d return 1\n", run_list_level); | 2049 | debug_printf_exec("run_list lvl %d return 1\n", run_list_level); |
2027 | return 1; | 2050 | return 1; |
2028 | } | 2051 | } |
2029 | if (/* Extra statement after IN: "for a in a b; echo Hi; do ...; done" ? */ | 2052 | /* "FOR v; do ..." and "for v IN a b; do..." are ok */ |
2030 | (rpipe->res_word == RES_IN && rpipe->next->res_word == RES_IN && rpipe->next->progs[0].argv != NULL) | 2053 | if (rpipe->next->res_word == RES_DO) |
2031 | /* FOR not followed by IN or DO ("for var; do..." case)? */ | 2054 | continue; |
2032 | || (rpipe->res_word == RES_FOR && (rpipe->next->res_word != RES_IN && rpipe->next->res_word != RES_DO)) | 2055 | /* next word is not "do". It must be "in" then ("FOR v in ...") */ |
2056 | if (rpipe->res_word == RES_IN /* "for v IN a b; not_do..."? */ | ||
2057 | || rpipe->next->res_word != RES_IN /* FOR v not_do_and_not_in..."? */ | ||
2033 | ) { | 2058 | ) { |
2034 | syntax("malformed for"); | 2059 | syntax("malformed for"); |
2035 | debug_printf_exec("run_list lvl %d return 1\n", run_list_level); | 2060 | debug_printf_exec("run_list lvl %d return 1\n", run_list_level); |
@@ -2136,8 +2161,8 @@ static int run_list(struct pipe *pi) | |||
2136 | /* create list of variable values */ | 2161 | /* create list of variable values */ |
2137 | debug_print_strings("for_list made from", vals); | 2162 | debug_print_strings("for_list made from", vals); |
2138 | for_list = expand_strvec_to_strvec(vals); | 2163 | for_list = expand_strvec_to_strvec(vals); |
2139 | debug_print_strings("for_list", for_list); | ||
2140 | for_lcur = for_list; | 2164 | for_lcur = for_list; |
2165 | debug_print_strings("for_list", for_list); | ||
2141 | for_varname = pi->progs->argv[0]; | 2166 | for_varname = pi->progs->argv[0]; |
2142 | pi->progs->argv[0] = NULL; | 2167 | pi->progs->argv[0] = NULL; |
2143 | flag_rep = 1; | 2168 | flag_rep = 1; |
@@ -2169,6 +2194,26 @@ static int run_list(struct pipe *pi) | |||
2169 | } | 2194 | } |
2170 | } | 2195 | } |
2171 | #endif | 2196 | #endif |
2197 | #if ENABLE_HUSH_CASE | ||
2198 | if (rword == RES_CASE) { | ||
2199 | case_word = pi->progs->argv[0]; | ||
2200 | continue; | ||
2201 | } | ||
2202 | if (rword == RES_MATCH) { | ||
2203 | if (case_word) { | ||
2204 | next_if_code = strcmp(case_word, pi->progs->argv[0]); | ||
2205 | if (next_if_code == 0) | ||
2206 | case_word = NULL; | ||
2207 | continue; | ||
2208 | } | ||
2209 | break; | ||
2210 | } | ||
2211 | if (rword == RES_CASEI) { | ||
2212 | if (next_if_code != 0) | ||
2213 | continue; | ||
2214 | } | ||
2215 | #endif | ||
2216 | |||
2172 | if (pi->num_progs == 0) | 2217 | if (pi->num_progs == 0) |
2173 | continue; | 2218 | continue; |
2174 | debug_printf_exec(": run_pipe with %d members\n", pi->num_progs); | 2219 | debug_printf_exec(": run_pipe with %d members\n", pi->num_progs); |
@@ -2193,8 +2238,7 @@ static int run_list(struct pipe *pi) | |||
2193 | rcode = checkjobs_and_fg_shell(pi); | 2238 | rcode = checkjobs_and_fg_shell(pi); |
2194 | } else | 2239 | } else |
2195 | #endif | 2240 | #endif |
2196 | { | 2241 | { /* this one just waits for completion */ |
2197 | /* this one just waits for completion */ | ||
2198 | rcode = checkjobs(pi); | 2242 | rcode = checkjobs(pi); |
2199 | } | 2243 | } |
2200 | debug_printf_exec(": checkjobs returned %d\n", rcode); | 2244 | debug_printf_exec(": checkjobs returned %d\n", rcode); |
@@ -2217,12 +2261,13 @@ static int run_list(struct pipe *pi) | |||
2217 | skip_more_for_this_rword = rword; | 2261 | skip_more_for_this_rword = rword; |
2218 | } | 2262 | } |
2219 | checkjobs(NULL); | 2263 | checkjobs(NULL); |
2220 | } | 2264 | } /* for (pi) */ |
2221 | 2265 | ||
2222 | #if ENABLE_HUSH_JOB | 2266 | #if ENABLE_HUSH_JOB |
2223 | if (ctrl_z_flag) { | 2267 | if (ctrl_z_flag) { |
2224 | /* ctrl-Z forked somewhere in the past, we are the child, | 2268 | /* ctrl-Z forked somewhere in the past, we are the child, |
2225 | * and now we completed running the list. Exit. */ | 2269 | * and now we completed running the list. Exit. */ |
2270 | //TODO: _exit? | ||
2226 | exit(rcode); | 2271 | exit(rcode); |
2227 | } | 2272 | } |
2228 | ret: | 2273 | ret: |
@@ -2821,6 +2866,10 @@ static int reserved_word(const o_string *word, struct p_context *ctx) | |||
2821 | FLAG_DONE = (1 << RES_DONE ), | 2866 | FLAG_DONE = (1 << RES_DONE ), |
2822 | FLAG_IN = (1 << RES_IN ), | 2867 | FLAG_IN = (1 << RES_IN ), |
2823 | #endif | 2868 | #endif |
2869 | #if ENABLE_HUSH_CASE | ||
2870 | FLAG_MATCH = (1 << RES_MATCH), | ||
2871 | FLAG_ESAC = (1 << RES_ESAC ), | ||
2872 | #endif | ||
2824 | FLAG_START = (1 << RES_XXXX ), | 2873 | FLAG_START = (1 << RES_XXXX ), |
2825 | }; | 2874 | }; |
2826 | /* Mostly a list of accepted follow-up reserved words. | 2875 | /* Mostly a list of accepted follow-up reserved words. |
@@ -2843,16 +2892,30 @@ static int reserved_word(const o_string *word, struct p_context *ctx) | |||
2843 | { "until", RES_UNTIL, FLAG_DO | FLAG_START }, | 2892 | { "until", RES_UNTIL, FLAG_DO | FLAG_START }, |
2844 | { "in", RES_IN, FLAG_DO }, | 2893 | { "in", RES_IN, FLAG_DO }, |
2845 | { "do", RES_DO, FLAG_DONE }, | 2894 | { "do", RES_DO, FLAG_DONE }, |
2846 | { "done", RES_DONE, FLAG_END } | 2895 | { "done", RES_DONE, FLAG_END }, |
2896 | #endif | ||
2897 | #if ENABLE_HUSH_CASE | ||
2898 | { "case", RES_CASE, FLAG_MATCH | FLAG_START }, | ||
2899 | { "esac", RES_ESAC, FLAG_END }, | ||
2847 | #endif | 2900 | #endif |
2848 | }; | 2901 | }; |
2849 | 2902 | #if ENABLE_HUSH_CASE | |
2903 | static const struct reserved_combo reserved_match = { | ||
2904 | "" /* "match" */, RES_MATCH, FLAG_MATCH | FLAG_ESAC | ||
2905 | }; | ||
2906 | #endif | ||
2850 | const struct reserved_combo *r; | 2907 | const struct reserved_combo *r; |
2851 | 2908 | ||
2852 | for (r = reserved_list; r < reserved_list + ARRAY_SIZE(reserved_list); r++) { | 2909 | for (r = reserved_list; r < reserved_list + ARRAY_SIZE(reserved_list); r++) { |
2853 | if (strcmp(word->data, r->literal) != 0) | 2910 | if (strcmp(word->data, r->literal) != 0) |
2854 | continue; | 2911 | continue; |
2855 | debug_printf("found reserved word %s, res %d\n", r->literal, r->res); | 2912 | debug_printf("found reserved word %s, res %d\n", r->literal, r->res); |
2913 | #if ENABLE_HUSH_CASE | ||
2914 | if (r->res == RES_IN && ctx->ctx_res_w == RES_CASE) | ||
2915 | /* "case word IN ..." - IN part starts first match part */ | ||
2916 | r = &reserved_match; | ||
2917 | else | ||
2918 | #endif | ||
2856 | if (r->flag == 0) { /* '!' */ | 2919 | if (r->flag == 0) { /* '!' */ |
2857 | if (ctx->ctx_inverted) { /* bash doesn't accept '! ! true' */ | 2920 | if (ctx->ctx_inverted) { /* bash doesn't accept '! ! true' */ |
2858 | syntax(NULL); | 2921 | syntax(NULL); |
@@ -2864,13 +2927,6 @@ static int reserved_word(const o_string *word, struct p_context *ctx) | |||
2864 | if (r->flag & FLAG_START) { | 2927 | if (r->flag & FLAG_START) { |
2865 | struct p_context *new; | 2928 | struct p_context *new; |
2866 | debug_printf("push stack\n"); | 2929 | debug_printf("push stack\n"); |
2867 | #if ENABLE_HUSH_LOOPS | ||
2868 | if (ctx->ctx_res_w == RES_IN || ctx->ctx_res_w == RES_FOR) { | ||
2869 | syntax("malformed for"); /* example: 'for if' */ | ||
2870 | ctx->ctx_res_w = RES_SNTX; | ||
2871 | return 1; | ||
2872 | } | ||
2873 | #endif | ||
2874 | new = xmalloc(sizeof(*new)); | 2930 | new = xmalloc(sizeof(*new)); |
2875 | *new = *ctx; /* physical copy */ | 2931 | *new = *ctx; /* physical copy */ |
2876 | initialize_context(ctx); | 2932 | initialize_context(ctx); |
@@ -2924,12 +2980,20 @@ static int done_word(o_string *word, struct p_context *ctx) | |||
2924 | word->o_assignment = NOT_ASSIGNMENT; | 2980 | word->o_assignment = NOT_ASSIGNMENT; |
2925 | debug_printf("word stored in rd_filename: '%s'\n", word->data); | 2981 | debug_printf("word stored in rd_filename: '%s'\n", word->data); |
2926 | } else { | 2982 | } else { |
2927 | if (child->group) { /* TODO: example how to trigger? */ | 2983 | // if (child->group) { /* TODO: example how to trigger? */ |
2928 | syntax(NULL); | 2984 | // syntax(NULL); |
2929 | debug_printf_parse("done_word return 1: syntax error, groups and arglists don't mix\n"); | 2985 | // debug_printf_parse("done_word return 1: syntax error, groups and arglists don't mix\n"); |
2930 | return 1; | 2986 | // return 1; |
2931 | } | 2987 | // } |
2932 | #if HAS_KEYWORDS | 2988 | #if HAS_KEYWORDS |
2989 | #if ENABLE_HUSH_CASE | ||
2990 | if (ctx->ctx_dsemicolon) { | ||
2991 | /* already done when ctx_dsemicolon was set to 1 */ | ||
2992 | /* ctx->ctx_res_w = RES_MATCH; */ | ||
2993 | ctx->ctx_dsemicolon = 0; | ||
2994 | } else | ||
2995 | #endif | ||
2996 | |||
2933 | if (!child->argv /* if it's the first word... */ | 2997 | if (!child->argv /* if it's the first word... */ |
2934 | #if ENABLE_HUSH_LOOPS | 2998 | #if ENABLE_HUSH_LOOPS |
2935 | && ctx->ctx_res_w != RES_FOR /* ...not after FOR or IN */ | 2999 | && ctx->ctx_res_w != RES_FOR /* ...not after FOR or IN */ |
@@ -2984,6 +3048,12 @@ static int done_word(o_string *word, struct p_context *ctx) | |||
2984 | done_pipe(ctx, PIPE_SEQ); | 3048 | done_pipe(ctx, PIPE_SEQ); |
2985 | } | 3049 | } |
2986 | #endif | 3050 | #endif |
3051 | #if ENABLE_HUSH_CASE | ||
3052 | /* Force CASE to have just one word */ | ||
3053 | if (ctx->ctx_res_w == RES_CASE) { | ||
3054 | done_pipe(ctx, PIPE_SEQ); | ||
3055 | } | ||
3056 | #endif | ||
2987 | debug_printf_parse("done_word return 0\n"); | 3057 | debug_printf_parse("done_word return 0\n"); |
2988 | return 0; | 3058 | return 0; |
2989 | } | 3059 | } |
@@ -3056,6 +3126,10 @@ static void done_pipe(struct p_context *ctx, pipe_style type) | |||
3056 | || ctx->ctx_res_w == RES_IN) | 3126 | || ctx->ctx_res_w == RES_IN) |
3057 | ctx->ctx_res_w = RES_NONE; | 3127 | ctx->ctx_res_w = RES_NONE; |
3058 | #endif | 3128 | #endif |
3129 | #if ENABLE_HUSH_CASE | ||
3130 | if (ctx->ctx_res_w == RES_MATCH) | ||
3131 | ctx->ctx_res_w = RES_CASEI; | ||
3132 | #endif | ||
3059 | /* Create the memory for child, roughly: | 3133 | /* Create the memory for child, roughly: |
3060 | * ctx->pipe->progs = new struct child_prog; | 3134 | * ctx->pipe->progs = new struct child_prog; |
3061 | * ctx->pipe->progs[0].family = ctx->pipe; | 3135 | * ctx->pipe->progs[0].family = ctx->pipe; |
@@ -3458,10 +3532,9 @@ static int handle_dollar(o_string *dest, struct in_str *input) | |||
3458 | } | 3532 | } |
3459 | 3533 | ||
3460 | /* Scan input, call done_word() whenever full IFS delimited word was seen. | 3534 | /* Scan input, call done_word() whenever full IFS delimited word was seen. |
3461 | * call done_pipe if '\n' was seen (and end_trigger != NULL) | 3535 | * Call done_pipe if '\n' was seen (and end_trigger != NULL). |
3462 | * Return if (non-quoted) char in end_trigger was seen; or on parse error. */ | 3536 | * Return code is 0 if end_trigger char is met, |
3463 | /* Return code is 0 if end_trigger char is met, | 3537 | * -1 on EOF (but if end_trigger == NULL then return 0), |
3464 | * -1 on EOF (but if end_trigger == NULL then return 0) | ||
3465 | * 1 for syntax error */ | 3538 | * 1 for syntax error */ |
3466 | static int parse_stream(o_string *dest, struct p_context *ctx, | 3539 | static int parse_stream(o_string *dest, struct p_context *ctx, |
3467 | struct in_str *input, const char *end_trigger) | 3540 | struct in_str *input, const char *end_trigger) |
@@ -3664,8 +3737,26 @@ static int parse_stream(o_string *dest, struct p_context *ctx, | |||
3664 | setup_redirect(ctx, redir_fd, redir_style, input); | 3737 | setup_redirect(ctx, redir_fd, redir_style, input); |
3665 | break; | 3738 | break; |
3666 | case ';': | 3739 | case ';': |
3740 | #if ENABLE_HUSH_CASE | ||
3741 | case_semi: | ||
3742 | #endif | ||
3667 | done_word(dest, ctx); | 3743 | done_word(dest, ctx); |
3668 | done_pipe(ctx, PIPE_SEQ); | 3744 | done_pipe(ctx, PIPE_SEQ); |
3745 | #if ENABLE_HUSH_CASE | ||
3746 | /* Eat multiple semicolons, detect | ||
3747 | * whether it means something special */ | ||
3748 | while (1) { | ||
3749 | ch = i_peek(input); | ||
3750 | if (ch != ';') | ||
3751 | break; | ||
3752 | i_getch(input); | ||
3753 | if (ctx->ctx_res_w == RES_CASEI) { | ||
3754 | ctx->ctx_dsemicolon = 1; | ||
3755 | ctx->ctx_res_w = RES_MATCH; | ||
3756 | break; | ||
3757 | } | ||
3758 | } | ||
3759 | #endif | ||
3669 | break; | 3760 | break; |
3670 | case '&': | 3761 | case '&': |
3671 | done_word(dest, ctx); | 3762 | done_word(dest, ctx); |
@@ -3689,6 +3780,13 @@ static int parse_stream(o_string *dest, struct p_context *ctx, | |||
3689 | } | 3780 | } |
3690 | break; | 3781 | break; |
3691 | case '(': | 3782 | case '(': |
3783 | #if ENABLE_HUSH_CASE | ||
3784 | if (dest->length == 0 // && argv[0] == NULL | ||
3785 | && ctx->ctx_res_w == RES_MATCH | ||
3786 | ) { | ||
3787 | continue; | ||
3788 | } | ||
3789 | #endif | ||
3692 | case '{': | 3790 | case '{': |
3693 | if (parse_group(dest, ctx, input, ch) != 0) { | 3791 | if (parse_group(dest, ctx, input, ch) != 0) { |
3694 | debug_printf_parse("parse_stream return 1: parse_group returned non-0\n"); | 3792 | 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, | |||
3696 | } | 3794 | } |
3697 | break; | 3795 | break; |
3698 | case ')': | 3796 | case ')': |
3797 | #if ENABLE_HUSH_CASE | ||
3798 | if (ctx->ctx_res_w == RES_MATCH) | ||
3799 | goto case_semi; | ||
3800 | #endif | ||
3699 | case '}': | 3801 | case '}': |
3700 | /* proper use of this character is caught by end_trigger */ | 3802 | /* proper use of this character is caught by end_trigger */ |
3701 | syntax("unexpected } or )"); | 3803 | syntax("unexpected } or )"); |