#===----------------------------------------------------------------------===##
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
#===----------------------------------------------------------------------===##
"""GDB pretty-printers for libc++.

These should work for objects compiled when _LIBCPP_ABI_UNSTABLE is defined
and when it is undefined.
"""

from __future__ import print_function

import re
import gdb

# One under-documented feature of the gdb pretty-printer API
# is that clients can call any other member of the API
# before they call to_string.
# Therefore all self.FIELDs must be set in the pretty-printer's
# __init__ function.

_void_pointer_type = gdb.lookup_type("void").pointer()


_long_int_type = gdb.lookup_type("unsigned long long")

_libcpp_big_endian = False

def addr_as_long(addr):
    return int(addr.cast(_long_int_type))


# The size of a pointer in bytes.
_pointer_size = _void_pointer_type.sizeof


def _remove_cxx_namespace(typename):
    """Removed libc++ specific namespace from the type.

    Arguments:
      typename(string): A type, such as std::__u::something.

    Returns:
      A string without the libc++ specific part, such as std::something.
    """

    return re.sub("std::__.*?::", "std::", typename)


def _remove_generics(typename):
    """Remove generics part of the type. Assumes typename is not empty.

    Arguments:
      typename(string): A type such as std::my_collection<element>.

    Returns:
      The prefix up to the generic part, such as std::my_collection.
    """

    match = re.match("^([^<]+)", typename)
    return match.group(1)


# Some common substitutions on the types to reduce visual clutter (A user who
# wants to see the actual details can always use print/r).
_common_substitutions = [
    ("std::basic_string<char, std::char_traits<char>, std::allocator<char> >",
     "std::string"),
    ("std::basic_string_view<char, std::char_traits<char> >",
     "std::string_view"),
]


def _prettify_typename(gdb_type):
    """Returns a pretty name for the type, or None if no name can be found.

    Arguments:
      gdb_type(gdb.Type): A type object.

    Returns:
      A string, without type_defs, libc++ namespaces, and common substitutions
      applied.
    """

    type_without_typedefs = gdb_type.strip_typedefs()
    typename = type_without_typedefs.name or type_without_typedefs.tag or \
        str(type_without_typedefs)
    result = _remove_cxx_namespace(typename)
    for find_str, subst_str in _common_substitutions:
        result = re.sub(find_str, subst_str, result)
    return result


def _typename_for_nth_generic_argument(gdb_type, n):
    """Returns a pretty string for the nth argument of the given type.

    Arguments:
      gdb_type(gdb.Type): A type object, such as the one for std::map<int, int>
      n: The (zero indexed) index of the argument to return.

    Returns:
      A string for the nth argument, such a "std::string"
    """
    element_type = gdb_type.template_argument(n)
    return _prettify_typename(element_type)


def _typename_with_n_generic_arguments(gdb_type, n):
    """Return a string for the type with the first n (1, ...) generic args."""

    base_type = _remove_generics(_prettify_typename(gdb_type))
    arg_list = [base_type]
    template = "%s<"
    for i in range(n):
        arg_list.append(_typename_for_nth_generic_argument(gdb_type, i))
        template += "%s, "
    result = (template[:-2] + ">") % tuple(arg_list)
    return result


def _typename_with_first_generic_argument(gdb_type):
    return _typename_with_n_generic_arguments(gdb_type, 1)


class StdTuplePrinter(object):
    """Print a std::tuple."""

    class _Children(object):
        """Class to iterate over the tuple's children."""

        def __init__(self, val):
            self.val = val
            self.child_iter = iter(self.val["__base_"].type.fields())
            self.count = 0

        def __iter__(self):
            return self

        def __next__(self):
            # child_iter raises StopIteration when appropriate.
            field_name = self.child_iter.next()
            child = self.val["__base_"][field_name]["__value_"]
            self.count += 1
            return ("[%d]" % self.count, child)

        # TODO Delete when we drop Python 2.
        def next(self):
            return self.__next__()

    def __init__(self, val):
        self.val = val

    def to_string(self):
        typename = _remove_generics(_prettify_typename(self.val.type))
        if not self.val.type.fields():
            return "empty %s" % typename
        return "%s containing" % typename

    def children(self):
        if not self.val.type.fields():
            return iter(())
        return self._Children(self.val)


def _get_base_subobject(child_class_value, index=0):
    """Returns the object's value in the form of the parent class at index.

    This function effectively casts the child_class_value to the base_class's
    type, but the type-to-cast to is stored in the field at index, and once
    we know the field, we can just return the data.

    Args:
      child_class_value: the value to cast
      index: the parent class index

    Raises:
      Exception: field at index was not a base-class field.
    """

    field = child_class_value.type.fields()[index]
    if not field.is_base_class:
        raise Exception("Not a base-class field.")
    return child_class_value[field]


def _value_of_pair_first(value):
    """Convenience for _get_base_subobject, for the common case."""
    return _get_base_subobject(value, 0)["__value_"]


class StdStringPrinter(object):
    """Print a std::string."""

    def _get_short_size(self, short_field, short_size):
        """Short size depends on both endianness and a compile-time define."""

        # If the padding field is present after all this indirection, then string
        # was compiled with _LIBCPP_ABI_ALTERNATE_STRING_LAYOUT defined.
        field = short_field.type.fields()[1].type.fields()[0]
        libcpp_abi_alternate_string_layout = field.name and "__padding" in field.name

        # This logical structure closely follows the original code (which is clearer
        # in C++).  Keep them parallel to make them easier to compare.
        if libcpp_abi_alternate_string_layout:
            if _libcpp_big_endian:
                return short_size >> 1
            else:
                return short_size
        elif _libcpp_big_endian:
            return short_size
        else:
            return short_size >> 1

    def __init__(self, val):
        self.val = val

    def to_string(self):
        """Build a python string from the data whether stored inline or separately."""

        value_field = _value_of_pair_first(self.val["__r_"])
        short_field = value_field["__s"]
        short_size = short_field["__size_"]
        if short_size == 0:
            return ""
        short_mask = self.val["__short_mask"]
        # Counter intuitive to compare the size and short_mask to see if the string
        # is long, but that's the way the implementation does it. Note that
        # __is_long() doesn't use get_short_size in C++.
        is_long = short_size & short_mask
        if is_long:
            long_field = value_field["__l"]
            data = long_field["__data_"]
            size = long_field["__size_"]
        else:
            data = short_field["__data_"]
            size = self._get_short_size(short_field, short_size)
        if hasattr(data, "lazy_string"):
            return data.lazy_string(length=size)
        return data.string(length=size)

    def display_hint(self):
        return "string"


class StdStringViewPrinter(object):
    """Print a std::string_view."""

    def __init__(self, val):
      self.val = val

    def to_string(self):  # pylint: disable=g-bad-name
      """GDB calls this to compute the pretty-printed form."""

      ptr = self.val["__data"]
      length = self.val["__size"]
      print_length = length
      # We print more than just a simple string (i.e. we also print
      # "of length %d").  Thus we can't use the "string" display_hint,
      # and thus we have to handle "print elements" ourselves.
      # For reference sake, gdb ensures limit == None or limit > 0.
      limit = gdb.parameter("print elements")
      if limit is not None:
        print_length = min(print_length, limit)
      # FIXME: Passing ISO-8859-1 here isn't always correct.
      string = ptr.string("ISO-8859-1", "ignore", print_length)
      if length > print_length:
        string += "..."
      return "std::string_view of length %d: \"%s\"" % (length, string)


class StdUniquePtrPrinter(object):
    """Print a std::unique_ptr."""

    def __init__(self, val):
        self.val = val
        self.addr = _value_of_pair_first(self.val["__ptr_"])
        self.pointee_type = self.val.type.template_argument(0)

    def to_string(self):
        typename = _remove_generics(_prettify_typename(self.val.type))
        if not self.addr:
            return "%s is nullptr" % typename
        return ("%s<%s> containing" %
                (typename,
                 _remove_generics(_prettify_typename(self.pointee_type))))

    def __iter__(self):
        if self.addr:
            yield "__ptr_", self.addr.cast(self.pointee_type.pointer())

    def children(self):
        return self


class StdSharedPointerPrinter(object):
    """Print a std::shared_ptr."""

    def __init__(self, val):
        self.val = val
        self.addr = self.val["__ptr_"]

    def to_string(self):
        """Returns self as a string."""
        typename = _remove_generics(_prettify_typename(self.val.type))
        pointee_type = _remove_generics(
            _prettify_typename(self.val.type.template_argument(0)))
        if not self.addr:
            return "%s is nullptr" % typename
        refcount = self.val["__cntrl_"]
        if refcount != 0:
            usecount = refcount["__shared_owners_"] + 1
            weakcount = refcount["__shared_weak_owners_"]
            if usecount == 0:
                state = "expired, weak %d" % weakcount
            else:
                state = "count %d, weak %d" % (usecount, weakcount)
        return "%s<%s> %s containing" % (typename, pointee_type, state)

    def __iter__(self):
        if self.addr:
            yield "__ptr_", self.addr

    def children(self):
        return self


