* rewrites.
*
* Other credits:
- * o_addchr() derived from similar w_addchar function in glibc-2.2
+ * o_addchr() derived from similar w_addchar function in glibc-2.2.
* setup_redirect(), redirect_opt_num(), and big chunks of main()
* and many builtins derived from contributions by Erik Andersen
- * miscellaneous bugfixes from Matt Kraai
+ * miscellaneous bugfixes from Matt Kraai.
*
* There are two big (and related) architecture differences between
* this parser and the lash parser. One is that this version is
*
* Bash grammar not implemented: (how many of these were in original sh?)
* $_
- * ! negation operator for pipes
* &> and >& redirection of stdout+stderr
* Brace Expansion
* Tilde Expansion
* reserved word execution woefully incomplete and buggy
* to-do:
* port selected bugfixes from post-0.49 busybox lash - done?
- * finish implementing reserved words: for, while, until, do, done
* change { and } from special chars to reserved words
* builtins: break, continue, eval, return, set, trap, ulimit
* test magic exec
- * handle children going into background
- * clean up recognition of null pipes
* check setting of global_argc and global_argv
- * control-C handling, probably with longjmp
* follow IFS rules more precisely, including update semantics
* figure out what to do with backslash-newline
- * explain why we use signal instead of sigaction
* propagate syntax errors, die on resource errors?
* continuation lines, both explicit and implicit - done?
* memory leak finding and plugging - done?
- * more testing, especially quoting rules and redirection
- * document how quoting rules not precisely followed for variable assignments
* maybe change charmap[] to use 2-bit entries
- * (eventually) remove all the printf's
*
* Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
*/
RES_XXXX = 12,
RES_SNTX = 13
} reserved_style;
-enum {
- FLAG_END = (1 << RES_NONE ),
-#if ENABLE_HUSH_IF
- FLAG_IF = (1 << RES_IF ),
- FLAG_THEN = (1 << RES_THEN ),
- FLAG_ELIF = (1 << RES_ELIF ),
- FLAG_ELSE = (1 << RES_ELSE ),
- FLAG_FI = (1 << RES_FI ),
-#endif
-#if ENABLE_HUSH_LOOPS
- FLAG_FOR = (1 << RES_FOR ),
- FLAG_WHILE = (1 << RES_WHILE),
- FLAG_UNTIL = (1 << RES_UNTIL),
- FLAG_DO = (1 << RES_DO ),
- FLAG_DONE = (1 << RES_DONE ),
- FLAG_IN = (1 << RES_IN ),
-#endif
- FLAG_START = (1 << RES_XXXX ),
-};
-
/* This holds pointers to the various results of parsing */
struct p_context {
struct child_prog *child;
struct pipe *pipe;
struct redir_struct *pending_redirect;
smallint res_w;
- smallint parse_type; /* bitmask of PARSEFLAG_xxx, defines type of parser : ";$" common or special symbol */
+ smallint parse_type; /* bitmask of PARSEFLAG_xxx, defines type of parser: ";$" common or special symbol */
+ smallint ctx_inverted; /* "! cmd | cmd" */
int old_flag; /* bitmask of FLAG_xxx, for figuring out valid reserved words */
struct p_context *stack;
/* How about quoting status? */
char *cmdbuf; /* buffer various argv's point into */
struct child_prog *progs; /* array of commands in pipe */
int job_context; /* bitmask defining current context */
+ smallint pi_inverted; /* "! cmd | cmd" */
smallint followup; /* PIPE_BG, PIPE_SEQ, PIPE_OR, PIPE_AND */
smallint res_word; /* needed for if, for, while, until... */
};
static void initialize_context(struct p_context *ctx);
static int done_word(o_string *dest, struct p_context *ctx);
static int done_command(struct p_context *ctx);
-static int done_pipe(struct p_context *ctx, pipe_style type);
+static void done_pipe(struct p_context *ctx, pipe_style type);
/* primary string parsing: */
static int redirect_dup_num(struct in_str *input);
static int redirect_opt_num(o_string *o);
static int builtin_pwd(char **argv);
static int builtin_read(char **argv);
static int builtin_test(char **argv);
+static int builtin_true(char **argv);
static int builtin_set(char **argv);
static int builtin_shift(char **argv);
static int builtin_source(char **argv);
* For example, 'unset foo | whatever' will parse and run, but foo will
* still be set at the end. */
struct built_in_command {
- const char *cmd; /* name */
- int (*function) (char **argv); /* function ptr */
+ const char *cmd;
+ int (*function)(char **argv);
#if ENABLE_HUSH_HELP
- const char *descr; /* description */
+ const char *descr;
#define BLTIN(cmd, func, help) { cmd, func, help }
#else
#define BLTIN(cmd, func, help) { cmd, func }
/* For now, echo and test are unconditionally enabled.
* Maybe make it configurable? */
static const struct built_in_command bltins[] = {
+ BLTIN("." , builtin_source, "Run commands in a file"),
+ BLTIN(":" , builtin_true, "No-op"),
BLTIN("[" , builtin_test, "Test condition"),
BLTIN("[[" , builtin_test, "Test condition"),
#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("cd" , builtin_cd, "Change working directory"),
+ 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("eval" , builtin_eval, "Construct and run shell command"),
- BLTIN("exec" , builtin_exec, "Exec command, replacing this shell with the exec'd process"),
- BLTIN("exit" , builtin_exit, "Exit from shell"),
+ BLTIN("exec" , builtin_exec, "Execute command, don't return to shell"),
+ BLTIN("exit" , builtin_exit, "Exit"),
BLTIN("export", builtin_export, "Set environment variable"),
#if ENABLE_HUSH_JOB
BLTIN("fg" , builtin_fg_bg, "Bring job into the foreground"),
- BLTIN("jobs" , builtin_jobs, "Lists the active jobs"),
+ BLTIN("jobs" , builtin_jobs, "List active jobs"),
#endif
-// TODO: remove pwd? we have it as an applet...
BLTIN("pwd" , builtin_pwd, "Print current directory"),
BLTIN("read" , builtin_read, "Input environment variable"),
// BLTIN("return", builtin_not_written, "Return from a function"),
BLTIN("shift" , builtin_shift, "Shift positional parameters"),
// BLTIN("trap" , builtin_not_written, "Trap signals"),
BLTIN("test" , builtin_test, "Test condition"),
-// BLTIN("ulimit", builtin_not_written, "Controls resource limits"),
- BLTIN("umask" , builtin_umask, "Sets file creation mask"),
+// BLTIN("ulimit", builtin_not_written, "Control resource limits"),
+ BLTIN("umask" , builtin_umask, "Set file creation mask"),
BLTIN("unset" , builtin_unset, "Unset environment variable"),
- BLTIN("." , builtin_source, "Source-in and run commands in a file"),
#if ENABLE_HUSH_HELP
BLTIN("help" , builtin_help, "List shell built-in commands"),
#endif
- BLTIN(NULL, NULL, NULL)
};
/* Signals are grouped, we handle them in batches */
}
+/* built-in 'true' handler */
+static int builtin_true(char **argv ATTRIBUTE_UNUSED)
+{
+ return 0;
+}
+
/* built-in 'test' handler */
static int builtin_test(char **argv)
{
printf("\nBuilt-in commands:\n");
printf("-------------------\n");
- for (x = bltins; x->cmd; x++) {
+ for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) {
printf("%s\t%s\n", x->cmd, x->descr);
}
printf("\n\n");
/* My analysis of quoting semantics tells me that state information
* is associated with a destination, not a source.
*/
-static void o_addqchr(o_string *o, int ch, int quote)
+static void o_addqchr(o_string *o, int ch)
+{
+ int sz = 1;
+ if (strchr("*?[\\", ch)) {
+ sz++;
+ o->data[o->length] = '\\';
+ o->length++;
+ }
+ o_grow_by(o, sz);
+ o->data[o->length] = ch;
+ o->length++;
+ o->data[o->length] = '\0';
+}
+
+static void o_addQchr(o_string *o, int ch)
{
- if (quote && strchr("*?[\\", ch)) {
- o_addchr(o, '\\');
+ int sz = 1;
+ if (o->o_quote && strchr("*?[\\", ch)) {
+ sz++;
+ o->data[o->length] = '\\';
+ o->length++;
}
- o_addchr(o, ch);
+ o_grow_by(o, sz);
+ o->data[o->length] = ch;
+ o->length++;
+ o->data[o->length] = '\0';
}
-static void o_addqstr(o_string *o, const char *str, int len, int quote)
+static void o_addQstr(o_string *o, const char *str, int len)
{
- char ch;
- if (!quote || str[strcspn(str, "*?[\\")] == '\0') {
+ if (!o->o_quote) {
o_addstr(o, str, len);
return;
}
while (len) {
+ char ch;
+ int sz;
+ int ordinary_cnt = strcspn(str, "*?[\\");
+ if (ordinary_cnt > len) /* paranoia */
+ ordinary_cnt = len;
+ o_addstr(o, str, ordinary_cnt);
+ if (ordinary_cnt == len)
+ return;
+ str += ordinary_cnt;
+ len -= ordinary_cnt - 1; /* we are processing + 1 char below */
+
ch = *str++;
- if (ch && strchr("*?[\\", ch)) {
- o_addchr(o, '\\');
+ sz = 1;
+ if (ch) { /* it is necessarily one of "*?[\\" */
+ sz++;
+ o->data[o->length] = '\\';
+ o->length++;
}
- o_addchr(o, ch);
- len--;
+ o_grow_by(o, sz);
+ o->data[o->length] = ch;
+ o->length++;
+ o->data[o->length] = '\0';
}
}
* easier to waste a few CPU cycles than it is to figure out
* if this is one of those cases.
*/
- for (x = bltins; x->cmd; x++) {
+ for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) {
if (strcmp(argv[0], x->cmd) == 0) {
debug_printf_exec("running builtin '%s'\n", argv[0]);
rcode = x->function(argv);
*/
static void pseudo_exec(char **ptrs2free, struct child_prog *child)
{
-// FIXME: buggy wrt NOMMU! Must not modify any global data
-// until it does exec/_exit, but currently it does
-// (puts malloc'ed stuff into environment)
if (child->argv)
pseudo_exec_argv(ptrs2free, child->argv);
if (pi->cmdtext)
return pi->cmdtext;
argv = pi->progs[0].argv;
- if (!argv || !argv[0])
- return (pi->cmdtext = xzalloc(1));
+ if (!argv || !argv[0]) {
+ pi->cmdtext = xzalloc(1);
+ return pi->cmdtext;
+ }
len = 0;
do len += strlen(*argv) + 1; while (*++argv);
if (dead) {
fg_pipe->progs[i].pid = 0;
fg_pipe->running_progs--;
- if (i == fg_pipe->num_progs - 1)
+ if (i == fg_pipe->num_progs - 1) {
/* last process gives overall exitstatus */
rcode = WEXITSTATUS(status);
+ if (fg_pipe->pi_inverted)
+ rcode = !rcode;
+ }
} else {
fg_pipe->progs[i].is_stopped = 1;
fg_pipe->stopped_progs++;
rcode = run_list(child->group) & 0xff;
restore_redirects(squirrel);
debug_printf_exec("run_pipe return %d\n", rcode);
+ if (pi->pi_inverted)
+ rcode = !rcode;
return rcode;
}
p = expand_string_to_string(argv[i]);
putenv(p);
}
- for (x = bltins; x->cmd; x++) {
+ for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) {
if (strcmp(argv[i], x->cmd) == 0) {
if (x->function == builtin_exec && argv[i+1] == NULL) {
debug_printf("magic exec\n");
free(argv_expanded);
restore_redirects(squirrel);
debug_printf_exec("run_pipe return %d\n", rcode);
+ if (pi->pi_inverted)
+ rcode = !rcode;
return rcode;
}
}
free(argv_expanded);
restore_redirects(squirrel);
debug_printf_exec("run_pipe return %d\n", rcode);
+ if (pi->pi_inverted)
+ rcode = !rcode;
return rcode;
}
}
while (1) {
int word_len = strcspn(str, ifs);
if (word_len) {
- o_addqstr(output, str, word_len, output->o_quote);
+ o_addQstr(output, str, word_len);
str += word_len;
}
if (!*str) /* EOL - do not finalize word */
while ((p = strchr(arg, SPECIAL_VAR_SYMBOL)) != NULL) {
o_string subst_result = NULL_O_STRING;
- o_addqstr(output, arg, p - arg, output->o_quote);
+ o_addQstr(output, arg, p - arg);
o_debug_list("expand_vars_to_list[1]", output, n);
arg = ++p;
p = strchr(p, SPECIAL_VAR_SYMBOL);
* and in this case should treat it like '$*' - see 'else...' below */
if (first_ch == ('@'|0x80) && !or_mask) { /* quoted $@ */
while (1) {
- o_addqstr(output, global_argv[i], strlen(global_argv[i]), output->o_quote);
+ o_addQstr(output, global_argv[i], strlen(global_argv[i]));
if (++i >= global_argc)
break;
o_addchr(output, '\0');
}
} else { /* quoted $*: add as one word */
while (1) {
- o_addqstr(output, global_argv[i], strlen(global_argv[i]), output->o_quote);
+ o_addQstr(output, global_argv[i], strlen(global_argv[i]));
if (!global_argv[++i])
break;
if (ifs[0])
} /* else: quoted $VAR, val will be appended below */
}
if (val) {
- o_addqstr(output, val, strlen(val), output->o_quote);
+ o_addQstr(output, val, strlen(val));
}
o_free(&subst_result);
if (arg[0]) {
o_debug_list("expand_vars_to_list[a]", output, n);
- o_addqstr(output, arg, strlen(arg) + 1, output->o_quote);
+ o_addQstr(output, arg, strlen(arg) + 1);
o_debug_list("expand_vars_to_list[b]", output, n);
} else if (output->length == o_get_last_ptr(output, n) /* expansion is empty */
&& !(ored_ch & 0x80) /* and all vars were not quoted. */
/* Check for a '2>&1' type redirect */
redir->dup = redirect_dup_num(input);
- if (redir->dup == -2) return 1; /* syntax error */
+ if (redir->dup == -2)
+ return 1; /* syntax error */
if (redir->dup != -1) {
/* Erik had a check here that the file descriptor in question
* is legit; I postpone that to "run time"
{
struct pipe *pi;
pi = xzalloc(sizeof(struct pipe));
- /*pi->num_progs = 0;*/
- /*pi->progs = NULL;*/
- /*pi->next = NULL;*/
- /*pi->followup = 0; invalid */
+ /*pi->followup = 0; - deliberately invalid value */
if (RES_NONE)
pi->res_word = RES_NONE;
return pi;
static void initialize_context(struct p_context *ctx)
{
- ctx->child = NULL;
+ smallint sv = ctx->parse_type;
+ memset(ctx, 0, sizeof(*ctx));
+ ctx->parse_type = sv;
ctx->pipe = ctx->list_head = new_pipe();
- ctx->pending_redirect = NULL;
- ctx->res_w = RES_NONE;
- //only ctx->parse_type is not touched... is this intentional?
- ctx->old_flag = 0;
- ctx->stack = NULL;
- done_command(ctx); /* creates the memory for working child */
+ /* Create the memory for child, roughly:
+ * ctx->pipe->progs = new struct child_prog;
+ * ctx->pipe->progs[0].family = ctx->pipe;
+ * ctx->child = &ctx->pipe->progs[0];
+ */
+ done_command(ctx);
}
-/* normal return is 0
- * if a reserved word is found, and processed, return 1
- * should handle if, then, elif, else, fi, for, while, until, do, done.
+/* If a reserved word is found and processed, parse context is modified
+ * and 1 is returned.
+ * Handles if, then, elif, else, fi, for, while, until, do, done.
* case, function, and select are obnoxious, save those for later.
*/
#if ENABLE_HUSH_IF || ENABLE_HUSH_LOOPS
-static int reserved_word(o_string *dest, struct p_context *ctx)
+static int reserved_word(const o_string *word, struct p_context *ctx)
{
struct reserved_combo {
char literal[7];
- unsigned char code;
+ unsigned char res;
int flag;
};
+ enum {
+ FLAG_END = (1 << RES_NONE ),
+#if ENABLE_HUSH_IF
+ FLAG_IF = (1 << RES_IF ),
+ FLAG_THEN = (1 << RES_THEN ),
+ FLAG_ELIF = (1 << RES_ELIF ),
+ FLAG_ELSE = (1 << RES_ELSE ),
+ FLAG_FI = (1 << RES_FI ),
+#endif
+#if ENABLE_HUSH_LOOPS
+ FLAG_FOR = (1 << RES_FOR ),
+ FLAG_WHILE = (1 << RES_WHILE),
+ FLAG_UNTIL = (1 << RES_UNTIL),
+ FLAG_DO = (1 << RES_DO ),
+ FLAG_DONE = (1 << RES_DONE ),
+ FLAG_IN = (1 << RES_IN ),
+#endif
+ FLAG_START = (1 << RES_XXXX ),
+ };
/* Mostly a list of accepted follow-up reserved words.
* FLAG_END means we are done with the sequence, and are ready
* to turn the compound list into a command.
*/
static const struct reserved_combo reserved_list[] = {
#if ENABLE_HUSH_IF
+ { "!", RES_NONE, 0 },
{ "if", RES_IF, FLAG_THEN | FLAG_START },
{ "then", RES_THEN, FLAG_ELIF | FLAG_ELSE | FLAG_FI },
{ "elif", RES_ELIF, FLAG_THEN },
{ "fi", RES_FI, FLAG_END },
#endif
#if ENABLE_HUSH_LOOPS
- { "for", RES_FOR, FLAG_IN | FLAG_START },
- { "while", RES_WHILE, FLAG_DO | FLAG_START },
- { "until", RES_UNTIL, FLAG_DO | FLAG_START },
+ { "for", RES_FOR, FLAG_IN | FLAG_START },
+ { "while", RES_WHILE, FLAG_DO | FLAG_START },
+ { "until", RES_UNTIL, FLAG_DO | FLAG_START },
{ "in", RES_IN, FLAG_DO },
{ "do", RES_DO, FLAG_DONE },
{ "done", RES_DONE, FLAG_END }
const struct reserved_combo *r;
for (r = reserved_list; r < reserved_list + ARRAY_SIZE(reserved_list); r++) {
- if (strcmp(dest->data, r->literal) != 0)
+ if (strcmp(word->data, r->literal) != 0)
continue;
- debug_printf("found reserved word %s, code %d\n", r->literal, r->code);
+ debug_printf("found reserved word %s, res %d\n", r->literal, r->res);
+ if (r->flag == 0) { /* '!' */
+ if (ctx->res_w == RES_IN) {
+ /* 'for a in ! a b c; ...' - ! isn't a keyword here */
+ break;
+ }
+ if (ctx->res_w == RES_FOR /* example: 'for ! a' */
+ || ctx->ctx_inverted /* bash doesn't accept '! ! true' */
+ ) {
+ syntax(NULL);
+ ctx->res_w = RES_SNTX;
+ }
+ ctx->ctx_inverted = 1;
+ return 1;
+ }
if (r->flag & FLAG_START) {
struct p_context *new;
debug_printf("push stack\n");
if (ctx->res_w == RES_IN || ctx->res_w == RES_FOR) {
syntax("malformed for"); /* example: 'for if' */
ctx->res_w = RES_SNTX;
- o_reset(dest);
return 1;
}
#endif
*new = *ctx; /* physical copy */
initialize_context(ctx);
ctx->stack = new;
- } else if (ctx->res_w == RES_NONE || !(ctx->old_flag & (1 << r->code))) {
+ } else if (ctx->res_w == RES_NONE || !(ctx->old_flag & (1 << r->res))) {
syntax(NULL);
ctx->res_w = RES_SNTX;
- o_reset(dest);
return 1;
}
- ctx->res_w = r->code;
+ ctx->res_w = r->res;
ctx->old_flag = r->flag;
if (ctx->old_flag & FLAG_END) {
struct p_context *old;
*ctx = *old; /* physical copy */
free(old);
}
- o_reset(dest);
return 1;
}
return 0;
}
#else
-#define reserved_word(dest, ctx) ((int)0)
+#define reserved_word(word, ctx) ((int)0)
#endif
-/* Normal return is 0.
+/* Word is complete, look at it and update parsing context.
+ * Normal return is 0.
* Syntax or xglob errors return 1. */
-static int done_word(o_string *dest, struct p_context *ctx)
+static int done_word(o_string *word, struct p_context *ctx)
{
struct child_prog *child = ctx->child;
char ***glob_target;
int gr;
- debug_printf_parse("done_word entered: '%s' %p\n", dest->data, child);
- if (dest->length == 0 && !dest->nonnull) {
+ debug_printf_parse("done_word entered: '%s' %p\n", word->data, child);
+ if (word->length == 0 && !word->nonnull) {
debug_printf_parse("done_word return 0: true null, ignored\n");
return 0;
}
if (ctx->pending_redirect) {
glob_target = &ctx->pending_redirect->glob_word;
} else {
- if (child->group) {
+ 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->argv && (ctx->parse_type & PARSEFLAG_SEMICOLON)) {
- debug_printf_parse(": checking '%s' for reserved-ness\n", dest->data);
- if (reserved_word(dest, ctx)) {
+ debug_printf_parse(": checking '%s' for reserved-ness\n", word->data);
+ if (reserved_word(word, ctx)) {
+ o_reset(word);
debug_printf_parse("done_word return %d\n", (ctx->res_w == RES_SNTX));
return (ctx->res_w == RES_SNTX);
}
}
glob_target = &child->argv;
}
- gr = xglob(dest, glob_target);
+//BUG! globbing should be done after variable expansion!
+//See glob_and_vars testcase
+ gr = xglob(word, glob_target);
if (gr != 0) {
debug_printf_parse("done_word return 1: xglob returned %d\n", gr);
return 1;
}
- o_reset(dest);
+ o_reset(word);
if (ctx->pending_redirect) {
/* NB: don't free_strings(ctx->pending_redirect->glob_word) here */
if (ctx->pending_redirect->glob_word
ctx->pending_redirect = NULL;
}
#if ENABLE_HUSH_LOOPS
- if (ctx->res_w == RES_FOR) {
- done_word(dest, ctx);
+ if (ctx->res_w == RES_FOR) { /* comment? */
+//TESTING
+//looks like (word->length == 0 && !word->nonnull) is true here, always
+//(due to o_reset). done_word would return at once. Why then?
+// done_word(word, ctx);
done_pipe(ctx, PIPE_SEQ);
}
#endif
child = &pi->progs[pi->num_progs];
memset(child, 0, sizeof(*child));
- /*child->redirects = NULL;*/
- /*child->argv = NULL;*/
- /*child->is_stopped = 0;*/
- /*child->group = NULL;*/
child->family = pi;
ctx->child = child;
return pi->num_progs; /* used only for 0/nonzero check */
}
-static int done_pipe(struct p_context *ctx, pipe_style type)
+static void done_pipe(struct p_context *ctx, pipe_style type)
{
struct pipe *new_p;
int not_null;
not_null = done_command(ctx); /* implicit closure of previous command */
ctx->pipe->followup = type;
ctx->pipe->res_word = ctx->res_w;
+ ctx->pipe->pi_inverted = ctx->ctx_inverted;
+ ctx->ctx_inverted = 0;
/* Without this check, even just <enter> on command line generates
* tree of three NOPs (!). Which is harmless but annoying.
* IOW: it is safe to do it unconditionally. */
new_p = new_pipe();
ctx->pipe->next = new_p;
ctx->pipe = new_p;
- ctx->child = NULL;
- done_command(ctx); /* set up new pipe to accept commands */
+ ctx->child = NULL; /* needed! */
+ /* Create the memory for child, roughly:
+ * ctx->pipe->progs = new struct child_prog;
+ * ctx->pipe->progs[0].family = ctx->pipe;
+ * ctx->child = &ctx->pipe->progs[0];
+ */
+ done_command(ctx);
}
- debug_printf_parse("done_pipe return 0\n");
- return 0;
+ debug_printf_parse("done_pipe return\n");
}
/* peek ahead in the in_str to find out if we have a "&n" construct,
continue;
}
while (eol_cnt) {
- o_addqchr(dest, '\n', dest->o_quote);
+ o_addQchr(dest, '\n');
eol_cnt--;
}
- o_addqchr(dest, ch, dest->o_quote);
+ o_addQchr(dest, ch);
}
debug_printf("done reading from pipe, pclose()ing\n");
break;
if (ch == '\'')
break;
- o_addqchr(dest, ch, 1);
+ o_addqchr(dest, ch);
}
}
/* "...\"...`..`...." - do we need to handle "...$(..)..." too? */
if (ch == '"')
break;
if (ch == '\\') { /* \x. Copy both chars. */
- o_addqchr(dest, ch, 1);
+ o_addqchr(dest, ch);
ch = i_getch(input);
}
if (ch == EOF)
break;
- o_addqchr(dest, ch, 1);
+ o_addqchr(dest, ch);
if (ch == '`') {
add_till_backquote(dest, input);
- o_addqchr(dest, ch, 1);
+ o_addqchr(dest, ch);
continue;
}
// if (ch == '$') ...
if (ch == '\\') { /* \x. Copy both chars unless it is \` */
int ch2 = i_getch(input);
if (ch2 != '`' && ch2 != '$' && ch2 != '\\')
- o_addqchr(dest, ch, 1);
+ o_addqchr(dest, ch);
ch = ch2;
}
if (ch == EOF)
break;
- o_addqchr(dest, ch, 1);
+ o_addqchr(dest, ch);
}
}
/* Process $(cmd) - copy contents until ")" is seen. Complicated by
if (ch == ')')
if (--count < 0)
break;
- o_addqchr(dest, ch, 1);
+ o_addqchr(dest, ch);
if (ch == '\'') {
add_till_single_quote(dest, input);
- o_addqchr(dest, ch, 1);
+ o_addqchr(dest, ch);
continue;
}
if (ch == '"') {
add_till_double_quote(dest, input);
- o_addqchr(dest, ch, 1);
+ o_addqchr(dest, ch);
+ continue;
+ }
+ if (ch == '\\') { /* \x. Copy verbatim. Important for \(, \) */
+ ch = i_getch(input);
+ if (ch == EOF)
+ break;
+ o_addqchr(dest, ch);
continue;
}
}
o_addchr(dest, SPECIAL_VAR_SYMBOL);
break;
#if ENABLE_HUSH_TICK
- case '(':
+ case '(': {
+ //int pos = dest->length;
i_getch(input);
o_addchr(dest, SPECIAL_VAR_SYMBOL);
o_addchr(dest, quote_mask | '`');
add_till_closing_curly_brace(dest, input);
+ //bb_error_msg("RES '%s'", dest->data + pos);
o_addchr(dest, SPECIAL_VAR_SYMBOL);
break;
+ }
#endif
case '-':
case '_':
return 1;
break;
default:
- o_addqchr(dest, '$', dest->o_quote);
+ o_addQchr(dest, '$');
}
debug_printf_parse("handle_dollar return 0\n");
return 0;
debug_printf_parse("parse_stream return 1: unterminated \"\n");
return 1;
}
- o_addqchr(dest, ch, dest->o_quote);
+ o_addQchr(dest, ch);
continue;
}
if (m == CHAR_IFS) {
i_getch(input);
}
} else {
- o_addqchr(dest, ch, dest->o_quote);
+ o_addQchr(dest, ch);
}
break;
case '\\':
*/
if (dest->o_quote) {
if (strchr("$`\"\\", next) != NULL) {
- o_addqchr(dest, i_getch(input), 1);
+ o_addqchr(dest, i_getch(input));
} else {
- o_addqchr(dest, '\\', 1);
+ o_addqchr(dest, '\\');
}
} else {
o_addchr(dest, '\\');
ch = i_getch(input);
if (ch == EOF || ch == '\'')
break;
- o_addqchr(dest, ch, 1);
+ o_addqchr(dest, ch);
}
if (ch == EOF) {
syntax("unterminated '");
break;
case ')':
case '}':
- syntax("unexpected }"); /* Proper use of this character is caught by end_trigger */
+ /* proper use of this character is caught by end_trigger */
+ syntax("unexpected } or )");
debug_printf_parse("parse_stream return 1: unexpected '}'\n");
return 1;
default:
* from builtin_source() and builtin_eval() */
static int parse_and_run_stream(struct in_str *inp, int parse_flag)
{
+//TODO: PARSEFLAG_SEMICOLON bit is always set in parse_flag. fishy
+//TODO: PARSEFLAG_REPARSING bit is never set (grep for it). wow
struct p_context ctx;
o_string temp = NULL_O_STRING;
int rcode;
do {
+// It always has PARSEFLAG_SEMICOLON, can we remove all checks for this bit?
+// After that, the whole parse_type fiels is not needed.
ctx.parse_type = parse_flag;
initialize_context(&ctx);
update_charmap();
static int parse_and_run_string(const char *s, int parse_flag)
{
+//TODO: PARSEFLAG_SEMICOLON bit is always set in parse_flag. fishy
struct in_str input;
setup_string_in_str(&input, s);
return parse_and_run_stream(&input, parse_flag);