Index: bin/sh/options.h =================================================================== --- bin/sh/options.h +++ bin/sh/options.h @@ -68,9 +68,10 @@ #define nologflag optval[19] #define pipefailflag optval[20] #define verifyflag optval[21] +#define braceexpandflag optval[22] #define NSHORTOPTS 19 -#define NOPTS 22 +#define NOPTS 23 extern char optval[NOPTS]; extern const char optletter[NSHORTOPTS]; @@ -100,6 +101,7 @@ "\005nolog" "\010pipefail" "\006verify" + "\013braceexpand" ; #endif Index: bin/sh/parser.c =================================================================== --- bin/sh/parser.c +++ bin/sh/parser.c @@ -39,7 +39,9 @@ #endif /* not lint */ #include #include +#include #include +#include #include #include #include @@ -132,6 +134,7 @@ static void setprompt(int); static int pgetc_linecont(void); static void getusername(char *, size_t); +static bool expandbrace(FILE *, const char *); static void * @@ -820,6 +823,174 @@ return (t); } +static bool expandsequence(FILE *stream, const char prefix[static 1], const char *pattern) { + char format[] = "%0.*d%.*s"; + int start, end, step, i; + const char *p, *close; + char *portal, *endptr; + const char *terminator = strchr(pattern, '\0'); + int prefixlen = (int)(pattern - prefix - 1); + int size = prefixlen + 32; + int zeroes = 0; + + /* Parse start value (letter or number) */ + if (isalpha(pattern[0])) { + start = pattern[0]; + p = pattern + 1; + } else if (isspace(pattern[0])) + /* strtol accepts space here, brace expansion doesn't */ + return false; + else { + zeroes = strspn(pattern, "0"); + start = strtol(pattern + zeroes, &endptr, 10); + if (pattern == endptr) + return false; + p = endptr; + } + + /* Verify ".." separator */ + if (*p++ != '.' || *p++ != '.') + return false; + + /* Parse end value (must match type of start) */ + if (isalpha(p[0])) { + if (!isalpha(pattern[0])) + return false; + end = *p++; + format[4] = 'c'; + } else if (isspace(p[0])) + return false; + else { + int z = strspn(p, "0"); + + if (z > zeroes) + zeroes = z; + close = p + z; + end = strtol(close, &endptr, 10); + if (close == endptr) + return false; + p = endptr; + } + + /* Parse optional step value */ + if (*p == '}') + step = 1; + else if (*p++ == '.' && *p++ == '.') { + if (*p == '+' || *p == '-') + p++; /* Direction will be determined by testing start > end */ + if (!isdigit(*p)) + return false; + step = strtol(p, &endptr, 10); + if (*endptr != '}') + return false; + p = endptr; + } else + return false; + close = p; + + /* Each of the generated terms will start from the same prefix */ + if (!(portal = malloc(size))) + return false; + snprintf(portal, size, "%.*s", prefixlen, prefix); + size -= prefixlen; + + /* Generate sequence */ + i = start; + if (start > end) + step = -step; + goto first_iteration; + while (start > end ? i >= end : i <= end) { + if (isalpha(pattern[0]) && !isalpha(i)) + break; + fputc(' ', stream); +first_iteration: + /* Copy the generated term and the suffix */ + snprintf(portal + prefixlen, size, format, + zeroes + 1, i, (int)(terminator - close), close + 1); + + /* The string may be a brace expansion pattern too */ + expandbrace(stream, portal); + i += step; + } + free(portal); + + return true; +} + +static bool expandbrace(FILE *stream, const char pattern[static 1]) { + const char *close, *separator, *last; + int prefixlen; + char *portal; + const char *open = pattern; + const char *end = strchr(pattern, '\0'); + int size = end - pattern; + + /* Unescaped braces and one of the separators are required */ + if (open && open[0] != '{') + while ((open = strchr(open + 1, '{'))) { + if (open[-1] == '\\') + continue; + if (open[-1] != '$') + break; + /* '${' restarts the search for '{' from the closest '}' */ + if (!(open = strchr(open + 1, '}'))) + break; + } + + /* When checking for sequence, don't go past the first closing brace */ + if (open && (separator = strpbrk(open + 1, ".}"))) + if (separator[0] == '.' && separator[1] == '.') + if (expandsequence(stream, pattern, open + 1)) + return true; + + /* If not a sequence, the comma is required */ + if ((separator = open)) + while ((separator = strchr(separator + 1, ','))) + if (separator[-1] != '\\') + break; + + /* Only look for closing brace once the comma has been found */ + if ((close = separator)) + while ((close = strchr(close + 1, '}'))) + if (close[-1] != '\\') + break; + + /* Not a brace expansion pattern, treat literally */ + if (!close || !(portal = malloc(size))) { + fputs(pattern, stream); + return false; + } + + /* Each of the generated alternatives will start from the same prefix */ + prefixlen = snprintf(portal, size, "%.*s", + (int)(open - pattern), pattern); + size -= prefixlen; + + /* Generate alternatives */ + last = open + 1; + goto first_iteration; + for (;;) { + fputc(' ', stream); +first_iteration: + /* Copy the generated alternative and the suffix */ + snprintf(portal + prefixlen, size, "%.*s%.*s", + (int)((separator ? separator : close) - last), last, + (int)(end - close), close + 1); + + /* The string may be a brace expansion pattern too */ + expandbrace(stream, portal); + + /* Find the next alternative */ + if (!separator) + break; + last = separator + 1; + separator = memchr(last, ',', close - separator - 1); + } + free(portal); + return true; +} + + static int readtoken(void) { @@ -863,6 +1034,23 @@ pushstring(ap->val, strlen(ap->val), ap); goto top; } + if (braceexpandflag && wordtext != NULL) { + FILE *memstream; + char *buf; + size_t len; + bool expanded; + + if ((memstream = open_memstream(&buf, &len)) == NULL) + goto out; + + expanded = expandbrace(memstream, wordtext); + fclose(memstream); + + if (expanded) { + pushstring(buf, len, NULL); + goto top; + } + } } out: if (t != TNOT) Index: bin/sh/sh.1 =================================================================== --- bin/sh/sh.1 +++ bin/sh/sh.1 @@ -364,6 +364,8 @@ when sourcing files or loading profiles. .\" See also .\" .Xr mac_veriexec 4 . TODO Does not exist; write it. +.It Li braceexpand +Enable brace expansion. .El .Pp The