|  | /* CLI options framework, for GDB. | 
|  |  | 
|  | Copyright (C) 2017-2022 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; | 
|  |  | 
|  | /* For var_string options.  This is malloc-allocated.  */ | 
|  | std::string *string; | 
|  | }; | 
|  |  | 
|  | /* 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; | 
|  |  | 
|  | /* Constructor.  */ | 
|  | option_def_and_value (const option_def &option_, void *ctx_, | 
|  | gdb::optional<option_value> &&value_ = {}) | 
|  | : option (option_), | 
|  | ctx (ctx_), | 
|  | value (std::move (value_)) | 
|  | { | 
|  | clear_value (option_, value_); | 
|  | } | 
|  |  | 
|  | /* Move constructor.  Need this because for some types the values | 
|  | are allocated on the heap.  */ | 
|  | option_def_and_value (option_def_and_value &&rval) | 
|  | : option (rval.option), | 
|  | ctx (rval.ctx), | 
|  | value (std::move (rval.value)) | 
|  | { | 
|  | clear_value (rval.option, rval.value); | 
|  | } | 
|  |  | 
|  | DISABLE_COPY_AND_ASSIGN (option_def_and_value); | 
|  |  | 
|  | ~option_def_and_value () | 
|  | { | 
|  | if (value.has_value ()) | 
|  | { | 
|  | if (option.type == var_string) | 
|  | delete value->string; | 
|  | } | 
|  | } | 
|  |  | 
|  | private: | 
|  |  | 
|  | /* Clear the option_value, without releasing it.  This is used after | 
|  | the value has been moved to some other option_def_and_value | 
|  | instance.  This is needed because for some types the value is | 
|  | allocated on the heap, so we must clear the pointer in the | 
|  | source, to avoid a double free.  */ | 
|  | static void clear_value (const option_def &option, | 
|  | gdb::optional<option_value> &value) | 
|  | { | 
|  | if (value.has_value ()) | 
|  | { | 
|  | if (option.type == var_string) | 
|  | value->string = nullptr; | 
|  | } | 
|  | } | 
|  | }; | 
|  |  | 
|  | static void save_option_value_in_ctx (gdb::optional<option_def_and_value> &ov); | 
|  |  | 
|  | /* 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); | 
|  | if (completion->tracker.have_completions ()) | 
|  | return {}; | 
|  |  | 
|  | /* If we don't have completions, let the | 
|  | non-completion path throw on invalid enum value | 
|  | below, so that completion processing stops.  */ | 
|  | } | 
|  | } | 
|  |  | 
|  | 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}; | 
|  | } | 
|  | case var_string: | 
|  | { | 
|  | if (check_for_argument (args, "--")) | 
|  | { | 
|  | /* Treat e.g., "maint test-options -string --" as if there | 
|  | was no argument after "-string".  */ | 
|  | error (_("-%s requires an argument"), match->name); | 
|  | } | 
|  |  | 
|  | const char *arg_start = *args; | 
|  | std::string str = extract_string_maybe_quoted (args); | 
|  | if (*args == arg_start) | 
|  | error (_("-%s requires an argument"), match->name); | 
|  |  | 
|  | option_value val; | 
|  | val.string = new std::string (std::move (str)); | 
|  | 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; | 
|  | } | 
|  |  | 
|  | /* If the caller passed in a context, then it is | 
|  | interested in the option argument values.  */ | 
|  | if (ov && ov->ctx != nullptr) | 
|  | save_option_value_in_ctx (ov); | 
|  | } | 
|  | 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; | 
|  | } | 
|  |  | 
|  | /* Save the parsed value in the option's context.  */ | 
|  |  | 
|  | static void | 
|  | save_option_value_in_ctx (gdb::optional<option_def_and_value> &ov) | 
|  | { | 
|  | 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; | 
|  | case var_string: | 
|  | *ov->option.var_address.string (ov->option, ov->ctx) | 
|  | = std::move (*ov->value->string); | 
|  | break; | 
|  | default: | 
|  | gdb_assert_not_reached ("unhandled option type"); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* 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; | 
|  |  | 
|  | save_option_value_in_ctx (ov); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* 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 (); | 
|  | } | 
|  | case var_string: | 
|  | return "STRING"; | 
|  | 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; | 
|  | } | 
|  |  | 
|  | /* 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) | 
|  | { | 
|  | help += "\n"; | 
|  | append_indented_doc (o.help_doc, help); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /* See cli-option.h.  */ | 
|  |  | 
|  | std::string | 
|  | build_help (const char *help_tmpl, | 
|  | gdb::array_view<const option_def_group> options_group) | 
|  | { | 
|  | bool need_newlines = false; | 
|  | 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) | 
|  | { | 
|  | if (need_newlines) | 
|  | help_str += "\n\n"; | 
|  | else | 
|  | need_newlines = true; | 
|  | 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 if (option.type == var_string) | 
|  | { | 
|  | add_setshow_string_cmd (option.name, cmd_class, | 
|  | option.var_address.string (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 */ |