hush: add case statement support. It is incomplete and disabled for now.
costs ~300 bytes when enabled.
diff --git a/shell/hush.c b/shell/hush.c
index aab53c3..47e53f8 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -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 {
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,
+ RES_FOR ,
+ RES_WHILE ,
+ RES_UNTIL ,
+ RES_DO ,
+ RES_DONE ,
+ RES_IN ,
#endif
- RES_XXXX = 12,
- RES_SNTX = 13
+#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 @@
#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 @@
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) */
@@ -1955,6 +1968,12 @@
[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" ,
};
@@ -2003,6 +2022,9 @@
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 */
int flag_restore = 0;
@@ -2019,17 +2041,20 @@
#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 @@
/* 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 @@
}
}
#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 @@
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 @@
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:
@@ -2821,6 +2866,10 @@
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 ),
};
/* Mostly a list of accepted follow-up reserved words.
@@ -2843,16 +2892,30 @@
{ "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 @@
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 @@
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 */
@@ -2984,6 +3048,12 @@
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;
}
@@ -3056,6 +3126,10 @@
|| 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;
* ctx->pipe->progs[0].family = ctx->pipe;
@@ -3458,10 +3532,9 @@
}
/* 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 @@
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 @@
}
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 @@
}
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 )");