hush: implement break and continue

function                                             old     new   delta
bltins                                               252     276     +24
builtin_continue                                       -      12     +12
builtin_break                                          -      12     +12
static.version_str                                    18      17      -1
run_list                                            1984    1948     -36
------------------------------------------------------------------------------
(add/remove: 2/0 grow/shrink: 1/2 up/down: 48/-27)             Total: 11 bytes
This commit is contained in:
Denis Vlasenko 2008-07-28 23:04:34 +00:00
parent cf22c89f9a
commit bcb25537d0
2 changed files with 107 additions and 77 deletions

View File

@ -54,7 +54,7 @@
* to-do: * to-do:
* port selected bugfixes from post-0.49 busybox lash - done? * port selected bugfixes from post-0.49 busybox lash - done?
* change { and } from special chars to reserved words * change { and } from special chars to reserved words
* builtins: break, continue, eval, return, set, trap, ulimit * builtins: return, trap, ulimit
* test magic exec * test magic exec
* check setting of global_argc and global_argv * check setting of global_argc and global_argv
* follow IFS rules more precisely, including update semantics * follow IFS rules more precisely, including update semantics
@ -74,6 +74,7 @@
#include <fnmatch.h> #include <fnmatch.h>
#endif #endif
#define HUSH_VER_STR "0.9"
#if !BB_MMU && ENABLE_HUSH_TICK #if !BB_MMU && ENABLE_HUSH_TICK
//#undef ENABLE_HUSH_TICK //#undef ENABLE_HUSH_TICK
@ -230,7 +231,7 @@ void xxfree(void *ptr)
#define SPECIAL_VAR_SYMBOL 3 #define SPECIAL_VAR_SYMBOL 3
#define PARSEFLAG_EXIT_FROM_LOOP 1 #define PARSEFLAG_EXIT_FROM_LOOP 1
typedef enum { typedef enum redir_type {
REDIRECT_INPUT = 1, REDIRECT_INPUT = 1,
REDIRECT_OVERWRITE = 2, REDIRECT_OVERWRITE = 2,
REDIRECT_APPEND = 3, REDIRECT_APPEND = 3,
@ -253,14 +254,14 @@ static const struct {
{ O_RDWR, 1, "<>" } { O_RDWR, 1, "<>" }
}; };
typedef enum { typedef enum pipe_style {
PIPE_SEQ = 1, PIPE_SEQ = 1,
PIPE_AND = 2, PIPE_AND = 2,
PIPE_OR = 3, PIPE_OR = 3,
PIPE_BG = 4, PIPE_BG = 4,
} pipe_style; } pipe_style;
typedef enum { typedef enum reserved_style {
RES_NONE = 0, RES_NONE = 0,
#if ENABLE_HUSH_IF #if ENABLE_HUSH_IF
RES_IF , RES_IF ,
@ -358,7 +359,7 @@ struct variable {
smallint flg_read_only; smallint flg_read_only;
}; };
typedef struct { typedef struct o_string {
char *data; char *data;
int length; /* position where data is appended */ int length; /* position where data is appended */
int maxlen; int maxlen;
@ -380,7 +381,7 @@ enum {
/* I can almost use ordinary FILE*. Is open_memstream() universally /* I can almost use ordinary FILE*. Is open_memstream() universally
* available? Where is it documented? */ * available? Where is it documented? */
struct in_str { typedef struct in_str {
const char *p; const char *p;
/* eof_flag=1: last char in ->p is really an EOF */ /* eof_flag=1: last char in ->p is really an EOF */
char eof_flag; /* meaningless if ->p == NULL */ char eof_flag; /* meaningless if ->p == NULL */
@ -392,7 +393,7 @@ struct in_str {
FILE *file; FILE *file;
int (*get) (struct in_str *); int (*get) (struct in_str *);
int (*peek) (struct in_str *); int (*peek) (struct in_str *);
}; } in_str;
#define i_getch(input) ((input)->get(input)) #define i_getch(input) ((input)->get(input))
#define i_peek(input) ((input)->peek(input)) #define i_peek(input) ((input)->peek(input))
@ -403,7 +404,11 @@ enum {
CHAR_SPECIAL = 3, /* example: $ */ CHAR_SPECIAL = 3, /* example: $ */
}; };
#define HUSH_VER_STR "0.02" enum {
BC_BREAK = 1,
BC_CONTINUE = 2,
};
/* "Globals" within this file */ /* "Globals" within this file */
@ -429,6 +434,7 @@ struct globals {
struct pipe *toplevel_list; struct pipe *toplevel_list;
smallint ctrl_z_flag; smallint ctrl_z_flag;
#endif #endif
smallint flag_break_continue;
smallint fake_mode; smallint fake_mode;
/* these three support $?, $#, and $1 */ /* these three support $?, $#, and $1 */
smalluint last_return_code; smalluint last_return_code;
@ -481,6 +487,7 @@ enum { run_list_level = 0 };
#define global_argc (G.global_argc ) #define global_argc (G.global_argc )
#define last_return_code (G.last_return_code) #define last_return_code (G.last_return_code)
#define ifs (G.ifs ) #define ifs (G.ifs )
#define flag_break_continue (G.flag_break_continue)
#define fake_mode (G.fake_mode ) #define fake_mode (G.fake_mode )
#define cwd (G.cwd ) #define cwd (G.cwd )
#define last_bg_pid (G.last_bg_pid ) #define last_bg_pid (G.last_bg_pid )
@ -713,6 +720,8 @@ static int builtin_shift(char **argv);
static int builtin_source(char **argv); static int builtin_source(char **argv);
static int builtin_umask(char **argv); static int builtin_umask(char **argv);
static int builtin_unset(char **argv); static int builtin_unset(char **argv);
static int builtin_break(char **argv);
static int builtin_continue(char **argv);
//static int builtin_not_written(char **argv); //static int builtin_not_written(char **argv);
/* Table of built-in functions. They can be forked or not, depending on /* Table of built-in functions. They can be forked or not, depending on
@ -742,10 +751,10 @@ static const struct built_in_command bltins[] = {
#if ENABLE_HUSH_JOB #if ENABLE_HUSH_JOB
BLTIN("bg" , builtin_fg_bg, "Resume a job in the background"), BLTIN("bg" , builtin_fg_bg, "Resume a job in the background"),
#endif #endif
// BLTIN("break" , builtin_not_written, "Exit for, while or until loop"), BLTIN("break" , builtin_break, "Exit from a loop"),
BLTIN("cd" , builtin_cd, "Change directory"), BLTIN("cd" , builtin_cd, "Change directory"),
// BLTIN("continue", builtin_not_written, "Continue for, while or until loop"), BLTIN("continue", builtin_continue, "Start new loop iteration"),
BLTIN("echo" , builtin_echo, "Write strings to stdout"), BLTIN("echo" , builtin_echo, "Write to stdout"),
BLTIN("eval" , builtin_eval, "Construct and run shell command"), BLTIN("eval" , builtin_eval, "Construct and run shell command"),
BLTIN("exec" , builtin_exec, "Execute command, don't return to shell"), BLTIN("exec" , builtin_exec, "Execute command, don't return to shell"),
BLTIN("exit" , builtin_exit, "Exit"), BLTIN("exit" , builtin_exit, "Exit"),
@ -2016,29 +2025,25 @@ static int run_list(struct pipe *pi)
char *case_word = NULL; char *case_word = NULL;
#endif #endif
#if ENABLE_HUSH_LOOPS #if ENABLE_HUSH_LOOPS
struct pipe *loop_top = loop_top; /* just for compiler */ struct pipe *loop_top = NULL;
char *for_varname = NULL; char *for_varname = NULL;
char **for_lcur = NULL; char **for_lcur = NULL;
char **for_list = NULL; char **for_list = NULL;
smallint flag_run_loop = 0;
smallint flag_goto_looptop = 0;
#endif #endif
smallint flag_skip = 1; smallint flag_skip = 1;
smalluint rcode = 0; /* probably just for compiler */ smalluint rcode = 0; /* probably just for compiler */
#if ENABLE_HUSH_IF #if ENABLE_HUSH_IF
smalluint cond_code = 0; smalluint cond_code = 0;
///experimentally off: last_cond_code seems to be bogus
///smalluint last_cond_code = 0; /* need double-buffer to handle "elif" */
#else #else
enum { cond_code = 0, /* ///last_cond_code = 0 */ }; enum { cond_code = 0, };
#endif #endif
/*reserved_style*/ smallint rword IF_HAS_NO_KEYWORDS(= RES_NONE); /*enum reserved_style*/ smallint rword = RES_NONE;
/*reserved_style*/ smallint skip_more_for_this_rword = RES_XXXX; /*enum reserved_style*/ smallint skip_more_for_this_rword = RES_XXXX;
debug_printf_exec("run_list start lvl %d\n", run_list_level + 1); debug_printf_exec("run_list start lvl %d\n", run_list_level + 1);
#if ENABLE_HUSH_LOOPS #if ENABLE_HUSH_LOOPS
/* check syntax for "for" */ /* Check syntax for "for" */
for (struct pipe *cpipe = pi; cpipe; cpipe = cpipe->next) { for (struct pipe *cpipe = pi; cpipe; cpipe = cpipe->next) {
if (cpipe->res_word != RES_FOR && cpipe->res_word != RES_IN) if (cpipe->res_word != RES_FOR && cpipe->res_word != RES_IN)
continue; continue;
@ -2108,20 +2113,16 @@ static int run_list(struct pipe *pi)
} }
#endif /* JOB */ #endif /* JOB */
/* Go through list of pipes, (maybe) executing them */ /* Go through list of pipes, (maybe) executing them. */
for (; pi; pi = USE_HUSH_LOOPS( flag_goto_looptop ? loop_top : ) pi->next) { for (; pi; pi = USE_HUSH_LOOPS(rword == RES_DONE ? loop_top : ) pi->next) {
IF_HAS_KEYWORDS(rword = pi->res_word;) IF_HAS_KEYWORDS(rword = pi->res_word;)
IF_HAS_NO_KEYWORDS(rword = RES_NONE;) IF_HAS_NO_KEYWORDS(rword = RES_NONE;)
debug_printf_exec(": rword=%d cond_code=%d last_cond_code=%d skip_more=%d flag_run_loop=%d\n", debug_printf_exec(": rword=%d cond_code=%d skip_more=%d\n",
rword, cond_code, last_cond_code, skip_more_for_this_rword, flag_run_loop); rword, cond_code, skip_more_for_this_rword);
#if ENABLE_HUSH_LOOPS #if ENABLE_HUSH_LOOPS
if (rword == RES_WHILE || rword == RES_UNTIL || rword == RES_FOR) { if (rword == RES_WHILE || rword == RES_UNTIL || rword == RES_FOR) {
/* start of a loop: remember it */ /* start of a loop: remember where loop starts */
flag_goto_looptop = 0; /* not yet reached final "done" */ loop_top = pi;
// if (!loop_top) { /* hmm why this check is needed? */
// flag_run_loop = 0; /* suppose loop condition is false (for now) */
loop_top = pi; /* remember where loop starts */
// }
} }
#endif #endif
if (rword == skip_more_for_this_rword && flag_skip) { if (rword == skip_more_for_this_rword && flag_skip) {
@ -2134,18 +2135,21 @@ static int run_list(struct pipe *pi)
flag_skip = 1; flag_skip = 1;
skip_more_for_this_rword = RES_XXXX; skip_more_for_this_rword = RES_XXXX;
#if ENABLE_HUSH_IF #if ENABLE_HUSH_IF
/// if (rword == RES_THEN) // || rword == RES_ELSE) if (cond_code) {
/// cond_code = last_cond_code; if (rword == RES_THEN) {
if (rword == RES_THEN && cond_code) /* "if <false> THEN cmd": skip cmd */
continue; /* "if <false> THEN cmd": skip cmd */ continue;
if (rword == RES_ELSE && !cond_code) }
//continue; /* "if <true> then ... ELSE cmd": skip cmd */ } else {
break; //TEST if (rword == RES_ELSE || rword == RES_ELIF) {
if (rword == RES_ELIF && !cond_code) /* "if <true> then ... ELSE/ELIF cmd":
break; /* "if <true> then ... ELIF cmd": skip cmd and all following ones */ * skip cmd and all following ones */
break;
}
}
#endif #endif
#if ENABLE_HUSH_LOOPS #if ENABLE_HUSH_LOOPS
if (rword == RES_FOR && pi->num_progs) { /* hmm why "&& pi->num_progs"? */ if (rword == RES_FOR) { /* && pi->num_progs - always == 1 */
if (!for_lcur) { if (!for_lcur) {
/* first loop through for */ /* first loop through for */
@ -2161,7 +2165,7 @@ static int run_list(struct pipe *pi)
if (pi->next->res_word == RES_IN) { if (pi->next->res_word == RES_IN) {
/* if no variable values after "in" we skip "for" */ /* if no variable values after "in" we skip "for" */
if (!pi->next->progs->argv) if (!pi->next->progs->argv)
continue; break;
vals = pi->next->progs->argv; vals = pi->next->progs->argv;
} /* else: "for var; do..." -> assume "$@" list */ } /* else: "for var; do..." -> assume "$@" list */
/* create list of variable values */ /* create list of variable values */
@ -2171,7 +2175,6 @@ static int run_list(struct pipe *pi)
debug_print_strings("for_list", for_list); debug_print_strings("for_list", for_list);
for_varname = pi->progs->argv[0]; for_varname = pi->progs->argv[0];
pi->progs->argv[0] = NULL; pi->progs->argv[0] = NULL;
flag_run_loop = 1; /* "for" has no loop condition, loop... */
} }
free(pi->progs->argv[0]); free(pi->progs->argv[0]);
if (!*for_lcur) { if (!*for_lcur) {
@ -2179,33 +2182,22 @@ static int run_list(struct pipe *pi)
free(for_list); free(for_list);
for_list = NULL; for_list = NULL;
for_lcur = NULL; for_lcur = NULL;
flag_run_loop = 0; /* ... until end of value list */
pi->progs->argv[0] = for_varname; pi->progs->argv[0] = for_varname;
continue; break;
} }
/* insert next value from for_lcur */ /* insert next value from for_lcur */
//TODO: does it need escaping? //TODO: does it need escaping?
pi->progs->argv[0] = xasprintf("%s=%s", for_varname, *for_lcur++); pi->progs->argv[0] = xasprintf("%s=%s", for_varname, *for_lcur++);
} }
if (rword == RES_IN) /* "for v IN list; do ..." - no pipe to execute here */ if (rword == RES_IN) /* "for v IN list;..." - "in" has no cmds anyway */
continue; continue;
if (rword == RES_DO) { /* "...; DO cmd; cmd" - this pipe is in loop body */ if (rword == RES_DONE) {
if (!flag_run_loop) continue; /* "done" has no cmds too */
continue; /* we are skipping this iteration */
}
if (rword == RES_DONE) { /* end of loop? */
if (flag_run_loop) {
flag_goto_looptop = 1;
// } else {
// loop_top = NULL;
}
continue; //TEST /* "done" has no cmd anyway */
} }
#endif #endif
#if ENABLE_HUSH_CASE #if ENABLE_HUSH_CASE
if (rword == RES_CASE) { if (rword == RES_CASE) {
case_word = expand_strvec_to_string(pi->progs->argv); case_word = expand_strvec_to_string(pi->progs->argv);
//bb_error_msg("case: arg:'%s' case_word:'%s'", pi->progs->argv[0], case_word);
continue; continue;
} }
if (rword == RES_MATCH) { if (rword == RES_MATCH) {
@ -2215,35 +2207,53 @@ static int run_list(struct pipe *pi)
/* all prev words didn't match, does this one match? */ /* all prev words didn't match, does this one match? */
pattern = expand_strvec_to_string(pi->progs->argv); pattern = expand_strvec_to_string(pi->progs->argv);
/* TODO: which FNM_xxx flags to use? */ /* TODO: which FNM_xxx flags to use? */
/* ///last_ */ cond_code = (fnmatch(pattern, case_word, /*flags:*/ 0) != 0); cond_code = (fnmatch(pattern, case_word, /*flags:*/ 0) != 0);
//bb_error_msg("fnmatch('%s','%s'):%d", pattern, case_word, cond_code);
free(pattern); free(pattern);
if (/* ///last_ */ cond_code == 0) { /* match! we will execute this branch */ if (cond_code == 0) { /* match! we will execute this branch */
free(case_word); /* make future "word)" stop */ free(case_word); /* make future "word)" stop */
case_word = NULL; case_word = NULL;
} }
continue; continue;
} }
if (rword == RES_CASEI) { /* inside of a case branch */ if (rword == RES_CASEI) { /* inside of a case branch */
if (/* ///last_ */ cond_code != 0) if (cond_code != 0)
continue; /* not matched yet, skip this pipe */ continue; /* not matched yet, skip this pipe */
} }
#endif #endif
if (pi->num_progs == 0) if (pi->num_progs == 0)
continue; continue;
/* After analyzing all keywrds and conditions, we decided /* After analyzing all keywords and conditions, we decided
* to execute this pipe */ * to execute this pipe. NB: has to do checkjobs(NULL)
* after run_pipe() to collect any background children,
* even if list execution is to be stopped. */
debug_printf_exec(": run_pipe with %d members\n", pi->num_progs); debug_printf_exec(": run_pipe with %d members\n", pi->num_progs);
{ {
int r; int r;
flag_break_continue = 0;
rcode = r = run_pipe(pi); /* NB: rcode is a smallint */ rcode = r = run_pipe(pi); /* NB: rcode is a smallint */
if (r != -1) { if (r != -1) {
/* We only ran a builtin: rcode was set by the return value /* we only ran a builtin: rcode is already known
* of run_pipe(), and we don't need to wait for anything. */ * and we don't need to wait for anything. */
/* was it "break" or "continue"? */
if (flag_break_continue) {
smallint fbc = flag_break_continue;
/* we might fall into outer *loop*,
* don't want to break it too */
flag_break_continue = 0;
if (loop_top) {
if (fbc == BC_BREAK)
goto check_jobs_and_break;
/* "continue": simulate end of loop */
rword = RES_DONE;
continue;
}
bb_error_msg("break/continue: only meaningful in a loop");
/* bash compat: exit code is still 0 */
}
} else if (pi->followup == PIPE_BG) { } else if (pi->followup == PIPE_BG) {
/* What does bash do with attempts to background builtins? */ /* what does bash do with attempts to background builtins? */
/* Even bash 3.2 doesn't do that well with nested bg: /* even bash 3.2 doesn't do that well with nested bg:
* try "{ { sleep 10; echo DEEP; } & echo HERE; } &". * try "{ { sleep 10; echo DEEP; } & echo HERE; } &".
* I'm NOT treating inner &'s as jobs */ * I'm NOT treating inner &'s as jobs */
#if ENABLE_HUSH_JOB #if ENABLE_HUSH_JOB
@ -2271,16 +2281,19 @@ static int run_list(struct pipe *pi)
/* Analyze how result affects subsequent commands */ /* Analyze how result affects subsequent commands */
#if ENABLE_HUSH_IF #if ENABLE_HUSH_IF
if (rword == RES_IF || rword == RES_ELIF) if (rword == RES_IF || rword == RES_ELIF)
/* ///last_cond_code = */ cond_code = rcode; cond_code = rcode;
#endif #endif
#if ENABLE_HUSH_LOOPS #if ENABLE_HUSH_LOOPS
if (rword == RES_WHILE) { if (rword == RES_WHILE) {
flag_run_loop = !rcode; if (rcode)
debug_printf_exec(": setting flag_run_loop=%d\n", flag_run_loop); goto check_jobs_and_break;
} }
if (rword == RES_UNTIL) { if (rword == RES_UNTIL) {
flag_run_loop = rcode; if (!rcode) {
debug_printf_exec(": setting flag_run_loop=%d\n", flag_run_loop); check_jobs_and_break:
checkjobs(NULL);
break;
}
} }
#endif #endif
if ((rcode == 0 && pi->followup == PIPE_OR) if ((rcode == 0 && pi->followup == PIPE_OR)
@ -4498,3 +4511,15 @@ static int builtin_unset(char **argv)
unset_local_var(argv[1]); unset_local_var(argv[1]);
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
static int builtin_break(char **argv UNUSED_PARAM)
{
flag_break_continue = BC_BREAK;
return EXIT_SUCCESS;
}
static int builtin_continue(char **argv UNUSED_PARAM)
{
flag_break_continue = BC_CONTINUE;
return EXIT_SUCCESS;
}

View File

@ -2,9 +2,10 @@
Command parsing Command parsing
Command parsing results in "pipe" structures. "Pipe" structure Command parsing results in a list of "pipe" structures.
does not always correspond to what sh language calls "pipe", This list correspond not only to usual "pipe1 || pipe2 && pipe3"
it also controls execution of if, while, etc statements. lists, but it also controls execution of if, while, etc statements.
Every such statement is a list for hush. List consists of pipes.
struct pipe fields: struct pipe fields:
smallint res_word - "none" for normal commands, smallint res_word - "none" for normal commands,
@ -18,7 +19,7 @@ Blocks of commands { pipe; pipe; } and (pipe; pipe) are represented
as one pipe struct with one progs[0] element which is a "group" - as one pipe struct with one progs[0] element which is a "group" -
struct child_prog can contain a list of pipes. Sometimes these struct child_prog can contain a list of pipes. Sometimes these
"groups" are created implicitly, e.g. every control "groups" are created implicitly, e.g. every control
statement (if, while, etc) sits inside its own "pipe" struct). statement (if, while, etc) sits inside its own group.
res_word controls statement execution. Examples: res_word controls statement execution. Examples:
@ -41,6 +42,10 @@ res_word=NONE followup=SEQ
pipe 4 res_word=NONE followup=(null) pipe 4 res_word=NONE followup=(null)
pipe 1 res_word=NONE followup=SEQ pipe 1 res_word=NONE followup=SEQ
Above you see that if is a list, and it sits in a {} group
implicitly created by hush. Also note two THEN res_word's -
it is explained below.
"if true; then { echo Hello; true; }; fi" - "if true; then { echo Hello; true; }; fi" -
pipe 0 res_word=NONE followup=SEQ pipe 0 res_word=NONE followup=SEQ
prog 0 group {}: prog 0 group {}: