text: # Pretty-printer commands.
  # Copyright (C) 2010-2021 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/>.
  
  """GDB commands for working with pretty-printers."""
  
  import copy
  import gdb
  import re
  
  
  def parse_printer_regexps(arg):
      """Internal utility to parse a pretty-printer command argv.
  
      Arguments:
          arg: The arguments to the command.  The format is:
               [object-regexp [name-regexp]].
               Individual printers in a collection are named as
               printer-name;subprinter-name.
  
      Returns:
          The result is a 3-tuple of compiled regular expressions, except that
          the resulting compiled subprinter regexp is None if not provided.
  
      Raises:
          SyntaxError: an error processing ARG
      """
  
      argv = gdb.string_to_argv(arg);
      argc = len(argv)
      object_regexp = ""  # match everything
      name_regexp = ""  # match everything
      subname_regexp = None
      if argc > 3:
          raise SyntaxError("too many arguments")
      if argc >= 1:
          object_regexp = argv[0]
      if argc >= 2:
          name_subname = argv[1].split(";", 1)
          name_regexp = name_subname[0]
          if len(name_subname) == 2:
              subname_regexp = name_subname[1]
      # That re.compile raises SyntaxError was determined empirically.
      # We catch it and reraise it to provide a slightly more useful
      # error message for the user.
      try:
          object_re = re.compile(object_regexp)
      except SyntaxError:
          raise SyntaxError("invalid object regexp: %s" % object_regexp)
      try:
          name_re = re.compile (name_regexp)
      except SyntaxError:
          raise SyntaxError("invalid name regexp: %s" % name_regexp)
      if subname_regexp is not None:
          try:
              subname_re = re.compile(subname_regexp)
          except SyntaxError:
              raise SyntaxError("invalid subname regexp: %s" % subname_regexp)
      else:
          subname_re = None
      return(object_re, name_re, subname_re)
  
  
  def printer_enabled_p(printer):
      """Internal utility to see if printer (or subprinter) is enabled."""
      if hasattr(printer, "enabled"):
          return printer.enabled
      else:
          return True
  
  
  class InfoPrettyPrinter(gdb.Command):
      """GDB command to list all registered pretty-printers.
  
  Usage: info pretty-printer [OBJECT-REGEXP [NAME-REGEXP]]
  
  OBJECT-REGEXP is a regular expression matching the objects to list.
  Objects are "global", the program space's file, and the objfiles within
  that program space.
  
  NAME-REGEXP matches the name of the pretty-printer.
  Individual printers in a collection are named as
  printer-name;subprinter-name."""
  
      def __init__ (self):
          super(InfoPrettyPrinter, self).__init__("info pretty-printer",
                                                   gdb.COMMAND_DATA)
  
      @staticmethod
      def enabled_string(printer):
          """Return "" if PRINTER is enabled, otherwise " [disabled]"."""
          if printer_enabled_p(printer):
              return ""
          else:
              return " [disabled]"
  
      @staticmethod
      def printer_name(printer):
          """Return the printer's name."""
          if hasattr(printer, "name"):
              return printer.name
          if hasattr(printer, "__name__"):
              return printer.__name__
          # This "shouldn't happen", but the public API allows for
          # direct additions to the pretty-printer list, and we shouldn't
          # crash because someone added a bogus printer.
          # Plus we want to give the user a way to list unknown printers.
          return "unknown"
  
      def list_pretty_printers(self, pretty_printers, name_re, subname_re):
          """Print a list of pretty-printers."""
          # A potential enhancement is to provide an option to list printers in
          # "lookup order" (i.e. unsorted).
          sorted_pretty_printers = sorted (copy.copy(pretty_printers),
                                           key = self.printer_name)
          for printer in sorted_pretty_printers:
              name = self.printer_name(printer)
              enabled = self.enabled_string(printer)
              if name_re.match(name):
                  print ("  %s%s" % (name, enabled))
                  if (hasattr(printer, "subprinters") and
                      printer.subprinters is not None):
                      sorted_subprinters = sorted (copy.copy(printer.subprinters),
                                                   key = self.printer_name)
                      for subprinter in sorted_subprinters:
                          if (not subname_re or
                              subname_re.match(subprinter.name)):
                              print ("    %s%s" %
                                     (subprinter.name,
                                      self.enabled_string(subprinter)))
  
      def invoke1(self, title, printer_list,
                  obj_name_to_match, object_re, name_re, subname_re):
          """Subroutine of invoke to simplify it."""
          if printer_list and object_re.match(obj_name_to_match):
              print (title)
              self.list_pretty_printers(printer_list, name_re, subname_re)
  
      def invoke(self, arg, from_tty):
          """GDB calls this to perform the command."""
          (object_re, name_re, subname_re) = parse_printer_regexps(arg)
          self.invoke1("global pretty-printers:", gdb.pretty_printers,
                       "global", object_re, name_re, subname_re)
          cp = gdb.current_progspace()
          self.invoke1("progspace %s pretty-printers:" % cp.filename,
                       cp.pretty_printers, "progspace",
                       object_re, name_re, subname_re)
          for objfile in gdb.objfiles():
              self.invoke1("objfile %s pretty-printers:" % objfile.filename,
                           objfile.pretty_printers, objfile.filename,
                           object_re, name_re, subname_re)
  
  
  def count_enabled_printers(pretty_printers):
      """Return a 2-tuple of number of enabled and total printers."""
      enabled = 0
      total = 0
      for printer in pretty_printers:
          if (hasattr(printer, "subprinters")
              and printer.subprinters is not None):
              if printer_enabled_p(printer):
                  for subprinter in printer.subprinters:
                      if printer_enabled_p(subprinter):
                          enabled += 1
              total += len(printer.subprinters)
          else:
              if printer_enabled_p(printer):
                  enabled += 1
              total += 1
      return (enabled, total)
  
  
  def count_all_enabled_printers():
      """Return a 2-tuble of the enabled state and total number of all printers.
      This includes subprinters.
      """
      enabled_count = 0
      total_count = 0
      (t_enabled, t_total) = count_enabled_printers(gdb.pretty_printers)
      enabled_count += t_enabled
      total_count += t_total
      (t_enabled, t_total) = count_enabled_printers(gdb.current_progspace().pretty_printers)
      enabled_count += t_enabled
      total_count += t_total
      for objfile in gdb.objfiles():
          (t_enabled, t_total) = count_enabled_printers(objfile.pretty_printers)
          enabled_count += t_enabled
          total_count += t_total
      return (enabled_count, total_count)
  
  
  def pluralize(text, n, suffix="s"):
      """Return TEXT pluralized if N != 1."""
      if n != 1:
          return "%s%s" % (text, suffix)
      else:
          return text
  
  
  def show_pretty_printer_enabled_summary():
      """Print the number of printers enabled/disabled.
      We count subprinters individually.
      """
      (enabled_count, total_count) = count_all_enabled_printers()
      print ("%d of %d printers enabled" % (enabled_count, total_count))
  
  
  def do_enable_pretty_printer_1 (pretty_printers, name_re, subname_re, flag):
      """Worker for enabling/disabling pretty-printers.
  
      Arguments:
          pretty_printers: list of pretty-printers
          name_re: regular-expression object to select printers
          subname_re: regular expression object to select subprinters or None
                      if all are affected
          flag: True for Enable, False for Disable
  
      Returns:
          The number of printers affected.
          This is just for informational purposes for the user.
      """
      total = 0
      for printer in pretty_printers:
          if (hasattr(printer, "name") and name_re.match(printer.name) or
              hasattr(printer, "__name__") and name_re.match(printer.__name__)):
              if (hasattr(printer, "subprinters") and
                  printer.subprinters is not None):
                  if not subname_re:
                      # Only record printers that change state.
                      if printer_enabled_p(printer) != flag:
                          for subprinter in printer.subprinters:
                              if printer_enabled_p(subprinter):
                                  total += 1
                      # NOTE: We preserve individual subprinter settings.
                      printer.enabled = flag
                  else:
                      # NOTE: Whether this actually disables the subprinter
                      # depends on whether the printer's lookup function supports
                      # the "enable" API.  We can only assume it does.
                      for subprinter in printer.subprinters:
                          if subname_re.match(subprinter.name):
                              # Only record printers that change state.
                             if (printer_enabled_p(printer) and
                                 printer_enabled_p(subprinter) != flag):
                                 total += 1
                             subprinter.enabled = flag
              else:
                  # This printer has no subprinters.
                  # If the user does "disable pretty-printer .* .* foo"
                  # should we disable printers that don't have subprinters?
                  # How do we apply "foo" in this context?  Since there is no
                  # "foo" subprinter it feels like we should skip this printer.
                  # There's still the issue of how to handle
                  # "disable pretty-printer .* .* .*", and every other variation
                  # that can match everything.  For now punt and only support
                  # "disable pretty-printer .* .*" (i.e. subname is elided)
                  # to disable everything.
                  if not subname_re:
                      # Only record printers that change state.
                      if printer_enabled_p(printer) != flag:
                          total += 1
                      printer.enabled = flag
      return total
  
  
  def do_enable_pretty_printer (arg, flag):
      """Internal worker for enabling/disabling pretty-printers."""
      (object_re, name_re, subname_re) = parse_printer_regexps(arg)
  
      total = 0
      if object_re.match("global"):
          total += do_enable_pretty_printer_1(gdb.pretty_printers,
                                              name_re, subname_re, flag)
      cp = gdb.current_progspace()
      if object_re.match("progspace"):
          total += do_enable_pretty_printer_1(cp.pretty_printers,
                                              name_re, subname_re, flag)
      for objfile in gdb.objfiles():
          if object_re.match(objfile.filename):
              total += do_enable_pretty_printer_1(objfile.pretty_printers,
                                                  name_re, subname_re, flag)
  
      if flag:
          state = "enabled"
      else:
          state = "disabled"
      print ("%d %s %s" % (total, pluralize("printer", total), state))
  
      # Print the total list of printers currently enabled/disabled.
      # This is to further assist the user in determining whether the result
      # is expected.  Since we use regexps to select it's useful.
      show_pretty_printer_enabled_summary()
  
  
  # Enable/Disable one or more pretty-printers.
  #
  # This is intended for use when a broken pretty-printer is shipped/installed
  # and the user wants to disable that printer without disabling all the other
  # printers.
  #
  # A useful addition would be -v (verbose) to show each printer affected.
  
  class EnablePrettyPrinter (gdb.Command):
      """GDB command to enable the specified pretty-printer.
  
  Usage: enable pretty-printer [OBJECT-REGEXP [NAME-REGEXP]]
  
  OBJECT-REGEXP is a regular expression matching the objects to examine.
  Objects are "global", the program space's file, and the objfiles within
  that program space.
  
  NAME-REGEXP matches the name of the pretty-printer.
  Individual printers in a collection are named as
  printer-name;subprinter-name."""
  
      def __init__(self):
          super(EnablePrettyPrinter, self).__init__("enable pretty-printer",
                                                     gdb.COMMAND_DATA)
  
      def invoke(self, arg, from_tty):
          """GDB calls this to perform the command."""
          do_enable_pretty_printer(arg, True)
  
  
  class DisablePrettyPrinter (gdb.Command):
      """GDB command to disable the specified pretty-printer.
  
  Usage: disable pretty-printer [OBJECT-REGEXP [NAME-REGEXP]]
  
  OBJECT-REGEXP is a regular expression matching the objects to examine.
  Objects are "global", the program space's file, and the objfiles within
  that program space.
  
  NAME-REGEXP matches the name of the pretty-printer.
  Individual printers in a collection are named as
  printer-name;subprinter-name."""
  
      def __init__(self):
          super(DisablePrettyPrinter, self).__init__("disable pretty-printer",
                                                     gdb.COMMAND_DATA)
  
      def invoke(self, arg, from_tty):
          """GDB calls this to perform the command."""
          do_enable_pretty_printer(arg, False)
  
  
  def register_pretty_printer_commands():
      """Call from a top level script to install the pretty-printer commands."""
      InfoPrettyPrinter()
      EnablePrettyPrinter()
      DisablePrettyPrinter()
  
  register_pretty_printer_commands()
  