mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-02-07 15:03:34 +00:00
844 lines
20 KiB
C
844 lines
20 KiB
C
|
/* braces.c -- code for doing word expansion in curly braces. */
|
|||
|
|
|||
|
/* Copyright (C) 1987-2020 Free Software Foundation, Inc.
|
|||
|
|
|||
|
This file is part of GNU Bash, the Bourne Again SHell.
|
|||
|
|
|||
|
Bash is free software: you can redistribute it and/or modify
|
|||
|
it under the terms of the GNU General Public License as published by
|
|||
|
the Free Software Foundation, either version 3 of the License, or
|
|||
|
(at your option) any later version.
|
|||
|
|
|||
|
Bash is distributed in the hope that it will be useful,
|
|||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|||
|
GNU General Public License for more details.
|
|||
|
|
|||
|
You should have received a copy of the GNU General Public License
|
|||
|
along with Bash. If not, see <http://www.gnu.org/licenses/>.
|
|||
|
*/
|
|||
|
|
|||
|
/* Stuff in curly braces gets expanded before all other shell expansions. */
|
|||
|
|
|||
|
#include "config.h"
|
|||
|
|
|||
|
#if defined (BRACE_EXPANSION)
|
|||
|
|
|||
|
#if defined (HAVE_UNISTD_H)
|
|||
|
# ifdef _MINIX
|
|||
|
# include <sys/types.h>
|
|||
|
# endif
|
|||
|
# include <unistd.h>
|
|||
|
#endif
|
|||
|
|
|||
|
#include <errno.h>
|
|||
|
|
|||
|
#include "bashansi.h"
|
|||
|
#include "bashintl.h"
|
|||
|
|
|||
|
#if defined (SHELL)
|
|||
|
# include "shell.h"
|
|||
|
#else
|
|||
|
# if defined (TEST)
|
|||
|
typedef char *WORD_DESC;
|
|||
|
typedef char **WORD_LIST;
|
|||
|
#define _(X) X
|
|||
|
# endif /* TEST */
|
|||
|
#endif /* SHELL */
|
|||
|
|
|||
|
#include "typemax.h" /* INTMAX_MIN, INTMAX_MAX */
|
|||
|
#include "general.h"
|
|||
|
#include "shmbutil.h"
|
|||
|
#include "chartypes.h"
|
|||
|
|
|||
|
#ifndef errno
|
|||
|
extern int errno;
|
|||
|
#endif
|
|||
|
|
|||
|
#define brace_whitespace(c) (!(c) || (c) == ' ' || (c) == '\t' || (c) == '\n')
|
|||
|
|
|||
|
#define BRACE_SEQ_SPECIFIER ".."
|
|||
|
|
|||
|
extern int asprintf PARAMS((char **, const char *, ...)) __attribute__((__format__ (printf, 2, 3)));
|
|||
|
|
|||
|
/* Basic idea:
|
|||
|
|
|||
|
Segregate the text into 3 sections: preamble (stuff before an open brace),
|
|||
|
postamble (stuff after the matching close brace) and amble (stuff after
|
|||
|
preamble, and before postamble). Expand amble, and then tack on the
|
|||
|
expansions to preamble. Expand postamble, and tack on the expansions to
|
|||
|
the result so far.
|
|||
|
*/
|
|||
|
|
|||
|
/* The character which is used to separate arguments. */
|
|||
|
static const int brace_arg_separator = ',';
|
|||
|
|
|||
|
#if defined (PARAMS)
|
|||
|
static int brace_gobbler PARAMS((char *, size_t, int *, int));
|
|||
|
static char **expand_amble PARAMS((char *, size_t, int));
|
|||
|
static char **expand_seqterm PARAMS((char *, size_t));
|
|||
|
static char **mkseq PARAMS((intmax_t, intmax_t, intmax_t, int, int));
|
|||
|
static char **array_concat PARAMS((char **, char **));
|
|||
|
#else
|
|||
|
static int brace_gobbler ();
|
|||
|
static char **expand_amble ();
|
|||
|
static char **expand_seqterm ();
|
|||
|
static char **mkseq();
|
|||
|
static char **array_concat ();
|
|||
|
#endif
|
|||
|
|
|||
|
#if 0
|
|||
|
static void
|
|||
|
dump_result (a)
|
|||
|
char **a;
|
|||
|
{
|
|||
|
int i;
|
|||
|
|
|||
|
for (i = 0; a[i]; i++)
|
|||
|
printf ("dump_result: a[%d] = -%s-\n", i, a[i]);
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
/* Return an array of strings; the brace expansion of TEXT. */
|
|||
|
char **
|
|||
|
brace_expand (text)
|
|||
|
char *text;
|
|||
|
{
|
|||
|
register int start;
|
|||
|
size_t tlen;
|
|||
|
char *preamble, *postamble, *amble;
|
|||
|
size_t alen;
|
|||
|
char **tack, **result;
|
|||
|
int i, j, c, c1;
|
|||
|
|
|||
|
DECLARE_MBSTATE;
|
|||
|
|
|||
|
/* Find the text of the preamble. */
|
|||
|
tlen = strlen (text);
|
|||
|
i = 0;
|
|||
|
#if defined (CSH_BRACE_COMPAT)
|
|||
|
c = brace_gobbler (text, tlen, &i, '{'); /* } */
|
|||
|
#else
|
|||
|
/* Make sure that when we exit this loop, c == 0 or text[i] begins a
|
|||
|
valid brace expansion sequence. */
|
|||
|
do
|
|||
|
{
|
|||
|
c = brace_gobbler (text, tlen, &i, '{'); /* } */
|
|||
|
c1 = c;
|
|||
|
/* Verify that c begins a valid brace expansion word. If it doesn't, we
|
|||
|
go on. Loop stops when there are no more open braces in the word. */
|
|||
|
if (c)
|
|||
|
{
|
|||
|
start = j = i + 1; /* { */
|
|||
|
c = brace_gobbler (text, tlen, &j, '}');
|
|||
|
if (c == 0) /* it's not */
|
|||
|
{
|
|||
|
i++;
|
|||
|
c = c1;
|
|||
|
continue;
|
|||
|
}
|
|||
|
else /* it is */
|
|||
|
{
|
|||
|
c = c1;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
break;
|
|||
|
}
|
|||
|
while (c);
|
|||
|
#endif /* !CSH_BRACE_COMPAT */
|
|||
|
|
|||
|
preamble = (char *)xmalloc (i + 1);
|
|||
|
if (i > 0)
|
|||
|
strncpy (preamble, text, i);
|
|||
|
preamble[i] = '\0';
|
|||
|
|
|||
|
result = (char **)xmalloc (2 * sizeof (char *));
|
|||
|
result[0] = preamble;
|
|||
|
result[1] = (char *)NULL;
|
|||
|
|
|||
|
/* Special case. If we never found an exciting character, then
|
|||
|
the preamble is all of the text, so just return that. */
|
|||
|
if (c != '{')
|
|||
|
return (result);
|
|||
|
|
|||
|
/* Find the amble. This is the stuff inside this set of braces. */
|
|||
|
start = ++i;
|
|||
|
c = brace_gobbler (text, tlen, &i, '}');
|
|||
|
|
|||
|
/* What if there isn't a matching close brace? */
|
|||
|
if (c == 0)
|
|||
|
{
|
|||
|
#if defined (NOTDEF)
|
|||
|
/* Well, if we found an unquoted BRACE_ARG_SEPARATOR between START
|
|||
|
and I, then this should be an error. Otherwise, it isn't. */
|
|||
|
j = start;
|
|||
|
while (j < i)
|
|||
|
{
|
|||
|
if (text[j] == '\\')
|
|||
|
{
|
|||
|
j++;
|
|||
|
ADVANCE_CHAR (text, tlen, j);
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
if (text[j] == brace_arg_separator)
|
|||
|
{ /* { */
|
|||
|
strvec_dispose (result);
|
|||
|
set_exit_status (EXECUTION_FAILURE);
|
|||
|
report_error ("no closing `%c' in %s", '}', text);
|
|||
|
throw_to_top_level ();
|
|||
|
}
|
|||
|
ADVANCE_CHAR (text, tlen, j);
|
|||
|
}
|
|||
|
#endif
|
|||
|
free (preamble); /* Same as result[0]; see initialization. */
|
|||
|
result[0] = savestring (text);
|
|||
|
return (result);
|
|||
|
}
|
|||
|
|
|||
|
#if defined (SHELL)
|
|||
|
amble = substring (text, start, i);
|
|||
|
alen = i - start;
|
|||
|
#else
|
|||
|
amble = (char *)xmalloc (1 + (i - start));
|
|||
|
strncpy (amble, &text[start], (i - start));
|
|||
|
alen = i - start;
|
|||
|
amble[alen] = '\0';
|
|||
|
#endif
|
|||
|
|
|||
|
#if defined (SHELL)
|
|||
|
INITIALIZE_MBSTATE;
|
|||
|
|
|||
|
/* If the amble does not contain an unquoted BRACE_ARG_SEPARATOR, then
|
|||
|
just return without doing any expansion. */
|
|||
|
j = 0;
|
|||
|
while (amble[j])
|
|||
|
{
|
|||
|
if (amble[j] == '\\')
|
|||
|
{
|
|||
|
j++;
|
|||
|
ADVANCE_CHAR (amble, alen, j);
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
if (amble[j] == brace_arg_separator)
|
|||
|
break;
|
|||
|
|
|||
|
ADVANCE_CHAR (amble, alen, j);
|
|||
|
}
|
|||
|
|
|||
|
if (amble[j] == 0)
|
|||
|
{
|
|||
|
tack = expand_seqterm (amble, alen);
|
|||
|
if (tack)
|
|||
|
goto add_tack;
|
|||
|
else if (text[i + 1])
|
|||
|
{
|
|||
|
/* If the sequence expansion fails (e.g., because the integers
|
|||
|
overflow), but there is more in the string, try and process
|
|||
|
the rest of the string, which may contain additional brace
|
|||
|
expansions. Treat the unexpanded sequence term as a simple
|
|||
|
string (including the braces). */
|
|||
|
tack = strvec_create (2);
|
|||
|
tack[0] = savestring (text+start-1);
|
|||
|
tack[0][i-start+2] = '\0';
|
|||
|
tack[1] = (char *)0;
|
|||
|
goto add_tack;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
free (amble);
|
|||
|
free (preamble);
|
|||
|
result[0] = savestring (text);
|
|||
|
return (result);
|
|||
|
}
|
|||
|
}
|
|||
|
#endif /* SHELL */
|
|||
|
|
|||
|
tack = expand_amble (amble, alen, 0);
|
|||
|
add_tack:
|
|||
|
result = array_concat (result, tack);
|
|||
|
free (amble);
|
|||
|
if (tack != result)
|
|||
|
strvec_dispose (tack);
|
|||
|
|
|||
|
postamble = text + i + 1;
|
|||
|
|
|||
|
if (postamble && *postamble)
|
|||
|
{
|
|||
|
tack = brace_expand (postamble);
|
|||
|
result = array_concat (result, tack);
|
|||
|
if (tack != result)
|
|||
|
strvec_dispose (tack);
|
|||
|
}
|
|||
|
|
|||
|
return (result);
|
|||
|
}
|
|||
|
|
|||
|
/* Expand the text found inside of braces. We simply try to split the
|
|||
|
text at BRACE_ARG_SEPARATORs into separate strings. We then brace
|
|||
|
expand each slot which needs it, until there are no more slots which
|
|||
|
need it. */
|
|||
|
static char **
|
|||
|
expand_amble (text, tlen, flags)
|
|||
|
char *text;
|
|||
|
size_t tlen;
|
|||
|
int flags;
|
|||
|
{
|
|||
|
char **result, **partial, **tresult;
|
|||
|
char *tem;
|
|||
|
int start, i, c;
|
|||
|
|
|||
|
#if defined (SHELL)
|
|||
|
DECLARE_MBSTATE;
|
|||
|
#endif
|
|||
|
|
|||
|
result = (char **)NULL;
|
|||
|
|
|||
|
start = i = 0;
|
|||
|
c = 1;
|
|||
|
while (c)
|
|||
|
{
|
|||
|
c = brace_gobbler (text, tlen, &i, brace_arg_separator);
|
|||
|
#if defined (SHELL)
|
|||
|
tem = substring (text, start, i);
|
|||
|
#else
|
|||
|
tem = (char *)xmalloc (1 + (i - start));
|
|||
|
strncpy (tem, &text[start], (i - start));
|
|||
|
tem[i - start] = '\0';
|
|||
|
#endif
|
|||
|
|
|||
|
partial = brace_expand (tem);
|
|||
|
|
|||
|
if (!result)
|
|||
|
result = partial;
|
|||
|
else
|
|||
|
{
|
|||
|
register int lr, lp, j;
|
|||
|
|
|||
|
lr = strvec_len (result);
|
|||
|
lp = strvec_len (partial);
|
|||
|
|
|||
|
tresult = strvec_mresize (result, lp + lr + 1);
|
|||
|
if (tresult == 0)
|
|||
|
{
|
|||
|
internal_error (_("brace expansion: cannot allocate memory for %s"), tem);
|
|||
|
free (tem);
|
|||
|
strvec_dispose (partial);
|
|||
|
strvec_dispose (result);
|
|||
|
result = (char **)NULL;
|
|||
|
return result;
|
|||
|
}
|
|||
|
else
|
|||
|
result = tresult;
|
|||
|
|
|||
|
for (j = 0; j < lp; j++)
|
|||
|
result[lr + j] = partial[j];
|
|||
|
|
|||
|
result[lr + j] = (char *)NULL;
|
|||
|
free (partial);
|
|||
|
}
|
|||
|
free (tem);
|
|||
|
#if defined (SHELL)
|
|||
|
ADVANCE_CHAR (text, tlen, i);
|
|||
|
#else
|
|||
|
i++;
|
|||
|
#endif
|
|||
|
start = i;
|
|||
|
}
|
|||
|
return (result);
|
|||
|
}
|
|||
|
|
|||
|
#define ST_BAD 0
|
|||
|
#define ST_INT 1
|
|||
|
#define ST_CHAR 2
|
|||
|
#define ST_ZINT 3
|
|||
|
|
|||
|
static char **
|
|||
|
mkseq (start, end, incr, type, width)
|
|||
|
intmax_t start, end, incr;
|
|||
|
int type, width;
|
|||
|
{
|
|||
|
intmax_t n, prevn;
|
|||
|
int i, nelem;
|
|||
|
char **result, *t;
|
|||
|
|
|||
|
if (incr == 0)
|
|||
|
incr = 1;
|
|||
|
|
|||
|
if (start > end && incr > 0)
|
|||
|
incr = -incr;
|
|||
|
else if (start < end && incr < 0)
|
|||
|
{
|
|||
|
if (incr == INTMAX_MIN) /* Don't use -INTMAX_MIN */
|
|||
|
return ((char **)NULL);
|
|||
|
incr = -incr;
|
|||
|
}
|
|||
|
|
|||
|
/* Check that end-start will not overflow INTMAX_MIN, INTMAX_MAX. The +3
|
|||
|
and -2, not strictly necessary, are there because of the way the number
|
|||
|
of elements and value passed to strvec_create() are calculated below. */
|
|||
|
if (SUBOVERFLOW (end, start, INTMAX_MIN+3, INTMAX_MAX-2))
|
|||
|
return ((char **)NULL);
|
|||
|
|
|||
|
prevn = sh_imaxabs (end - start);
|
|||
|
/* Need to check this way in case INT_MAX == INTMAX_MAX */
|
|||
|
if (INT_MAX == INTMAX_MAX && (ADDOVERFLOW (prevn, 2, INT_MIN, INT_MAX)))
|
|||
|
return ((char **)NULL);
|
|||
|
/* Make sure the assignment to nelem below doesn't end up <= 0 due to
|
|||
|
intmax_t overflow */
|
|||
|
else if (ADDOVERFLOW ((prevn/sh_imaxabs(incr)), 1, INTMAX_MIN, INTMAX_MAX))
|
|||
|
return ((char **)NULL);
|
|||
|
|
|||
|
/* XXX - TOFIX: potentially allocating a lot of extra memory if
|
|||
|
imaxabs(incr) != 1 */
|
|||
|
/* Instead of a simple nelem = prevn + 1, something like:
|
|||
|
nelem = (prevn / imaxabs(incr)) + 1;
|
|||
|
would work */
|
|||
|
if ((prevn / sh_imaxabs (incr)) > INT_MAX - 3) /* check int overflow */
|
|||
|
return ((char **)NULL);
|
|||
|
nelem = (prevn / sh_imaxabs(incr)) + 1;
|
|||
|
result = strvec_mcreate (nelem + 1);
|
|||
|
if (result == 0)
|
|||
|
{
|
|||
|
internal_error (_("brace expansion: failed to allocate memory for %u elements"), (unsigned int)nelem);
|
|||
|
return ((char **)NULL);
|
|||
|
}
|
|||
|
|
|||
|
/* Make sure we go through the loop at least once, so {3..3} prints `3' */
|
|||
|
i = 0;
|
|||
|
n = start;
|
|||
|
do
|
|||
|
{
|
|||
|
#if defined (SHELL)
|
|||
|
if (ISINTERRUPT)
|
|||
|
{
|
|||
|
result[i] = (char *)NULL;
|
|||
|
strvec_dispose (result);
|
|||
|
result = (char **)NULL;
|
|||
|
}
|
|||
|
QUIT;
|
|||
|
#endif
|
|||
|
if (type == ST_INT)
|
|||
|
result[i++] = t = itos (n);
|
|||
|
else if (type == ST_ZINT)
|
|||
|
{
|
|||
|
int len, arg;
|
|||
|
arg = n;
|
|||
|
len = asprintf (&t, "%0*d", width, arg);
|
|||
|
result[i++] = t;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if (t = (char *)malloc (2))
|
|||
|
{
|
|||
|
t[0] = n;
|
|||
|
t[1] = '\0';
|
|||
|
}
|
|||
|
result[i++] = t;
|
|||
|
}
|
|||
|
|
|||
|
/* We failed to allocate memory for this number, so we bail. */
|
|||
|
if (t == 0)
|
|||
|
{
|
|||
|
char *p, lbuf[INT_STRLEN_BOUND(intmax_t) + 1];
|
|||
|
|
|||
|
/* Easier to do this than mess around with various intmax_t printf
|
|||
|
formats (%ld? %lld? %jd?) and PRIdMAX. */
|
|||
|
p = inttostr (n, lbuf, sizeof (lbuf));
|
|||
|
internal_error (_("brace expansion: failed to allocate memory for `%s'"), p);
|
|||
|
strvec_dispose (result);
|
|||
|
return ((char **)NULL);
|
|||
|
}
|
|||
|
|
|||
|
/* Handle overflow and underflow of n+incr */
|
|||
|
if (ADDOVERFLOW (n, incr, INTMAX_MIN, INTMAX_MAX))
|
|||
|
break;
|
|||
|
|
|||
|
n += incr;
|
|||
|
|
|||
|
if ((incr < 0 && n < end) || (incr > 0 && n > end))
|
|||
|
break;
|
|||
|
}
|
|||
|
while (1);
|
|||
|
|
|||
|
result[i] = (char *)0;
|
|||
|
return (result);
|
|||
|
}
|
|||
|
|
|||
|
static char **
|
|||
|
expand_seqterm (text, tlen)
|
|||
|
char *text;
|
|||
|
size_t tlen;
|
|||
|
{
|
|||
|
char *t, *lhs, *rhs;
|
|||
|
int lhs_t, rhs_t, lhs_l, rhs_l, width;
|
|||
|
intmax_t lhs_v, rhs_v, incr;
|
|||
|
intmax_t tl, tr;
|
|||
|
char **result, *ep, *oep;
|
|||
|
|
|||
|
t = strstr (text, BRACE_SEQ_SPECIFIER);
|
|||
|
if (t == 0)
|
|||
|
return ((char **)NULL);
|
|||
|
|
|||
|
lhs_l = t - text; /* index of start of BRACE_SEQ_SPECIFIER */
|
|||
|
lhs = substring (text, 0, lhs_l);
|
|||
|
rhs = substring (text, lhs_l + sizeof(BRACE_SEQ_SPECIFIER) - 1, tlen);
|
|||
|
|
|||
|
if (lhs[0] == 0 || rhs[0] == 0)
|
|||
|
{
|
|||
|
free (lhs);
|
|||
|
free (rhs);
|
|||
|
return ((char **)NULL);
|
|||
|
}
|
|||
|
|
|||
|
/* Now figure out whether LHS and RHS are integers or letters. Both
|
|||
|
sides have to match. */
|
|||
|
lhs_t = (legal_number (lhs, &tl)) ? ST_INT :
|
|||
|
((ISALPHA (lhs[0]) && lhs[1] == 0) ? ST_CHAR : ST_BAD);
|
|||
|
|
|||
|
/* Decide on rhs and whether or not it looks like the user specified
|
|||
|
an increment */
|
|||
|
ep = 0;
|
|||
|
if (ISDIGIT (rhs[0]) || ((rhs[0] == '+' || rhs[0] == '-') && ISDIGIT (rhs[1])))
|
|||
|
{
|
|||
|
rhs_t = ST_INT;
|
|||
|
errno = 0;
|
|||
|
tr = strtoimax (rhs, &ep, 10);
|
|||
|
if (errno == ERANGE || (ep && *ep != 0 && *ep != '.'))
|
|||
|
rhs_t = ST_BAD; /* invalid */
|
|||
|
}
|
|||
|
else if (ISALPHA (rhs[0]) && (rhs[1] == 0 || rhs[1] == '.'))
|
|||
|
{
|
|||
|
rhs_t = ST_CHAR;
|
|||
|
ep = rhs + 1;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
rhs_t = ST_BAD;
|
|||
|
ep = 0;
|
|||
|
}
|
|||
|
|
|||
|
incr = 1;
|
|||
|
if (rhs_t != ST_BAD)
|
|||
|
{
|
|||
|
oep = ep;
|
|||
|
errno = 0;
|
|||
|
if (ep && *ep == '.' && ep[1] == '.' && ep[2])
|
|||
|
incr = strtoimax (ep + 2, &ep, 10);
|
|||
|
if (*ep != 0 || errno == ERANGE)
|
|||
|
rhs_t = ST_BAD; /* invalid incr or overflow */
|
|||
|
tlen -= ep - oep;
|
|||
|
}
|
|||
|
|
|||
|
if (lhs_t != rhs_t || lhs_t == ST_BAD || rhs_t == ST_BAD)
|
|||
|
{
|
|||
|
free (lhs);
|
|||
|
free (rhs);
|
|||
|
return ((char **)NULL);
|
|||
|
}
|
|||
|
|
|||
|
/* OK, we have something. It's either a sequence of integers, ascending
|
|||
|
or descending, or a sequence or letters, ditto. Generate the sequence,
|
|||
|
put it into a string vector, and return it. */
|
|||
|
|
|||
|
if (lhs_t == ST_CHAR)
|
|||
|
{
|
|||
|
lhs_v = (unsigned char)lhs[0];
|
|||
|
rhs_v = (unsigned char)rhs[0];
|
|||
|
width = 1;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
lhs_v = tl; /* integer truncation */
|
|||
|
rhs_v = tr;
|
|||
|
|
|||
|
/* Decide whether or not the terms need zero-padding */
|
|||
|
rhs_l = tlen - lhs_l - sizeof (BRACE_SEQ_SPECIFIER) + 1;
|
|||
|
width = 0;
|
|||
|
if (lhs_l > 1 && lhs[0] == '0')
|
|||
|
width = lhs_l, lhs_t = ST_ZINT;
|
|||
|
if (lhs_l > 2 && lhs[0] == '-' && lhs[1] == '0')
|
|||
|
width = lhs_l, lhs_t = ST_ZINT;
|
|||
|
if (rhs_l > 1 && rhs[0] == '0' && width < rhs_l)
|
|||
|
width = rhs_l, lhs_t = ST_ZINT;
|
|||
|
if (rhs_l > 2 && rhs[0] == '-' && rhs[1] == '0' && width < rhs_l)
|
|||
|
width = rhs_l, lhs_t = ST_ZINT;
|
|||
|
|
|||
|
if (width < lhs_l && lhs_t == ST_ZINT)
|
|||
|
width = lhs_l;
|
|||
|
if (width < rhs_l && lhs_t == ST_ZINT)
|
|||
|
width = rhs_l;
|
|||
|
}
|
|||
|
|
|||
|
result = mkseq (lhs_v, rhs_v, incr, lhs_t, width);
|
|||
|
|
|||
|
free (lhs);
|
|||
|
free (rhs);
|
|||
|
|
|||
|
return (result);
|
|||
|
}
|
|||
|
|
|||
|
/* Start at INDEX, and skip characters in TEXT. Set INDEX to the
|
|||
|
index of the character matching SATISFY. This understands about
|
|||
|
quoting. Return the character that caused us to stop searching;
|
|||
|
this is either the same as SATISFY, or 0. */
|
|||
|
/* If SATISFY is `}', we are looking for a brace expression, so we
|
|||
|
should enforce the rules that govern valid brace expansions:
|
|||
|
1) to count as an arg separator, a comma or `..' has to be outside
|
|||
|
an inner set of braces.
|
|||
|
*/
|
|||
|
static int
|
|||
|
brace_gobbler (text, tlen, indx, satisfy)
|
|||
|
char *text;
|
|||
|
size_t tlen;
|
|||
|
int *indx;
|
|||
|
int satisfy;
|
|||
|
{
|
|||
|
register int i, c, quoted, level, commas, pass_next;
|
|||
|
#if defined (SHELL)
|
|||
|
int si;
|
|||
|
char *t;
|
|||
|
#endif
|
|||
|
DECLARE_MBSTATE;
|
|||
|
|
|||
|
level = quoted = pass_next = 0;
|
|||
|
#if defined (CSH_BRACE_COMPAT)
|
|||
|
commas = 1;
|
|||
|
#else
|
|||
|
commas = (satisfy == '}') ? 0 : 1;
|
|||
|
#endif
|
|||
|
|
|||
|
i = *indx;
|
|||
|
while (c = text[i])
|
|||
|
{
|
|||
|
if (pass_next)
|
|||
|
{
|
|||
|
pass_next = 0;
|
|||
|
#if defined (SHELL)
|
|||
|
ADVANCE_CHAR (text, tlen, i);
|
|||
|
#else
|
|||
|
i++;
|
|||
|
#endif
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
/* A backslash escapes the next character. This allows backslash to
|
|||
|
escape the quote character in a double-quoted string. */
|
|||
|
if (c == '\\' && (quoted == 0 || quoted == '"' || quoted == '`'))
|
|||
|
{
|
|||
|
pass_next = 1;
|
|||
|
i++;
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
#if defined (SHELL)
|
|||
|
/* If compiling for the shell, treat ${...} like \{...} */
|
|||
|
if (c == '$' && text[i+1] == '{' && quoted != '\'') /* } */
|
|||
|
{
|
|||
|
pass_next = 1;
|
|||
|
i++;
|
|||
|
if (quoted == 0)
|
|||
|
level++;
|
|||
|
continue;
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
if (quoted)
|
|||
|
{
|
|||
|
if (c == quoted)
|
|||
|
quoted = 0;
|
|||
|
#if defined (SHELL)
|
|||
|
/* The shell allows quoted command substitutions */
|
|||
|
if (quoted == '"' && c == '$' && text[i+1] == '(') /*)*/
|
|||
|
goto comsub;
|
|||
|
#endif
|
|||
|
#if defined (SHELL)
|
|||
|
ADVANCE_CHAR (text, tlen, i);
|
|||
|
#else
|
|||
|
i++;
|
|||
|
#endif
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
if (c == '"' || c == '\'' || c == '`')
|
|||
|
{
|
|||
|
quoted = c;
|
|||
|
i++;
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
#if defined (SHELL)
|
|||
|
/* Pass new-style command and process substitutions through unchanged. */
|
|||
|
if ((c == '$' || c == '<' || c == '>') && text[i+1] == '(') /* ) */
|
|||
|
{
|
|||
|
comsub:
|
|||
|
si = i + 2;
|
|||
|
t = extract_command_subst (text, &si, 0);
|
|||
|
i = si;
|
|||
|
free (t);
|
|||
|
i++;
|
|||
|
continue;
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
if (c == satisfy && level == 0 && quoted == 0 && commas > 0)
|
|||
|
{
|
|||
|
/* We ignore an open brace surrounded by whitespace, and also
|
|||
|
an open brace followed immediately by a close brace preceded
|
|||
|
by whitespace. */
|
|||
|
if (c == '{' &&
|
|||
|
((!i || brace_whitespace (text[i - 1])) &&
|
|||
|
(brace_whitespace (text[i + 1]) || text[i + 1] == '}')))
|
|||
|
{
|
|||
|
i++;
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
if (c == '{')
|
|||
|
level++;
|
|||
|
else if (c == '}' && level)
|
|||
|
level--;
|
|||
|
#if !defined (CSH_BRACE_COMPAT)
|
|||
|
else if (satisfy == '}' && c == brace_arg_separator && level == 0)
|
|||
|
commas++;
|
|||
|
else if (satisfy == '}' && STREQN (text+i, BRACE_SEQ_SPECIFIER, 2) &&
|
|||
|
text[i+2] != satisfy && level == 0)
|
|||
|
commas++;
|
|||
|
#endif
|
|||
|
|
|||
|
#if defined (SHELL)
|
|||
|
ADVANCE_CHAR (text, tlen, i);
|
|||
|
#else
|
|||
|
i++;
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
*indx = i;
|
|||
|
return (c);
|
|||
|
}
|
|||
|
|
|||
|
/* Return a new array of strings which is the result of appending each
|
|||
|
string in ARR2 to each string in ARR1. The resultant array is
|
|||
|
len (arr1) * len (arr2) long. For convenience, ARR1 (and its contents)
|
|||
|
are free ()'ed. ARR1 can be NULL, in that case, a new version of ARR2
|
|||
|
is returned. */
|
|||
|
static char **
|
|||
|
array_concat (arr1, arr2)
|
|||
|
char **arr1, **arr2;
|
|||
|
{
|
|||
|
register int i, j, len, len1, len2;
|
|||
|
register char **result;
|
|||
|
|
|||
|
if (arr1 == 0)
|
|||
|
return (arr2); /* XXX - see if we can get away without copying? */
|
|||
|
|
|||
|
if (arr2 == 0)
|
|||
|
return (arr1); /* XXX - caller expects us to free arr1 */
|
|||
|
|
|||
|
/* We can only short-circuit if the array consists of a single null element;
|
|||
|
otherwise we need to replicate the contents of the other array and
|
|||
|
prefix (or append, below) an empty element to each one. */
|
|||
|
if (arr1[0] && arr1[0][0] == 0 && arr1[1] == 0)
|
|||
|
{
|
|||
|
strvec_dispose (arr1);
|
|||
|
return (arr2); /* XXX - use flags to see if we can avoid copying here */
|
|||
|
}
|
|||
|
|
|||
|
if (arr2[0] && arr2[0][0] == 0 && arr2[1] == 0)
|
|||
|
return (arr1); /* XXX - rather than copying and freeing it */
|
|||
|
|
|||
|
len1 = strvec_len (arr1);
|
|||
|
len2 = strvec_len (arr2);
|
|||
|
|
|||
|
result = (char **)malloc ((1 + (len1 * len2)) * sizeof (char *));
|
|||
|
if (result == 0)
|
|||
|
return (result);
|
|||
|
|
|||
|
len = 0;
|
|||
|
for (i = 0; i < len1; i++)
|
|||
|
{
|
|||
|
int strlen_1 = strlen (arr1[i]);
|
|||
|
|
|||
|
for (j = 0; j < len2; j++)
|
|||
|
{
|
|||
|
result[len] = (char *)xmalloc (1 + strlen_1 + strlen (arr2[j]));
|
|||
|
strcpy (result[len], arr1[i]);
|
|||
|
strcpy (result[len] + strlen_1, arr2[j]);
|
|||
|
len++;
|
|||
|
}
|
|||
|
free (arr1[i]);
|
|||
|
}
|
|||
|
free (arr1);
|
|||
|
|
|||
|
result[len] = (char *)NULL;
|
|||
|
return (result);
|
|||
|
}
|
|||
|
|
|||
|
#if defined (TEST)
|
|||
|
#include <stdio.h>
|
|||
|
|
|||
|
void *
|
|||
|
xmalloc(n)
|
|||
|
size_t n;
|
|||
|
{
|
|||
|
return (malloc (n));
|
|||
|
}
|
|||
|
|
|||
|
void *
|
|||
|
xrealloc(p, n)
|
|||
|
void *p;
|
|||
|
size_t n;
|
|||
|
{
|
|||
|
return (realloc (p, n));
|
|||
|
}
|
|||
|
|
|||
|
int
|
|||
|
internal_error (format, arg1, arg2)
|
|||
|
char *format, *arg1, *arg2;
|
|||
|
{
|
|||
|
fprintf (stderr, format, arg1, arg2);
|
|||
|
fprintf (stderr, "\n");
|
|||
|
}
|
|||
|
|
|||
|
main ()
|
|||
|
{
|
|||
|
char example[256];
|
|||
|
|
|||
|
for (;;)
|
|||
|
{
|
|||
|
char **result;
|
|||
|
int i;
|
|||
|
|
|||
|
fprintf (stderr, "brace_expand> ");
|
|||
|
|
|||
|
if ((!fgets (example, 256, stdin)) ||
|
|||
|
(strncmp (example, "quit", 4) == 0))
|
|||
|
break;
|
|||
|
|
|||
|
if (strlen (example))
|
|||
|
example[strlen (example) - 1] = '\0';
|
|||
|
|
|||
|
result = brace_expand (example);
|
|||
|
|
|||
|
for (i = 0; result[i]; i++)
|
|||
|
printf ("%s\n", result[i]);
|
|||
|
|
|||
|
strvec_dispose (result);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Local variables:
|
|||
|
* compile-command: "gcc -g -Bstatic -DTEST -o brace_expand braces.c general.o"
|
|||
|
* end:
|
|||
|
*/
|
|||
|
|
|||
|
#endif /* TEST */
|
|||
|
#endif /* BRACE_EXPANSION */
|