aboutsummaryrefslogtreecommitdiff
path: root/gdb/cli/cli-option.c
diff options
context:
space:
mode:
Diffstat (limited to 'gdb/cli/cli-option.c')
-rw-r--r--gdb/cli/cli-option.c724
1 files changed, 724 insertions, 0 deletions
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 */