text: """
  Python parser for gdb's machine interface interpreter.
  
  Parses string output from gdb with the `--interpreter=mi2` flag into
  structured objects.
  
  See more at https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI.html#GDB_002fMI
  """
  
  import logging
  import re
  from pprint import pprint
  from typing import Dict, Union
  
  from pygdbmi.printcolor import fmt_green
  from pygdbmi.StringStream import StringStream
  
  _DEBUG = False
  logger = logging.getLogger(__name__)
  
  
  def _setup_logger(logger, debug):
      logger.propagate = False
  
      handler = logging.StreamHandler()
      handler.setFormatter(
          logging.Formatter("[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s")
      )
      if debug:
          level = logging.DEBUG
      else:
          level = logging.ERROR
  
      logger.setLevel(level)
      logger.addHandler(handler)
  
  
  _setup_logger(logger, _DEBUG)
  
  
  def parse_response(gdb_mi_text: str) -> Dict:
      """Parse gdb mi text and turn it into a dictionary.
  
      See https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI-Stream-Records.html#GDB_002fMI-Stream-Records
      for details on types of gdb mi output.
  
      Args:
          gdb_mi_text: String output from gdb
  
      Returns:
          dictionary with keys "type", "message", "payload", "token"
      """
      stream = StringStream(gdb_mi_text, debug=_DEBUG)
  
      if _GDB_MI_NOTIFY_RE.match(gdb_mi_text):
          token, message, payload = _get_notify_msg_and_payload(gdb_mi_text, stream)
          return {
              "type": "notify",
              "message": message,
              "payload": payload,
              "token": token,
          }
  
      elif _GDB_MI_RESULT_RE.match(gdb_mi_text):
          token, message, payload = _get_result_msg_and_payload(gdb_mi_text, stream)
          return {
              "type": "result",
              "message": message,
              "payload": payload,
              "token": token,
          }
  
      elif _GDB_MI_CONSOLE_RE.match(gdb_mi_text):
          match = _GDB_MI_CONSOLE_RE.match(gdb_mi_text)
          if match:
              payload = match.groups()[0]
          else:
              payload = None
          return {
              "type": "console",
              "message": None,
              "payload": payload,
          }
  
      elif _GDB_MI_LOG_RE.match(gdb_mi_text):
          match = _GDB_MI_LOG_RE.match(gdb_mi_text)
          if match:
              payload = match.groups()[0]
          else:
              payload = None
          return {"type": "log", "message": None, "payload": payload}
  
      elif _GDB_MI_TARGET_OUTPUT_RE.match(gdb_mi_text):
          match = _GDB_MI_TARGET_OUTPUT_RE.match(gdb_mi_text)
          if match:
              payload = match.groups()[0]
          else:
              payload = None
          return {"type": "target", "message": None, "payload": payload}
  
      elif response_is_finished(gdb_mi_text):
          return {"type": "done", "message": None, "payload": None}
  
      else:
          # This was not gdb mi output, so it must have just been printed by
          # the inferior program that's being debugged
          return {"type": "output", "message": None, "payload": gdb_mi_text}
  
  
  def response_is_finished(gdb_mi_text: str) -> bool:
      """Return true if the gdb mi response is ending
  
      Args:
          gdb_mi_text: String output from gdb
  
      Returns:
          True if gdb response is finished
      """
      if _GDB_MI_RESPONSE_FINISHED_RE.match(gdb_mi_text):
          return True
  
      else:
          return False
  
  
  def assert_match(actual_char_or_str, expected_char_or_str):
      # Skip: If values don't match, print them and raise a ValueError, otherwise,
      # continue
      # Raises: ValueError if arguments do not match"""
      if expected_char_or_str != actual_char_or_str:
          print("Expected")
          pprint(expected_char_or_str)
          print("")
          print("Got")
          pprint(actual_char_or_str)
          raise ValueError()
  
  
  # ========================================================================
  # All functions and variables below are used internally to parse mi output
  # ========================================================================
  
  
  # GDB machine interface output patterns to match
  # https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI-Stream-Records.html#GDB_002fMI-Stream-Records
  
  # https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI-Result-Records.html#GDB_002fMI-Result-Records
  # In addition to a number of out-of-band notifications,
  # the response to a gdb/mi command includes one of the following result indications:
  # done, running, connected, error, exit
  _GDB_MI_RESULT_RE = re.compile(r"^(\d*)\^(\S+?)(,(.*))?$")
  
  # https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI-Async-Records.html#GDB_002fMI-Async-Records
  # Async records are used to notify the gdb/mi client of additional
  # changes that have occurred. Those changes can either be a consequence
  # of gdb/mi commands (e.g., a breakpoint modified) or a result of target activity
  # (e.g., target stopped).
  _GDB_MI_NOTIFY_RE = re.compile(r"^(\d*)[*=](\S+?),(.*)$")
  
  # https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI-Stream-Records.html#GDB_002fMI-Stream-Records
  # "~" string-output
  # The console output stream contains text that should be displayed
  # in the CLI console window. It contains the textual responses to CLI commands.
  _GDB_MI_CONSOLE_RE = re.compile(r'~"(.*)"', re.DOTALL)
  
  # https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI-Stream-Records.html#GDB_002fMI-Stream-Records
  # "&" string-output
  # The log stream contains debugging messages being produced by gdb's internals.
  _GDB_MI_LOG_RE = re.compile(r'&"(.*)"', re.DOTALL)
  
  # https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI-Stream-Records.html#GDB_002fMI-Stream-Records
  # "@" string-output
  # The target output stream contains any textual output from the
  # running target. This is only present when GDB's event loop is truly asynchronous,
  # which is currently only the case for remote targets.
  _GDB_MI_TARGET_OUTPUT_RE = re.compile(r'@"(.*)"', re.DOTALL)
  
  # Response finished
  _GDB_MI_RESPONSE_FINISHED_RE = re.compile(r"^\(gdb\)\s*$")
  
  _WHITESPACE = [" ", "\t", "\r", "\n"]
  
  _GDB_MI_CHAR_DICT_START = "{"
  _GDB_MI_CHAR_ARRAY_START = "["
  _GDB_MI_CHAR_STRING_START = '"'
  _GDB_MI_VALUE_START_CHARS = [
      _GDB_MI_CHAR_DICT_START,
      _GDB_MI_CHAR_ARRAY_START,
      _GDB_MI_CHAR_STRING_START,
  ]
  
  
  def _get_notify_msg_and_payload(result, stream: StringStream):
      """Get notify message and payload dict"""
      token = stream.advance_past_chars(["=", "*"])
      token = int(token) if token != "" else None
      logger.debug("%s", fmt_green("parsing message"))
      message = stream.advance_past_chars([","])
  
      logger.debug("parsed message")
      logger.debug("%s", fmt_green(message))
  
      payload = _parse_dict(stream)
      return token, message.strip(), payload
  
  
  def _get_result_msg_and_payload(result, stream: StringStream):
      """Get result message and payload dict"""
  
      match = _GDB_MI_RESULT_RE.match(result)
      groups = match.groups() if match else [""]
      token = int(groups[0]) if groups[0] != "" else None
      message = groups[1]
  
      if groups[2] is None:
          payload = None
      else:
          stream.advance_past_chars([","])
          payload = _parse_dict(stream)
  
      return token, message, payload
  
  
  def _parse_dict(stream: StringStream):
      """Parse dictionary, with optional starting character '{'
      return (tuple):
          Number of characters parsed from to_parse
          Parsed dictionary
      """
      obj: Dict[str, Union[str, list]] = {}
  
      logger.debug("%s", fmt_green("parsing dict"))
  
      while True:
          c = stream.read(1)
          if c in _WHITESPACE:
              pass
          elif c in ["{", ","]:
              pass
          elif c in ["}", ""]:
              # end of object, exit loop
              break
  
          else:
              stream.seek(-1)
              key, val = _parse_key_val(stream)
              if key in obj:
                  # This is a gdb bug. We should never get repeated keys in a dict!
                  # See https://sourceware.org/bugzilla/show_bug.cgi?id=22217
                  # and https://github.com/cs01/pygdbmi/issues/19
                  # Example:
                  #   thread-ids={thread-id="1",thread-id="2"}
                  # Results in:
                  #   thread-ids: {{'thread-id': ['1', '2']}}
                  # Rather than the lossy
                  #   thread-ids: {'thread-id': 2}  # '1' got overwritten!
                  if isinstance(obj[key], list):
                      obj[key].append(val)  # type: ignore
                  else:
                      obj[key] = [obj[key], val]
              else:
                  obj[key] = val
  
              look_ahead_for_garbage = True
              c = stream.read(1)
              while look_ahead_for_garbage:
                  if c in ["}", ",", ""]:
                      look_ahead_for_garbage = False
                  else:
                      # got some garbage text, skip it. for example:
                      # name="gdb"gargage  # skip over 'garbage'
                      # name="gdb"\n  # skip over '\n'
                      logger.debug("skipping unexpected charcter: " + c)
                      c = stream.read(1)
              stream.seek(-1)
  
      logger.debug("parsed dict")
      logger.debug("%s", fmt_green(obj))
      return obj
  
  
  def _parse_key_val(stream: StringStream):
      """Parse key, value combination
      return (tuple):
          Parsed key (string)
          Parsed value (either a string, array, or dict)
      """
  
      logger.debug("parsing key/val")
      key = _parse_key(stream)
      val = _parse_val(stream)
  
      logger.debug("parsed key/val")
      logger.debug("%s", fmt_green(key))
      logger.debug("%s", fmt_green(val))
  
      return key, val
  
  
  def _parse_key(stream: StringStream):
      """Parse key, value combination
      returns :
          Parsed key (string)
      """
      logger.debug("parsing key")
  
      key = stream.advance_past_chars(["="])
  
      logger.debug("parsed key:")
      logger.debug("%s", fmt_green(key))
      return key
  
  
  def _parse_val(stream: StringStream):
      """Parse value from string
      returns:
          Parsed value (either a string, array, or dict)
      """
  
      logger.debug("parsing value")
  
      while True:
          c = stream.read(1)
  
          if c == "{":
              # Start object
              val = _parse_dict(stream)
              break
  
          elif c == "[":
              # Start of an array
              val = _parse_array(stream)
              break
  
          elif c == '"':
              # Start of a string
              val = stream.advance_past_string_with_gdb_escapes()
              break
  
          elif _DEBUG:
              raise ValueError("unexpected character: %s" % c)
  
          else:
              logger.warn(f'unexpected character: "{c}" ({ord(c)}). Continuing.')
              val = ""  # this will be overwritten if there are more characters to be read
  
      logger.debug("parsed value:")
      logger.debug("%s", fmt_green(val))
  
      return val
  
  
  def _parse_array(stream: StringStream):
      """Parse an array, stream should be passed the initial [
      returns:
          Parsed array
      """
  
      logger.debug("parsing array")
      arr = []
      while True:
          c = stream.read(1)
  
          if c in _GDB_MI_VALUE_START_CHARS:
              stream.seek(-1)
              val = _parse_val(stream)
              arr.append(val)
          elif c in _WHITESPACE:
              pass
          elif c == ",":
              pass
          elif c == "]":
              # Stop when this array has finished. Note
              # that elements of this array can be also be arrays.
              break
  
      logger.debug("parsed array:")
      logger.debug("%s", fmt_green(arr))
      return arr
  