+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
 
        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 \
        macrotab.c \
        main.c \
        maint.c \
+       maint-test-options.c \
        maint-test-settings.c \
        mdebugread.c \
        mem-break.c \
 
   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
   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
                         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,
 
 
 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.
 
--- /dev/null
+/* 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 */
 
--- /dev/null
+/* 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 */
 
 /* 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;
 }
+
 \f
 void
 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
     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)
          {
              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);
       }
     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);
 
       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)
          {
 
    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,
 
 
 /* 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)
 {
 
 
 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
    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
                             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,
 
--- /dev/null
+/* 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);
+}
+
+\f
+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);
+}
 
+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
 
--- /dev/null
+/* 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;
+}
 
--- /dev/null
+# 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
+}