class StdVectorPrinter(object):
    """Print a std::vector."""

    class _VectorBoolIterator(object):
        """Class to iterate over the bool vector's children."""

        def __init__(self, begin, size, bits_per_word):
            self.item = begin
            self.size = size
            self.bits_per_word = bits_per_word
            self.count = 0
            self.offset = 0

        def __iter__(self):
            return self

        def __next__(self):
            """Retrieve the next element."""

            self.count += 1
            if self.count > self.size:
                raise StopIteration
            entry = self.item.dereference()
            if entry & (1 << self.offset):
                outbit = 1
            else:
                outbit = 0
            self.offset += 1
            if self.offset >= self.bits_per_word:
                self.item += 1
                self.offset = 0
            return ("[%d]" % self.count, outbit)

        # TODO Delete when we drop Python 2.
        def next(self):
            return self.__next__()

    class _VectorIterator(object):
        """Class to iterate over the non-bool vector's children."""

        def __init__(self, begin, end):
            self.item = begin
            self.end = end
            self.count = 0

        def __iter__(self):
            return self

        def __next__(self):
            self.count += 1
            if self.item == self.end:
                raise StopIteration
            entry = self.item.dereference()
            self.item += 1
            return ("[%d]" % self.count, entry)

        # TODO Delete when we drop Python 2.
        def next(self):
            return self.__next__()

    def __init__(self, val):
        """Set val, length, capacity, and iterator for bool and normal vectors."""
        self.val = val
        self.typename = _remove_generics(_prettify_typename(val.type))
        begin = self.val["__begin_"]
        if self.val.type.template_argument(0).code == gdb.TYPE_CODE_BOOL:
            self.typename += "<bool>"
            self.length = self.val["__size_"]
            bits_per_word = self.val["__bits_per_word"]
            self.capacity = _value_of_pair_first(
                self.val["__cap_alloc_"]) * bits_per_word
            self.iterator = self._VectorBoolIterator(
                begin, self.length, bits_per_word)
        else:
            end = self.val["__end_"]
            self.length = end - begin
            self.capacity = _get_base_subobject(
                self.val["__end_cap_"])["__value_"] - begin
            self.iterator = self._VectorIterator(begin, end)

    def to_string(self):
        return ("%s of length %d, capacity %d" %
                (self.typename, self.length, self.capacity))

    def children(self):
        return self.iterator

    def display_hint(self):
        return "array"


class StdBitsetPrinter(object):
    """Print a std::bitset."""

    def __init__(self, val):
        self.val = val
        self.n_words = int(self.val["__n_words"])
        self.bits_per_word = int(self.val["__bits_per_word"])
        if self.n_words == 1:
            self.values = [int(self.val["__first_"])]
        else:
            self.values = [int(self.val["__first_"][index])
                           for index in range(self.n_words)]

    def to_string(self):
        typename = _prettify_typename(self.val.type)
        return "%s" % typename

    def _byte_it(self, value):
        index = -1
        while value:
            index += 1
            will_yield = value % 2
            value /= 2
            if will_yield:
                yield index

    def _list_it(self):
        for word_index in range(self.n_words):
            current = self.values[word_index]
            if current:
                for n in self._byte_it(current):
                    yield ("[%d]" % (word_index * self.bits_per_word + n), 1)

    def __iter__(self):
        return self._list_it()

    def children(self):
        return self


