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:
* port selected bugfixes from post-0.49 busybox lash - done?
* change { and } from special chars to reserved words
* builtins: break, continue, eval, return, set, trap, ulimit
* builtins: return, trap, ulimit
* test magic exec
* check setting of global_argc and global_argv
* follow IFS rules more precisely, including update semantics
@ -74,6 +74,7 @@
#include <fnmatch.h>
#endif
#define HUSH_VER_STR "0.9"
#if !BB_MMU && ENABLE_HUSH_TICK
//#undef ENABLE_HUSH_TICK
@ -230,7 +231,7 @@ void xxfree(void *ptr)
#define SPECIAL_VAR_SYMBOL 3
#define PARSEFLAG_EXIT_FROM_LOOP 1
typedef enum {
typedef enum redir_type {
REDIRECT_INPUT = 1,
REDIRECT_OVERWRITE = 2,
REDIRECT_APPEND = 3,
@ -253,14 +254,14 @@ static const struct {
{ O_RDWR, 1, "<>" }
};
typedef enum {
typedef enum pipe_style {
PIPE_SEQ = 1,
PIPE_AND = 2,
PIPE_OR = 3,
PIPE_BG = 4,
} pipe_style;
typedef enum {
typedef enum reserved_style {
RES_NONE = 0,
#if ENABLE_HUSH_IF
RES_IF ,
@ -358,7 +359,7 @@ struct variable {
smallint flg_read_only;
};
typedef struct {
typedef struct o_string {
char *data;
int length; /* position where data is appended */
int maxlen;
@ -378,9 +379,9 @@ enum {
/* Used for initialization: o_string foo = NULL_O_STRING; */
#define NULL_O_STRING { NULL }
/* 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? */
struct in_str {
typedef struct in_str {
const char *p;
/* eof_flag=1: last char in ->p is really an EOF */
char eof_flag; /* meaningless if ->p == NULL */
@ -392,7 +393,7 @@ struct in_str {
FILE *file;
int (*get) (struct in_str *);
int (*peek) (struct in_str *);
};
} in_str;
#define i_getch(input) ((input)->get(input))
#define i_peek(input) ((input)->peek(input))
@ -403,7 +404,11 @@ enum {
CHAR_SPECIAL = 3, /* example: $ */
};
#define HUSH_VER_STR "0.02"
enum {
BC_BREAK = 1,
BC_CONTINUE = 2,
};
/* "Globals" within this file */
@ -429,6 +434,7 @@ struct globals {
struct pipe *toplevel_list;
smallint ctrl_z_flag;
#endif
smallint flag_break_continue;
smallint fake_mode;
/* these three support $?, $#, and $1 */
smalluint last_return_code;
@ -481,6 +487,7 @@ enum { run_list_level = 0 };
#define global_argc (G.global_argc )
#define last_return_code (G.last_return_code)
#define ifs (G.ifs )
#define flag_break_continue (G.flag_break_continue)
#define fake_mode (G.fake_mode )
#define cwd (G.cwd )
#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_umask(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);
/* 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
BLTIN("bg" , builtin_fg_bg, "Resume a job in the background"),
#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("continue", builtin_not_written, "Continue for, while or until loop"),
BLTIN("echo" , builtin_echo, "Write strings to stdout"),
BLTIN("continue", builtin_continue, "Start new loop iteration"),
BLTIN("echo" , builtin_echo, "Write to stdout"),
BLTIN("eval" , builtin_eval, "Construct and run shell command"),
BLTIN("exec" , builtin_exec, "Execute command, don't return to shell"),
BLTIN("exit" , builtin_exit, "Exit"),
@ -2016,29 +2025,25 @@ static int run_list(struct pipe *pi)
char *case_word = NULL;
#endif
#if ENABLE_HUSH_LOOPS
struct pipe *loop_top = loop_top; /* just for compiler */
struct pipe *loop_top = NULL;
char *for_varname = NULL;
char **for_lcur = NULL;
char **for_list = NULL;
smallint flag_run_loop = 0;
smallint flag_goto_looptop = 0;
#endif
smallint flag_skip = 1;
smalluint rcode = 0; /* probably just for compiler */
#if ENABLE_HUSH_IF
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
enum { cond_code = 0, /* ///last_cond_code = 0 */ };
enum { cond_code = 0, };
#endif
/*reserved_style*/ smallint rword IF_HAS_NO_KEYWORDS(= RES_NONE);
/*reserved_style*/ smallint skip_more_for_this_rword = RES_XXXX;
/*enum reserved_style*/ smallint rword = RES_NONE;
/*enum reserved_style*/ smallint skip_more_for_this_rword = RES_XXXX;
debug_printf_exec("run_list start lvl %d\n", run_list_level + 1);
#if ENABLE_HUSH_LOOPS
/* check syntax for "for" */
/* Check syntax for "for" */
for (struct pipe *cpipe = pi; cpipe; cpipe = cpipe->next) {
if (cpipe->res_word != RES_FOR && cpipe->res_word != RES_IN)
continue;
@ -2108,20 +2113,16 @@ static int run_list(struct pipe *pi)
}
#endif /* JOB */
/* Go through list of pipes, (maybe) executing them */
for (; pi; pi = USE_HUSH_LOOPS( flag_goto_looptop ? loop_top : ) pi->next) {
/* Go through list of pipes, (maybe) executing them. */
for (; pi; pi = USE_HUSH_LOOPS(rword == RES_DONE ? loop_top : ) pi->next) {
IF_HAS_KEYWORDS(rword = pi->res_word;)
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",
rword, cond_code, last_cond_code, skip_more_for_this_rword, flag_run_loop);
debug_printf_exec(": rword=%d cond_code=%d skip_more=%d\n",
rword, cond_code, skip_more_for_this_rword);
#if ENABLE_HUSH_LOOPS
if (rword == RES_WHILE || rword == RES_UNTIL || rword == RES_FOR) {
/* start of a loop: remember it */
flag_goto_looptop = 0; /* not yet reached final "done" */
// 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 */
// }
/* start of a loop: remember where loop starts */
loop_top = pi;
}
#endif
if (rword == skip_more_for_this_rword && flag_skip) {
@ -2134,18 +2135,21 @@ static int run_list(struct pipe *pi)
flag_skip = 1;
skip_more_for_this_rword = RES_XXXX;
#if ENABLE_HUSH_IF
/// if (rword == RES_THEN) // || rword == RES_ELSE)
/// cond_code = last_cond_code;
if (rword == RES_THEN && cond_code)
continue; /* "if <false> THEN cmd": skip cmd */
if (rword == RES_ELSE && !cond_code)
//continue; /* "if <true> then ... ELSE cmd": skip cmd */
break; //TEST
if (rword == RES_ELIF && !cond_code)
break; /* "if <true> then ... ELIF cmd": skip cmd and all following ones */
if (cond_code) {
if (rword == RES_THEN) {
/* "if <false> THEN cmd": skip cmd */
continue;
}
} else {
if (rword == RES_ELSE || rword == RES_ELIF) {
/* "if <true> then ... ELSE/ELIF cmd":
* skip cmd and all following ones */
break;
}
}
#endif
#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) {
/* first loop through for */
@ -2161,7 +2165,7 @@ static int run_list(struct pipe *pi)
if (pi->next->res_word == RES_IN) {
/* if no variable values after "in" we skip "for" */
if (!pi->next->progs->argv)
continue;
break;
vals = pi->next->progs->argv;
} /* else: "for var; do..." -> assume "$@" list */
/* create list of variable values */
@ -2171,7 +2175,6 @@ static int run_list(struct pipe *pi)
debug_print_strings("for_list", for_list);
for_varname = pi->progs->argv[0];
pi->progs->argv[0] = NULL;
flag_run_loop = 1; /* "for" has no loop condition, loop... */
}
free(pi->progs->argv[0]);
if (!*for_lcur) {
@ -2179,33 +2182,22 @@ static int run_list(struct pipe *pi)
free(for_list);
for_list = NULL;
for_lcur = NULL;
flag_run_loop = 0; /* ... until end of value list */
pi->progs->argv[0] = for_varname;
continue;
break;
}
/* insert next value from for_lcur */
//TODO: does it need escaping?
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;
if (rword == RES_DO) { /* "...; DO cmd; cmd" - this pipe is in loop body */
if (!flag_run_loop)
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 */
if (rword == RES_DONE) {
continue; /* "done" has no cmds too */
}
#endif
#if ENABLE_HUSH_CASE
if (rword == RES_CASE) {
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;
}
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? */
pattern = expand_strvec_to_string(pi->progs->argv);
/* TODO: which FNM_xxx flags to use? */
/* ///last_ */ cond_code = (fnmatch(pattern, case_word, /*flags:*/ 0) != 0);
//bb_error_msg("fnmatch('%s','%s'):%d", pattern, case_word, cond_code);
cond_code = (fnmatch(pattern, case_word, /*flags:*/ 0) != 0);
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 */
case_word = NULL;
}
continue;
}
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 */
}
#endif
if (pi->num_progs == 0)
continue;
/* After analyzing all keywrds and conditions, we decided
* to execute this pipe */
/* After analyzing all keywords and conditions, we decided
* 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);
{
int r;
flag_break_continue = 0;
rcode = r = run_pipe(pi); /* NB: rcode is a smallint */
if (r != -1) {
/* We only ran a builtin: rcode was set by the return value
* of run_pipe(), and we don't need to wait for anything. */
/* we only ran a builtin: rcode is already known
* 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) {
/* What does bash do with attempts to background builtins? */
/* Even bash 3.2 doesn't do that well with nested bg:
/* what does bash do with attempts to background builtins? */
/* even bash 3.2 doesn't do that well with nested bg:
* try "{ { sleep 10; echo DEEP; } & echo HERE; } &".
* I'm NOT treating inner &'s as jobs */
#if ENABLE_HUSH_JOB
@ -2271,16 +2281,19 @@ static int run_list(struct pipe *pi)
/* Analyze how result affects subsequent commands */
#if ENABLE_HUSH_IF
if (rword == RES_IF || rword == RES_ELIF)
/* ///last_cond_code = */ cond_code = rcode;
cond_code = rcode;
#endif
#if ENABLE_HUSH_LOOPS
if (rword == RES_WHILE) {
flag_run_loop = !rcode;
debug_printf_exec(": setting flag_run_loop=%d\n", flag_run_loop);
if (rcode)
goto check_jobs_and_break;
}
if (rword == RES_UNTIL) {
flag_run_loop = rcode;
debug_printf_exec(": setting flag_run_loop=%d\n", flag_run_loop);
if (!rcode) {
check_jobs_and_break:
checkjobs(NULL);
break;
}
}
#endif
if ((rcode == 0 && pi->followup == PIPE_OR)
@ -4498,3 +4511,15 @@ static int builtin_unset(char **argv)
unset_local_var(argv[1]);
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 results in "pipe" structures. "Pipe" structure
does not always correspond to what sh language calls "pipe",
it also controls execution of if, while, etc statements.
Command parsing results in a list of "pipe" structures.
This list correspond not only to usual "pipe1 || pipe2 && pipe3"
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:
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" -
struct child_prog can contain a list of pipes. Sometimes these
"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:
@ -41,6 +42,10 @@ res_word=NONE followup=SEQ
pipe 4 res_word=NONE followup=(null)
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" -
pipe 0 res_word=NONE followup=SEQ
prog 0 group {}: