summaryrefslogtreecommitdiffstats
path: root/src/parse.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/parse.c')
-rw-r--r--src/parse.c267
1 files changed, 190 insertions, 77 deletions
diff --git a/src/parse.c b/src/parse.c
index a3e32fe..8e454e4 100644
--- a/src/parse.c
+++ b/src/parse.c
@@ -8,12 +8,12 @@
* flags like always-true options, and skipping over paths wherever they appear.
*/
+#include "prelude.h"
#include "parse.h"
#include "alloc.h"
#include "bfstd.h"
#include "bftw.h"
#include "color.h"
-#include "config.h"
#include "ctx.h"
#include "diag.h"
#include "dir.h"
@@ -115,21 +115,28 @@ struct bfs_parser {
};
/**
- * Possible token types.
+ * Token types and flags.
*/
-enum token_type {
+enum token_info {
/** A flag. */
- T_FLAG,
+ T_FLAG = 1,
/** A root path. */
- T_PATH,
+ T_PATH = 2,
/** An option. */
- T_OPTION,
+ T_OPTION = 3,
/** A test. */
- T_TEST,
+ T_TEST = 4,
/** An action. */
- T_ACTION,
+ T_ACTION = 5,
/** An operator. */
- T_OPERATOR,
+ T_OPERATOR = 6,
+ /** Mask for token types. */
+ T_TYPE = (1 << 3) - 1,
+
+ /** A token can match a prefix of an argument, like -On, -newerXY, etc. */
+ T_PREFIX = 1 << 3,
+ /** A flag that takes an argument. */
+ T_NEEDS_ARG = 1 << 4,
};
/**
@@ -160,7 +167,6 @@ static void highlight_args(const struct bfs_ctx *ctx, char **argv, size_t argc,
*/
attr(printf(2, 3))
static void parse_error(const struct bfs_parser *parser, const char *format, ...) {
- int error = errno;
const struct bfs_ctx *ctx = parser->ctx;
bool highlight[ctx->argc];
@@ -170,7 +176,6 @@ static void parse_error(const struct bfs_parser *parser, const char *format, ...
va_list args;
va_start(args, format);
- errno = error;
bfs_verror(parser->ctx, format, args);
va_end(args);
}
@@ -180,7 +185,6 @@ static void parse_error(const struct bfs_parser *parser, const char *format, ...
*/
attr(printf(4, 5))
static void parse_argv_error(const struct bfs_parser *parser, char **argv, size_t argc, const char *format, ...) {
- int error = errno;
const struct bfs_ctx *ctx = parser->ctx;
bool highlight[ctx->argc];
@@ -190,7 +194,6 @@ static void parse_argv_error(const struct bfs_parser *parser, char **argv, size_
va_list args;
va_start(args, format);
- errno = error;
bfs_verror(ctx, format, args);
va_end(args);
}
@@ -200,7 +203,6 @@ static void parse_argv_error(const struct bfs_parser *parser, char **argv, size_
*/
attr(printf(6, 7))
static void parse_conflict_error(const struct bfs_parser *parser, char **argv1, size_t argc1, char **argv2, size_t argc2, const char *format, ...) {
- int error = errno;
const struct bfs_ctx *ctx = parser->ctx;
bool highlight[ctx->argc];
@@ -211,7 +213,6 @@ static void parse_conflict_error(const struct bfs_parser *parser, char **argv1,
va_list args;
va_start(args, format);
- errno = error;
bfs_verror(ctx, format, args);
va_end(args);
}
@@ -221,14 +222,12 @@ static void parse_conflict_error(const struct bfs_parser *parser, char **argv1,
*/
attr(printf(3, 4))
static void parse_expr_error(const struct bfs_parser *parser, const struct bfs_expr *expr, const char *format, ...) {
- int error = errno;
const struct bfs_ctx *ctx = parser->ctx;
bfs_expr_error(ctx, expr);
va_list args;
va_start(args, format);
- errno = error;
bfs_verror(ctx, format, args);
va_end(args);
}
@@ -238,7 +237,6 @@ static void parse_expr_error(const struct bfs_parser *parser, const struct bfs_e
*/
attr(printf(2, 3))
static bool parse_warning(const struct bfs_parser *parser, const char *format, ...) {
- int error = errno;
const struct bfs_ctx *ctx = parser->ctx;
bool highlight[ctx->argc];
@@ -250,7 +248,6 @@ static bool parse_warning(const struct bfs_parser *parser, const char *format, .
va_list args;
va_start(args, format);
- errno = error;
bool ret = bfs_vwarning(parser->ctx, format, args);
va_end(args);
return ret;
@@ -261,7 +258,6 @@ static bool parse_warning(const struct bfs_parser *parser, const char *format, .
*/
attr(printf(6, 7))
static bool parse_conflict_warning(const struct bfs_parser *parser, char **argv1, size_t argc1, char **argv2, size_t argc2, const char *format, ...) {
- int error = errno;
const struct bfs_ctx *ctx = parser->ctx;
bool highlight[ctx->argc];
@@ -274,7 +270,6 @@ static bool parse_conflict_warning(const struct bfs_parser *parser, char **argv1
va_list args;
va_start(args, format);
- errno = error;
bool ret = bfs_vwarning(ctx, format, args);
va_end(args);
return ret;
@@ -285,7 +280,6 @@ static bool parse_conflict_warning(const struct bfs_parser *parser, char **argv1
*/
attr(printf(3, 4))
static bool parse_expr_warning(const struct bfs_parser *parser, const struct bfs_expr *expr, const char *format, ...) {
- int error = errno;
const struct bfs_ctx *ctx = parser->ctx;
if (!bfs_expr_warning(ctx, expr)) {
@@ -294,7 +288,6 @@ static bool parse_expr_warning(const struct bfs_parser *parser, const struct bfs
va_list args;
va_start(args, format);
- errno = error;
bool ret = bfs_vwarning(ctx, format, args);
va_end(args);
return ret;
@@ -381,7 +374,7 @@ static int expr_open(struct bfs_parser *parser, struct bfs_expr *expr, const cha
return 0;
fail:
- parse_expr_error(parser, expr, "%m.\n");
+ parse_expr_error(parser, expr, "%s.\n", errstr());
if (cfile) {
cfclose(cfile);
} else if (file) {
@@ -401,7 +394,7 @@ static int stat_arg(const struct bfs_parser *parser, char **arg, struct bfs_stat
int ret = bfs_stat(AT_FDCWD, *arg, flags, sb);
if (ret != 0) {
- parse_argv_error(parser, arg, 1, "%m.\n");
+ parse_argv_error(parser, arg, 1, "%s.\n", errstr());
}
return ret;
}
@@ -414,7 +407,9 @@ static struct bfs_expr *parse_expr(struct bfs_parser *parser);
/**
* Advance by a single token.
*/
-static char **parser_advance(struct bfs_parser *parser, enum token_type type, size_t argc) {
+static char **parser_advance(struct bfs_parser *parser, enum token_info type, size_t argc) {
+ bfs_assert(type == (type & T_TYPE));
+
if (type != T_FLAG && type != T_PATH) {
parser->expr_started = true;
}
@@ -517,20 +512,14 @@ enum int_flags {
* Parse an integer.
*/
static const char *parse_int(const struct bfs_parser *parser, char **arg, const char *str, void *result, enum int_flags flags) {
- // strtoll() skips leading spaces, but we want to reject them
- if (xisspace(str[0])) {
- goto bad;
- }
-
int base = flags & IF_BASE_MASK;
if (base == 0) {
base = 10;
}
char *endptr;
- errno = 0;
- long long value = strtoll(str, &endptr, base);
- if (errno != 0) {
+ long long value;
+ if (xstrtoll(str, &endptr, base, &value) != 0) {
if (errno == ERANGE) {
goto range;
} else {
@@ -538,13 +527,6 @@ static const char *parse_int(const struct bfs_parser *parser, char **arg, const
}
}
- // https://github.com/llvm/llvm-project/issues/64946
- sanitize_init(&endptr);
-
- if (endptr == str) {
- goto bad;
- }
-
if (!(flags & IF_PARTIAL_OK) && *endptr != '\0') {
goto bad;
}
@@ -657,9 +639,11 @@ static struct bfs_expr *parse_nullary_flag(struct bfs_parser *parser) {
*/
static struct bfs_expr *parse_unary_flag(struct bfs_parser *parser) {
const char *arg = parser->argv[0];
+ char flag = arg[strlen(arg) - 1];
+
const char *value = parser->argv[1];
if (!value) {
- parse_error(parser, "${cyn}%s${rs} needs a value.\n", arg);
+ parse_error(parser, "${cyn}-%c${rs} needs a value.\n", flag);
return NULL;
}
@@ -667,6 +651,29 @@ static struct bfs_expr *parse_unary_flag(struct bfs_parser *parser) {
}
/**
+ * Parse a prefix flag like -O3, -j8, etc.
+ */
+static struct bfs_expr *parse_prefix_flag(struct bfs_parser *parser, char flag, bool allow_separate, const char **value) {
+ const char *arg = parser->argv[0];
+
+ const char *suffix = strchr(arg, flag) + 1;
+ if (*suffix) {
+ *value = suffix;
+ return parse_nullary_flag(parser);
+ }
+
+ suffix = parser->argv[1];
+ if (allow_separate && suffix) {
+ *value = suffix;
+ } else {
+ parse_error(parser, "${cyn}-%c${rs} needs a value.\n", flag);
+ return NULL;
+ }
+
+ return parse_unary_flag(parser);
+}
+
+/**
* Parse a single option.
*/
static struct bfs_expr *parse_option(struct bfs_parser *parser, size_t argc) {
@@ -811,7 +818,8 @@ static bool parse_debug_flag(const char *flag, size_t len, const char *expected)
static struct bfs_expr *parse_debug(struct bfs_parser *parser, int arg1, int arg2) {
struct bfs_ctx *ctx = parser->ctx;
- struct bfs_expr *expr = parse_unary_flag(parser);
+ const char *flags;
+ struct bfs_expr *expr = parse_prefix_flag(parser, 'D', true, &flags);
if (!expr) {
cfprintf(ctx->cerr, "\n");
debug_help(ctx->cerr);
@@ -820,7 +828,7 @@ static struct bfs_expr *parse_debug(struct bfs_parser *parser, int arg1, int arg
bool unrecognized = false;
- for (const char *flag = expr->argv[1], *next; flag; flag = next) {
+ for (const char *flag = flags, *next; flag; flag = next) {
size_t len = strcspn(flag, ",");
if (flag[len]) {
next = flag + len + 1;
@@ -868,21 +876,22 @@ static struct bfs_expr *parse_debug(struct bfs_parser *parser, int arg1, int arg
* Parse -On.
*/
static struct bfs_expr *parse_optlevel(struct bfs_parser *parser, int arg1, int arg2) {
- struct bfs_expr *expr = parse_nullary_flag(parser);
+ const char *arg;
+ struct bfs_expr *expr = parse_prefix_flag(parser, 'O', false, &arg);
if (!expr) {
return NULL;
}
int *optlevel = &parser->ctx->optlevel;
- if (strcmp(expr->argv[0], "-Ofast") == 0) {
+ if (strcmp(arg, "fast") == 0) {
*optlevel = 4;
- } else if (!parse_int(parser, expr->argv, expr->argv[0] + 2, optlevel, IF_INT | IF_UNSIGNED)) {
+ } else if (!parse_int(parser, expr->argv, arg, optlevel, IF_INT | IF_UNSIGNED)) {
return NULL;
}
if (*optlevel > 4) {
- parse_expr_warning(parser, expr, "${cyn}-O${bld}%s${rs} is the same as ${cyn}-O${bld}4${rs}.\n\n", expr->argv[0] + 2);
+ parse_expr_warning(parser, expr, "${cyn}-O${bld}%s${rs} is the same as ${cyn}-O${bld}4${rs}.\n\n", arg);
}
return expr;
@@ -1344,7 +1353,7 @@ static struct bfs_expr *parse_files0_from(struct bfs_parser *parser, int arg1, i
file = xfopen(from, O_RDONLY | O_CLOEXEC);
}
if (!file) {
- parse_expr_error(parser, expr, "%m.\n");
+ parse_expr_error(parser, expr, "%s.\n", errstr());
return NULL;
}
@@ -1509,7 +1518,7 @@ static struct bfs_expr *parse_fstype(struct bfs_parser *parser, int arg1, int ar
}
if (!bfs_ctx_mtab(parser->ctx)) {
- parse_expr_error(parser, expr, "Couldn't parse the mount table: %m.\n");
+ parse_expr_error(parser, expr, "Couldn't parse the mount table: %s.\n", errstr());
return NULL;
}
@@ -1534,7 +1543,7 @@ static struct bfs_expr *parse_group(struct bfs_parser *parser, int arg1, int arg
return NULL;
}
} else if (errno) {
- parse_expr_error(parser, expr, "%m.\n");
+ parse_expr_error(parser, expr, "%s.\n", errstr());
return NULL;
} else {
parse_expr_error(parser, expr, "No such group.\n");
@@ -1577,7 +1586,7 @@ static struct bfs_expr *parse_user(struct bfs_parser *parser, int arg1, int arg2
return NULL;
}
} else if (errno) {
- parse_expr_error(parser, expr, "%m.\n");
+ parse_expr_error(parser, expr, "%s.\n", errstr());
return NULL;
} else {
parse_expr_error(parser, expr, "No such user.\n");
@@ -1613,13 +1622,14 @@ static struct bfs_expr *parse_inum(struct bfs_parser *parser, int arg1, int arg2
* Parse -j<n>.
*/
static struct bfs_expr *parse_jobs(struct bfs_parser *parser, int arg1, int arg2) {
- struct bfs_expr *expr = parse_nullary_flag(parser);
+ const char *arg;
+ struct bfs_expr *expr = parse_prefix_flag(parser, 'j', false, &arg);
if (!expr) {
return NULL;
}
unsigned int n;
- if (!parse_int(parser, expr->argv, expr->argv[0] + 2, &n, IF_INT | IF_UNSIGNED)) {
+ if (!parse_int(parser, expr->argv, arg, &n, IF_INT | IF_UNSIGNED)) {
return NULL;
}
@@ -1737,7 +1747,7 @@ static int parse_reftime(const struct bfs_parser *parser, struct bfs_expr *expr)
if (xgetdate(expr->argv[1], &expr->reftime) == 0) {
return 0;
} else if (errno != EINVAL) {
- parse_expr_error(parser, expr, "%m.\n");
+ parse_expr_error(parser, expr, "%s.\n", errstr());
return -1;
}
@@ -1755,7 +1765,7 @@ static int parse_reftime(const struct bfs_parser *parser, struct bfs_expr *expr)
fprintf(stderr, " - %04d-%02d-%02d\n", year, month, tm.tm_mday);
fprintf(stderr, " - %04d-%02d-%02dT%02d:%02d:%02d\n", year, month, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
-#if __FreeBSD__
+#if BFS_HAS_TM_GMTOFF
int gmtoff = tm.tm_gmtoff;
#else
int gmtoff = -timezone;
@@ -2253,16 +2263,27 @@ static struct bfs_expr *parse_regextype(struct bfs_parser *parser, int arg1, int
// See https://www.gnu.org/software/gnulib/manual/html_node/Predefined-Syntaxes.html
const char *type = expr->argv[1];
if (strcmp(type, "posix-basic") == 0
+ || strcmp(type, "posix-minimal-basic") == 0
|| strcmp(type, "ed") == 0
|| strcmp(type, "sed") == 0) {
parser->regex_type = BFS_REGEX_POSIX_BASIC;
} else if (strcmp(type, "posix-extended") == 0) {
parser->regex_type = BFS_REGEX_POSIX_EXTENDED;
-#if BFS_USE_ONIGURUMA
+#if BFS_WITH_ONIGURUMA
+ } else if (strcmp(type, "awk") == 0
+ || strcmp(type, "posix-awk") == 0) {
+ parser->regex_type = BFS_REGEX_AWK;
+ } else if (strcmp(type, "gnu-awk") == 0) {
+ parser->regex_type = BFS_REGEX_GNU_AWK;
} else if (strcmp(type, "emacs") == 0) {
parser->regex_type = BFS_REGEX_EMACS;
} else if (strcmp(type, "grep") == 0) {
parser->regex_type = BFS_REGEX_GREP;
+ } else if (strcmp(type, "egrep") == 0
+ || strcmp(type, "posix-egrep") == 0) {
+ parser->regex_type = BFS_REGEX_EGREP;
+ } else if (strcmp(type, "findutils-default") == 0) {
+ parser->regex_type = BFS_REGEX_GNU_FIND;
#endif
} else if (strcmp(type, "help") == 0) {
parser->just_info = true;
@@ -2277,14 +2298,23 @@ static struct bfs_expr *parse_regextype(struct bfs_parser *parser, int arg1, int
list_types:
cfprintf(cfile, "Supported types are:\n\n");
- cfprintf(cfile, " ${bld}posix-basic${rs}: POSIX basic regular expressions (BRE)\n");
- cfprintf(cfile, " ${bld}posix-extended${rs}: POSIX extended regular expressions (ERE)\n");
- cfprintf(cfile, " ${bld}ed${rs}: Like ${grn}ed${rs} (same as ${bld}posix-basic${rs})\n");
-#if BFS_USE_ONIGURUMA
- cfprintf(cfile, " ${bld}emacs${rs}: Like ${grn}emacs${rs}\n");
- cfprintf(cfile, " ${bld}grep${rs}: Like ${grn}grep${rs}\n");
+ cfprintf(cfile, " ${bld}posix-basic${rs}: POSIX basic regular expressions (BRE)\n");
+ cfprintf(cfile, " ${bld}ed${rs}: Like ${grn}ed${rs} (same as ${bld}posix-basic${rs})\n");
+ cfprintf(cfile, " ${bld}sed${rs}: Like ${grn}sed${rs} (same as ${bld}posix-basic${rs})\n\n");
+
+ cfprintf(cfile, " ${bld}posix-extended${rs}: POSIX extended regular expressions (ERE)\n\n");
+
+#if BFS_WITH_ONIGURUMA
+ cfprintf(cfile, " [${bld}posix-${rs}]${bld}awk${rs}: Like ${grn}awk${rs}\n");
+ cfprintf(cfile, " ${bld}gnu-awk${rs}: Like GNU ${grn}awk${rs}\n\n");
+
+ cfprintf(cfile, " ${bld}emacs${rs}: Like ${grn}emacs${rs}\n\n");
+
+ cfprintf(cfile, " ${bld}grep${rs}: Like ${grn}grep${rs}\n");
+ cfprintf(cfile, " [${bld}posix-${rs}]${bld}egrep${rs}: Like ${grn}grep${rs} ${cyn}-E${rs}\n\n");
+
+ cfprintf(cfile, " ${bld}findutils-default${rs}: Like GNU ${grn}find${rs}\n");
#endif
- cfprintf(cfile, " ${bld}sed${rs}: Like ${grn}sed${rs} (same as ${bld}posix-basic${rs})\n");
return NULL;
}
@@ -2322,13 +2352,13 @@ static struct bfs_expr *parse_search_strategy(struct bfs_parser *parser, int arg
struct bfs_ctx *ctx = parser->ctx;
CFILE *cfile = ctx->cerr;
- struct bfs_expr *expr = parse_unary_flag(parser);
+ const char *arg;
+ struct bfs_expr *expr = parse_prefix_flag(parser, 'S', true, &arg);
if (!expr) {
cfprintf(cfile, "\n");
goto list_strategies;
}
- const char *arg = expr->argv[1];
if (strcmp(arg, "bfs") == 0) {
ctx->strategy = BFTW_BFS;
} else if (strcmp(arg, "dfs") == 0) {
@@ -2934,6 +2964,7 @@ static struct bfs_expr *parse_version(struct bfs_parser *parser, int arg1, int a
return NULL;
}
+/** Parser callback function type. */
typedef struct bfs_expr *parse_fn(struct bfs_parser *parser, int arg1, int arg2);
/**
@@ -2941,11 +2972,10 @@ typedef struct bfs_expr *parse_fn(struct bfs_parser *parser, int arg1, int arg2)
*/
struct table_entry {
char *arg;
- enum token_type type;
+ enum token_info info;
parse_fn *parse;
int arg1;
int arg2;
- bool prefix;
};
/**
@@ -2959,13 +2989,13 @@ static const struct table_entry parse_table[] = {
{"-Bnewer", T_TEST, parse_newer, BFS_STAT_BTIME},
{"-Bsince", T_TEST, parse_since, BFS_STAT_BTIME},
{"-Btime", T_TEST, parse_time, BFS_STAT_BTIME},
- {"-D", T_FLAG, parse_debug},
+ {"-D", T_FLAG | T_PREFIX, parse_debug},
{"-E", T_FLAG, parse_regex_extended},
{"-H", T_FLAG, parse_follow, BFTW_FOLLOW_ROOTS, false},
{"-L", T_FLAG, parse_follow, BFTW_FOLLOW_ALL, false},
- {"-O", T_FLAG, parse_optlevel, 0, 0, true},
+ {"-O", T_FLAG | T_PREFIX, parse_optlevel},
{"-P", T_FLAG, parse_follow, 0, false},
- {"-S", T_FLAG, parse_search_strategy},
+ {"-S", T_FLAG | T_PREFIX, parse_search_strategy},
{"-X", T_FLAG, parse_xargs_safe},
{"-a", T_OPERATOR},
{"-acl", T_TEST, parse_acl},
@@ -2991,7 +3021,7 @@ static const struct table_entry parse_table[] = {
{"-execdir", T_ACTION, parse_exec, BFS_EXEC_CHDIR},
{"-executable", T_TEST, parse_access, X_OK},
{"-exit", T_ACTION, parse_exit},
- {"-f", T_FLAG, parse_f},
+ {"-f", T_FLAG | T_NEEDS_ARG, parse_f},
{"-false", T_TEST, parse_const, false},
{"-files0-from", T_OPTION, parse_files0_from},
{"-flags", T_TEST, parse_flags},
@@ -3012,7 +3042,7 @@ static const struct table_entry parse_table[] = {
{"-ipath", T_TEST, parse_path, true},
{"-iregex", T_TEST, parse_regex, BFS_REGEX_ICASE},
{"-iwholename", T_TEST, parse_path, true},
- {"-j", T_FLAG, parse_jobs, 0, 0, true},
+ {"-j", T_FLAG | T_PREFIX, parse_jobs},
{"-limit", T_ACTION, parse_limit},
{"-links", T_TEST, parse_links},
{"-lname", T_TEST, parse_lname, false},
@@ -3026,7 +3056,7 @@ static const struct table_entry parse_table[] = {
{"-mtime", T_TEST, parse_time, BFS_STAT_MTIME},
{"-name", T_TEST, parse_name, false},
{"-newer", T_TEST, parse_newer, BFS_STAT_MTIME},
- {"-newer", T_TEST, parse_newerxy, 0, 0, true},
+ {"-newer", T_TEST | T_PREFIX, parse_newerxy},
{"-nocolor", T_OPTION, parse_color, false},
{"-nogroup", T_TEST, parse_nogroup},
{"-nohidden", T_TEST, parse_nohidden},
@@ -3079,7 +3109,7 @@ static const struct table_entry parse_table[] = {
static const struct table_entry *table_lookup(const char *arg) {
for (const struct table_entry *entry = parse_table; entry->arg; ++entry) {
bool match;
- if (entry->prefix) {
+ if (entry->info & T_PREFIX) {
match = strncmp(arg, entry->arg, strlen(entry->arg)) == 0;
} else {
match = strcmp(arg, entry->arg) == 0;
@@ -3092,6 +3122,85 @@ static const struct table_entry *table_lookup(const char *arg) {
return NULL;
}
+/** Look up a single-character flag in the parse table. */
+static const struct table_entry *flag_lookup(char flag) {
+ for (const struct table_entry *entry = parse_table; entry->arg; ++entry) {
+ enum token_info type = entry->info & T_TYPE;
+ if (type == T_FLAG && entry->arg[1] == flag && !entry->arg[2]) {
+ return entry;
+ }
+ }
+
+ return NULL;
+}
+
+/** Check for a multi-flag argument like -LEXO2. */
+static bool is_flag_group(const char *arg) {
+ // We enforce that at least one flag in a flag group must be a capital
+ // letter, to avoid ambiguity with primary expressions
+ bool has_upper = false;
+
+ // Flags that take an argument must appear last
+ bool needs_arg = false;
+
+ for (size_t i = 1; arg[i]; ++i) {
+ char c = arg[i];
+ if (c >= 'A' && c <= 'Z') {
+ has_upper = true;
+ }
+
+ if (needs_arg) {
+ return false;
+ }
+
+ const struct table_entry *entry = flag_lookup(c);
+ if (!entry || !entry->parse) {
+ return false;
+ }
+
+ if (entry->info & T_PREFIX) {
+ // The rest is the flag's argument
+ break;
+ }
+
+ if (entry->info & T_NEEDS_ARG) {
+ needs_arg = true;
+ }
+ }
+
+ return has_upper;
+}
+
+/** Parse a multi-flag argument. */
+static struct bfs_expr *parse_flag_group(struct bfs_parser *parser) {
+ struct bfs_expr *expr = NULL;
+
+ char **start = parser->argv;
+ char **end = start;
+ const char *arg = start[0];
+
+ for (size_t i = 1; arg[i]; ++i) {
+ parser->argv = start;
+
+ const struct table_entry *entry = flag_lookup(arg[i]);
+ expr = entry->parse(parser, entry->arg1, entry->arg2);
+
+ if (parser->argv > end) {
+ end = parser->argv;
+ }
+
+ if (!expr || entry->info & T_PREFIX) {
+ break;
+ }
+ }
+
+ if (expr) {
+ bfs_assert(parser->argv == end, "Didn't eat enough tokens");
+ }
+
+ return expr;
+}
+
/** Search for a fuzzy match in the parse table. */
static const struct table_entry *table_lookup_fuzzy(const char *arg) {
const struct table_entry *best = NULL;
@@ -3130,11 +3239,15 @@ static struct bfs_expr *parse_primary(struct bfs_parser *parser) {
}
}
+ if (is_flag_group(arg)) {
+ return parse_flag_group(parser);
+ }
+
match = table_lookup_fuzzy(arg);
CFILE *cerr = parser->ctx->cerr;
parse_error(parser, "Unknown argument; did you mean ");
- switch (match->type) {
+ switch (match->info & T_TYPE) {
case T_FLAG:
cfprintf(cerr, "${cyn}%s${rs}?", match->arg);
break;
@@ -3458,7 +3571,7 @@ static void dump_expr_multiline(const struct bfs_ctx *ctx, enum debug_flags flag
++rparens;
} else {
cfprintf(ctx->cerr, "(${red}%s${rs}\n", expr->argv[0]);
- for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) {
+ for_expr (child, expr) {
int parens = child->next ? 0 : rparens + 1;
dump_expr_multiline(ctx, flag, child, indent + 1, parens);
}