class StdDequePrinter(object):
    """Print a std::deque."""

    def __init__(self, val):
        self.val = val
        self.size = int(_value_of_pair_first(val["__size_"]))
        self.start_ptr = self.val["__map_"]["__begin_"]
        self.first_block_start_index = int(self.val["__start_"])
        self.node_type = self.start_ptr.type
        self.block_size = self._calculate_block_size(
            val.type.template_argument(0))

    def _calculate_block_size(self, element_type):
        """Calculates the number of elements in a full block."""
        size = element_type.sizeof
        # Copied from struct __deque_block_size implementation of libcxx.
        return 4096 / size if size < 256 else 16

    def _bucket_it(self, start_addr, start_index, end_index):
        for i in range(start_index, end_index):
            yield i, (start_addr.dereference() + i).dereference()

    def _list_it(self):
        """Primary iteration worker."""
        num_emitted = 0
        current_addr = self.start_ptr
        start_index = self.first_block_start_index
        while num_emitted < self.size:
            end_index = min(start_index + self.size -
                            num_emitted, self.block_size)
            for _, elem in self._bucket_it(current_addr, start_index, end_index):
                yield "", elem
            num_emitted += end_index - start_index
            current_addr = gdb.Value(addr_as_long(current_addr) + _pointer_size) \
                              .cast(self.node_type)
            start_index = 0

    def to_string(self):
        typename = _remove_generics(_prettify_typename(self.val.type))
        if self.size:
            return "%s with %d elements" % (typename, self.size)
        return "%s is empty" % typename

    def __iter__(self):
        return self._list_it()

    def children(self):
        return self

    def display_hint(self):
        return "array"


class StdListPrinter(object):
    """Print a std::list."""

    def __init__(self, val):
        self.val = val
        size_alloc_field = self.val["__size_alloc_"]
        self.size = int(_value_of_pair_first(size_alloc_field))
        dummy_node = self.val["__end_"]
        self.nodetype = gdb.lookup_type(
            re.sub("__list_node_base", "__list_node",
                   str(dummy_node.type.strip_typedefs()))).pointer()
        self.first_node = dummy_node["__next_"]

    def to_string(self):
        typename = _remove_generics(_prettify_typename(self.val.type))
        if self.size:
            return "%s with %d elements" % (typename, self.size)
        return "%s is empty" % typename

    def _list_iter(self):
        current_node = self.first_node
        for _ in range(self.size):
            yield "", current_node.cast(self.nodetype).dereference()["__value_"]
            current_node = current_node.dereference()["__next_"]

    def __iter__(self):
        return self._list_iter()

    def children(self):
        return self if self.nodetype else iter(())

    def display_hint(self):
        return "array"


class StdQueueOrStackPrinter(object):
    """Print a std::queue or std::stack."""

    def __init__(self, val):
        self.val = val
        self.underlying = val["c"]

    def to_string(self):
        typename = _remove_generics(_prettify_typename(self.val.type))
        return "%s wrapping" % typename

    def children(self):
        return iter([("", self.underlying)])

    def display_hint(self):
        return "array"


class StdPriorityQueuePrinter(object):
    """Print a std::priority_queue."""

    def __init__(self, val):
        self.val = val
        self.underlying = val["c"]

    def to_string(self):
        # TODO(tamur): It would be nice to print the top element. The technical
        # difficulty is that, the implementation refers to the underlying
        # container, which is a generic class. libstdcxx pretty printers do not
        # print the top element.
        typename = _remove_generics(_prettify_typename(self.val.type))
        return "%s wrapping" % typename

    def children(self):
        return iter([("", self.underlying)])

    def display_hint(self):
        return "array"


class RBTreeUtils(object):
    """Utility class for std::(multi)map, and std::(multi)set and iterators."""

    def __init__(self, cast_type, root):
        self.cast_type = cast_type
        self.root = root

    def left_child(self, node):
        result = node.cast(self.cast_type).dereference()["__left_"]
        return result

    def right_child(self, node):
        result = node.cast(self.cast_type).dereference()["__right_"]
        return result

    def parent(self, node):
        """Return the parent of node, if it exists."""
        # If this is the root, then from the algorithm's point of view, it has no
        # parent.
        if node == self.root:
            return None

        # We don't have enough information to tell if this is the end_node (which
        # doesn't have a __parent_ field), or the root (which doesn't have a parent
        # from the algorithm's point of view), so cast_type may not be correct for
        # this particular node. Use heuristics.

        # The end_node's left child is the root. Note that when printing interators
        # in isolation, the root is unknown.
        if self.left_child(node) == self.root:
            return None

        parent = node.cast(self.cast_type).dereference()["__parent_"]
        # If the value at the offset of __parent_ doesn't look like a valid pointer,
        # then assume that node is the end_node (and therefore has no parent).
        # End_node type has a pointer embedded, so should have pointer alignment.
        if addr_as_long(parent) % _void_pointer_type.alignof:
            return None
        # This is ugly, but the only other option is to dereference an invalid
        # pointer.  0x8000 is fairly arbitrary, but has had good results in
        # practice.  If there was a way to tell if a pointer is invalid without
        # actually dereferencing it and spewing error messages, that would be ideal.
        if parent < 0x8000:
            return None
        return parent

    def is_left_child(self, node):
        parent = self.parent(node)
        return parent is not None and self.left_child(parent) == node

    def is_right_child(self, node):
        parent = self.parent(node)
        return parent is not None and self.right_child(parent) == node


