aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gdb/ChangeLog27
-rw-r--r--gdb/Makefile.in2
-rw-r--r--gdb/cli/cli-decode.c5
-rw-r--r--gdb/cli/cli-decode.h4
-rw-r--r--gdb/cli/cli-option.c724
-rw-r--r--gdb/cli/cli-option.h335
-rw-r--r--gdb/cli/cli-setshow.c280
-rw-r--r--gdb/cli/cli-setshow.h27
-rw-r--r--gdb/cli/cli-utils.c51
-rw-r--r--gdb/cli/cli-utils.h18
-rw-r--r--gdb/maint-test-options.c461
-rw-r--r--gdb/testsuite/ChangeLog5
-rw-r--r--gdb/testsuite/gdb.base/options.c33
-rw-r--r--gdb/testsuite/gdb.base/options.exp554
14 files changed, 2408 insertions, 118 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog
index 73fd65e..8c9eed4 100644
--- a/gdb/ChangeLog
+++ b/gdb/ChangeLog
@@ -1,5 +1,32 @@
2019-06-13 Pedro Alves <palves@redhat.com>
+ * Makefile.in (SUBDIR_CLI_SRCS): Add cli/cli-option.c.
+ (COMMON_SFILES): Add maint-test-settings.c.
+ * cli/cli-decode.c (boolean_enums): New global, factored out from
+ ...
+ (add_setshow_boolean_cmd): ... here.
+ * cli/cli-decode.h (boolean_enums): Declare.
+ * cli/cli-option.c: New file.
+ * cli/cli-option.h: New file.
+ * cli/cli-setshow.c (parse_cli_boolean_value(const char **)): New,
+ factored out from ...
+ (parse_cli_boolean_value(const char *)): ... this.
+ (is_unlimited_literal): Change parameter type to pointer to
+ pointer. Adjust and advance ARG pointer.
+ (parse_cli_var_uinteger, parse_cli_var_zuinteger_unlimited)
+ (parse_cli_var_enum): New, factored out from ...
+ (do_set_command): ... this. Adjust.
+ * cli/cli-setshow.h (parse_cli_boolean_value)
+ (parse_cli_var_uinteger, parse_cli_var_zuinteger_unlimited)
+ (parse_cli_var_enum): Declare.
+ * cli/cli-utils.c: Include "cli/cli-option.h".
+ (get_ulongest): New.
+ * cli/cli-utils.h (get_ulongest): Declare.
+ (check_for_argument): New overloads.
+ * maint-test-options.c: New file.
+
+2019-06-13 Pedro Alves <palves@redhat.com>
+
* cli/cli-utils.c (number_or_range_parser::get_number): Do not
parse a range if "-" is at the end of the string.
diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index beb69b5..03a5159 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -241,6 +241,7 @@ SUBDIR_CLI_SRCS = \
cli/cli-dump.c \
cli/cli-interp.c \
cli/cli-logging.c \
+ cli/cli-option.c \
cli/cli-script.c \
cli/cli-setshow.c \
cli/cli-style.c \
@@ -1063,6 +1064,7 @@ COMMON_SFILES = \
macrotab.c \
main.c \
maint.c \
+ maint-test-options.c \
maint-test-settings.c \
mdebugread.c \
mem-break.c \
diff --git a/gdb/cli/cli-decode.c b/gdb/cli/cli-decode.c
index 30f79b5..d739a0d 100644
--- a/gdb/cli/cli-decode.c
+++ b/gdb/cli/cli-decode.c
@@ -551,6 +551,7 @@ add_setshow_enum_cmd (const char *name,
set_cmd_context (show, context);
}
+/* See cli-decode.h. */
const char * const auto_boolean_enums[] = { "on", "off", "auto", NULL };
/* Add an auto-boolean command named NAME to both the set and show
@@ -578,6 +579,9 @@ add_setshow_auto_boolean_cmd (const char *name,
c->enums = auto_boolean_enums;
}
+/* See cli-decode.h. */
+const char * const boolean_enums[] = { "on", "off", NULL };
+
/* Add element named NAME to both the set and show command LISTs (the
list for set/show or some sublist thereof). CLASS is as in
add_cmd. VAR is address of the variable which will contain the
@@ -591,7 +595,6 @@ add_setshow_boolean_cmd (const char *name, enum command_class theclass, int *var
struct cmd_list_element **set_list,
struct cmd_list_element **show_list)
{
- static const char *boolean_enums[] = { "on", "off", NULL };
struct cmd_list_element *c;
add_setshow_cmd_full (name, theclass, var_boolean, var,
diff --git a/gdb/cli/cli-decode.h b/gdb/cli/cli-decode.h
index a9f9cbf..05280d9 100644
--- a/gdb/cli/cli-decode.h
+++ b/gdb/cli/cli-decode.h
@@ -261,6 +261,10 @@ extern void not_just_help_class_command (const char *arg, int from_tty);
extern void print_doc_line (struct ui_file *, const char *);
+/* The enums of boolean commands. */
+extern const char * const boolean_enums[];
+
+/* The enums of auto-boolean commands. */
extern const char * const auto_boolean_enums[];
/* Verify whether a given cmd_list_element is a user-defined command.
diff --git a/gdb/cli/cli-option.c b/gdb/cli/cli-option.c
new file mode 100644
index 0000000..9a53ec0
--- /dev/null
+++ b/gdb/cli/cli-option.c
@@ -0,0 +1,724 @@
+/* CLI options framework, for GDB.
+
+ Copyright (C) 2017-2019 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ This program 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.
+
+ This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "defs.h"
+#include "cli/cli-option.h"
+#include "cli/cli-decode.h"
+#include "cli/cli-utils.h"
+#include "cli/cli-setshow.h"
+#include "command.h"
+#include <vector>
+
+namespace gdb {
+namespace option {
+
+/* An option's value. Which field is active depends on the option's
+ type. */
+union option_value
+{
+ /* For var_boolean options. */
+ bool boolean;
+
+ /* For var_uinteger options. */
+ unsigned int uinteger;
+
+ /* For var_zuinteger_unlimited options. */
+ int integer;
+
+ /* For var_enum options. */
+ const char *enumeration;
+};
+
+/* Holds an options definition and its value. */
+struct option_def_and_value
+{
+ /* The option definition. */
+ const option_def &option;
+
+ /* A context. */
+ void *ctx;
+
+ /* The option's value, if any. */
+ gdb::optional<option_value> value;
+};
+
+/* Info passed around when handling completion. */
+struct parse_option_completion_info
+{
+ /* The completion word. */
+ const char *word;
+
+ /* The tracker. */
+ completion_tracker &tracker;
+};
+
+/* If ARGS starts with "-", look for a "--" delimiter. If one is
+ found, then interpret everything up until the "--" as command line
+ options. Otherwise, interpret unknown input as the beginning of
+ the command's operands. */
+
+static const char *
+find_end_options_delimiter (const char *args)
+{
+ if (args[0] == '-')
+ {
+ const char *p = args;
+
+ p = skip_spaces (p);
+ while (*p)
+ {
+ if (check_for_argument (&p, "--"))
+ return p;
+ else
+ p = skip_to_space (p);
+ p = skip_spaces (p);
+ }
+ }
+
+ return nullptr;
+}
+
+/* Complete TEXT/WORD on all options in OPTIONS_GROUP. */
+
+static void
+complete_on_options (gdb::array_view<const option_def_group> options_group,
+ completion_tracker &tracker,
+ const char *text, const char *word)
+{
+ size_t textlen = strlen (text);
+ for (const auto &grp : options_group)
+ for (const auto &opt : grp.options)
+ if (strncmp (opt.name, text, textlen) == 0)
+ {
+ tracker.add_completion
+ (make_completion_match_str (opt.name, text, word));
+ }
+}
+
+/* See cli-option.h. */
+
+void
+complete_on_all_options (completion_tracker &tracker,
+ gdb::array_view<const option_def_group> options_group)
+{
+ static const char opt[] = "-";
+ complete_on_options (options_group, tracker, opt + 1, opt);
+}
+
+/* Parse ARGS, guided by OPTIONS_GROUP. HAVE_DELIMITER is true if the
+ whole ARGS line included the "--" options-terminator delimiter. */
+
+static gdb::optional<option_def_and_value>
+parse_option (gdb::array_view<const option_def_group> options_group,
+ process_options_mode mode,
+ bool have_delimiter,
+ const char **args,
+ parse_option_completion_info *completion = nullptr)
+{
+ if (*args == nullptr)
+ return {};
+ else if (**args != '-')
+ {
+ if (have_delimiter)
+ error (_("Unrecognized option at: %s"), *args);
+ return {};
+ }
+ else if (check_for_argument (args, "--"))
+ return {};
+
+ /* Skip the initial '-'. */
+ const char *arg = *args + 1;
+
+ const char *after = skip_to_space (arg);
+ size_t len = after - arg;
+ const option_def *match = nullptr;
+ void *match_ctx = nullptr;
+
+ for (const auto &grp : options_group)
+ {
+ for (const auto &o : grp.options)
+ {
+ if (strncmp (o.name, arg, len) == 0)
+ {
+ if (match != nullptr)
+ {
+ if (completion != nullptr && arg[len] == '\0')
+ {
+ complete_on_options (options_group,
+ completion->tracker,
+ arg, completion->word);
+ return {};
+ }
+
+ error (_("Ambiguous option at: -%s"), arg);
+ }
+
+ match = &o;
+ match_ctx = grp.ctx;
+
+ if ((isspace (arg[len]) || arg[len] == '\0')
+ && strlen (o.name) == len)
+ break; /* Exact match. */
+ }
+ }
+ }
+
+ if (match == nullptr)
+ {
+ if (have_delimiter || mode != PROCESS_OPTIONS_UNKNOWN_IS_OPERAND)
+ error (_("Unrecognized option at: %s"), *args);
+
+ return {};
+ }
+
+ if (completion != nullptr && arg[len] == '\0')
+ {
+ complete_on_options (options_group, completion->tracker,
+ arg, completion->word);
+ return {};
+ }
+
+ *args += 1 + len;
+ *args = skip_spaces (*args);
+ if (completion != nullptr)
+ completion->word = *args;
+
+ switch (match->type)
+ {
+ case var_boolean:
+ {
+ if (!match->have_argument)
+ {
+ option_value val;
+ val.boolean = true;
+ return option_def_and_value {*match, match_ctx, val};
+ }
+
+ const char *val_str = *args;
+ int res;
+
+ if (**args == '\0' && completion != nullptr)
+ {
+ /* Complete on both "on/off" and more options. */
+
+ if (mode == PROCESS_OPTIONS_REQUIRE_DELIMITER)
+ {
+ complete_on_enum (completion->tracker,
+ boolean_enums, val_str, val_str);
+ complete_on_all_options (completion->tracker, options_group);
+ }
+ return option_def_and_value {*match, match_ctx};
+ }
+ else if (**args == '-')
+ {
+ /* Treat:
+ "cmd -boolean-option -another-opt..."
+ as:
+ "cmd -boolean-option on -another-opt..."
+ */
+ res = 1;
+ }
+ else if (**args == '\0')
+ {
+ /* Treat:
+ (1) "cmd -boolean-option "
+ as:
+ (1) "cmd -boolean-option on"
+ */
+ res = 1;
+ }
+ else
+ {
+ res = parse_cli_boolean_value (args);
+ if (res < 0)
+ {
+ const char *end = skip_to_space (*args);
+ if (completion != nullptr)
+ {
+ if (*end == '\0')
+ {
+ complete_on_enum (completion->tracker,
+ boolean_enums, val_str, val_str);
+ return option_def_and_value {*match, match_ctx};
+ }
+ }
+
+ if (have_delimiter)
+ error (_("Value given for `-%s' is not a boolean: %.*s"),
+ match->name, (int) (end - val_str), val_str);
+ /* The user didn't separate options from operands
+ using "--", so treat this unrecognized value as the
+ start of the operands. This makes "frame apply all
+ -past-main CMD" work. */
+ return option_def_and_value {*match, match_ctx};
+ }
+ else if (completion != nullptr && **args == '\0')
+ {
+ /* While "cmd -boolean [TAB]" only offers "on" and
+ "off", the boolean option actually accepts "1",
+ "yes", etc. as boolean values. We complete on all
+ of those instead of BOOLEAN_ENUMS here to make
+ these work:
+
+ "p -object 1[TAB]" -> "p -object 1 "
+ "p -object ye[TAB]" -> "p -object yes "
+
+ Etc. Note that it's important that the space is
+ auto-appended. Otherwise, if we only completed on
+ on/off here, then it might look to the user like
+ "1" isn't valid, like:
+ "p -object 1[TAB]" -> "p -object 1" (i.e., nothing happens).
+ */
+ static const char *const all_boolean_enums[] = {
+ "on", "off",
+ "yes", "no",
+ "enable", "disable",
+ "0", "1",
+ nullptr,
+ };
+ complete_on_enum (completion->tracker, all_boolean_enums,
+ val_str, val_str);
+ return {};
+ }
+ }
+
+ option_value val;
+ val.boolean = res;
+ return option_def_and_value {*match, match_ctx, val};
+ }
+ case var_uinteger:
+ case var_zuinteger_unlimited:
+ {
+ if (completion != nullptr)
+ {
+ if (**args == '\0')
+ {
+ /* Convenience to let the user know what the option
+ can accept. Note there's no common prefix between
+ the strings on purpose, so that readline doesn't do
+ a partial match. */
+ completion->tracker.add_completion
+ (make_unique_xstrdup ("NUMBER"));
+ completion->tracker.add_completion
+ (make_unique_xstrdup ("unlimited"));
+ return {};
+ }
+ else if (startswith ("unlimited", *args))
+ {
+ completion->tracker.add_completion
+ (make_unique_xstrdup ("unlimited"));
+ return {};
+ }
+ }
+
+ if (match->type == var_zuinteger_unlimited)
+ {
+ option_value val;
+ val.integer = parse_cli_var_zuinteger_unlimited (args, false);
+ return option_def_and_value {*match, match_ctx, val};
+ }
+ else
+ {
+ option_value val;
+ val.uinteger = parse_cli_var_uinteger (match->type, args, false);
+ return option_def_and_value {*match, match_ctx, val};
+ }
+ }
+ case var_enum:
+ {
+ if (completion != nullptr)
+ {
+ const char *after_arg = skip_to_space (*args);
+ if (*after_arg == '\0')
+ {
+ complete_on_enum (completion->tracker,
+ match->enums, *args, *args);
+ *args = after_arg;
+
+ option_value val;
+ val.enumeration = nullptr;
+ return option_def_and_value {*match, match_ctx, val};
+ }
+ }
+
+ if (check_for_argument (args, "--"))
+ {
+ /* Treat e.g., "backtrace -entry-values --" as if there
+ was no argument after "-entry-values". This makes
+ parse_cli_var_enum throw an error with a suggestion of
+ what are the valid options. */
+ args = nullptr;
+ }
+
+ option_value val;
+ val.enumeration = parse_cli_var_enum (args, match->enums);
+ return option_def_and_value {*match, match_ctx, val};
+ }
+
+ default:
+ /* Not yet. */
+ gdb_assert_not_reached (_("option type not supported"));
+ }
+
+ return {};
+}
+
+/* See cli-option.h. */
+
+bool
+complete_options (completion_tracker &tracker,
+ const char **args,
+ process_options_mode mode,
+ gdb::array_view<const option_def_group> options_group)
+{
+ const char *text = *args;
+
+ tracker.set_use_custom_word_point (true);
+
+ const char *delimiter = find_end_options_delimiter (text);
+ bool have_delimiter = delimiter != nullptr;
+
+ if (text[0] == '-' && (!have_delimiter || *delimiter == '\0'))
+ {
+ parse_option_completion_info completion_info {nullptr, tracker};
+
+ while (1)
+ {
+ *args = skip_spaces (*args);
+ completion_info.word = *args;
+
+ if (strcmp (*args, "-") == 0)
+ {
+ complete_on_options (options_group, tracker, *args + 1,
+ completion_info.word);
+ }
+ else if (strcmp (*args, "--") == 0)
+ {
+ tracker.add_completion (make_unique_xstrdup (*args));
+ }
+ else if (**args == '-')
+ {
+ gdb::optional<option_def_and_value> ov
+ = parse_option (options_group, mode, have_delimiter,
+ args, &completion_info);
+ if (!ov && !tracker.have_completions ())
+ {
+ tracker.advance_custom_word_point_by (*args - text);
+ return mode == PROCESS_OPTIONS_REQUIRE_DELIMITER;
+ }
+
+ if (ov
+ && ov->option.type == var_boolean
+ && !ov->value.has_value ())
+ {
+ /* Looked like a boolean option, but we failed to
+ parse the value. If this command requires a
+ delimiter, this value can't be the start of the
+ operands, so return true. Otherwise, if the
+ command doesn't require a delimiter return false
+ so that the caller tries to complete on the
+ operand. */
+ tracker.advance_custom_word_point_by (*args - text);
+ return mode == PROCESS_OPTIONS_REQUIRE_DELIMITER;
+ }
+
+ /* If we parsed an option with an argument, and reached
+ the end of the input string with no trailing space,
+ return true, so that our callers don't try to
+ complete anything by themselves. E.g., this makes it
+ so that with:
+
+ (gdb) frame apply all -limit 10[TAB]
+
+ we don't try to complete on command names. */
+ if (ov
+ && !tracker.have_completions ()
+ && **args == '\0'
+ && *args > text && !isspace ((*args)[-1]))
+ {
+ tracker.advance_custom_word_point_by
+ (*args - text);
+ return true;
+ }
+ }
+ else
+ {
+ tracker.advance_custom_word_point_by
+ (completion_info.word - text);
+
+ /* If the command requires a delimiter, but we haven't
+ seen one, then return true, so that the caller
+ doesn't try to complete on whatever follows options,
+ which for these commands should only be done if
+ there's a delimiter. */
+ if (mode == PROCESS_OPTIONS_REQUIRE_DELIMITER
+ && !have_delimiter)
+ {
+ /* If we reached the end of the input string, then
+ offer all options, since that's all the user can
+ type (plus "--"). */
+ if (completion_info.word[0] == '\0')
+ complete_on_all_options (tracker, options_group);
+ return true;
+ }
+ else
+ return false;
+ }
+
+ if (tracker.have_completions ())
+ {
+ tracker.advance_custom_word_point_by
+ (completion_info.word - text);
+ return true;
+ }
+ }
+ }
+ else if (delimiter != nullptr)
+ {
+ tracker.advance_custom_word_point_by (delimiter - text);
+ *args = delimiter;
+ return false;
+ }
+
+ return false;
+}
+
+/* See cli-option.h. */
+
+bool
+process_options (const char **args,
+ process_options_mode mode,
+ gdb::array_view<const option_def_group> options_group)
+{
+ if (*args == nullptr)
+ return false;
+
+ /* If ARGS starts with "-", look for a "--" sequence. If one is
+ found, then interpret everything up until the "--" as
+ 'gdb::option'-style command line options. Otherwise, interpret
+ ARGS as possibly the command's operands. */
+ bool have_delimiter = find_end_options_delimiter (*args) != nullptr;
+
+ if (mode == PROCESS_OPTIONS_REQUIRE_DELIMITER && !have_delimiter)
+ return false;
+
+ bool processed_any = false;
+
+ while (1)
+ {
+ *args = skip_spaces (*args);
+
+ auto ov = parse_option (options_group, mode, have_delimiter, args);
+ if (!ov)
+ {
+ if (processed_any)
+ return true;
+ return false;
+ }
+
+ processed_any = true;
+
+ switch (ov->option.type)
+ {
+ case var_boolean:
+ {
+ bool value = ov->value.has_value () ? ov->value->boolean : true;
+ *ov->option.var_address.boolean (ov->option, ov->ctx) = value;
+ }
+ break;
+ case var_uinteger:
+ *ov->option.var_address.uinteger (ov->option, ov->ctx)
+ = ov->value->uinteger;
+ break;
+ case var_zuinteger_unlimited:
+ *ov->option.var_address.integer (ov->option, ov->ctx)
+ = ov->value->integer;
+ break;
+ case var_enum:
+ *ov->option.var_address.enumeration (ov->option, ov->ctx)
+ = ov->value->enumeration;
+ break;
+ default:
+ gdb_assert_not_reached ("unhandled option type");
+ }
+ }
+}
+
+/* Helper for build_help. Return a fragment of a help string showing
+ OPT's possible values. Returns NULL if OPT doesn't take an
+ argument. */
+
+static const char *
+get_val_type_str (const option_def &opt, std::string &buffer)
+{
+ if (!opt.have_argument)
+ return nullptr;
+
+ switch (opt.type)
+ {
+ case var_boolean:
+ return "[on|off]";
+ case var_uinteger:
+ case var_zuinteger_unlimited:
+ return "NUMBER|unlimited";
+ case var_enum:
+ {
+ buffer = "";
+ for (size_t i = 0; opt.enums[i] != nullptr; i++)
+ {
+ if (i != 0)
+ buffer += "|";
+ buffer += opt.enums[i];
+ }
+ return buffer.c_str ();
+ }
+ default:
+ return nullptr;
+ }
+}
+
+/* Helper for build_help. Appends an indented version of DOC into
+ HELP. */
+
+static void
+append_indented_doc (const char *doc, std::string &help)
+{
+ const char *p = doc;
+ const char *n = strchr (p, '\n');
+
+ while (n != nullptr)
+ {
+ help += " ";
+ help.append (p, n - p + 1);
+ p = n + 1;
+ n = strchr (p, '\n');
+ }
+ help += " ";
+ help += p;
+ help += '\n';
+}
+
+/* Fill HELP with an auto-generated "help" string fragment for
+ OPTIONS. */
+
+static void
+build_help_option (gdb::array_view<const option_def> options,
+ std::string &help)
+{
+ std::string buffer;
+
+ for (const auto &o : options)
+ {
+ if (o.set_doc == nullptr)
+ continue;
+
+ help += " -";
+ help += o.name;
+
+ const char *val_type_str = get_val_type_str (o, buffer);
+ if (val_type_str != nullptr)
+ {
+ help += ' ';
+ help += val_type_str;
+ }
+ help += "\n";
+ append_indented_doc (o.set_doc, help);
+ if (o.help_doc != nullptr)
+ append_indented_doc (o.help_doc, help);
+ help += '\n';
+ }
+}
+
+/* See cli-option.h. */
+
+std::string
+build_help (const char *help_tmpl,
+ gdb::array_view<const option_def_group> options_group)
+{
+ std::string help_str;
+
+ const char *p = strstr (help_tmpl, "%OPTIONS%");
+ help_str.assign (help_tmpl, p);
+
+ for (const auto &grp : options_group)
+ for (const auto &opt : grp.options)
+ build_help_option (opt, help_str);
+
+ p += strlen ("%OPTIONS%");
+ help_str.append (p);
+
+ return help_str;
+}
+
+/* See cli-option.h. */
+
+void
+add_setshow_cmds_for_options (command_class cmd_class,
+ void *data,
+ gdb::array_view<const option_def> options,
+ struct cmd_list_element **set_list,
+ struct cmd_list_element **show_list)
+{
+ for (const auto &option : options)
+ {
+ if (option.type == var_boolean)
+ {
+ add_setshow_boolean_cmd (option.name, cmd_class,
+ option.var_address.boolean (option, data),
+ option.set_doc, option.show_doc,
+ option.help_doc,
+ nullptr, option.show_cmd_cb,
+ set_list, show_list);
+ }
+ else if (option.type == var_uinteger)
+ {
+ add_setshow_uinteger_cmd (option.name, cmd_class,
+ option.var_address.uinteger (option, data),
+ option.set_doc, option.show_doc,
+ option.help_doc,
+ nullptr, option.show_cmd_cb,
+ set_list, show_list);
+ }
+ else if (option.type == var_zuinteger_unlimited)
+ {
+ add_setshow_zuinteger_unlimited_cmd
+ (option.name, cmd_class,
+ option.var_address.integer (option, data),
+ option.set_doc, option.show_doc,
+ option.help_doc,
+ nullptr, option.show_cmd_cb,
+ set_list, show_list);
+ }
+ else if (option.type == var_enum)
+ {
+ add_setshow_enum_cmd (option.name, cmd_class,
+ option.enums,
+ option.var_address.enumeration (option, data),
+ option.set_doc, option.show_doc,
+ option.help_doc,
+ nullptr, option.show_cmd_cb,
+ set_list, show_list);
+ }
+ else
+ gdb_assert_not_reached (_("option type not handled"));
+ }
+}
+
+} /* namespace option */
+} /* namespace gdb */
diff --git a/gdb/cli/cli-option.h b/gdb/cli/cli-option.h
new file mode 100644
index 0000000..1bfbfce
--- /dev/null
+++ b/gdb/cli/cli-option.h
@@ -0,0 +1,335 @@
+/* CLI options framework, for GDB.
+
+ Copyright (C) 2017-2019 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ This program 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.
+
+ This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef CLI_OPTION_H
+#define CLI_OPTION_H 1
+
+#include "common/gdb_optional.h"
+#include "common/array-view.h"
+#include "completer.h"
+#include <string>
+#include "command.h"
+
+namespace gdb {
+namespace option {
+
+/* A type-erased option definition. The actual type of the option is
+ stored in the TYPE field. Client code cannot define objects of
+ this type directly (the ctor is protected). Instead, one of the
+ wrapper types below that extends this (boolean_option_def,
+ flag_option_def, uinteger_option_def, etc.) should be defined. */
+struct option_def
+{
+ /* The ctor is protected because you're supposed to construct using
+ one of bool_option_def, etc. below. */
+protected:
+ typedef void *(erased_get_var_address_ftype) ();
+
+ /* Construct an option. NAME_ is the option's name. VAR_TYPE_
+ defines the option's type. ERASED_GET_VAR_ADDRESS_ is a pointer
+ to a function that returns the option's control variable.
+ SHOW_CMD_CB_ is a pointer to callback for the "show" command that
+ is installed for this option. SET_DOC_, SHOW_DOC_, HELP_DOC_ are
+ used to create the option's "set/show" commands. */
+ constexpr option_def (const char *name_,
+ var_types var_type_,
+ erased_get_var_address_ftype *erased_get_var_address_,
+ show_value_ftype *show_cmd_cb_,
+ const char *set_doc_,
+ const char *show_doc_,
+ const char *help_doc_)
+ : name (name_), type (var_type_),
+ erased_get_var_address (erased_get_var_address_),
+ var_address {},
+ show_cmd_cb (show_cmd_cb_),
+ set_doc (set_doc_), show_doc (show_doc_), help_doc (help_doc_)
+ {}
+
+public:
+ /* The option's name. */
+ const char *name;
+
+ /* The option's type. */
+ var_types type;
+
+ /* A function that gets the controlling variable's address, type
+ erased. */
+ erased_get_var_address_ftype *erased_get_var_address;
+
+ /* Get the controlling variable's address. Each type of variable
+ uses a different union member. We do this instead of having a
+ single hook that return a "void *", for better type safety. This
+ way, actual instances of concrete option_def types
+ (boolean_option_def, etc.) fail to compile if you pass in a
+ function with incorrect return type. CTX here is the aggregate
+ object that groups the option variables from which the callback
+ returns the address of some member. */
+ union
+ {
+ int *(*boolean) (const option_def &, void *ctx);
+ unsigned int *(*uinteger) (const option_def &, void *ctx);
+ int *(*integer) (const option_def &, void *ctx);
+ const char **(*enumeration) (const option_def &, void *ctx);
+ }
+ var_address;
+
+ /* Pointer to null terminated list of enumerated values (like argv).
+ Only used by var_enum options. */
+ const char *const *enums = nullptr;
+
+ /* True if the option takes an argument. */
+ bool have_argument = true;
+
+ /* The "show" callback to use in the associated "show" command.
+ E.g., "show print elements". */
+ show_value_ftype *show_cmd_cb;
+
+ /* The set/show/help strings. These are shown in both the help of
+ commands that use the option group this option belongs to (e.g.,
+ "help print"), and in the associated commands (e.g., "set/show
+ print elements", "help set print elements"). */
+ const char *set_doc;
+ const char *show_doc;
+ const char *help_doc;
+
+ /* Convenience method that returns THIS as an option_def. Useful
+ when you're putting an option_def subclass in an option_def
+ array_view. */
+ const option_def &def () const
+ {
+ return *this;
+ }
+};
+
+namespace detail
+{
+
+/* Get the address of the option's value, cast to the right type.
+ RetType is the restored type of the variable, and Context is the
+ restored type of the context pointer. */
+template<typename RetType, typename Context>
+static inline RetType *
+get_var_address (const option_def &option, void *ctx)
+{
+ using unerased_ftype = RetType *(Context *);
+ unerased_ftype *fun = (unerased_ftype *) option.erased_get_var_address;
+ return fun ((Context *) ctx);
+}
+
+/* Convenience identity helper that just returns SELF. */
+
+template<typename T>
+static T *
+return_self (T *self)
+{
+ return self;
+}
+
+} /* namespace detail */
+
+/* Follows the definitions of the option types that client code should
+ define. Note that objects of these types are placed in option_def
+ arrays, by design, so they must not have data fields of their
+ own. */
+
+/* A var_boolean command line option. */
+
+template<typename Context>
+struct boolean_option_def : option_def
+{
+ boolean_option_def (const char *long_option_,
+ int *(*get_var_address_cb_) (Context *),
+ show_value_ftype *show_cmd_cb_,
+ const char *set_doc_,
+ const char *show_doc_ = nullptr,
+ const char *help_doc_ = nullptr)
+ : option_def (long_option_, var_boolean,
+ (erased_get_var_address_ftype *) get_var_address_cb_,
+ show_cmd_cb_,
+ set_doc_, show_doc_, help_doc_)
+ {
+ var_address.boolean = detail::get_var_address<int, Context>;
+ }
+};
+
+/* A flag command line option. This is a var_boolean option under the
+ hood, but unlike boolean options, flag options don't take an on/off
+ argument. */
+
+template<typename Context = int>
+struct flag_option_def : boolean_option_def<Context>
+{
+ flag_option_def (const char *long_option_,
+ int *(*var_address_cb_) (Context *),
+ const char *set_doc_,
+ const char *help_doc_ = nullptr)
+ : boolean_option_def<Context> (long_option_,
+ var_address_cb_,
+ NULL,
+ set_doc_, NULL, help_doc_)
+ {
+ this->have_argument = false;
+ }
+
+ flag_option_def (const char *long_option_,
+ const char *set_doc_,
+ const char *help_doc_ = nullptr)
+ : boolean_option_def<Context> (long_option_,
+ gdb::option::detail::return_self,
+ NULL,
+ set_doc_, nullptr, help_doc_)
+ {
+ this->have_argument = false;
+ }
+};
+
+/* A var_uinteger command line option. */
+
+template<typename Context>
+struct uinteger_option_def : option_def
+{
+ uinteger_option_def (const char *long_option_,
+ unsigned int *(*get_var_address_cb_) (Context *),
+ show_value_ftype *show_cmd_cb_,
+ const char *set_doc_,
+ const char *show_doc_ = nullptr,
+ const char *help_doc_ = nullptr)
+ : option_def (long_option_, var_uinteger,
+ (erased_get_var_address_ftype *) get_var_address_cb_,
+ show_cmd_cb_,
+ set_doc_, show_doc_, help_doc_)
+ {
+ var_address.uinteger = detail::get_var_address<unsigned int, Context>;
+ }
+};
+
+/* A var_zuinteger_unlimited command line option. */
+
+template<typename Context>
+struct zuinteger_unlimited_option_def : option_def
+{
+ zuinteger_unlimited_option_def (const char *long_option_,
+ int *(*get_var_address_cb_) (Context *),
+ show_value_ftype *show_cmd_cb_,
+ const char *set_doc_,
+ const char *show_doc_ = nullptr,
+ const char *help_doc_ = nullptr)
+ : option_def (long_option_, var_zuinteger_unlimited,
+ (erased_get_var_address_ftype *) get_var_address_cb_,
+ show_cmd_cb_,
+ set_doc_, show_doc_, help_doc_)
+ {
+ var_address.integer = detail::get_var_address<int, Context>;
+ }
+};
+
+/* An var_enum command line option. */
+
+template<typename Context>
+struct enum_option_def : option_def
+{
+ enum_option_def (const char *long_option_,
+ const char *const *enumlist,
+ const char **(*get_var_address_cb_) (Context *),
+ show_value_ftype *show_cmd_cb_,
+ const char *set_doc_,
+ const char *show_doc_ = nullptr,
+ const char *help_doc_ = nullptr)
+ : option_def (long_option_, var_enum,
+ (erased_get_var_address_ftype *) get_var_address_cb_,
+ show_cmd_cb_,
+ set_doc_, show_doc_, help_doc_)
+ {
+ var_address.enumeration = detail::get_var_address<const char *, Context>;
+ this->enums = enumlist;
+ }
+};
+
+/* A group of options that all share the same context pointer to pass
+ to the options' get-current-value callbacks. */
+struct option_def_group
+{
+ /* The list of options. */
+ gdb::array_view<const option_def> options;
+
+ /* The context pointer to pass to the options' get-current-value
+ callbacks. */
+ void *ctx;
+};
+
+/* Modes of operation for process_options. */
+enum process_options_mode
+{
+ /* In this mode, options are only processed if we find a "--"
+ delimiter. Throws an error if unknown options are found. */
+ PROCESS_OPTIONS_REQUIRE_DELIMITER,
+
+ /* In this mode, a "--" delimiter is not required. Throws an error
+ if unknown options are found, regardless of whether a delimiter
+ is present. */
+ PROCESS_OPTIONS_UNKNOWN_IS_ERROR,
+
+ /* In this mode, a "--" delimiter is not required. If an unknown
+ option is found, assume it is the command's argument/operand. */
+ PROCESS_OPTIONS_UNKNOWN_IS_OPERAND,
+};
+
+/* Process ARGS, using OPTIONS_GROUP as valid options. Returns true
+ if the string has been fully parsed and there are no operands to
+ handle by the caller. Return false if options were parsed, and
+ *ARGS now points at the first operand. */
+extern bool process_options
+ (const char **args,
+ process_options_mode mode,
+ gdb::array_view<const option_def_group> options_group);
+
+/* Complete ARGS on options listed by OPTIONS_GROUP. Returns true if
+ the string has been fully parsed and there are no operands to
+ handle by the caller. Return false if options were parsed, and
+ *ARGS now points at the first operand. */
+extern bool complete_options
+ (completion_tracker &tracker,
+ const char **args,
+ process_options_mode mode,
+ gdb::array_view<const option_def_group> options_group);
+
+/* Complete on all options listed by OPTIONS_GROUP. */
+extern void
+ complete_on_all_options (completion_tracker &tracker,
+ gdb::array_view<const option_def_group> options_group);
+
+/* Return a string with the result of replacing %OPTIONS% in HELP_TMLP
+ with an auto-generated "help" string fragment for all the options
+ in OPTIONS_GROUP. */
+extern std::string build_help
+ (const char *help_tmpl,
+ gdb::array_view<const option_def_group> options_group);
+
+/* Install set/show commands for options defined in OPTIONS. DATA is
+ a pointer to the structure that holds the data associated with the
+ OPTIONS array. */
+extern void add_setshow_cmds_for_options (command_class cmd_class, void *data,
+ gdb::array_view<const option_def> options,
+ struct cmd_list_element **set_list,
+ struct cmd_list_element **show_list);
+
+} /* namespace option */
+} /* namespace gdb */
+
+#endif /* CLI_OPTION_H */
diff --git a/gdb/cli/cli-setshow.c b/gdb/cli/cli-setshow.c
index 9841ec1..14ea723 100644
--- a/gdb/cli/cli-setshow.c
+++ b/gdb/cli/cli-setshow.c
@@ -78,33 +78,48 @@ parse_auto_binary_operation (const char *arg)
/* See cli-setshow.h. */
int
-parse_cli_boolean_value (const char *arg)
+parse_cli_boolean_value (const char **arg)
{
- int length;
-
- if (!arg || !*arg)
- return 1;
+ const char *p = skip_to_space (*arg);
+ size_t length = p - *arg;
- length = strlen (arg);
+ /* Note that "o" is ambiguous. */
- while (arg[length - 1] == ' ' || arg[length - 1] == '\t')
- length--;
+ if ((length == 2 && strncmp (*arg, "on", length) == 0)
+ || strncmp (*arg, "1", length) == 0
+ || strncmp (*arg, "yes", length) == 0
+ || strncmp (*arg, "enable", length) == 0)
+ {
+ *arg = skip_spaces (*arg + length);
+ return 1;
+ }
+ else if ((length >= 2 && strncmp (*arg, "off", length) == 0)
+ || strncmp (*arg, "0", length) == 0
+ || strncmp (*arg, "no", length) == 0
+ || strncmp (*arg, "disable", length) == 0)
+ {
+ *arg = skip_spaces (*arg + length);
+ return 0;
+ }
+ else
+ return -1;
+}
- /* Note that "o" is ambiguous. */
+/* See cli-setshow.h. */
- if ((length == 2 && strncmp (arg, "on", length) == 0)
- || strncmp (arg, "1", length) == 0
- || strncmp (arg, "yes", length) == 0
- || strncmp (arg, "enable", length) == 0)
+int
+parse_cli_boolean_value (const char *arg)
+{
+ if (!arg || !*arg)
return 1;
- else if ((length >= 2 && strncmp (arg, "off", length) == 0)
- || strncmp (arg, "0", length) == 0
- || strncmp (arg, "no", length) == 0
- || strncmp (arg, "disable", length) == 0)
- return 0;
- else
+
+ int b = parse_cli_boolean_value (&arg);
+ if (b >= 0 && *arg != '\0')
return -1;
+
+ return b;
}
+
void
deprecated_show_value_hack (struct ui_file *ignore_file,
@@ -134,21 +149,136 @@ deprecated_show_value_hack (struct ui_file *ignore_file,
/* Returns true if ARG is "unlimited". */
-static int
-is_unlimited_literal (const char *arg)
+static bool
+is_unlimited_literal (const char **arg)
{
- arg = skip_spaces (arg);
+ *arg = skip_spaces (*arg);
- const char *p = skip_to_space (arg);
+ const char *p = skip_to_space (*arg);
- size_t len = p - arg;
+ size_t len = p - *arg;
- if (len > 0 && strncmp ("unlimited", arg, len) == 0)
- return true;
+ if (len > 0 && strncmp ("unlimited", *arg, len) == 0)
+ {
+ *arg += len;
+ return true;
+ }
return false;
}
+/* See cli-setshow.h. */
+
+unsigned int
+parse_cli_var_uinteger (var_types var_type, const char **arg,
+ bool expression)
+{
+ LONGEST val;
+
+ if (*arg == nullptr)
+ {
+ if (var_type == var_uinteger)
+ error_no_arg (_("integer to set it to, or \"unlimited\"."));
+ else
+ error_no_arg (_("integer to set it to."));
+ }
+
+ if (var_type == var_uinteger && is_unlimited_literal (arg))
+ val = 0;
+ else if (expression)
+ val = parse_and_eval_long (*arg);
+ else
+ val = get_ulongest (arg);
+
+ if (var_type == var_uinteger && val == 0)
+ val = UINT_MAX;
+ else if (val < 0
+ /* For var_uinteger, don't let the user set the value
+ to UINT_MAX directly, as that exposes an
+ implementation detail to the user interface. */
+ || (var_type == var_uinteger && val >= UINT_MAX)
+ || (var_type == var_zuinteger && val > UINT_MAX))
+ error (_("integer %s out of range"), plongest (val));
+
+ return val;
+}
+
+/* See cli-setshow.h. */
+
+int
+parse_cli_var_zuinteger_unlimited (const char **arg, bool expression)
+{
+ LONGEST val;
+
+ if (*arg == nullptr)
+ error_no_arg (_("integer to set it to, or \"unlimited\"."));
+
+ if (is_unlimited_literal (arg))
+ val = -1;
+ else if (expression)
+ val = parse_and_eval_long (*arg);
+ else
+ val = get_ulongest (arg);
+
+ if (val > INT_MAX)
+ error (_("integer %s out of range"), plongest (val));
+ else if (val < -1)
+ error (_("only -1 is allowed to set as unlimited"));
+
+ return val;
+}
+
+/* See cli-setshow.h. */
+
+const char *
+parse_cli_var_enum (const char **args, const char *const *enums)
+{
+ /* If no argument was supplied, print an informative error
+ message. */
+ if (args == NULL || *args == NULL || **args == '\0')
+ {
+ std::string msg;
+
+ for (size_t i = 0; enums[i]; i++)
+ {
+ if (i != 0)
+ msg += ", ";
+ msg += enums[i];
+ }
+ error (_("Requires an argument. Valid arguments are %s."),
+ msg.c_str ());
+ }
+
+ const char *p = skip_to_space (*args);
+ size_t len = p - *args;
+
+ int nmatches = 0;
+ const char *match = NULL;
+ for (size_t i = 0; enums[i]; i++)
+ if (strncmp (*args, enums[i], len) == 0)
+ {
+ if (enums[i][len] == '\0')
+ {
+ match = enums[i];
+ nmatches = 1;
+ break; /* Exact match. */
+ }
+ else
+ {
+ match = enums[i];
+ nmatches++;
+ }
+ }
+
+ if (nmatches == 0)
+ error (_("Undefined item: \"%.*s\"."), (int) len, *args);
+
+ if (nmatches > 1)
+ error (_("Ambiguous item \"%.*s\"."), (int) len, *args);
+
+ *args += len;
+ return match;
+}
/* Do a "set" command. ARG is NULL if no argument, or the
text of the argument, and FROM_TTY is nonzero if this command is
@@ -295,30 +425,7 @@ do_set_command (const char *arg, int from_tty, struct cmd_list_element *c)
case var_uinteger:
case var_zuinteger:
{
- LONGEST val;
-
- if (arg == NULL)
- {
- if (c->var_type == var_uinteger)
- error_no_arg (_("integer to set it to, or \"unlimited\"."));
- else
- error_no_arg (_("integer to set it to."));
- }
-
- if (c->var_type == var_uinteger && is_unlimited_literal (arg))
- val = 0;
- else
- val = parse_and_eval_long (arg);
-
- if (c->var_type == var_uinteger && val == 0)
- val = UINT_MAX;
- else if (val < 0
- /* For var_uinteger, don't let the user set the value
- to UINT_MAX directly, as that exposes an
- implementation detail to the user interface. */
- || (c->var_type == var_uinteger && val >= UINT_MAX)
- || (c->var_type == var_zuinteger && val > UINT_MAX))
- error (_("integer %s out of range"), plongest (val));
+ unsigned int val = parse_cli_var_uinteger (c->var_type, &arg, true);
if (*(unsigned int *) c->var != val)
{
@@ -341,7 +448,7 @@ do_set_command (const char *arg, int from_tty, struct cmd_list_element *c)
error_no_arg (_("integer to set it to."));
}
- if (c->var_type == var_integer && is_unlimited_literal (arg))
+ if (c->var_type == var_integer && is_unlimited_literal (&arg))
val = 0;
else
val = parse_and_eval_long (arg);
@@ -366,59 +473,11 @@ do_set_command (const char *arg, int from_tty, struct cmd_list_element *c)
}
case var_enum:
{
- int i;
- int len;
- int nmatches;
- const char *match = NULL;
- const char *p;
-
- /* If no argument was supplied, print an informative error
- message. */
- if (arg == NULL)
- {
- std::string msg;
-
- for (i = 0; c->enums[i]; i++)
- {
- if (i != 0)
- msg += ", ";
- msg += c->enums[i];
- }
- error (_("Requires an argument. Valid arguments are %s."),
- msg.c_str ());
- }
-
- p = strchr (arg, ' ');
+ const char *end_arg = arg;
+ const char *match = parse_cli_var_enum (&end_arg, c->enums);
- if (p)
- len = p - arg;
- else
- len = strlen (arg);
-
- nmatches = 0;
- for (i = 0; c->enums[i]; i++)
- if (strncmp (arg, c->enums[i], len) == 0)
- {
- if (c->enums[i][len] == '\0')
- {
- match = c->enums[i];
- nmatches = 1;
- break; /* Exact match. */
- }
- else
- {
- match = c->enums[i];
- nmatches++;
- }
- }
-
- if (nmatches <= 0)
- error (_("Undefined item: \"%s\"."), arg);
-
- if (nmatches > 1)
- error (_("Ambiguous item \"%s\"."), arg);
-
- const char *after = skip_spaces (arg + len);
+ int len = end_arg - arg;
+ const char *after = skip_spaces (end_arg);
if (*after != '\0')
error (_("Junk after item \"%.*s\": %s"), len, arg, after);
@@ -432,20 +491,7 @@ do_set_command (const char *arg, int from_tty, struct cmd_list_element *c)
break;
case var_zuinteger_unlimited:
{
- LONGEST val;
-
- if (arg == NULL)
- error_no_arg (_("integer to set it to, or \"unlimited\"."));
-
- if (is_unlimited_literal (arg))
- val = -1;
- else
- val = parse_and_eval_long (arg);
-
- if (val > INT_MAX)
- error (_("integer %s out of range"), plongest (val));
- else if (val < -1)
- error (_("only -1 is allowed to set as unlimited"));
+ int val = parse_cli_var_zuinteger_unlimited (&arg, true);
if (*(int *) c->var != val)
{
diff --git a/gdb/cli/cli-setshow.h b/gdb/cli/cli-setshow.h
index e4d13c3..c00a098 100644
--- a/gdb/cli/cli-setshow.h
+++ b/gdb/cli/cli-setshow.h
@@ -23,6 +23,33 @@ struct cmd_list_element;
Returns 1 for true, 0 for false, and -1 if invalid. */
extern int parse_cli_boolean_value (const char *arg);
+/* Same as above, but work with a pointer to pointer. ARG is advanced
+ past a successfully parsed value. */
+extern int parse_cli_boolean_value (const char **arg);
+
+/* Parse ARG, an option to a var_uinteger or var_zuinteger variable.
+ Either returns the parsed value on success or throws an error. If
+ EXPRESSION is true, *ARG is parsed as an expression; otherwise, it
+ is parsed with get_ulongest. It's not possible to parse the
+ integer as an expression when there may be valid input after the
+ integer, such as when parsing command options. E.g., "print
+ -elements NUMBER -obj --". In such case, parsing as an expression
+ would parse "-obj --" as part of the expression as well. */
+extern unsigned int parse_cli_var_uinteger (var_types var_type,
+ const char **arg,
+ bool expression);
+
+/* Like parse_cli_var_uinteger, for var_zuinteger_unlimited. */
+extern int parse_cli_var_zuinteger_unlimited (const char **arg,
+ bool expression);
+
+/* Parse ARG, an option to a var_enum variable. ENUM is a
+ null-terminated array of possible values. Either returns the parsed
+ value on success or throws an error. ARG is advanced past the
+ parsed value. */
+const char *parse_cli_var_enum (const char **args,
+ const char *const *enums);
+
extern void do_set_command (const char *arg, int from_tty,
struct cmd_list_element *c);
extern void do_show_command (const char *arg, int from_tty,
diff --git a/gdb/cli/cli-utils.c b/gdb/cli/cli-utils.c
index 23296ce..306b69e 100644
--- a/gdb/cli/cli-utils.c
+++ b/gdb/cli/cli-utils.c
@@ -27,6 +27,57 @@ static std::string extract_arg_maybe_quoted (const char **arg);
/* See documentation in cli-utils.h. */
+ULONGEST
+get_ulongest (const char **pp, int trailer)
+{
+ LONGEST retval = 0; /* default */
+ const char *p = *pp;
+
+ if (*p == '$')
+ {
+ value *val = value_from_history_ref (p, &p);
+
+ if (val != NULL) /* Value history reference */
+ {
+ if (TYPE_CODE (value_type (val)) == TYPE_CODE_INT)
+ retval = value_as_long (val);
+ else
+ error (_("History value must have integer type."));
+ }
+ else /* Convenience variable */
+ {
+ /* Internal variable. Make a copy of the name, so we can
+ null-terminate it to pass to lookup_internalvar(). */
+ const char *start = ++p;
+ while (isalnum (*p) || *p == '_')
+ p++;
+ std::string varname (start, p - start);
+ if (!get_internalvar_integer (lookup_internalvar (varname.c_str ()),
+ &retval))
+ error (_("Convenience variable $%s does not have integer value."),
+ varname.c_str ());
+ }
+ }
+ else
+ {
+ retval = strtoulst (p, pp, 0);
+ if (p == *pp)
+ {
+ /* There is no number here. (e.g. "cond a == b"). */
+ error (_("Expected integer at: %s"), p);
+ }
+ p = *pp;
+ }
+
+ if (!(isspace (*p) || *p == '\0' || *p == trailer))
+ error (_("Trailing junk at: %s"), p);
+ p = skip_spaces (p);
+ *pp = p;
+ return retval;
+}
+
+/* See documentation in cli-utils.h. */
+
int
get_number_trailer (const char **pp, int trailer)
{
diff --git a/gdb/cli/cli-utils.h b/gdb/cli/cli-utils.h
index 9425fb4..41c2356 100644
--- a/gdb/cli/cli-utils.h
+++ b/gdb/cli/cli-utils.h
@@ -39,6 +39,10 @@ extern int get_number (const char **);
extern int get_number (char **);
+/* Like get_number_trailer, but works with ULONGEST, and throws on
+ error instead of returning 0. */
+extern ULONGEST get_ulongest (const char **pp, int trailer = '\0');
+
/* Extract from ARGS the arguments [-q] [-t TYPEREGEXP] [--] NAMEREGEXP.
The caller is responsible to initialize *QUIET to false, *REGEXP
@@ -193,6 +197,14 @@ extern std::string extract_arg (const char **arg);
argument. */
extern int check_for_argument (const char **str, const char *arg, int arg_len);
+/* Same as above, but ARG's length is computed. */
+
+static inline int
+check_for_argument (const char **str, const char *arg)
+{
+ return check_for_argument (str, arg, strlen (arg));
+}
+
/* Same, for non-const STR. */
static inline int
@@ -202,6 +214,12 @@ check_for_argument (char **str, const char *arg, int arg_len)
arg, arg_len);
}
+static inline int
+check_for_argument (char **str, const char *arg)
+{
+ return check_for_argument (str, arg, strlen (arg));
+}
+
/* A helper function that looks for a set of flags at the start of a
string. The possible flags are given as a null terminated string.
A flag in STR must either be at the end of the string,
diff --git a/gdb/maint-test-options.c b/gdb/maint-test-options.c
new file mode 100644
index 0000000..599155c
--- /dev/null
+++ b/gdb/maint-test-options.c
@@ -0,0 +1,461 @@
+/* Maintenance commands for testing the options framework.
+
+ Copyright (C) 2019 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ This program 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.
+
+ This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "defs.h"
+#include "gdbcmd.h"
+#include "cli/cli-option.h"
+
+/* This file defines three "maintenance test-options" subcommands to
+ exercise TAB-completion and option processing:
+
+ (gdb) maint test-options require-delimiter
+ (gdb) maint test-options unknown-is-error
+ (gdb) maint test-options unknown-is-operand
+
+ And a fourth one to help with TAB-completion testing.
+
+ (gdb) maint show test-options-completion-result
+
+ Each of the test-options subcommands exercise
+ gdb::option::process_options with a different enum
+ process_options_mode value. Examples for commands they model:
+
+ - "print" and "compile print", are like "require-delimiter",
+ because they accept random expressions as argument.
+
+ - "backtrace" and "frame/thread apply" are like
+ "unknown-is-operand", because "-" is a valid command.
+
+ - "compile file" and "compile code" are like "unknown-is-error".
+
+ These commands allow exercising all aspects of option processing
+ without having to pick some existing command. That should be more
+ stable going forward than relying on an existing user command,
+ since if we picked say "print", that command or its options could
+ change in future, and then we'd be left with having to pick some
+ other command or option to exercise some non-command-specific
+ option processing detail. Also, actual user commands have side
+ effects that we're not interested in when we're focusing on unit
+ testing the options machinery. BTW, a maintenance command is used
+ as a sort of unit test driver instead of actual "maint selftest"
+ unit tests, since we need to go all the way via gdb including
+ readline, for proper testing of TAB completion.
+
+ These maintenance commands support options of all the different
+ available kinds of commands (boolean, enum, flag, uinteger):
+
+ (gdb) maint test-options require-delimiter -[TAB]
+ -bool -enum -flag -uinteger -xx1 -xx2
+
+ (gdb) maint test-options require-delimiter -bool o[TAB]
+ off on
+ (gdb) maint test-options require-delimiter -enum [TAB]
+ xxx yyy zzz
+ (gdb) maint test-options require-delimiter -uinteger [TAB]
+ NUMBER unlimited
+
+ '-xx1' and '-xx2' are flag options too. They exist in order to
+ test ambiguous option names, like '-xx'.
+
+ Invoking the commands makes them print out the options parsed:
+
+ (gdb) maint test-options unknown-is-error -flag -enum yyy cmdarg
+ -flag 1 -xx1 0 -xx2 0 -bool 0 -enum yyy -uint 0 -zuint-unl 0 -- cmdarg
+
+ (gdb) maint test-options require-delimiter -flag -enum yyy cmdarg
+ -flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint 0 -zuint-unl 0 -- -flag -enum yyy cmdarg
+ (gdb) maint test-options require-delimiter -flag -enum yyy cmdarg --
+ Unrecognized option at: cmdarg --
+ (gdb) maint test-options require-delimiter -flag -enum yyy -- cmdarg
+ -flag 1 -xx1 0 -xx2 0 -bool 0 -enum yyy -uint 0 -zuint-unl 0 -- cmdarg
+
+ The "maint show test-options-completion-result" command exists in
+ order to do something similar for completion:
+
+ (gdb) maint test-options unknown-is-error -flag -b 0 -enum yyy OPERAND[TAB]
+ (gdb) maint show test-options-completion-result
+ 0 OPERAND
+
+ (gdb) maint test-options unknown-is-error -flag -b 0 -enum yyy[TAB]
+ (gdb) maint show test-options-completion-result
+ 1
+
+ (gdb) maint test-options require-dash -unknown[TAB]
+ (gdb) maint show test-options-completion-result
+ 1
+
+ Here, "1" means the completion function processed the whole input
+ line, and that the command shouldn't do anything with the arguments,
+ since there are no operands. While "0" indicates that there are
+ operands after options. The text after "0" is the operands.
+
+ This level of detail is particularly important because getting the
+ completion function's entry point to return back to the caller the
+ right pointer into the operand is quite tricky in several
+ scenarios. */
+
+/* Enum values for the "maintenance test-options" commands. */
+const char test_options_enum_values_xxx[] = "xxx";
+const char test_options_enum_values_yyy[] = "yyy";
+const char test_options_enum_values_zzz[] = "zzz";
+static const char *const test_options_enum_values_choices[] =
+{
+ test_options_enum_values_xxx,
+ test_options_enum_values_yyy,
+ test_options_enum_values_zzz,
+ NULL
+};
+
+/* Option data for the "maintenance test-options" commands. */
+
+struct test_options_opts
+{
+ int flag_opt = 0;
+ int xx1_opt = 0;
+ int xx2_opt = 0;
+ int boolean_opt = 0;
+ const char *enum_opt = test_options_enum_values_xxx;
+ unsigned int uint_opt = 0;
+ int zuint_unl_opt = 0;
+};
+
+/* Option definitions for the "maintenance test-options" commands. */
+
+static const gdb::option::option_def test_options_option_defs[] = {
+
+ /* A flag option. */
+ gdb::option::flag_option_def<test_options_opts> {
+ "flag",
+ [] (test_options_opts *opts) { return &opts->flag_opt; },
+ N_("A flag option."),
+ },
+
+ /* A couple flags with similar names, for "ambiguous option names"
+ testing. */
+ gdb::option::flag_option_def<test_options_opts> {
+ "xx1",
+ [] (test_options_opts *opts) { return &opts->xx1_opt; },
+ N_("A flag option."),
+ },
+ gdb::option::flag_option_def<test_options_opts> {
+ "xx2",
+ [] (test_options_opts *opts) { return &opts->xx2_opt; },
+ N_("A flag option."),
+ },
+
+ /* A boolean option. */
+ gdb::option::boolean_option_def<test_options_opts> {
+ "bool",
+ [] (test_options_opts *opts) { return &opts->boolean_opt; },
+ nullptr, /* show_cmd_cb */
+ N_("A boolean option."),
+ },
+
+ /* An enum option. */
+ gdb::option::enum_option_def<test_options_opts> {
+ "enum",
+ test_options_enum_values_choices,
+ [] (test_options_opts *opts) { return &opts->enum_opt; },
+ nullptr, /* show_cmd_cb */
+ N_("An enum option."),
+ },
+
+ /* A uinteger option. */
+ gdb::option::uinteger_option_def<test_options_opts> {
+ "uinteger",
+ [] (test_options_opts *opts) { return &opts->uint_opt; },
+ nullptr, /* show_cmd_cb */
+ N_("A uinteger option."),
+ nullptr, /* show_doc */
+ N_("A help doc that spawns\nmultiple lines."),
+ },
+
+ /* A zuinteger_unlimited option. */
+ gdb::option::zuinteger_unlimited_option_def<test_options_opts> {
+ "zuinteger-unlimited",
+ [] (test_options_opts *opts) { return &opts->zuint_unl_opt; },
+ nullptr, /* show_cmd_cb */
+ N_("A zuinteger-unlimited option."),
+ nullptr, /* show_doc */
+ nullptr, /* help_doc */
+ },
+};
+
+/* Create an option_def_group for the test_options_opts options, with
+ OPTS as context. */
+
+static inline gdb::option::option_def_group
+make_test_options_options_def_group (test_options_opts *opts)
+{
+ return {{test_options_option_defs}, opts};
+}
+
+/* Implementation of the "maintenance test-options
+ require-delimiter/unknown-is-error/unknown-is-operand" commands.
+ Each of the commands maps to a different enum process_options_mode
+ enumerator. The test strategy is simply processing the options in
+ a number of scenarios, and printing back the parsed result. */
+
+static void
+maintenance_test_options_command_mode (const char *args,
+ gdb::option::process_options_mode mode)
+{
+ test_options_opts opts;
+
+ gdb::option::process_options (&args, mode,
+ make_test_options_options_def_group (&opts));
+
+ if (args == nullptr)
+ args = "";
+ else
+ args = skip_spaces (args);
+
+ printf_unfiltered (_("-flag %d -xx1 %d -xx2 %d -bool %d "
+ "-enum %s -uint %s -zuint-unl %s -- %s\n"),
+ opts.flag_opt,
+ opts.xx1_opt,
+ opts.xx2_opt,
+ opts.boolean_opt,
+ opts.enum_opt,
+ (opts.uint_opt == UINT_MAX
+ ? "unlimited"
+ : pulongest (opts.uint_opt)),
+ (opts.zuint_unl_opt == -1
+ ? "unlimited"
+ : plongest (opts.zuint_unl_opt)),
+ args);
+}
+
+/* Variables used by the "maintenance show
+ test-options-completion-result" command. These variables are
+ stored by the completer of the "maint test-options"
+ subcommands. */
+
+/* The result of gdb::option::complete_options. */
+static int maintenance_test_options_command_completion_result;
+/* The text at the word point after gdb::option::complete_options
+ returns. */
+static std::string maintenance_test_options_command_completion_text;
+
+/* The "maintenance show test-options-completion-result" command. */
+
+static void
+maintenance_show_test_options_completion_result
+ (struct ui_file *file, int from_tty,
+ struct cmd_list_element *c, const char *value)
+{
+ if (maintenance_test_options_command_completion_result)
+ fprintf_filtered (file, "1\n");
+ else
+ fprintf_filtered
+ (file, _("0 %s\n"),
+ maintenance_test_options_command_completion_text.c_str ());
+}
+
+/* Implementation of completer for the "maintenance test-options
+ require-delimiter/unknown-is-error/unknown-is-operand" commands.
+ Each of the commands maps to a different enum process_options_mode
+ enumerator. */
+
+static void
+maintenance_test_options_completer_mode (completion_tracker &tracker,
+ const char *text,
+ gdb::option::process_options_mode mode)
+{
+ try
+ {
+ maintenance_test_options_command_completion_result
+ = gdb::option::complete_options
+ (tracker, &text, mode,
+ make_test_options_options_def_group (nullptr));
+ maintenance_test_options_command_completion_text = text;
+ }
+ catch (const gdb_exception_error &ex)
+ {
+ maintenance_test_options_command_completion_result = 1;
+ throw;
+ }
+}
+
+/* Implementation of the "maintenance test-options require-delimiter"
+ command. */
+
+static void
+maintenance_test_options_require_delimiter_command (const char *args,
+ int from_tty)
+{
+ maintenance_test_options_command_mode
+ (args, gdb::option::PROCESS_OPTIONS_REQUIRE_DELIMITER);
+}
+
+/* Implementation of the "maintenance test-options
+ unknown-is-error" command. */
+
+static void
+maintenance_test_options_unknown_is_error_command (const char *args,
+ int from_tty)
+{
+ maintenance_test_options_command_mode
+ (args, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_ERROR);
+}
+
+/* Implementation of the "maintenance test-options
+ unknown-is-operand" command. */
+
+static void
+maintenance_test_options_unknown_is_operand_command (const char *args,
+ int from_tty)
+{
+ maintenance_test_options_command_mode
+ (args, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_OPERAND);
+}
+
+/* Completer for the "maintenance test-options require-delimiter"
+ command. */
+
+static void
+maintenance_test_options_require_delimiter_command_completer
+ (cmd_list_element *ignore, completion_tracker &tracker,
+ const char *text, const char *word)
+{
+ maintenance_test_options_completer_mode
+ (tracker, text, gdb::option::PROCESS_OPTIONS_REQUIRE_DELIMITER);
+}
+
+/* Completer for the "maintenance test-options unknown-is-error"
+ command. */
+
+static void
+maintenance_test_options_unknown_is_error_command_completer
+ (cmd_list_element *ignore, completion_tracker &tracker,
+ const char *text, const char *word)
+{
+ maintenance_test_options_completer_mode
+ (tracker, text, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_ERROR);
+}
+
+/* Completer for the "maintenance test-options unknown-is-operand"
+ command. */
+
+static void
+maintenance_test_options_unknown_is_operand_command_completer
+ (cmd_list_element *ignore, completion_tracker &tracker,
+ const char *text, const char *word)
+{
+ maintenance_test_options_completer_mode
+ (tracker, text, gdb::option::PROCESS_OPTIONS_UNKNOWN_IS_OPERAND);
+}
+
+/* Command list for maint test-options. */
+struct cmd_list_element *maintenance_test_options_list;
+
+/* The "maintenance test-options" prefix command. */
+
+static void
+maintenance_test_options_command (const char *arg, int from_tty)
+{
+ printf_unfiltered
+ (_("\"maintenance test-options\" must be followed "
+ "by the name of a subcommand.\n"));
+ help_list (maintenance_test_options_list, "maintenance test-options ",
+ all_commands, gdb_stdout);
+}
+
+
+void
+_initialize_maint_test_options ()
+{
+ cmd_list_element *cmd;
+
+ add_prefix_cmd ("test-options", no_class, maintenance_test_options_command,
+ _("\
+Generic command for testing the options infrastructure."),
+ &maintenance_test_options_list,
+ "maintenance test-options ", 0,
+ &maintenancelist);
+
+ const auto def_group = make_test_options_options_def_group (nullptr);
+
+ static const std::string help_require_delim_str
+ = gdb::option::build_help (_("\
+Command used for testing options processing.\n\
+Usage: maint test-options require-delimiter [[OPTION]... --] [OPERAND]...\n\
+\n\
+Options:\n\
+\n\
+%OPTIONS%\n\
+If you specify any command option, you must use a double dash (\"--\")\n\
+to mark the end of option processing."),
+ def_group);
+
+ static const std::string help_unknown_is_error_str
+ = gdb::option::build_help (_("\
+Command used for testing options processing.\n\
+Usage: maint test-options unknown-is-error [OPTION]... [OPERAND]...\n\
+\n\
+Options:\n\
+\n\
+%OPTIONS%"),
+ def_group);
+
+ static const std::string help_unknown_is_operand_str
+ = gdb::option::build_help (_("\
+Command used for testing options processing.\n\
+Usage: maint test-options unknown-is-operand [OPTION]... [OPERAND]...\n\
+\n\
+Options:\n\
+\n\
+%OPTIONS%"),
+ def_group);
+
+ cmd = add_cmd ("require-delimiter", class_maintenance,
+ maintenance_test_options_require_delimiter_command,
+ help_require_delim_str.c_str (),
+ &maintenance_test_options_list);
+ set_cmd_completer_handle_brkchars
+ (cmd, maintenance_test_options_require_delimiter_command_completer);
+
+ cmd = add_cmd ("unknown-is-error", class_maintenance,
+ maintenance_test_options_unknown_is_error_command,
+ help_unknown_is_error_str.c_str (),
+ &maintenance_test_options_list);
+ set_cmd_completer_handle_brkchars
+ (cmd, maintenance_test_options_unknown_is_error_command_completer);
+
+ cmd = add_cmd ("unknown-is-operand", class_maintenance,
+ maintenance_test_options_unknown_is_operand_command,
+ help_unknown_is_operand_str.c_str (),
+ &maintenance_test_options_list);
+ set_cmd_completer_handle_brkchars
+ (cmd, maintenance_test_options_unknown_is_operand_command_completer);
+
+ add_setshow_zinteger_cmd ("test-options-completion-result", class_maintenance,
+ &maintenance_test_options_command_completion_result,
+ _("\
+Set maintenance test-options completion result."), _("\
+Show maintenance test-options completion result."), _("\
+Show the results of completing\n\
+\"maint test-options require-delimiter\",\n\
+\"maint test-options unknown-is-error\", or\n\
+\"maint test-options unknown-is-operand\"."),
+ NULL,
+ maintenance_show_test_options_completion_result,
+ &maintenance_set_cmdlist,
+ &maintenance_show_cmdlist);
+}
diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog
index 1c362f7..6e5dacb 100644
--- a/gdb/testsuite/ChangeLog
+++ b/gdb/testsuite/ChangeLog
@@ -1,5 +1,10 @@
2019-06-13 Pedro Alves <palves@redhat.com>
+ * gdb.base/options.c: New file.
+ * gdb.base/options.exp: New file.
+
+2019-06-13 Pedro Alves <palves@redhat.com>
+
* gdb.base/settings.exp (test-boolean, test-auto-boolean): Check
that "o" is ambiguous.
diff --git a/gdb/testsuite/gdb.base/options.c b/gdb/testsuite/gdb.base/options.c
new file mode 100644
index 0000000..fd19922
--- /dev/null
+++ b/gdb/testsuite/gdb.base/options.c
@@ -0,0 +1,33 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2019 Free Software Foundation, Inc.
+
+ This program 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.
+
+ This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */
+
+int xxx1= 123;
+
+struct S
+{
+ int a;
+ int b;
+ int c;
+};
+
+struct S g_s = {1, 2, 3};
+
+int
+main ()
+{
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.base/options.exp b/gdb/testsuite/gdb.base/options.exp
new file mode 100644
index 0000000..1891176
--- /dev/null
+++ b/gdb/testsuite/gdb.base/options.exp
@@ -0,0 +1,554 @@
+# This testcase is part of GDB, the GNU debugger.
+
+# Copyright 2019 Free Software Foundation, Inc.
+
+# This program 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.
+#
+# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Test the gdb::option framework.
+
+# The test uses the "maintenance test-options" subcommands to exercise
+# TAB-completion and option processing.
+
+load_lib completion-support.exp
+
+clean_restart
+
+if { ![readline_is_used] } {
+ untested "no tab completion support without readline"
+ return -1
+}
+
+# Check the completion result, as returned by the "maintenance show
+# test-options-completion-result" command. TEST is used as test name.
+proc check_completion_result {expected test} {
+ gdb_test "maintenance show test-options-completion-result" \
+ "$expected" \
+ "$test: res=$expected"
+}
+
+# Like test_gdb_complete_unique, but the expected output is expected
+# to be the input line. I.e., the line is already complete. We're
+# just checking whether GDB recognizes the option and auto-appends a
+# space.
+proc test_completer_recognizes {res input_line} {
+ set expected_re [string_to_regexp $input_line]
+ test_gdb_complete_unique $input_line $expected_re
+ check_completion_result $res $input_line
+}
+
+# Wrapper around test_gdb_complete_multiple that also checks the
+# completion result is RES.
+proc res_test_gdb_complete_multiple {res cmd_prefix completion_word args} {
+ test_gdb_complete_multiple $cmd_prefix $completion_word {*}$args
+ check_completion_result $res "$cmd_prefix$completion_word"
+}
+
+# Wrapper around test_gdb_complete_none that also checks the
+# completion result is RES.
+proc res_test_gdb_complete_none { res input_line } {
+ test_gdb_complete_none $input_line
+ check_completion_result $res "$input_line"
+}
+
+# Wrapper around test_gdb_complete_unique that also checks the
+# completion result is RES.
+proc res_test_gdb_complete_unique { res input_line args} {
+ test_gdb_complete_unique $input_line {*}$args
+ check_completion_result $res "$input_line"
+}
+
+# Make a full command name from VARIANT. VARIANT is either
+# "require-delimiter", "unknown-is-error" or "unknown-is-operand".
+proc make_cmd {variant} {
+ return "maint test-options $variant"
+}
+
+# Return a string for the expected result of running "maint
+# test-options xxx", with no flag/option set. OPERAND is the expected
+# operand.
+proc expect_none {operand} {
+ return "-flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint 0 -zuint-unl 0 -- $operand"
+}
+
+# Return a string for the expected result of running "maint
+# test-options xxx", with -flag set. OPERAND is the expected operand.
+proc expect_flag {operand} {
+ return "-flag 1 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint 0 -zuint-unl 0 -- $operand"
+}
+
+# Return a string for the expected result of running "maint
+# test-options xxx", with -bool set. OPERAND is the expected operand.
+proc expect_bool {operand} {
+ return "-flag 0 -xx1 0 -xx2 0 -bool 1 -enum xxx -uint 0 -zuint-unl 0 -- $operand"
+}
+
+# Return a string for the expected result of running "maint
+# test-options xxx", with one of the integer options set to $VAL.
+# OPTION determines which option to expect set. OPERAND is the
+# expected operand.
+proc expect_integer {option val operand} {
+ if {$option == "uinteger"} {
+ return "-flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint $val -zuint-unl 0 -- $operand"
+ } elseif {$option == "zuinteger-unlimited"} {
+ return "-flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint 0 -zuint-unl $val -- $operand"
+ } else {
+ error "unsupported option: $option"
+ }
+}
+
+set all_options {
+ "-bool"
+ "-enum"
+ "-flag"
+ "-uinteger"
+ "-xx1"
+ "-xx2"
+ "-zuinteger-unlimited"
+}
+
+# Miscellaneous tests.
+proc_with_prefix test-misc {variant} {
+ global all_options
+
+ set cmd [make_cmd $variant]
+
+ # Call test command with no arguments at all.
+ gdb_test "$cmd" [expect_none ""]
+
+ # Now with a single dash.
+ if {$variant == "require-delimiter"} {
+ gdb_test "$cmd -" [expect_none "-"]
+ } else {
+ gdb_test "$cmd -" "Ambiguous option at: -"
+ }
+
+ # Completing at "-" should list all options.
+ res_test_gdb_complete_multiple "1" "$cmd " "-" "" $all_options
+
+ # Now with a double dash.
+ gdb_test "$cmd --" [expect_none ""]
+
+ # "--" is recognized by options completer, gdb auto-appends a
+ # space.
+ test_completer_recognizes 1 "$cmd --"
+
+ # Now with a double dash, plus a dash as operand.
+ gdb_test "$cmd -- -" [expect_none "-"]
+ res_test_gdb_complete_none "0 -" "$cmd -- -"
+
+ # Completing an unambiguous option just appends an empty space.
+ test_completer_recognizes 1 "$cmd -flag"
+
+ # Try running an ambiguous option.
+ if {$variant == "require-delimiter"} {
+ gdb_test "$cmd -xx" [expect_none "-xx"]
+ } else {
+ gdb_test "$cmd -xx" "Ambiguous option at: -xx"
+ }
+
+ # Check that options are not case insensitive.
+ gdb_test "$cmd -flag --" [expect_flag ""]
+
+ # Check how the different modes behave on unknown option, with a
+ # delimiter.
+ gdb_test "$cmd -FLAG --" \
+ "Unrecognized option at: -FLAG --"
+
+ # Check how the different modes behave on unknown option, without
+ # a delimiter.
+ if {$variant == "unknown-is-error"} {
+ gdb_test "$cmd -FLAG" \
+ "Unrecognized option at: -FLAG"
+ } else {
+ gdb_test "$cmd -FLAG" [expect_none "-FLAG"]
+ }
+
+ # Test parsing stops at a negative integer.
+ gdb_test "$cmd -1 --" \
+ "Unrecognized option at: -1 --"
+ gdb_test "$cmd -2 --" \
+ "Unrecognized option at: -2 --"
+}
+
+# Flag option tests.
+proc_with_prefix test-flag {variant} {
+ global all_options
+
+ set cmd [make_cmd $variant]
+
+ # Completing a flag just appends a space.
+ test_completer_recognizes 1 "$cmd -flag"
+
+ # Add a dash, and all options should be shown.
+ test_gdb_complete_multiple "$cmd -flag " "-" "" $all_options
+
+ # Basic smoke tests of accepted / not accepted values.
+
+ # Check all the different variants a bool option may be specified.
+ if {$variant == "require-delimiter"} {
+ gdb_test "$cmd -flag 999" [expect_none "-flag 999"]
+ } else {
+ gdb_test "$cmd -flag 999" [expect_flag "999"]
+ }
+ gdb_test "$cmd -flag -- 999" [expect_flag "999"]
+
+ # If the "--" separator is present, then GDB errors out if the
+ # flag option is passed some value -- check that too.
+ gdb_test "$cmd -flag xxx 999 --" "Unrecognized option at: xxx 999 --"
+ gdb_test "$cmd -flag o 999 --" "Unrecognized option at: o 999 --"
+ gdb_test "$cmd -flag 1 999 --" "Unrecognized option at: 1 999 --"
+
+ # Extract twice the same flag, separated by one space.
+ gdb_test "$cmd -flag -flag -- non flags args" \
+ [expect_flag "non flags args"]
+
+ # Extract twice the same flag, separated by one space.
+ gdb_test "$cmd -xx1 -xx2 -xx1 -xx2 -xx1 -- non flags args" \
+ "-flag 0 -xx1 1 -xx2 1 -bool 0 -enum xxx -uint 0 -zuint-unl 0 -- non flags args"
+
+ # Extract 2 known flags in front of unknown flags.
+ gdb_test "$cmd -xx1 -xx2 -a -b -c -xx1 --" \
+ "Unrecognized option at: -a -b -c -xx1 --"
+
+ # Check that combined flags are not recognised.
+ gdb_test "$cmd -xx1 -xx1xx2 -xx1 --" \
+ "Unrecognized option at: -xx1xx2 -xx1 --"
+
+ # Make sure the completer don't confuse a flag option with a
+ # boolean option. Specifically, "o" should not complete to
+ # "on/off".
+
+ if {$variant == "require-delimiter"} {
+ res_test_gdb_complete_none "1" "$cmd -flag o"
+
+ gdb_test "$cmd -flag o" [expect_none "-flag o"]
+ } else {
+ res_test_gdb_complete_none "0 o" "$cmd -flag o"
+
+ gdb_test "$cmd -flag o" [expect_flag "o"]
+ }
+}
+
+# Boolean option tests.
+proc_with_prefix test-boolean {variant} {
+ global all_options
+
+ set cmd [make_cmd $variant]
+
+ # Boolean option's values are optional -- "on" is implied. Check
+ # that:
+ #
+ # - For require-delimiter commands, completing after a boolean
+ # option lists all other options, plus "on/off". This is
+ # because operands won't be processed until we see a "--"
+ # delimiter.
+ #
+ # - For !require-delimiter commands, completing after a boolean
+ # option completes as an operand, since that will tend to be
+ # more common than typing "on/off".
+ # E.g., "frame apply all -past-main COMMAND".
+
+ if {$variant == "require-delimiter"} {
+ res_test_gdb_complete_multiple 1 "$cmd -bool " "" "" {
+ "-bool"
+ "-enum"
+ "-flag"
+ "-uinteger"
+ "-xx1"
+ "-xx2"
+ "-zuinteger-unlimited"
+ "off"
+ "on"
+ }
+ } else {
+ res_test_gdb_complete_none "0 " "$cmd -bool "
+ }
+
+ # Add another dash, and "on/off" are no longer offered:
+ res_test_gdb_complete_multiple 1 "$cmd -bool " "-" "" $all_options
+
+ # Basic smoke tests of accepted / not accepted values.
+
+ # The command accepts all of "1/0/enable/disable/yes/no" too, even
+ # though like the "set" command, we don't offer those as
+ # completion candidates if you complete right after the boolean
+ # command's name, like:
+ #
+ # (gdb) maint test-options require-delimiter -bool [TAB]
+ # off on
+ #
+ # However, the completer does recognize them if you start typing
+ # the boolean value.
+ foreach value {"0" "1"} {
+ test_completer_recognizes 1 "$cmd -bool $value"
+ }
+ foreach value {"of" "off"} {
+ res_test_gdb_complete_unique 1 \
+ "$cmd -bool $value" \
+ "$cmd -bool off"
+ }
+ foreach value {"y" "ye" "yes"} {
+ res_test_gdb_complete_unique 1 \
+ "$cmd -bool $value" \
+ "$cmd -bool yes"
+ }
+ foreach value {"n" "no"} {
+ res_test_gdb_complete_unique 1 \
+ "$cmd -bool $value" \
+ "$cmd -bool no"
+ }
+ foreach value {
+ "e"
+ "en"
+ "ena"
+ "enab"
+ "enabl"
+ "enable"
+ } {
+ res_test_gdb_complete_unique 1 \
+ "$cmd -bool $value" \
+ "$cmd -bool enable"
+ }
+ foreach value {
+ "d"
+ "di"
+ "dis"
+ "disa"
+ "disab"
+ "disabl"
+ "disable"
+ } {
+ res_test_gdb_complete_unique 1 \
+ "$cmd -bool $value" \
+ "$cmd -bool disable"
+ }
+
+ if {$variant == "require-delimiter"} {
+ res_test_gdb_complete_none "1" "$cmd -bool xxx"
+ } else {
+ res_test_gdb_complete_none "0 xxx" "$cmd -bool xxx"
+ }
+
+ # The command accepts abbreviations of "enable/disable/yes/no",
+ # even though we don't offer those for completion.
+ foreach value {
+ "1"
+ "y" "ye" "yes"
+ "e"
+ "en"
+ "ena"
+ "enab"
+ "enabl"
+ "enable"} {
+ gdb_test "$cmd -bool $value --" [expect_bool ""]
+ }
+ foreach value {
+ "0"
+ "of" "off"
+ "n" "no"
+ "d"
+ "di"
+ "dis"
+ "disa"
+ "disab"
+ "disabl"
+ "disable"} {
+ gdb_test "$cmd -bool $value --" [expect_none ""]
+ }
+
+ if {$variant == "require-delimiter"} {
+ gdb_test "$cmd -bool 999" [expect_none "-bool 999"]
+ } else {
+ gdb_test "$cmd -bool 999" [expect_bool "999"]
+ }
+ gdb_test "$cmd -bool -- 999" [expect_bool "999"]
+
+ # Since "on" is implied after a boolean option, for
+ # !require-delimiter commands, anything that is not
+ # yes/no/1/0/on/off/enable/disable should be considered as the raw
+ # input after the last option. Also check "o", which might look
+ # like "on" or "off", but it's treated the same.
+
+ foreach arg {"xxx" "o"} {
+ if {$variant == "require-delimiter"} {
+ gdb_test "$cmd -bool $arg" [expect_none "-bool $arg"]
+ } else {
+ gdb_test "$cmd -bool $arg" [expect_bool "$arg"]
+ }
+ }
+ # Also try -1. "unknown-is-error" commands error out saying that
+ # that's not a valid option.
+ if {$variant == "require-delimiter"} {
+ gdb_test "$cmd -bool -1" \
+ [expect_none "-bool -1"]
+ } elseif {$variant == "unknown-is-error"} {
+ gdb_test "$cmd -bool -1" \
+ "Unrecognized option at: -1"
+ } else {
+ gdb_test "$cmd -bool -1" [expect_bool "-1"]
+ }
+
+ # OTOH, if the "--" separator is present, then GDB errors out if
+ # the boolean option is passed an invalid value -- check that too.
+ gdb_test "$cmd -bool -1 999 --" \
+ "Unrecognized option at: -1 999 --"
+ gdb_test "$cmd -bool xxx 999 --" \
+ "Value given for `-bool' is not a boolean: xxx"
+ gdb_test "$cmd -bool o 999 --" \
+ "Value given for `-bool' is not a boolean: o"
+
+ # Completing after a boolean option + "o" does list "on/off",
+ # though.
+ if {$variant == "require-delimiter"} {
+ res_test_gdb_complete_multiple 1 "$cmd -bool " "o" "" {
+ "off"
+ "on"
+ }
+ } else {
+ res_test_gdb_complete_multiple "0 o" "$cmd -bool " "o" "" {
+ "off"
+ "on"
+ }
+ }
+}
+
+# Uinteger option tests. OPTION is which integer option we're
+# testing. Can be "uinteger" or "zuinteger-unlimited".
+proc_with_prefix test-uinteger {variant option} {
+ global all_options
+
+ set cmd "[make_cmd $variant] -$option"
+
+ # Test completing a uinteger option:
+ res_test_gdb_complete_multiple 1 "$cmd " "" "" {
+ "NUMBER"
+ "unlimited"
+ }
+
+ # NUMBER above is just a placeholder, make sure we don't complete
+ # it as a valid option.
+ res_test_gdb_complete_none 1 "$cmd NU"
+
+ # "unlimited" is valid though.
+ res_test_gdb_complete_unique 1 \
+ "$cmd u" \
+ "$cmd unlimited"
+
+ # Basic smoke test of accepted / not accepted values.
+ gdb_test "$cmd 1 -- 999" [expect_integer $option "1" "999"]
+ gdb_test "$cmd unlimited -- 999" \
+ [expect_integer $option "unlimited" "999"]
+ if {$option == "zuinteger-unlimited"} {
+ gdb_test "$cmd -1 --" [expect_integer $option "unlimited" ""]
+ gdb_test "$cmd 0 --" [expect_integer $option "0" ""]
+ } else {
+ gdb_test "$cmd -1 --" "integer -1 out of range"
+ gdb_test "$cmd 0 --" [expect_integer $option "unlimited" ""]
+ }
+ gdb_test "$cmd xxx --" \
+ "Expected integer at: xxx --"
+ gdb_test "$cmd unlimitedx --" \
+ "Expected integer at: unlimitedx --"
+
+ # Don't offer completions until we're past the
+ # -uinteger/-zuinteger-unlimited argument.
+ res_test_gdb_complete_none 1 "$cmd 1"
+
+ # A number of invalid values.
+ foreach value {"x" "x " "1a" "1a " "1-" "1- " "unlimitedx"} {
+ res_test_gdb_complete_none 1 "$cmd $value"
+ }
+
+ # Try "-1".
+ if {$option == "uinteger"} {
+ # -1 is invalid uinteger.
+ foreach value {"-1" "-1 "} {
+ res_test_gdb_complete_none 1 "$cmd $value"
+ }
+ } else {
+ # -1 is valid for zuinteger-unlimited.
+ res_test_gdb_complete_none 1 "$cmd -1"
+ if {$variant == "require-delimiter"} {
+ res_test_gdb_complete_multiple 1 "$cmd -1 " "" "-" $all_options
+ } else {
+ res_test_gdb_complete_none "0 " "$cmd -1 "
+ }
+ }
+
+ # Check that after a fully parsed option:
+ #
+ # - for require-delimiter commands, completion offers all
+ # options.
+ #
+ # - for !require-delimiter commands, completion offers nothing
+ # and returns false.
+ if {$variant == "require-delimiter"} {
+ res_test_gdb_complete_multiple 1 "$cmd 1 " "" "-" $all_options
+ } else {
+ res_test_gdb_complete_none "0 " "$cmd 1 "
+ }
+
+ # Test completing non-option arguments after "-uinteger 1 ".
+ foreach operand {"x" "x " "1a" "1a " "1-" "1- "} {
+ if {$variant == "require-delimiter"} {
+ res_test_gdb_complete_none 1 "$cmd 1 $operand"
+ } else {
+ res_test_gdb_complete_none "0 $operand" "$cmd 1 $operand"
+ }
+ }
+ # These look like options, but they aren't.
+ foreach operand {"-1" "-1 "} {
+ if {$variant == "unknown-is-operand"} {
+ res_test_gdb_complete_none "0 $operand" "$cmd 1 $operand"
+ } else {
+ res_test_gdb_complete_none 1 "$cmd 1 $operand"
+ }
+ }
+}
+
+# Enum option tests.
+proc_with_prefix test-enum {variant} {
+ set cmd [make_cmd $variant]
+
+ res_test_gdb_complete_multiple 1 "$cmd -enum " "" "" {
+ "xxx"
+ "yyy"
+ "zzz"
+ }
+
+ # Check that "-" where a value is expected does not show the
+ # command's options. I.e., an enum's value is not optional.
+ # Check both completion and running the command.
+ res_test_gdb_complete_none 1 "$cmd -enum -"
+ gdb_test "$cmd -enum --"\
+ "Requires an argument. Valid arguments are xxx, yyy, zzz\\."
+
+ # Try passing an undefined item to an enum option.
+ gdb_test "$cmd -enum www --" "Undefined item: \"www\"."
+}
+
+# Run the options framework tests first.
+foreach_with_prefix cmd {
+ "require-delimiter"
+ "unknown-is-error"
+ "unknown-is-operand"
+} {
+ test-misc $cmd
+ test-flag $cmd
+ test-boolean $cmd
+ foreach subcmd {"uinteger" "zuinteger-unlimited" } {
+ test-uinteger $cmd $subcmd
+ }
+ test-enum $cmd
+}