class AbstractRBTreePrinter(object):
    """Abstract super class for std::(multi)map, and std::(multi)set."""

    def __init__(self, val):
        self.val = val
        tree = self.val["__tree_"]
        self.size = int(_value_of_pair_first(tree["__pair3_"]))
        dummy_root = tree["__pair1_"]
        root = _value_of_pair_first(dummy_root)["__left_"]
        cast_type = self._init_cast_type(val.type)
        self.util = RBTreeUtils(cast_type, root)

    def _get_key_value(self, node):
        """Subclasses should override to return a list of values to yield."""
        raise NotImplementedError

    def _traverse(self):
        """Traverses the binary search tree in order."""
        current = self.util.root
        skip_left_child = False
        while True:
            if not skip_left_child and self.util.left_child(current):
                current = self.util.left_child(current)
                continue
            skip_left_child = False
            for key_value in self._get_key_value(current):
                yield "", key_value
            right_child = self.util.right_child(current)
            if right_child:
                current = right_child
                continue
            while self.util.is_right_child(current):
                current = self.util.parent(current)
            if self.util.is_left_child(current):
                current = self.util.parent(current)
                skip_left_child = True
                continue
            break

    def __iter__(self):
        return self._traverse()

    def children(self):
        return self if self.util.cast_type and self.size > 0 else iter(())

    def to_string(self):
        typename = _remove_generics(_prettify_typename(self.val.type))
        if self.size:
            return "%s with %d elements" % (typename, self.size)
        return "%s is empty" % typename


class StdMapPrinter(AbstractRBTreePrinter):
    """Print a std::map or std::multimap."""

    def _init_cast_type(self, val_type):
        map_it_type = gdb.lookup_type(
            str(val_type) + "::iterator").strip_typedefs()
        tree_it_type = map_it_type.template_argument(0)
        node_ptr_type = tree_it_type.template_argument(1)
        return node_ptr_type

    def display_hint(self):
        return "map"

    def _get_key_value(self, node):
        key_value = node.cast(self.util.cast_type).dereference()[
            "__value_"]["__cc"]
        return [key_value["first"], key_value["second"]]


class StdSetPrinter(AbstractRBTreePrinter):
    """Print a std::set."""

    def _init_cast_type(self, val_type):
        set_it_type = gdb.lookup_type(
            str(val_type) + "::iterator").strip_typedefs()
        node_ptr_type = set_it_type.template_argument(1)
        return node_ptr_type

    def display_hint(self):
        return "array"

    def _get_key_value(self, node):
        key_value = node.cast(self.util.cast_type).dereference()["__value_"]
        return [key_value]


class AbstractRBTreeIteratorPrinter(object):
    """Abstract super class for std::(multi)map, and std::(multi)set iterator."""

    def _initialize(self, val, typename):
        self.typename = typename
        self.val = val
        self.addr = self.val["__ptr_"]
        cast_type = self.val.type.template_argument(1)
        self.util = RBTreeUtils(cast_type, None)
        if self.addr:
            self.node = self.addr.cast(cast_type).dereference()

    def _is_valid_node(self):
        if not self.util.parent(self.addr):
            return False
        return self.util.is_left_child(self.addr) or \
            self.util.is_right_child(self.addr)

    def to_string(self):
        if not self.addr:
            return "%s is nullptr" % self.typename
        return "%s " % self.typename

    def _get_node_value(self, node):
        raise NotImplementedError

    def __iter__(self):
        addr_str = "[%s]" % str(self.addr)
        if not self._is_valid_node():
            yield addr_str, " end()"
        else:
            yield addr_str, self._get_node_value(self.node)

    def children(self):
        return self if self.addr else iter(())


class MapIteratorPrinter(AbstractRBTreeIteratorPrinter):
    """Print a std::(multi)map iterator."""

    def __init__(self, val):
        self._initialize(val["__i_"],
                         _remove_generics(_prettify_typename(val.type)))

    def _get_node_value(self, node):
        return node["__value_"]["__cc"]


class SetIteratorPrinter(AbstractRBTreeIteratorPrinter):
    """Print a std::(multi)set iterator."""

    def __init__(self, val):
        self._initialize(val, _remove_generics(_prettify_typename(val.type)))

    def _get_node_value(self, node):
        return node["__value_"]


class StdFposPrinter(object):
    """Print a std::fpos or std::streampos."""

    def __init__(self, val):
        self.val = val

    def to_string(self):
        typename = _remove_generics(_prettify_typename(self.val.type))
        offset = self.val["__off_"]
        state = self.val["__st_"]
        count = state["__count"]
        value = state["__value"]["__wch"]
        return "%s with stream offset:%s with state: {count:%s value:%s}" % (
            typename, offset, count, value)


class AbstractUnorderedCollectionPrinter(object):
    """Abstract super class for std::unordered_(multi)[set|map]."""

    def __init__(self, val):
        self.val = val
        self.table = val["__table_"]
        self.sentinel = self.table["__p1_"]
        self.size = int(_value_of_pair_first(self.table["__p2_"]))
        node_base_type = self.sentinel.type.template_argument(0)
        self.cast_type = node_base_type.template_argument(0)

    def _list_it(self, sentinel_ptr):
        next_ptr = _value_of_pair_first(sentinel_ptr)["__next_"]
        while str(next_ptr.cast(_void_pointer_type)) != "0x0":
            next_val = next_ptr.cast(self.cast_type).dereference()
            for key_value in self._get_key_value(next_val):
                yield "", key_value
            next_ptr = next_val["__next_"]

    def to_string(self):
        typename = _remove_generics(_prettify_typename(self.val.type))
        if self.size:
            return "%s with %d elements" % (typename, self.size)
        return "%s is empty" % typename

    def _get_key_value(self, node):
        """Subclasses should override to return a list of values to yield."""
        raise NotImplementedError

    def children(self):
        return self if self.cast_type and self.size > 0 else iter(())

    def __iter__(self):
        return self._list_it(self.sentinel)


class StdUnorderedSetPrinter(AbstractUnorderedCollectionPrinter):
    """Print a std::unordered_(multi)set."""

    def _get_key_value(self, node):
        return [node["__value_"]]

    def display_hint(self):
        return "array"


class StdUnorderedMapPrinter(AbstractUnorderedCollectionPrinter):
    """Print a std::unordered_(multi)map."""

    def _get_key_value(self, node):
        key_value = node["__value_"]["__cc"]
        return [key_value["first"], key_value["second"]]

    def display_hint(self):
        return "map"


class AbstractHashMapIteratorPrinter(object):
    """Abstract class for unordered collection iterators."""

    def _initialize(self, val, addr):
        self.val = val
        self.typename = _remove_generics(_prettify_typename(self.val.type))
        self.addr = addr
        if self.addr:
            self.node = self.addr.cast(self.cast_type).dereference()

    def _get_key_value(self):
        """Subclasses should override to return a list of values to yield."""
        raise NotImplementedError

    def to_string(self):
        if not self.addr:
            return "%s = end()" % self.typename
        return "%s " % self.typename

    def children(self):
        return self if self.addr else iter(())

    def __iter__(self):
        for key_value in self._get_key_value():
            yield "", key_value


class StdUnorderedSetIteratorPrinter(AbstractHashMapIteratorPrinter):
    """Print a std::(multi)set iterator."""

    def __init__(self, val):
        self.cast_type = val.type.template_argument(0)
        self._initialize(val, val["__node_"])

    def _get_key_value(self):
        return [self.node["__value_"]]

    def display_hint(self):
        return "array"


class StdUnorderedMapIteratorPrinter(AbstractHashMapIteratorPrinter):
    """Print a std::(multi)map iterator."""

    def __init__(self, val):
        self.cast_type = val.type.template_argument(0).template_argument(0)
        self._initialize(val, val["__i_"]["__node_"])

    def _get_key_value(self):
        key_value = self.node["__value_"]["__cc"]
        return [key_value["first"], key_value["second"]]

    def display_hint(self):
        return "map"


def _remove_std_prefix(typename):
    match = re.match("^std::(.+)", typename)
    return match.group(1) if match is not None else ""


class LibcxxPrettyPrinter(object):
    """PrettyPrinter object so gdb-commands like 'info pretty-printers' work."""

    def __init__(self, name):
        super(LibcxxPrettyPrinter, self).__init__()
        self.name = name
        self.enabled = True

        self.lookup = {
            "basic_string": StdStringPrinter,
            "string": StdStringPrinter,
            "string_view": StdStringViewPrinter,
            "tuple": StdTuplePrinter,
            "unique_ptr": StdUniquePtrPrinter,
            "shared_ptr": StdSharedPointerPrinter,
            "weak_ptr": StdSharedPointerPrinter,
            "bitset": StdBitsetPrinter,
            "deque": StdDequePrinter,
            "list": StdListPrinter,
            "queue": StdQueueOrStackPrinter,
            "stack": StdQueueOrStackPrinter,
            "priority_queue": StdPriorityQueuePrinter,
            "map": StdMapPrinter,
            "multimap": StdMapPrinter,
            "set": StdSetPrinter,
            "multiset": StdSetPrinter,
            "vector": StdVectorPrinter,
            "__map_iterator": MapIteratorPrinter,
            "__map_const_iterator": MapIteratorPrinter,
            "__tree_iterator": SetIteratorPrinter,
            "__tree_const_iterator": SetIteratorPrinter,
            "fpos": StdFposPrinter,
            "unordered_set": StdUnorderedSetPrinter,
            "unordered_multiset": StdUnorderedSetPrinter,
            "unordered_map": StdUnorderedMapPrinter,
            "unordered_multimap": StdUnorderedMapPrinter,
            "__hash_map_iterator": StdUnorderedMapIteratorPrinter,
            "__hash_map_const_iterator": StdUnorderedMapIteratorPrinter,
            "__hash_iterator": StdUnorderedSetIteratorPrinter,
            "__hash_const_iterator": StdUnorderedSetIteratorPrinter,
        }

        self.subprinters = []
        for name, subprinter in self.lookup.items():
            # Subprinters and names are used only for the rarely used command "info
            # pretty" (and related), so the name of the first data structure it prints
            # is a reasonable choice.
            if subprinter not in self.subprinters:
                subprinter.name = name
                self.subprinters.append(subprinter)

    def __call__(self, val):
        """Return the pretty printer for a val, if the type is supported."""

        # Do not handle any type that is not a struct/class.
        if val.type.strip_typedefs().code != gdb.TYPE_CODE_STRUCT:
            return None

        # Don't attempt types known to be inside libstdcxx.
        typename = val.type.name or val.type.tag or str(val.type)
        match = re.match("^std::(__.*?)::", typename)
        if match is not None and match.group(1) in ["__cxx1998",
                                                    "__debug",
                                                    "__7",
                                                    "__g"]:
            return None

        # Handle any using declarations or other typedefs.
        typename = _prettify_typename(val.type)
        if not typename:
            return None
        without_generics = _remove_generics(typename)
        lookup_name = _remove_std_prefix(without_generics)
        if lookup_name in self.lookup:
            return self.lookup[lookup_name](val)
        return None


_libcxx_printer_name = "libcxx_pretty_printer"


# These are called for every binary object file, which could be thousands in
# certain pathological cases. Limit our pretty printers to the progspace.
def _register_libcxx_printers(event):
    progspace = event.new_objfile.progspace
    # It would be ideal to get the endianness at print time, but
    # gdb.execute clears gdb's internal wrap buffer, removing any values
    # already generated as part of a larger data structure, and there is
    # no python api to get the endianness. Mixed-endianness debugging
    # rare enough that this workaround should be adequate.
    _libcpp_big_endian = "big endian" in gdb.execute("show endian",
                                                     to_string=True)

    if not getattr(progspace, _libcxx_printer_name, False):
        print("Loading libc++ pretty-printers.")
        gdb.printing.register_pretty_printer(
            progspace, LibcxxPrettyPrinter(_libcxx_printer_name))
        setattr(progspace, _libcxx_printer_name, True)


def _unregister_libcxx_printers(event):
    progspace = event.progspace
    if getattr(progspace, _libcxx_printer_name, False):
        for printer in progspace.pretty_printers:
            if getattr(printer, "name", "none") == _libcxx_printer_name:
                progspace.pretty_printers.remove(printer)
                setattr(progspace, _libcxx_printer_name, False)
                break


def register_libcxx_printer_loader():
    """Register event handlers to load libc++ pretty-printers."""
    gdb.events.new_objfile.connect(_register_libcxx_printers)
    gdb.events.clear_objfiles.connect(_unregister_libcxx_printers)
