# -*- coding: utf-8 -*-
"""
    sphinx.domains.cpp
    ~~~~~~~~~~~~~~~~~~

    The C++ language domain.

    :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS.
    :license: BSD, see LICENSE for details.
"""

import re
from copy import deepcopy

from six import iteritems, text_type
from docutils import nodes

from sphinx import addnodes
from sphinx.roles import XRefRole
from sphinx.locale import l_, _
from sphinx.domains import Domain, ObjType
from sphinx.directives import ObjectDescription
from sphinx.util.nodes import make_refnode
from sphinx.util.compat import Directive
from sphinx.util.pycompat import UnicodeMixin
from sphinx.util.docfields import Field, GroupedField

"""
    Important note on ids:
    Multiple id generation schemes are used due to backwards compatibility.
    - v1: 1.2.3 <= version < 1.3
          The style used before the rewrite.
          It is not the actual old code, but a replication of the behaviour.
    - v2: 1.3 <= version < now
          Standardised mangling scheme from
          http://mentorembedded.github.io/cxx-abi/abi.html#mangling
          though not completely implemented.
    All versions are generated and attached to elements. The newest is used for
    the index. All of the versions should work as permalinks.

    See http://www.nongnu.org/hcb/ for the grammar,
    or https://github.com/cplusplus/draft/blob/master/source/grammar.tex
    for the newest grammar.

    common grammar things:
        template-declaration ->
            "template" "<" template-parameter-list ">" declaration
        template-parameter-list ->
              template-parameter
            | template-parameter-list "," template-parameter
        template-parameter ->
              type-parameter
            | parameter-declaration # i.e., same as a function argument

        type-parameter ->
              "class"    "..."[opt] identifier[opt]
            | "class"               identifier[opt] "=" type-id
            | "typename" "..."[opt] identifier[opt]
            | "typename"            identifier[opt] "=" type-id
            | "template" "<" template-parameter-list ">"
                "class"  "..."[opt] identifier[opt]
            | "template" "<" template-parameter-list ">"
                "class"             identifier[opt] "=" id-expression
            # also, from C++17 we can have "typname" in template templates
        templateDeclPrefix ->
            "template" "<" template-parameter-list ">"

        simple-declaration ->
            attribute-specifier-seq[opt] decl-specifier-seq[opt]
                init-declarator-list[opt] ;
        # Drop the semi-colon. For now: drop the attributes (TODO).
        # Use at most 1 init-declerator.
        -> decl-specifier-seq init-declerator
        -> decl-specifier-seq declerator initializer

        decl-specifier ->
              storage-class-specifier ->
                    "static" (only for member_object and function_object)
                  | "register"
            | type-specifier -> trailing-type-specifier
            | function-specifier -> "inline" | "virtual" | "explicit" (only
              for function_object)
            | "friend" (only for function_object)
            | "constexpr" (only for member_object and function_object)
        trailing-type-specifier ->
              simple-type-specifier
            | elaborated-type-specifier
            | typename-specifier
            | cv-qualifier -> "const" | "volatile"
        stricter grammar for decl-specifier-seq (with everything, each object
        uses a subset):
            visibility storage-class-specifier function-specifier "friend"
            "constexpr" "volatile" "const" trailing-type-specifier
            # where trailing-type-specifier can no be cv-qualifier
        # Inside e.g., template paramters a strict subset is used
        # (see type-specifier-seq)
        trailing-type-specifier ->
              simple-type-specifier ->
                ::[opt] nested-name-specifier[opt] type-name
              | ::[opt] nested-name-specifier "template" simple-template-id
              | "char" | "bool" | ect.
              | decltype-specifier
            | elaborated-type-specifier ->
                class-key attribute-specifier-seq[opt] ::[opt]
                nested-name-specifier[opt] identifier
              | class-key ::[opt] nested-name-specifier[opt] template[opt]
                simple-template-id
              | "enum" ::[opt] nested-name-specifier[opt] identifier
            | typename-specifier ->
                "typename" ::[opt] nested-name-specifier identifier
              | "typename" ::[opt] nested-name-specifier template[opt]
                simple-template-id
        class-key -> "class" | "struct" | "union"
        type-name ->* identifier | simple-template-id
        # ignoring attributes and decltype, and then some left-factoring
        trailing-type-specifier ->
            rest-of-trailing
            ("class" | "struct" | "union" | "typename") rest-of-trailing
            build-in -> "char" | "bool" | ect.
            decltype-specifier
        rest-of-trailing -> (with some simplification)
            "::"[opt] list-of-elements-separated-by-::
        element ->
            "template"[opt] identifier ("<" template-argument-list ">")[opt]
        template-argument-list ->
              template-argument "..."[opt]
            | template-argument-list "," template-argument "..."[opt]
        template-argument ->
              constant-expression
            | type-specifier-seq abstract-declerator
            | id-expression


        declerator ->
              ptr-declerator
            | noptr-declarator parameters-and-qualifiers trailing-return-type
              (TODO: for now we don't support trailing-eturn-type)
        ptr-declerator ->
              noptr-declerator
            | ptr-operator ptr-declarator
        noptr-declerator ->
              declarator-id attribute-specifier-seq[opt] ->
                    "..."[opt] id-expression
                  | rest-of-trailing
            | noptr-declerator parameters-and-qualifiers
            | noptr-declarator "[" constant-expression[opt] "]"
              attribute-specifier-seq[opt]
            | "(" ptr-declarator ")"
        ptr-operator ->
              "*"  attribute-specifier-seq[opt] cv-qualifier-seq[opt]
            | "&   attribute-specifier-seq[opt]
            | "&&" attribute-specifier-seq[opt]
            | "::"[opt] nested-name-specifier "*" attribute-specifier-seq[opt]
                cv-qualifier-seq[opt]
        # function_object must use a parameters-and-qualifiers, the others may
        # use it (e.g., function poitners)
        parameters-and-qualifiers ->
            "(" parameter-clause ")" attribute-specifier-seq[opt]
            cv-qualifier-seq[opt] ref-qualifier[opt]
            exception-specification[opt]
        ref-qualifier -> "&" | "&&"
        exception-specification ->
            "noexcept" ("(" constant-expression ")")[opt]
            "throw" ("(" type-id-list ")")[opt]
        # TODO: we don't implement attributes
        # member functions can have initializers, but we fold them into here
        memberFunctionInit -> "=" "0"
        # (note: only "0" is allowed as the value, according to the standard,
        # right?)

        enum-head ->
            enum-key attribute-specifier-seq[opt] nested-name-specifier[opt]
                identifier enum-base[opt]
        enum-key -> "enum" | "enum struct" | "enum class"
        enum-base ->
            ":" type
        enumerator-definition ->
              identifier
            | identifier "=" constant-expression

    We additionally add the possibility for specifying the visibility as the
    first thing.

    type_object:
        goal:
            either a single type (e.g., "MyClass:Something_T" or a typedef-like
            thing (e.g. "Something Something_T" or "int I_arr[]"
        grammar, single type: based on a type in a function parameter, but
        without a name:
               parameter-declaration
            -> attribute-specifier-seq[opt] decl-specifier-seq
               abstract-declarator[opt]
            # Drop the attributes
            -> decl-specifier-seq abstract-declarator[opt]
        grammar, typedef-like: no initilizer
            decl-specifier-seq declerator
        Can start with a templateDeclPrefix.

    member_object:
        goal: as a type_object which must have a declerator, and optionally
        with a initializer
        grammar:
            decl-specifier-seq declerator initializer
        Can start with a templateDeclPrefix.

    function_object:
        goal: a function declaration, TODO: what about templates? for now: skip
        grammar: no initializer
           decl-specifier-seq declerator
        Can start with a templateDeclPrefix.

    class_object:
        goal: a class declaration, but with specification of a base class
        grammar:
              nested-name "final"[opt] (":" base-specifier-list)[opt]
            base-specifier-list ->
              base-specifier "..."[opt]
            | base-specifier-list, base-specifier "..."[opt]
            base-specifier ->
              base-type-specifier
            | "virtual" access-spe"cifier[opt]    base-type-specifier
            | access-specifier[opt] "virtual"[opt] base-type-specifier
        Can start with a templateDeclPrefix.

    enum_object:
        goal: an unscoped enum or a scoped enum, optionally with the underlying
              type specified
        grammar:
            ("class" | "struct")[opt] visibility[opt] nested-name (":" type)[opt]
    enumerator_object:
        goal: an element in a scoped or unscoped enum. The name should be
              injected according to the scopedness.
        grammar:
            nested-name ("=" constant-expression)

    namespace_object:
        goal: a directive to put all following declarations in a specific scope
        grammar:
            nested-name
"""

_identifier_re = re.compile(r'(~?\b[a-zA-Z_][a-zA-Z0-9_]*)\b')
_whitespace_re = re.compile(r'\s+(?u)')
_string_re = re.compile(r"[LuU8]?('([^'\\]*(?:\\.[^'\\]*)*)'"
                        r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S)
_visibility_re = re.compile(r'\b(public|private|protected)\b')
_operator_re = re.compile(r'''(?x)
        \[\s*\]
    |   \(\s*\)
    |   \+\+ | --
    |   ->\*? | \,
    |   (<<|>>)=? | && | \|\|
    |   [!<>=/*%+|&^~-]=?
''')
# see http://en.cppreference.com/w/cpp/keyword
_keywords = [
    'alignas', 'alignof', 'and', 'and_eq', 'asm', 'auto', 'bitand', 'bitor',
    'bool', 'break', 'case', 'catch', 'char', 'char16_t', 'char32_t', 'class',
    'compl', 'concept', 'const', 'constexpr', 'const_cast', 'continue',
    'decltype', 'default', 'delete', 'do', 'double', 'dynamic_cast', 'else',
    'enum', 'explicit', 'export', 'extern', 'false', 'float', 'for', 'friend',
    'goto', 'if', 'inline', 'int', 'long', 'mutable', 'namespace', 'new',
    'noexcept', 'not', 'not_eq', 'nullptr', 'operator', 'or', 'or_eq',
    'private', 'protected', 'public', 'register', 'reinterpret_cast',
    'requires', 'return', 'short', 'signed', 'sizeof', 'static',
    'static_assert', 'static_cast', 'struct', 'switch', 'template', 'this',
    'thread_local', 'throw', 'true', 'try', 'typedef', 'typeid', 'typename',
    'union', 'unsigned', 'using', 'virtual', 'void', 'volatile', 'wchar_t',
    'while', 'xor', 'xor_eq'
]

# ------------------------------------------------------------------------------
# Id v1 constants
# ------------------------------------------------------------------------------

_id_fundamental_v1 = {
    'char': 'c',
    'signed char': 'c',
    'unsigned char': 'C',
    'int': 'i',
    'signed int': 'i',
    'unsigned int': 'U',
    'long': 'l',
    'signed long': 'l',
    'unsigned long': 'L',
    'bool': 'b'
}
_id_shorthands_v1 = {
    'std::string': 'ss',
    'std::ostream': 'os',
    'std::istream': 'is',
    'std::iostream': 'ios',
    'std::vector': 'v',
    'std::map': 'm'
}
_id_operator_v1 = {
    'new': 'new-operator',
    'new[]': 'new-array-operator',
    'delete': 'delete-operator',
    'delete[]': 'delete-array-operator',
    # the arguments will make the difference between unary and binary
    # '+(unary)' : 'ps',
    # '-(unary)' : 'ng',
    # '&(unary)' : 'ad',
    # '*(unary)' : 'de',
    '~': 'inv-operator',
    '+': 'add-operator',
    '-': 'sub-operator',
    '*': 'mul-operator',
    '/': 'div-operator',
    '%': 'mod-operator',
    '&': 'and-operator',
    '|': 'or-operator',
    '^': 'xor-operator',
    '=': 'assign-operator',
    '+=': 'add-assign-operator',
    '-=': 'sub-assign-operator',
    '*=': 'mul-assign-operator',
    '/=': 'div-assign-operator',
    '%=': 'mod-assign-operator',
    '&=': 'and-assign-operator',
    '|=': 'or-assign-operator',
    '^=': 'xor-assign-operator',
    '<<': 'lshift-operator',
    '>>': 'rshift-operator',
    '<<=': 'lshift-assign-operator',
    '>>=': 'rshift-assign-operator',
    '==': 'eq-operator',
    '!=': 'neq-operator',
    '<': 'lt-operator',
    '>': 'gt-operator',
    '<=': 'lte-operator',
    '>=': 'gte-operator',
    '!': 'not-operator',
    '&&': 'sand-operator',
    '||': 'sor-operator',
    '++': 'inc-operator',
    '--': 'dec-operator',
    ',': 'comma-operator',
    '->*': 'pointer-by-pointer-operator',
    '->': 'pointer-operator',
    '()': 'call-operator',
    '[]': 'subscript-operator'
}

# ------------------------------------------------------------------------------
# Id v2 constants
# ------------------------------------------------------------------------------

_id_prefix_v2 = '_CPPv2'
_id_fundamental_v2 = {
    # not all of these are actually parsed as fundamental types, TODO: do that
    'void': 'v',
    'bool': 'b',
    'char': 'c',
    'signed char': 'a',
    'unsigned char': 'h',
    'wchar_t': 'w',
    'char32_t': 'Di',
    'char16_t': 'Ds',
    'short': 's',
    'short int': 's',
    'signed short': 's',
    'signed short int': 's',
    'unsigned short': 't',
    'unsigned short int': 't',
    'int': 'i',
    'signed': 'i',
    'signed int': 'i',
    'unsigned': 'j',
    'unsigned int': 'j',
    'long': 'l',
    'long int': 'l',
    'signed long': 'l',
    'signed long int': 'l',
    'unsigned long': 'm',
    'unsigned long int': 'm',
    'long long': 'x',
    'long long int': 'x',
    'signed long long': 'x',
    'signed long long int': 'x',
    'unsigned long long': 'y',
    'unsigned long long int': 'y',
    'float': 'f',
    'double': 'd',
    'long double': 'e',
    'auto': 'Da',
    'decltype(auto)': 'Dc',
    'std::nullptr_t': 'Dn'
}
_id_operator_v2 = {
    'new': 'nw',
    'new[]': 'na',
    'delete': 'dl',
    'delete[]': 'da',
    # the arguments will make the difference between unary and binary
    # '+(unary)' : 'ps',
    # '-(unary)' : 'ng',
    # '&(unary)' : 'ad',
    # '*(unary)' : 'de',
    '~': 'co',
    '+': 'pl',
    '-': 'mi',
    '*': 'ml',
    '/': 'dv',
    '%': 'rm',
    '&': 'an',
    '|': 'or',
    '^': 'eo',
    '=': 'aS',
    '+=': 'pL',
    '-=': 'mI',
    '*=': 'mL',
    '/=': 'dV',
    '%=': 'rM',
    '&=': 'aN',
    '|=': 'oR',
    '^=': 'eO',
    '<<': 'ls',
    '>>': 'rs',
    '<<=': 'lS',
    '>>=': 'rS',
    '==': 'eq',
    '!=': 'ne',
    '<': 'lt',
    '>': 'gt',
    '<=': 'le',
    '>=': 'ge',
    '!': 'nt',
    '&&': 'aa',
    '||': 'oo',
    '++': 'pp',
    '--': 'mm',
    ',': 'cm',
    '->*': 'pm',
    '->': 'pt',
    '()': 'cl',
    '[]': 'ix'
}


class NoOldIdError(UnicodeMixin, Exception):
    # Used to avoid implementing unneeded id generation for old id schmes.
    def __init__(self, description=""):
        self.description = description

    def __unicode__(self):
        return self.description


class DefinitionError(UnicodeMixin, Exception):
    def __init__(self, description):
        self.description = description

    def __unicode__(self):
        return self.description


class ASTBase(UnicodeMixin):
    def __eq__(self, other):
        if type(self) is not type(other):
            return False
        try:
            for key, value in iteritems(self.__dict__):
                if value != getattr(other, key):
                    return False
        except AttributeError:
            return False
        return True

    def __ne__(self, other):
        return not self.__eq__(other)

    __hash__ = None

    def clone(self):
        """Clone a definition expression node."""
        return deepcopy(self)

    def get_id_v1(self):
        """Return the v1 id for the node."""
        raise NotImplementedError(repr(self))

    def get_id_v2(self):
        """Return the v2 id for the node."""
        raise NotImplementedError(repr(self))

    def get_name(self):
        """Return the name.

        Returns either `None` or a node with a name you might call
        :meth:`split_owner` on.
        """
        raise NotImplementedError(repr(self))

    def prefix_nested_name(self, prefix):
        """Prefix a name node (a node returned by :meth:`get_name`)."""
        raise NotImplementedError(repr(self))

    def __unicode__(self):
        raise NotImplementedError(repr(self))

    def __repr__(self):
        return '<%s %s>' % (self.__class__.__name__, self)


def _verify_description_mode(mode):
    if mode not in ('lastIsName', 'noneIsName', 'markType', 'param'):
        raise Exception("Description mode '%s' is invalid." % mode)


class ASTIdentifier(ASTBase):
    def __init__(self, identifier):
        assert identifier is not None
        self.identifier = identifier

    def get_id_v1(self):
        if self.identifier == 'size_t':
            return 's'
        else:
            return self.identifier

    def get_id_v2(self):
        if self.identifier == "std":
            return 'St'
        elif self.identifier[0] == "~":
            # a destructor, just use an arbitrary version of dtors
            return 'D0'
        else:
            return text_type(len(self.identifier)) + self.identifier

    def __unicode__(self):
        return self.identifier

    def describe_signature(self, signode, mode, env, prefix, symbol):
        _verify_description_mode(mode)
        if mode == 'markType':
            targetText = prefix + self.identifier
            pnode = addnodes.pending_xref('', refdomain='cpp', reftype='type',
                                          reftarget=targetText, modname=None,
                                          classname=None)
            key = symbol.get_lookup_key()
            assert key
            pnode['cpp:parentKey'] = key
            pnode += nodes.Text(self.identifier)
            signode += pnode
        elif mode == 'lastIsName':
            signode += addnodes.desc_name(self.identifier, self.identifier)
        elif mode == 'noneIsName':
            signode += nodes.Text(self.identifier)
        else:
            raise Exception('Unknown description mode: %s' % mode)


class ASTTemplateKeyParamPackIdDefault(ASTBase):
    def __init__(self, key, identifier, parameterPack, default):
        assert key
        if parameterPack:
            assert default is None
        self.key = key
        self.identifier = identifier
        self.parameterPack = parameterPack
        self.default = default

    def get_identifier(self):
        return self.identifier

    def get_id_v2(self):
        # this is not part of the normal name mangling in C++
        res = []
        if self.parameterPack:
            res.append('Dp')
        else:
            res.append('0')  # we need to put something
        return ''.join(res)

    def __unicode__(self):
        res = [self.key]
        if self.parameterPack:
            if self.identifier:
                res.append(' ')
            res.append('...')
        if self.identifier:
            if not self.parameterPack:
                res.append(' ')
            res.append(text_type(self.identifier))
        if self.default:
            res.append(' = ')
            res.append(text_type(self.default))
        return ''.join(res)

    def describe_signature(self, signode, mode, env, symbol):
        signode += nodes.Text(self.key)
        if self.parameterPack:
            if self.identifier:
                signode += nodes.Text(' ')
            signode += nodes.Text('...')
        if self.identifier:
            if not self.parameterPack:
                signode += nodes.Text(' ')
            self.identifier.describe_signature(signode, mode, env, '', symbol)
        if self.default:
            signode += nodes.Text(' = ')
            self.default.describe_signature(signode, 'markType', env, symbol)


class ASTTemplateParamType(ASTBase):
    def __init__(self, data):
        assert data
        self.data = data

    @property
    def name(self):
        id = self.get_identifier()
        return ASTNestedName([ASTNestedNameElement(id, None)], rooted=False)

    def get_identifier(self):
        return self.data.get_identifier()

    def get_id_v2(self, objectType=None, symbol=None):
        # this is not part of the normal name mangling in C++
        if symbol:
            # the anchor will be our parent
            return symbol.parent.declaration.get_id_v2(prefixed=None)
        else:
            return self.data.get_id_v2()

    def __unicode__(self):
        return text_type(self.data)

    def describe_signature(self, signode, mode, env, symbol):
        self.data.describe_signature(signode, mode, env, symbol)


class ASTTemplateParamTemplateType(ASTBase):
    def __init__(self, nestedParams, data):
        assert nestedParams
        assert data
        self.nestedParams = nestedParams
        self.data = data

    @property
    def name(self):
        id = self.get_identifier()
        return ASTNestedName([ASTNestedNameElement(id, None)], rooted=False)

    def get_identifier(self):
        return self.data.get_identifier()

    def get_id_v2(self, objectType=None, symbol=None):
        # this is not part of the normal name mangling in C++
        if symbol:
            # the anchor will be our parent
            return symbol.parent.declaration.get_id_v2(prefixed=None)
        else:
            return self.nestedParams.get_id_v2() + self.data.get_id_v2()

    def __unicode__(self):
        return text_type(self.nestedParams) + text_type(self.data)

    def describe_signature(self, signode, mode, env, symbol):
        self.nestedParams.describe_signature(signode, 'noneIsName', env, symbol)
        signode += nodes.Text(' ')
        self.data.describe_signature(signode, mode, env, symbol)


class ASTTemplateParamNonType(ASTBase):
    def __init__(self, param):
        assert param
        self.param = param

    @property
    def name(self):
        id = self.get_identifier()
        return ASTNestedName([ASTNestedNameElement(id, None)], rooted=False)

    def get_identifier(self):
        name = self.param.name
        if name:
            assert len(name.names) == 1
            assert name.names[0].identifier
            assert not name.names[0].templateArgs
            return name.names[0].identifier
        else:
            return None

    def get_id_v2(self, objectType=None, symbol=None):
        # this is not part of the normal name mangling in C++
        if symbol:
            # the anchor will be our parent
            return symbol.parent.declaration.get_id_v2(prefixed=None)
        else:
            return '_' + self.param.get_id_v2()

    def __unicode__(self):
        return text_type(self.param)

    def describe_signature(self, signode, mode, env, symbol):
        self.param.describe_signature(signode, mode, env, symbol)


class ASTTemplateParams(ASTBase):
    def __init__(self, params):
        assert params is not None
        self.params = params

    def get_id_v2(self):
        res = []
        res.append("I")
        for param in self.params:
            res.append(param.get_id_v2())
        res.append("E")
        return ''.join(res)

    def __unicode__(self):
        res = []
        res.append(u"template<")
        res.append(u", ".join(text_type(a) for a in self.params))
        res.append(u"> ")
        return ''.join(res)

    def describe_signature(self, signode, mode, env, symbol):
        signode += nodes.Text("template<")
        first = True
        for param in self.params:
            if not first:
                signode += nodes.Text(", ")
            first = False
            param.describe_signature(signode, mode, env, symbol)
        signode += nodes.Text(">")


class ASTTemplateDeclarationPrefix(ASTBase):
    def __init__(self, templates):
        assert templates is not None
        assert len(templates) > 0
        self.templates = templates

    # id_v1 does not exist

    def get_id_v2(self):
        # this is not part of a normal name mangling system
        res = []
        for t in self.templates:
            res.append(t.get_id_v2())
        return u''.join(res)

    def __unicode__(self):
        res = []
        for t in self.templates:
            res.append(text_type(t))
        return u''.join(res)

    def describe_signature(self, signode, mode, env, symbol):
        _verify_description_mode(mode)
        for t in self.templates:
            templateNode = addnodes.desc_signature()
            t.describe_signature(templateNode, 'lastIsName', env, symbol)
            signode += templateNode


class ASTOperatorBuildIn(ASTBase):
    def __init__(self, op):
        self.op = op

    def is_operator(self):
        return True

    def get_id_v1(self):
        if self.op not in _id_operator_v1:
            raise Exception('Internal error: Build-in operator "%s" can not '
                            'be mapped to an id.' % self.op)
        return _id_operator_v1[self.op]

    def get_id_v2(self):
        if self.op not in _id_operator_v2:
            raise Exception('Internal error: Build-in operator "%s" can not '
                            'be mapped to an id.' % self.op)
        return _id_operator_v2[self.op]

    def __unicode__(self):
        if self.op in ('new', 'new[]', 'delete', 'delete[]'):
            return u'operator ' + self.op
        else:
            return u'operator' + self.op

    def describe_signature(self, signode, mode, env, prefix, symbol):
        _verify_description_mode(mode)
        identifier = text_type(self)
        if mode == 'lastIsName':
            signode += addnodes.desc_name(identifier, identifier)
        else:
            signode += addnodes.desc_addname(identifier, identifier)


class ASTOperatorType(ASTBase):
    def __init__(self, type):
        self.type = type

    def is_operator(self):
        return True

    def get_id_v1(self):
        return u'castto-%s-operator' % self.type.get_id_v1()

    def get_id_v2(self):
        return u'cv' + self.type.get_id_v2()

    def __unicode__(self):
        return u''.join(['operator ', text_type(self.type)])

    def get_name_no_template(self):
        return text_type(self)

    def describe_signature(self, signode, mode, env, prefix, symbol):
        _verify_description_mode(mode)
        identifier = text_type(self)
        if mode == 'lastIsName':
            signode += addnodes.desc_name(identifier, identifier)
        else:
            signode += addnodes.desc_addname(identifier, identifier)


class ASTOperatorLiteral(ASTBase):
    def __init__(self, identifier):
        self.identifier = identifier

    def is_operator(self):
        return True

    def get_id_v1(self):
        raise NoOldIdError()

    def get_id_v2(self):
        return u'li' + self.identifier.get_id_v2()

    def __unicode__(self):
        return u'operator""' + text_type(self.identifier)

    def describe_signature(self, signode, mode, env, prefix, symbol):
        _verify_description_mode(mode)
        identifier = text_type(self)
        if mode == 'lastIsName':
            signode += addnodes.desc_name(identifier, identifier)
        else:
            signode += addnodes.desc_addname(identifier, identifier)


class ASTTemplateArgConstant(ASTBase):
    def __init__(self, value):
        self.value = value

    def __unicode__(self):
        return text_type(self.value)

    def get_id_v1(self):
        return text_type(self).replace(u' ', u'-')

    def get_id_v2(self):
        # TODO: doing this properly needs parsing of expressions, let's just
        # juse it verbatim for now
        return u'X' + text_type(self) + u'E'

    def describe_signature(self, signode, mode, env, symbol):
        _verify_description_mode(mode)
        signode += nodes.Text(text_type(self))


class ASTTemplateArgs(ASTBase):
    def __init__(self, args):
        assert args is not None
        assert len(args) > 0
        self.args = args

    def get_id_v1(self):
        res = []
        res.append(':')
        res.append(u'.'.join(a.get_id_v1() for a in self.args))
        res.append(':')
        return u''.join(res)

    def get_id_v2(self):
        res = []
        res.append('I')
        for a in self.args:
            res.append(a.get_id_v2())
        res.append('E')
        return u''.join(res)

    def __unicode__(self):
        res = ', '.join(text_type(a) for a in self.args)
        return '<' + res + '>'

    def describe_signature(self, signode, mode, env, symbol):
        _verify_description_mode(mode)
        signode += nodes.Text('<')
        first = True
        for a in self.args:
            if not first:
                signode += nodes.Text(', ')
            first = False
            a.describe_signature(signode, 'markType', env, symbol=symbol)
        signode += nodes.Text('>')


class ASTNestedNameElement(ASTBase):
    def __init__(self, identifier, templateArgs):
        self.identifier = identifier
        self.templateArgs = templateArgs

    def is_operator(self):
        return False

    def get_id_v1(self):
        res = self.identifier.get_id_v1()
        if self.templateArgs:
            res += self.templateArgs.get_id_v1()
        return res

    def get_id_v2(self):
        res = self.identifier.get_id_v2()
        if self.templateArgs:
            res += self.templateArgs.get_id_v2()
        return res

    def __unicode__(self):
        res = text_type(self.identifier)
        if self.templateArgs:
            res += text_type(self.templateArgs)
        return res

    def describe_signature(self, signode, mode, env, prefix, symbol):
        self.identifier.describe_signature(signode, mode, env, prefix, symbol)
        if self.templateArgs:
            self.templateArgs.describe_signature(signode, mode, env, symbol)


class ASTNestedName(ASTBase):
    def __init__(self, names, rooted):
        assert len(names) > 0
        self.names = names
        self.rooted = rooted

    @property
    def name(self):
        return self

    def num_templates(self):
        count = 0
        for n in self.names:
            if n.is_operator():
                continue
            if n.templateArgs:
                count += 1
        return count

    def get_id_v1(self):
        tt = text_type(self)
        if tt in _id_shorthands_v1:
            return _id_shorthands_v1[tt]
        else:
            return u'::'.join(n.get_id_v1() for n in self.names)

    def get_id_v2(self, modifiers=""):
        res = []
        if len(self.names) > 1 or len(modifiers) > 0:
            res.append('N')
        res.append(modifiers)
        for n in self.names:
            res.append(n.get_id_v2())
        if len(self.names) > 1 or len(modifiers) > 0:
            res.append('E')
        return u''.join(res)

    def __unicode__(self):
        res = []
        if self.rooted:
            res.append('')
        for n in self.names:
            res.append(text_type(n))
        return '::'.join(res)

    def describe_signature(self, signode, mode, env, symbol):
        _verify_description_mode(mode)
        # just print the name part, with template args, not template params
        if mode == 'lastIsName':
            addname = []
            if self.rooted:
                addname.append('')
            for n in self.names[:-1]:
                addname.append(text_type(n))
            addname = '::'.join(addname)
            if len(self.names) > 1:
                addname += '::'
            signode += addnodes.desc_addname(addname, addname)
            self.names[-1].describe_signature(signode, mode, env, '', symbol)
        elif mode == 'noneIsName':
            signode += nodes.Text(text_type(self))
        elif mode == 'param':
            name = text_type(self)
            signode += nodes.emphasis(name, name)
        elif mode == 'markType':
            # each element should be a pending xref targeting the complete
            # prefix. however, only the identifier part should be a link, such
            # that template args can be a link as well.
            prefix = ''
            first = True
            for name in self.names:
                if not first:
                    signode += nodes.Text('::')
                    prefix += '::'
                first = False
                if name != '':
                    name.describe_signature(signode, mode, env, prefix, symbol)
                prefix += text_type(name)
        else:
            raise Exception('Unknown description mode: %s' % mode)


class ASTTrailingTypeSpecFundamental(ASTBase):
    def __init__(self, name):
        self.name = name

    def __unicode__(self):
        return self.name

    def get_id_v1(self):
        res = []
        for a in self.name.split(' '):
            if a in _id_fundamental_v1:
                res.append(_id_fundamental_v1[a])
            else:
                res.append(a)
        return u'-'.join(res)

    def get_id_v2(self):
        if self.name not in _id_fundamental_v2:
            raise Exception(
                'Semi-internal error: Fundamental type "%s" can not be mapped '
                'to an id. Is it a true fundamental type? If not so, the '
                'parser should have rejected it.' % self.name)
        return _id_fundamental_v2[self.name]

    def describe_signature(self, signode, mode, env, symbol):
        signode += nodes.Text(text_type(self.name))


class ASTTrailingTypeSpecName(ASTBase):
    def __init__(self, prefix, nestedName):
        self.prefix = prefix
        self.nestedName = nestedName

    @property
    def name(self):
        return self.nestedName

    def get_id_v1(self):
        return self.nestedName.get_id_v1()

    def get_id_v2(self):
        return self.nestedName.get_id_v2()

    def __unicode__(self):
        res = []
        if self.prefix:
            res.append(self.prefix)
            res.append(' ')
        res.append(text_type(self.nestedName))
        return u''.join(res)

    def describe_signature(self, signode, mode, env, symbol):
        if self.prefix:
            signode += addnodes.desc_annotation(self.prefix, self.prefix)
            signode += nodes.Text(' ')
        self.nestedName.describe_signature(signode, mode, env, symbol=symbol)


class ASTFunctinoParameter(ASTBase):
    def __init__(self, arg, ellipsis=False):
        self.arg = arg
        self.ellipsis = ellipsis

    def get_id_v1(self):
        if self.ellipsis:
            return 'z'
        else:
            return self.arg.get_id_v1()

    def get_id_v2(self):
        if self.ellipsis:
            return 'z'
        else:
            return self.arg.get_id_v2()

    def __unicode__(self):
        if self.ellipsis:
            return '...'
        else:
            return text_type(self.arg)

    def describe_signature(self, signode, mode, env, symbol):
        _verify_description_mode(mode)
        if self.ellipsis:
            signode += nodes.Text('...')
        else:
            self.arg.describe_signature(signode, mode, env, symbol=symbol)


class ASTParametersQualifiers(ASTBase):
    def __init__(self, args, volatile, const, refQual, exceptionSpec, override,
                 final, initializer):
        self.args = args
        self.volatile = volatile
        self.const = const
        self.refQual = refQual
        self.exceptionSpec = exceptionSpec
        self.override = override
        self.final = final
        self.initializer = initializer

    # Id v1 ------------------------------------------------------------------

    def get_modifiers_id_v1(self):
        res = []
        if self.volatile:
            res.append('V')
        if self.const:
            res.append('C')
        if self.refQual == '&&':
            res.append('O')
        elif self.refQual == '&':
            res.append('R')
        return u''.join(res)

    def get_param_id_v1(self):
        if len(self.args) == 0:
            return ''
        else:
            return u'__' + u'.'.join(a.get_id_v1() for a in self.args)

    # Id v2 ------------------------------------------------------------------

    def get_modifiers_id_v2(self):
        res = []
        if self.volatile:
            res.append('V')
        if self.const:
            res.append('K')
        if self.refQual == '&&':
            res.append('O')
        elif self.refQual == '&':
            res.append('R')
        return u''.join(res)

    def get_param_id_v2(self):
        if len(self.args) == 0:
            return 'v'
        else:
            return u''.join(a.get_id_v2() for a in self.args)

    def __unicode__(self):
        res = []
        res.append('(')
        first = True
        for a in self.args:
            if not first:
                res.append(', ')
            first = False
            res.append(text_type(a))
        res.append(')')
        if self.volatile:
            res.append(' volatile')
        if self.const:
            res.append(' const')
        if self.refQual:
            res.append(' ')
            res.append(self.refQual)
        if self.exceptionSpec:
            res.append(' ')
            res.append(text_type(self.exceptionSpec))
        if self.final:
            res.append(' final')
        if self.override:
            res.append(' override')
        if self.initializer:
            res.append(' = ')
            res.append(self.initializer)
        return u''.join(res)

    def describe_signature(self, signode, mode, env, symbol):
        _verify_description_mode(mode)
        paramlist = addnodes.desc_parameterlist()
        for arg in self.args:
            param = addnodes.desc_parameter('', '', noemph=True)
            if mode == 'lastIsName':  # i.e., outer-function params
                arg.describe_signature(param, 'param', env, symbol=symbol)
            else:
                arg.describe_signature(param, 'markType', env, symbol=symbol)
            paramlist += param
        signode += paramlist

        def _add_anno(signode, text):
            signode += nodes.Text(' ')
            signode += addnodes.desc_annotation(text, text)

        def _add_text(signode, text):
            signode += nodes.Text(' ' + text)

        if self.volatile:
            _add_anno(signode, 'volatile')
        if self.const:
            _add_anno(signode, 'const')
        if self.refQual:
            _add_text(signode, self.refQual)
        if self.exceptionSpec:
            _add_anno(signode, text_type(self.exceptionSpec))
        if self.final:
            _add_anno(signode, 'final')
        if self.override:
            _add_anno(signode, 'override')
        if self.initializer:
            _add_text(signode, '= ' + text_type(self.initializer))


class ASTDeclSpecsSimple(ASTBase):
    def __init__(self, storage, inline, virtual, explicit,
                 constexpr, volatile, const, friend):
        self.storage = storage
        self.inline = inline
        self.virtual = virtual
        self.explicit = explicit
        self.constexpr = constexpr
        self.volatile = volatile
        self.const = const
        self.friend = friend

    def mergeWith(self, other):
        if not other:
            return self
        return ASTDeclSpecsSimple(self.storage or other.storage,
                                  self.inline or other.inline,
                                  self.virtual or other.virtual,
                                  self.explicit or other.explicit,
                                  self.constexpr or other.constexpr,
                                  self.volatile or other.volatile,
                                  self.const or other.const,
                                  self.friend or other.friend)

    def __unicode__(self):
        res = []
        if self.storage:
            res.append(self.storage)
        if self.inline:
            res.append('inline')
        if self.friend:
            res.append('friend')
        if self.virtual:
            res.append('virtual')
        if self.explicit:
            res.append('explicit')
        if self.constexpr:
            res.append('constexpr')
        if self.volatile:
            res.append('volatile')
        if self.const:
            res.append('const')
        return u' '.join(res)

    def describe_signature(self, modifiers):
        def _add(modifiers, text):
            if len(modifiers) > 0:
                modifiers.append(nodes.Text(' '))
            modifiers.append(addnodes.desc_annotation(text, text))
        if self.storage:
            _add(modifiers, self.storage)
        if self.inline:
            _add(modifiers, 'inline')
        if self.friend:
            _add(modifiers, 'friend')
        if self.virtual:
            _add(modifiers, 'virtual')
        if self.explicit:
            _add(modifiers, 'explicit')
        if self.constexpr:
            _add(modifiers, 'constexpr')
        if self.volatile:
            _add(modifiers, 'volatile')
        if self.const:
            _add(modifiers, 'const')


class ASTDeclSpecs(ASTBase):
    def __init__(self, outer, leftSpecs, rightSpecs, trailing):
        # leftSpecs and rightSpecs are used for output
        # allSpecs are used for id generation
        self.outer = outer
        self.leftSpecs = leftSpecs
        self.rightSpecs = rightSpecs
        self.allSpecs = self.leftSpecs.mergeWith(self.rightSpecs)
        self.trailingTypeSpec = trailing

    @property
    def name(self):
        return self.trailingTypeSpec.name

    def get_id_v1(self):
        res = []
        res.append(self.trailingTypeSpec.get_id_v1())
        if self.allSpecs.volatile:
            res.append('V')
        if self.allSpecs.const:
            res.append('C')
        return u''.join(res)

    def get_id_v2(self):
        res = []
        if self.leftSpecs.volatile or self.rightSpecs.volatile:
            res.append('V')
        if self.leftSpecs.const or self.rightSpecs.volatile:
            res.append('K')
        res.append(self.trailingTypeSpec.get_id_v2())
        return u''.join(res)

    def __unicode__(self):
        res = []
        l = text_type(self.leftSpecs)
        if len(l) > 0:
            if len(res) > 0:
                res.append(" ")
            res.append(l)
        if self.trailingTypeSpec:
            if len(res) > 0:
                res.append(" ")
            res.append(text_type(self.trailingTypeSpec))
            r = text_type(self.rightSpecs)
            if len(r) > 0:
                if len(res) > 0:
                    res.append(" ")
                res.append(r)
        return "".join(res)

    def describe_signature(self, signode, mode, env, symbol):
        _verify_description_mode(mode)
        modifiers = []

        def _add(modifiers, text):
            if len(modifiers) > 0:
                modifiers.append(nodes.Text(' '))
            modifiers.append(addnodes.desc_annotation(text, text))

        self.leftSpecs.describe_signature(modifiers)

        for m in modifiers:
            signode += m
        if self.trailingTypeSpec:
            if len(modifiers) > 0:
                signode += nodes.Text(' ')
            self.trailingTypeSpec.describe_signature(signode, mode, env,
                                                     symbol=symbol)
            modifiers = []
            self.rightSpecs.describe_signature(modifiers)
            if len(modifiers) > 0:
                signode += nodes.Text(' ')
            for m in modifiers:
                signode += m


class ASTArray(ASTBase):
    def __init__(self, size):
        self.size = size

    def __unicode__(self):
        return u''.join(['[', text_type(self.size), ']'])

    def get_id_v1(self):
        return u'A'

    def get_id_v2(self):
        # TODO: this should maybe be done differently
        return u'A' + text_type(self.size) + u'_'

    def describe_signature(self, signode, mode, env):
        _verify_description_mode(mode)
        signode += nodes.Text(text_type(self))


class ASTDeclaratorPtr(ASTBase):
    def __init__(self, next, volatile, const):
        assert next
        self.next = next
        self.volatile = volatile
        self.const = const

    @property
    def name(self):
        return self.next.name

    def require_space_after_declSpecs(self):
        # TODO: if has paramPack, then False ?
        return True

    def __unicode__(self):
        res = ['*']
        if self.volatile:
            res.append('volatile')
        if self.const:
            if self.volatile:
                res.append(' ')
            res.append('const')
        if self.const or self.volatile:
            if self.next.require_space_after_declSpecs:
                res.append(' ')
        res.append(text_type(self.next))
        return u''.join(res)

    # Id v1 ------------------------------------------------------------------

    def get_modifiers_id_v1(self):
        return self.next.get_modifiers_id_v1()

    def get_param_id_v1(self):
        return self.next.get_param_id_v1()

    def get_ptr_suffix_id_v1(self):
        res = 'P'
        if self.volatile:
            res += 'V'
        if self.const:
            res += 'C'
        return res + self.next.get_ptr_suffix_id_v1()

    # Id v2 ------------------------------------------------------------------

    def get_modifiers_id_v2(self):
        return self.next.get_modifiers_id_v2()

    def get_param_id_v2(self):
        return self.next.get_param_id_v2()

    def get_ptr_suffix_id_v2(self):
        res = [self.next.get_ptr_suffix_id_v2()]
        res.append('P')
        if self.volatile:
            res.append('V')
        if self.const:
            res.append('C')
        return u''.join(res)

    def get_type_id_v2(self, returnTypeId):
        # ReturnType *next, so we are part of the return type of 'next
        res = ['P']
        if self.volatile:
            res.append('V')
        if self.const:
            res.append('C')
        res.append(returnTypeId)
        return self.next.get_type_id_v2(returnTypeId=u''.join(res))

    # ------------------------------------------------------------------------

    def is_function_type(self):
        return self.next.is_function_type()

    def describe_signature(self, signode, mode, env, symbol):
        _verify_description_mode(mode)
        signode += nodes.Text("*")

        def _add_anno(signode, text):
            signode += addnodes.desc_annotation(text, text)
        if self.volatile:
            _add_anno(signode, 'volatile')
        if self.const:
            if self.volatile:
                signode += nodes.Text(' ')
            _add_anno(signode, 'const')
        if self.const or self.volatile:
            if self.next.require_space_after_declSpecs:
                signode += nodes.Text(' ')
        self.next.describe_signature(signode, mode, env, symbol)


class ASTDeclaratorRef(ASTBase):
    def __init__(self, next):
        assert next
        self.next = next

    @property
    def name(self):
        return self.next.name

    def require_space_after_declSpecs(self):
        return self.next.require_space_after_declSpecs()

    def __unicode__(self):
        return '&' + text_type(self.next)

    # Id v1 ------------------------------------------------------------------

    def get_modifiers_id_v1(self):
        return self.next.get_modifiers_id_v1()

    def get_param_id_v1(self):  # only the parameters (if any)
        return self.next.get_param_id_v1()

    def get_ptr_suffix_id_v1(self):
        return u'R' + self.next.get_ptr_suffix_id_v1()

    # Id v2 ------------------------------------------------------------------

    def get_modifiers_id_v2(self):
        return self.next.get_modifiers_id_v2()

    def get_param_id_v2(self):  # only the parameters (if any)
        return self.next.get_param_id_v2()

    def get_ptr_suffix_id_v2(self):
        return self.next.get_ptr_suffix_id_v2() + u'R'

    def get_type_id_v2(self, returnTypeId):
        # ReturnType &next, so we are part of the return type of 'next
        return self.next.get_type_id_v2(returnTypeId=u'R' + returnTypeId)

    # ------------------------------------------------------------------------

    def is_function_type(self):
        return self.next.is_function_type()

    def describe_signature(self, signode, mode, env, symbol):
        _verify_description_mode(mode)
        signode += nodes.Text("&")
        self.next.describe_signature(signode, mode, env, symbol)


class ASTDeclaratorParamPack(ASTBase):
    def __init__(self, next):
        assert next
        self.next = next

    @property
    def name(self):
        return self.next.name

    def require_space_after_declSpecs(self):
        return False

    def __unicode__(self):
        res = text_type(self.next)
        if self.next.name:
            res = ' ' + res
        return '...' + res

    # Id v1 ------------------------------------------------------------------

    def get_modifiers_id_v1(self):
        return self.next.get_modifiers_id_v1()

    def get_param_id_v1(self):  # only the parameters (if any)
        return self.next.get_param_id_v1()

    def get_ptr_suffix_id_v1(self):
        return 'Dp' + self.next.get_ptr_suffix_id_v2()

    # Id v2 ------------------------------------------------------------------

    def get_modifiers_id_v2(self):
        return self.next.get_modifiers_id_v2()

    def get_param_id_v2(self):  # only the parameters (if any)
        return self.next.get_param_id_v2()

    def get_ptr_suffix_id_v2(self):
        return self.next.get_ptr_suffix_id_v2() + u'Dp'

    def get_type_id_v2(self, returnTypeId):
        # ReturnType... next, so we are part of the return type of 'next
        return self.next.get_type_id_v2(returnTypeId=u'Dp' + returnTypeId)

    # ------------------------------------------------------------------------

    def is_function_type(self):
        return self.next.is_function_type()

    def describe_signature(self, signode, mode, env, symbol):
        _verify_description_mode(mode)
        signode += nodes.Text("...")
        if self.next.name:
            signode += nodes.Text(' ')
        self.next.describe_signature(signode, mode, env, symbol)


class ASTDeclaratorMemPtr(ASTBase):
    def __init__(self, className, const, volatile, next):
        assert className
        assert next
        self.className = className
        self.const = const
        self.volatile = volatile
        self.next = next

    @property
    def name(self):
        return self.next.name

    def require_space_after_declSpecs(self):
        return True

    def __unicode__(self):
        res = []
        res.append(text_type(self.className))
        res.append('::*')
        if self.volatile:
            res.append(' volatile')
        if self.const:
            res.append(' const')
        if self.next.require_space_after_declSpecs():
            res.append(' ')
        res.append(text_type(self.next))
        return ''.join(res)

    # Id v1 ------------------------------------------------------------------

    def get_modifiers_id_v1(self):
        raise NoOldIdError()

    def get_param_id_v1(self):  # only the parameters (if any)
        raise NoOldIdError()

    def get_ptr_suffix_id_v1(self):
        raise NoOldIdError()

    # Id v2 ------------------------------------------------------------------

    def get_modifiers_id_v2(self):
        return self.next.get_modifiers_id_v2()

    def get_param_id_v2(self):  # only the parameters (if any)
        return self.next.get_param_id_v2()

    def get_ptr_suffix_id_v2(self):
        raise NotImplementedError()
        return self.next.get_ptr_suffix_id_v2() + u'Dp'

    def get_type_id_v2(self, returnTypeId):
        # ReturnType name::* next, so we are part of the return type of next
        nextReturnTypeId = ''
        if self.volatile:
            nextReturnTypeId += 'V'
        if self.const:
            nextReturnTypeId += 'K'
        nextReturnTypeId += 'M'
        nextReturnTypeId += self.className.get_id_v2()
        nextReturnTypeId += returnTypeId
        return self.next.get_type_id_v2(nextReturnTypeId)

    # ------------------------------------------------------------------------

    def is_function_type(self):
        return self.next.is_function_type()

    def describe_signature(self, signode, mode, env, symbol):
        _verify_description_mode(mode)
        self.className.describe_signature(signode, mode, env, symbol)
        signode += nodes.Text('::*')

        def _add_anno(signode, text):
            signode += addnodes.desc_annotation(text, text)
        if self.volatile:
            _add_anno(signode, 'volatile')
        if self.const:
            if self.volatile:
                signode += nodes.Text(' ')
            _add_anno(signode, 'const')
        if self.next.require_space_after_declSpecs():
            if self.volatile or self.const:
                signode += nodes.Text(' ')
        self.next.describe_signature(signode, mode, env, symbol)


class ASTDeclaratorParen(ASTBase):
    def __init__(self, inner, next):
        assert inner
        assert next
        self.inner = inner
        self.next = next
        # TODO: we assume the name, params, and qualifiers are in inner

    @property
    def name(self):
        return self.inner.name

    def require_space_after_declSpecs(self):
        return True

    def __unicode__(self):
        res = ['(']
        res.append(text_type(self.inner))
        res.append(')')
        res.append(text_type(self.next))
        return ''.join(res)

    # Id v1 ------------------------------------------------------------------

    def get_modifiers_id_v1(self):
        return self.inner.get_modifiers_id_v1()

    def get_param_id_v1(self):  # only the parameters (if any)
        return self.inner.get_param_id_v1()

    def get_ptr_suffix_id_v1(self):
        raise NoOldIdError()  # TODO: was this implemented before?
        return self.next.get_ptr_suffix_id_v2() + \
            self.inner.get_ptr_suffix_id_v2()

    # Id v2 ------------------------------------------------------------------

    def get_modifiers_id_v2(self):
        return self.inner.get_modifiers_id_v2()

    def get_param_id_v2(self):  # only the parameters (if any)
        return self.inner.get_param_id_v2()

    def get_ptr_suffix_id_v2(self):
        return self.inner.get_ptr_suffix_id_v2() + \
            self.next.get_ptr_suffix_id_v2()

    def get_type_id_v2(self, returnTypeId):
        # ReturnType (inner)next, so 'inner' returns everything outside
        nextId = self.next.get_type_id_v2(returnTypeId)
        return self.inner.get_type_id_v2(returnTypeId=nextId)

    # ------------------------------------------------------------------------

    def is_function_type(self):
        return self.inner.is_function_type()

    def describe_signature(self, signode, mode, env, symbol):
        _verify_description_mode(mode)
        signode += nodes.Text('(')
        self.inner.describe_signature(signode, mode, env, symbol)
        signode += nodes.Text(')')
        self.next.describe_signature(signode, "noneIsName", env, symbol)


class ASTDecleratorNameParamQual(ASTBase):
    def __init__(self, declId, arrayOps, paramQual):
        self.declId = declId
        self.arrayOps = arrayOps
        self.paramQual = paramQual

    @property
    def name(self):
        return self.declId

    # Id v1 ------------------------------------------------------------------

    def get_modifiers_id_v1(self):  # only the modifiers for a function, e.g.,
        # cv-qualifiers
        if self.paramQual:
            return self.paramQual.get_modifiers_id_v1()
        raise Exception(
            "This should only be called on a function: %s" % text_type(self))

    def get_param_id_v1(self):  # only the parameters (if any)
        if self.paramQual:
            return self.paramQual.get_param_id_v1()
        else:
            return ''

    def get_ptr_suffix_id_v1(self):  # only the array specifiers
        return u''.join(a.get_id_v1() for a in self.arrayOps)

    # Id v2 ------------------------------------------------------------------

    def get_modifiers_id_v2(self):  # only the modifiers for a function, e.g.,
        # cv-qualifiers
        if self.paramQual:
            return self.paramQual.get_modifiers_id_v2()
        raise Exception(
            "This should only be called on a function: %s" % text_type(self))

    def get_param_id_v2(self):  # only the parameters (if any)
        if self.paramQual:
            return self.paramQual.get_param_id_v2()
        else:
            return ''

    def get_ptr_suffix_id_v2(self):  # only the array specifiers
        return u''.join(a.get_id_v2() for a in self.arrayOps)

    def get_type_id_v2(self, returnTypeId):
        res = []
        # TOOD: can we actually have both array ops and paramQual?
        res.append(self.get_ptr_suffix_id_v2())
        if self.paramQual:
            res.append(self.get_modifiers_id_v2())
            res.append('F')
            res.append(returnTypeId)
            res.append(self.get_param_id_v2())
            res.append('E')
        else:
            res.append(returnTypeId)
        return u''.join(res)

    # ------------------------------------------------------------------------

    def require_space_after_declSpecs(self):
        return self.declId is not None

    def is_function_type(self):
        return self.paramQual is not None

    def __unicode__(self):
        res = []
        if self.declId:
            res.append(text_type(self.declId))
        for op in self.arrayOps:
            res.append(text_type(op))
        if self.paramQual:
            res.append(text_type(self.paramQual))
        return u''.join(res)

    def describe_signature(self, signode, mode, env, symbol):
        _verify_description_mode(mode)
        if self.declId:
            self.declId.describe_signature(signode, mode, env, symbol)
        for op in self.arrayOps:
            op.describe_signature(signode, mode, env)
        if self.paramQual:
            self.paramQual.describe_signature(signode, mode, env, symbol)


class ASTInitializer(ASTBase):
    def __init__(self, value):
        self.value = value

    def __unicode__(self):
        return u''.join([' = ', text_type(self.value)])

    def describe_signature(self, signode, mode):
        _verify_description_mode(mode)
        signode += nodes.Text(text_type(self))


class ASTType(ASTBase):
    def __init__(self, declSpecs, decl):
        assert declSpecs
        assert decl
        self.declSpecs = declSpecs
        self.decl = decl

    @property
    def name(self):
        name = self.decl.name
        return name

    def get_id_v1(self, objectType=None, symbol=None):
        res = []
        if objectType:  # needs the name
            if objectType == 'function':  # also modifiers
                res.append(symbol.get_full_nested_name().get_id_v1())
                res.append(self.decl.get_param_id_v1())
                res.append(self.decl.get_modifiers_id_v1())
                if (self.declSpecs.leftSpecs.constexpr or
                        (self.declSpecs.rightSpecs and
                         self.declSpecs.rightSpecs.constexpr)):
                    res.append('CE')
            elif objectType == 'type':  # just the name
                res.append(symbol.get_full_nested_name().get_id_v1())
            else:
                print(objectType)
                assert False
        else:  # only type encoding
            if self.decl.is_function_type():
                raise NoOldIdError()
            res.append(self.declSpecs.get_id_v1())
            res.append(self.decl.get_ptr_suffix_id_v1())
            res.append(self.decl.get_param_id_v1())
        return u''.join(res)

    def get_id_v2(self, objectType=None, symbol=None):
        res = []
        if objectType:  # needs the name
            if objectType == 'function':  # also modifiers
                modifiers = self.decl.get_modifiers_id_v2()
                res.append(symbol.get_full_nested_name().get_id_v2(modifiers))
                res.append(self.decl.get_param_id_v2())
            elif objectType == 'type':  # just the name
                res.append(symbol.get_full_nested_name().get_id_v2())
            else:
                print(objectType)
                assert False
        else:  # only type encoding
            # the 'returnType' of a non-function type is simply just the last
            # type, i.e., for 'int*' it is 'int'
            returnTypeId = self.declSpecs.get_id_v2()
            typeId = self.decl.get_type_id_v2(returnTypeId)
            res.append(typeId)
        return u''.join(res)

    def __unicode__(self):
        res = []
        declSpecs = text_type(self.declSpecs)
        res.append(declSpecs)
        if self.decl.require_space_after_declSpecs() and len(declSpecs) > 0:
            res.append(u' ')
        res.append(text_type(self.decl))
        return u''.join(res)

    def describe_signature(self, signode, mode, env, symbol):
        _verify_description_mode(mode)
        self.declSpecs.describe_signature(signode, 'markType', env, symbol)
        if (self.decl.require_space_after_declSpecs() and
                len(text_type(self.declSpecs)) > 0):
            signode += nodes.Text(' ')
        self.decl.describe_signature(signode, mode, env, symbol)


class ASTTypeWithInit(ASTBase):
    def __init__(self, type, init):
        self.type = type
        self.init = init

    @property
    def name(self):
        return self.type.name

    def get_id_v1(self, objectType=None, symbol=None):
        if objectType == 'member':
            return symbol.get_full_nested_name().get_id_v1() + u'__' \
                + self.type.get_id_v1()
        else:
            return self.type.get_id_v1(objectType)

    def get_id_v2(self, objectType=None, symbol=None):
        if objectType == 'member':
            return symbol.declaration.name.get_id_v2()
        else:
            return self.type.get_id_v2()

    def __unicode__(self):
        res = []
        res.append(text_type(self.type))
        if self.init:
            res.append(text_type(self.init))
        return u''.join(res)

    def describe_signature(self, signode, mode, env, symbol):
        _verify_description_mode(mode)
        self.type.describe_signature(signode, mode, env, symbol=symbol)
        if self.init:
            self.init.describe_signature(signode, mode)


class ASTTypeUsing(ASTBase):
    def __init__(self, name, type):
        self.name = name
        self.type = type

    def get_id_v1(self, objectType=None, symbol=None):
        raise NoOldIdError()

    def get_id_v2(self, objectType=None, symbol=None):
        return symbol.get_full_nested_name().get_id_v2()

    def __unicode__(self):
        res = []
        res.append(text_type(self.name))
        if self.type:
            res.append(' = ')
            res.append(text_type(self.type))
        return u''.join(res)

    def describe_signature(self, signode, mode, env, symbol):
        _verify_description_mode(mode)
        self.name.describe_signature(signode, mode, env, symbol=symbol)
        if self.type:
            signode += nodes.Text(' = ')
            self.type.describe_signature(signode, 'markType', env, symbol=symbol)


class ASTBaseClass(ASTBase):
    def __init__(self, name, visibility, virtual, pack):
        self.name = name
        self.visibility = visibility
        self.virtual = virtual
        self.pack = pack

    def __unicode__(self):
        res = []
        if self.visibility != 'private':
            res.append(self.visibility)
            res.append(' ')
        if self.virtual:
            res.append('virtual ')
        res.append(text_type(self.name))
        if self.pack:
            res.append('...')
        return u''.join(res)

    def describe_signature(self, signode, mode, env, symbol):
        _verify_description_mode(mode)
        if self.visibility != 'private':
            signode += addnodes.desc_annotation(self.visibility,
                                                self.visibility)
            signode += nodes.Text(' ')
        if self.virtual:
            signode += addnodes.desc_annotation('virtual', 'virtual')
            signode += nodes.Text(' ')
        self.name.describe_signature(signode, 'markType', env, symbol=symbol)
        if self.pack:
            signode += nodes.Text('...')


class ASTClass(ASTBase):
    def __init__(self, name, final, bases):
        self.name = name
        self.final = final
        self.bases = bases

    def get_id_v1(self, objectType, symbol):
        return symbol.get_full_nested_name().get_id_v1()

    def get_id_v2(self, objectType, symbol):
        return symbol.get_full_nested_name().get_id_v2()

    def __unicode__(self):
        res = []
        res.append(text_type(self.name))
        if self.final:
            res.append(' final')
        if len(self.bases) > 0:
            res.append(' : ')
            first = True
            for b in self.bases:
                if not first:
                    res.append(', ')
                first = False
                res.append(text_type(b))
        return u''.join(res)

    def describe_signature(self, signode, mode, env, symbol):
        _verify_description_mode(mode)
        self.name.describe_signature(signode, mode, env, symbol=symbol)
        if self.final:
            signode += nodes.Text(' ')
            signode += addnodes.desc_annotation('final', 'final')
        if len(self.bases) > 0:
            signode += nodes.Text(' : ')
            for b in self.bases:
                b.describe_signature(signode, mode, env, symbol=symbol)
                signode += nodes.Text(', ')
            signode.pop()


class ASTEnum(ASTBase):
    def __init__(self, name, scoped, underlyingType):
        self.name = name
        self.scoped = scoped
        self.underlyingType = underlyingType

    def get_id_v1(self, objectType, symbol):
        raise NoOldIdError()

    def get_id_v2(self, objectType, symbol):
        return symbol.get_full_nested_name().get_id_v2()

    def __unicode__(self):
        res = []
        if self.scoped:
            res.append(self.scoped)
            res.append(' ')
        res.append(text_type(self.name))
        if self.underlyingType:
            res.append(' : ')
            res.append(text_type(self.underlyingType))
        return u''.join(res)

    def describe_signature(self, signode, mode, env, symbol):
        _verify_description_mode(mode)
        # self.scoped has been done by the CPPEnumObject
        self.name.describe_signature(signode, mode, env, symbol=symbol)
        if self.underlyingType:
            signode += nodes.Text(' : ')
            self.underlyingType.describe_signature(signode, 'noneIsName',
                                                   env, symbol=symbol)


class ASTEnumerator(ASTBase):
    def __init__(self, name, init):
        self.name = name
        self.init = init

    def get_id_v1(self, objectType, symbol):
        raise NoOldIdError()

    def get_id_v2(self, objectType, symbol):
        return symbol.get_full_nested_name().get_id_v2()

    def __unicode__(self):
        res = []
        res.append(text_type(self.name))
        if self.init:
            res.append(text_type(self.init))
        return u''.join(res)

    def describe_signature(self, signode, mode, env, symbol):
        _verify_description_mode(mode)
        self.name.describe_signature(signode, mode, env, symbol=symbol)
        if self.init:
            self.init.describe_signature(signode, 'noneIsName')


class ASTDeclaration(ASTBase):
    def __init__(self, objectType, visibility, templatePrefix, declaration):
        self.objectType = objectType
        self.visibility = visibility
        self.templatePrefix = templatePrefix
        self.declaration = declaration

        self.symbol = None
        # set by CPPObject._add_enumerator_to_parent
        self.enumeratorScopedSymbol = None

    def clone(self):
        if self.templatePrefix:
            templatePrefixClone = self.templatePrefix.clone()
        else:
            templatePrefixClone = None
        return ASTDeclaration(self.objectType, self.visibility,
                              templatePrefixClone,
                              self.declaration.clone())

    @property
    def name(self):
        return self.declaration.name

    def get_id_v1(self):
        if self.templatePrefix:
            raise NoOldIdError()
        if self.objectType == 'enumerator' and self.enumeratorScopedSymbol:
            return self.enumeratorScopedSymbol.declaration.get_id_v1()
        return self.declaration.get_id_v1(self.objectType, self.symbol)

    def get_id_v2(self, prefixed=True):
        if self.objectType == 'enumerator' and self.enumeratorScopedSymbol:
            return self.enumeratorScopedSymbol.declaration.get_id_v2(prefixed)
        if prefixed:
            res = [_id_prefix_v2]
        else:
            res = []
        if self.templatePrefix:
            res.append(self.templatePrefix.get_id_v2())
        res.append(self.declaration.get_id_v2(self.objectType, self.symbol))
        return u''.join(res)

    def get_newest_id(self):
        return self.get_id_v2()

    def __unicode__(self):
        res = []
        if self.visibility and self.visibility != "public":
            res.append(self.visibility)
            res.append(u' ')
        if self.templatePrefix:
            res.append(text_type(self.templatePrefix))
        res.append(text_type(self.declaration))
        return u''.join(res)

    def describe_signature(self, signode, mode, env):
        _verify_description_mode(mode)
        # the caller of the domain added a desc_signature node
        # let's pop it so we can add templates before that
        parentNode = signode.parent
        mainDeclNode = signode
        parentNode.pop()

        assert self.symbol
        if self.templatePrefix:
            self.templatePrefix.describe_signature(parentNode, mode, env,
                                                   symbol=self.symbol)
        if self.visibility and self.visibility != "public":
            mainDeclNode += addnodes.desc_annotation(self.visibility + " ",
                                                     self.visibility + " ")
        if self.objectType == 'type':
            mainDeclNode += addnodes.desc_annotation('type ', 'type ')
        elif self.objectType == 'member':
            pass
        elif self.objectType == 'function':
            pass
        elif self.objectType == 'class':
            mainDeclNode += addnodes.desc_annotation('class ', 'class ')
        elif self.objectType == 'enum':
            prefix = 'enum '
            if self.scoped:
                prefix += self.scoped
                prefix += ' '
            mainDeclNode += addnodes.desc_annotation(prefix, prefix)
        elif self.objectType == 'enumerator':
            mainDeclNode += addnodes.desc_annotation('enumerator ', 'enumerator ')
        else:
            assert False
        self.declaration.describe_signature(mainDeclNode, mode, env,
                                            symbol=self.symbol)
        parentNode += mainDeclNode


class ASTNamespace(ASTBase):
    def __init__(self, nestedName, templatePrefix):
        self.nestedName = nestedName
        self.templatePrefix = templatePrefix


class Symbol(object):
    def _assert_invariants(self):
        if not self.parent:
            # parent == None means global scope, so declaration means a parent
            assert not self.identifier
            assert not self.templateParams
            assert not self.templateArgs
            assert not self.declaration
            assert not self.docname
        else:
            if not self.identifier:
                # in case it's an operator
                assert self.declaration
            if self.declaration:
                assert self.docname

    def __init__(self, parent, identifier,
                 templateParams, templateArgs, declaration, docname):
        self.parent = parent
        self.identifier = identifier
        self.templateParams = templateParams  # template<templateParams>
        self.templateArgs = templateArgs  # identifier<templateArgs>
        self.declaration = declaration
        self.docname = docname
        self._assert_invariants()

        self.children = []
        if self.parent:
            self.parent.children.append(self)
        if self.declaration:
            self.declaration.symbol = self
        # add symbols for the template params
        # (do it after self.children has been initialised
        if self.templateParams:
            for p in self.templateParams.params:
                if not p.get_identifier():
                    continue
                # only add a declaration if we our selfs from a declaration
                if declaration:
                    decl = ASTDeclaration('templateParam', None, None, p)
                else:
                    decl = None
                nne = ASTNestedNameElement(p.get_identifier(), None)
                nn = ASTNestedName([nne], rooted=False)
                self._add_symbols(nn, [], decl, docname)

    def _fill_empty(self, declaration, docname):
        self._assert_invariants()
        assert not self.declaration
        assert not self.docname
        assert declaration
        assert docname
        self.declaration = declaration
        self.declaration.symbol = self
        self.docname = docname
        self._assert_invariants()

    def clear_doc(self, docname):
        newChildren = []
        for sChild in self.children:
            sChild.clear_doc(docname)
            if sChild.declaration and sChild.docname == docname:
                sChild.declaration = None
                sChild.docname = None
            # Just remove operators, because there is no identification if
            # they got removed.
            # Don't remove other symbols because they may be used in namespace
            # directives.
            if sChild.identifier or sChild.declaration:
                newChildren.append(sChild)
        self.children = newChildren

    def get_all_symbols(self):
        yield self
        for sChild in self.children:
            for s in sChild.get_all_symbols():
                yield s

    def get_lookup_key(self):
        if not self.parent:
            # specialise for the root
            return None
        symbols = []
        s = self
        while s.parent:
            symbols.append(s)
            s = s.parent
        symbols.reverse()
        key = []
        for s in symbols:
            if s.identifier:
                nne = ASTNestedNameElement(s.identifier, s.templateArgs)
            else:
                assert s.declaration
                nne = s.declaration.name.names[-1]
            key.append((nne, s.templateParams))
        return key

    def get_full_nested_name(self):
        names = []
        for nne, templateParams in self.get_lookup_key():
            names.append(nne)
        return ASTNestedName(names, rooted=False)

    def _find_named_symbol(self, identifier, templateParams,
                           templateArgs, operator,
                           templateShorthand, matchSelf):
        assert (identifier is None) != (operator is None)

        def matches(s):
            if s.identifier != identifier:
                return False
            if not s.identifier:
                if not s.declaration:
                    return False
                assert operator
                name = s.declaration.name.names[-1]
                if not name.is_operator():
                    return False
                if text_type(name) != text_type(operator):
                    return False
            if (s.templateParams is None) != (templateParams is None):
                if templateParams is not None:
                    # we query with params, they must match params
                    return False
                if not templateShorthand:
                    # we don't query with params, and we do care about them
                    return False
            if templateParams:
                # TODO: do better comparison
                if text_type(s.templateParams) != text_type(templateParams):
                    return False
            if (s.templateArgs is None) != (templateArgs is None):
                return False
            if s.templateArgs:
                # TODO: do better comparison
                if text_type(s.templateArgs) != text_type(templateArgs):
                    return False
            return True
        if matchSelf and matches(self):
            return self
        for s in self.children:
            if matches(s):
                return s
        return None

    def _add_symbols(self, nestedName, templateDecls, declaration, docname):
        # This condition should be checked at the parser level.
        # Each template argument list must have a template parameter list.
        # But to declare a template there must be an additional template parameter list.
        assert(nestedName.num_templates() == len(templateDecls) or
               nestedName.num_templates() + 1 == len(templateDecls))

        parentSymbol = self
        if nestedName.rooted:
            while parentSymbol.parent:
                parentSymbol = parentSymbol.parent
        names = nestedName.names
        iTemplateDecl = 0
        for name in names[:-1]:
            # there shouldn't be anything inside an operator
            # (other than template parameters, which are not added this way, right?)
            assert not name.is_operator()
            identifier = name.identifier
            templateArgs = name.templateArgs
            if templateArgs:
                assert iTemplateDecl < len(templateDecls)
                templateParams = templateDecls[iTemplateDecl]
                iTemplateDecl += 1
            else:
                templateParams = None
            symbol = parentSymbol._find_named_symbol(identifier,
                                                     templateParams,
                                                     templateArgs,
                                                     operator=None,
                                                     templateShorthand=False,
                                                     matchSelf=False)
            if symbol is None:
                symbol = Symbol(parent=parentSymbol, identifier=identifier,
                                templateParams=templateParams,
                                templateArgs=templateArgs, declaration=None,
                                docname=None)
            parentSymbol = symbol
        name = names[-1]
        if name.is_operator():
            identifier = None
            templateArgs = None
            operator = name
        else:
            identifier = name.identifier
            templateArgs = name.templateArgs
            operator = None
        if iTemplateDecl < len(templateDecls):
            if iTemplateDecl + 1 != len(templateDecls):
                print(text_type(templateDecls))
                print(text_type(nestedName))
            assert iTemplateDecl + 1 == len(templateDecls)
            templateParams = templateDecls[iTemplateDecl]
        else:
            assert iTemplateDecl == len(templateDecls)
            templateParams = None
        symbol = parentSymbol._find_named_symbol(identifier,
                                                 templateParams,
                                                 templateArgs,
                                                 operator,
                                                 templateShorthand=False,
                                                 matchSelf=False)
        if symbol:
            if not declaration:
                # good, just a scope creation
                return symbol
            if not symbol.declaration:
                # If someone first opened the scope, and then later
                # declares it, e.g,
                # .. namespace:: Test
                # .. namespace:: nullptr
                # .. class:: Test
                symbol._fill_empty(declaration, docname)
                return symbol
            # it may simply be a functin overload
            # TODO: it could be a duplicate but let's just insert anyway
            # the id generation will warn about it
            symbol = Symbol(parent=parentSymbol, identifier=identifier,
                            templateParams=templateParams,
                            templateArgs=templateArgs,
                            declaration=declaration,
                            docname=docname)
        else:
            symbol = Symbol(parent=parentSymbol, identifier=identifier,
                            templateParams=templateParams,
                            templateArgs=templateArgs,
                            declaration=declaration,
                            docname=docname)
        return symbol

    def merge_with(self, other, docnames, env):
        assert other is not None
        for otherChild in other.children:
            if not otherChild.identifier:
                if not otherChild.declaration:
                    print("Problem in symbol tree merging")
                    print("OtherChild.dump:")
                    print(otherChild.dump(0))
                    print("Other.dump:")
                    print(other.dump(0))
                assert otherChild.declaration
                operator = otherChild.declaration.name.names[-1]
                assert operator.is_operator()
            else:
                operator = None
            ourChild = self._find_named_symbol(otherChild.identifier,
                                               otherChild.templateParams,
                                               otherChild.templateArgs,
                                               operator,
                                               templateShorthand=False,
                                               matchSelf=False)
            if ourChild is None:
                # TODO: hmm, should we prune by docnames?
                self.children.append(otherChild)
                otherChild.parent = self
                otherChild._assert_invariants()
                continue
            if otherChild.declaration and otherChild.docname in docnames:
                if not ourChild.declaration:
                    ourChild._fill_empty(otherChild.declaration, otherChild.docname)
                elif ourChild.docname != otherChild.docname:
                    name = text_type(ourChild.declaration)
                    msg = "Duplicate declaration, also defined in '%s'.\n"
                    msg += "Declaration is '%s'."
                    msg = msg % (ourChild.docname, name)
                    env.warn(otherChild.docname, msg)
                else:
                    # Both have declarations, and in the same docname.
                    # This can apparently happen, it should be safe to
                    # just ignore it, right?
                    pass
            ourChild.merge_with(otherChild, docnames, env)

    def add_name(self, nestedName, templatePrefix=None):
        if templatePrefix:
            templateDecls = templatePrefix.templates
        else:
            templateDecls = []
        return self._add_symbols(nestedName, templateDecls,
                                 declaration=None, docname=None)

    def add_declaration(self, declaration, docname):
        assert declaration
        assert docname
        nestedName = declaration.name
        if declaration.templatePrefix:
            templateDecls = declaration.templatePrefix.templates
        else:
            templateDecls = []
        return self._add_symbols(nestedName, templateDecls, declaration, docname)

    def find_identifier(self, identifier, matchSelf):
        if matchSelf and self.identifier and self.identifier == identifier:
            return self
        for s in self.children:
            if s.identifier and s.identifier == identifier:
                return s
        return None

    def direct_lookup(self, key):
        s = self
        for name, templateParams in key:
            if name.is_operator():
                identifier = None
                templateArgs = None
                operator = name
            else:
                identifier = name.identifier
                templateArgs = name.templateArgs
                operator = None
            s = s._find_named_symbol(identifier, templateParams,
                                     templateArgs, operator,
                                     templateShorthand=False,
                                     matchSelf=True)
            if not s:
                return None
        return s

    def find_name(self, nestedName, templateDecls, templateShorthand, matchSelf):
        # templateShorthand: missing template parameter lists for templates is ok

        # TODO: unify this with the _add_symbols
        # This condition should be checked at the parser level.
        assert len(templateDecls) <= nestedName.num_templates() + 1
        parentSymbol = self
        if nestedName.rooted:
            while parentSymbol.parent:
                parentSymbol = parentSymbol.parent
        names = nestedName.names

        # walk up until we find the first identifier
        firstName = names[0]
        if not firstName.is_operator():
            while parentSymbol.parent:
                if parentSymbol.find_identifier(firstName.identifier,
                                                matchSelf=matchSelf):
                    break
                parentSymbol = parentSymbol.parent

        iTemplateDecl = 0
        for iName in range(len(names)):
            name = names[iName]
            if iName + 1 == len(names):
                if name.is_operator():
                    identifier = None
                    templateArgs = None
                    operator = name
                else:
                    identifier = name.identifier
                    templateArgs = name.templateArgs
                    operator = None
                if iTemplateDecl < len(templateDecls):
                    assert iTemplateDecl + 1 == len(templateDecls)
                    templateParams = templateDecls[iTemplateDecl]
                else:
                    assert iTemplateDecl == len(templateDecls)
                    templateParams = None
                symbol = parentSymbol._find_named_symbol(identifier,
                                                         templateParams,
                                                         templateArgs,
                                                         operator,
                                                         templateShorthand=templateShorthand,
                                                         matchSelf=matchSelf)
                if symbol:
                    return symbol
                else:
                    return None
            else:
                # there shouldn't be anything inside an operator
                assert not name.is_operator()
                identifier = name.identifier
                templateArgs = name.templateArgs
                if templateArgs and iTemplateDecl < len(templateDecls):
                    templateParams = templateDecls[iTemplateDecl]
                    iTemplateDecl += 1
                else:
                    templateParams = None
                symbol = parentSymbol._find_named_symbol(identifier,
                                                         templateParams,
                                                         templateArgs,
                                                         operator=None,
                                                         templateShorthand=templateShorthand,
                                                         matchSelf=matchSelf)
                if symbol is None:
                    # TODO: maybe search without template args
                    return None
            parentSymbol = symbol
        assert False  # should have returned in the loop

    def to_string(self, indent):
        res = ['\t'*indent]
        if not self.parent:
            res.append('::')
        else:
            if self.templateParams:
                res.append(text_type(self.templateParams))
                res.append('\n')
                res.append('\t'*indent)
            if self.identifier:
                res.append(text_type(self.identifier))
            else:
                res.append(text_type(self.declaration))
            if self.templateArgs:
                res.append(text_type(self.templateArgs))
            if self.declaration:
                res.append(": ")
                res.append(text_type(self.declaration))
        if self.docname:
            res.append('\t(')
            res.append(self.docname)
            res.append(')')
        res.append('\n')
        return ''.join(res)

    def dump(self, indent):
        res = [self.to_string(indent)]
        for c in self.children:
            res.append(c.dump(indent + 1))
        return ''.join(res)


class DefinitionParser(object):
    # those without signedness and size modifiers
    # see http://en.cppreference.com/w/cpp/language/types
    _simple_fundemental_types = (
        'void', 'bool', 'char', 'wchar_t', 'char16_t', 'char32_t', 'int',
        'float', 'double', 'auto'
    )

    _prefix_keys = ('class', 'struct', 'enum', 'union', 'typename')

    def __init__(self, definition, warnEnv):
        self.definition = definition.strip()
        self.pos = 0
        self.end = len(self.definition)
        self.last_match = None
        self._previous_state = (0, None)

        self.warnEnv = warnEnv

    def _make_multi_error(self, errors, header):
        if len(errors) == 1:
            return DefinitionError(header + '\n' + errors[0][0].description)
        result = [header, '\n']
        for e in errors:
            if len(e[1]) > 0:
                ident = '  '
                result.append(e[1])
                result.append(':\n')
                for line in e[0].description.split('\n'):
                    if len(line) == 0:
                        continue
                    result.append(ident)
                    result.append(line)
                    result.append('\n')
            else:
                result.append(e[0].description)
        return DefinitionError(''.join(result))

    def status(self, msg):
        # for debugging
        indicator = '-' * self.pos + '^'
        print("%s\n%s\n%s" % (msg, self.definition, indicator))

    def fail(self, msg):
        indicator = '-' * self.pos + '^'
        raise DefinitionError(
            'Invalid definition: %s [error at %d]\n  %s\n  %s' %
            (msg, self.pos, self.definition, indicator))

    def warn(self, msg):
        if self.warnEnv:
            self.warnEnv.warn(msg)
        else:
            print("Warning: %s" % msg)

    def match(self, regex):
        match = regex.match(self.definition, self.pos)
        if match is not None:
            self._previous_state = (self.pos, self.last_match)
            self.pos = match.end()
            self.last_match = match
            return True
        return False

    def backout(self):
        self.pos, self.last_match = self._previous_state

    def skip_string(self, string):
        strlen = len(string)
        if self.definition[self.pos:self.pos + strlen] == string:
            self.pos += strlen
            return True
        return False

    def skip_word(self, word):
        return self.match(re.compile(r'\b%s\b' % re.escape(word)))

    def skip_ws(self):
        return self.match(_whitespace_re)

    def skip_word_and_ws(self, word):
        if self.skip_word(word):
            self.skip_ws()
            return True
        return False

    @property
    def eof(self):
        return self.pos >= self.end

    @property
    def current_char(self):
        try:
            return self.definition[self.pos]
        except IndexError:
            return 'EOF'

    @property
    def matched_text(self):
        if self.last_match is not None:
            return self.last_match.group()

    def read_rest(self):
        rv = self.definition[self.pos:]
        self.pos = self.end
        return rv

    def assert_end(self):
        self.skip_ws()
        if not self.eof:
            self.fail('Expected end of definition.')

    def _parse_expression(self, end):
        # Stupidly "parse" an expression.
        # 'end' should be a list of characters which ends the expression.
        assert end
        self.skip_ws()
        startPos = self.pos
        if self.match(_string_re):
            value = self.matched_text
        else:
            # TODO: add handling of more bracket-like things, and quote handling
            brackets = {'(': ')', '[': ']'}
            symbols = []
            while not self.eof:
                if (len(symbols) == 0 and self.current_char in end):
                    break
                if self.current_char in brackets.keys():
                    symbols.append(brackets[self.current_char])
                elif len(symbols) > 0 and self.current_char == symbols[-1]:
                    symbols.pop()
                self.pos += 1
            if self.eof:
                self.fail("Could not find end of expression starting at %d."
                          % startPos)
            value = self.definition[startPos:self.pos].strip()
        return value.strip()

    def _parse_operator(self):
        self.skip_ws()
        # adapted from the old code
        # thank god, a regular operator definition
        if self.match(_operator_re):
            return ASTOperatorBuildIn(self.matched_text)

        # new/delete operator?
        for op in 'new', 'delete':
            if not self.skip_word(op):
                continue
            self.skip_ws()
            if self.skip_string('['):
                self.skip_ws()
                if not self.skip_string(']'):
                    self.fail('Expected "]" after  "operator ' + op + '["')
                op += '[]'
            return ASTOperatorBuildIn(op)

        # user-defined literal?
        if self.skip_string('""'):
            self.skip_ws()
            if not self.match(_identifier_re):
                self.fail("Expected user-defined literal suffix.")
            identifier = ASTIdentifier(self.matched_text)
            return ASTOperatorLiteral(identifier)

        # oh well, looks like a cast operator definition.
        # In that case, eat another type.
        type = self._parse_type(named=False, outer="operatorCast")
        return ASTOperatorType(type)

    def _parse_template_argument_list(self):
        self.skip_ws()
        if not self.skip_string('<'):
            return None
        prevErrors = []
        templateArgs = []
        while 1:
            pos = self.pos
            parsedComma = False
            parsedEnd = False
            try:
                type = self._parse_type(named=False)
                self.skip_ws()
                if self.skip_string('>'):
                    parsedEnd = True
                elif self.skip_string(','):
                    parsedComma = True
                else:
                    self.fail('Expected ">" or "," in template argument list.')
                templateArgs.append(type)
            except DefinitionError as e:
                prevErrors.append((e, "If type argument"))
                self.pos = pos
                try:
                    value = self._parse_expression(end=[',', '>'])
                    self.skip_ws()
                    if self.skip_string('>'):
                        parsedEnd = True
                    elif self.skip_string(','):
                        parsedComma = True
                    else:
                        self.fail('Expected ">" or "," in template argument list.')
                    templateArgs.append(ASTTemplateArgConstant(value))
                except DefinitionError as e:
                    self.pos = pos
                    prevErrors.append((e, "If non-type argument"))
                    header = "Error in parsing template argument list."
                    raise self._make_multi_error(prevErrors, header)
            if parsedEnd:
                assert not parsedComma
                break
        return ASTTemplateArgs(templateArgs)

    def _parse_nested_name(self, memberPointer=False):
        names = []

        self.skip_ws()
        rooted = False
        if self.skip_string('::'):
            rooted = True
        while 1:
            self.skip_ws()
            if self.skip_word_and_ws('template'):
                self.fail("'template' in nested name not implemented.")
            elif self.skip_word_and_ws('operator'):
                op = self._parse_operator()
                names.append(op)
            else:
                if not self.match(_identifier_re):
                    if memberPointer and len(names) > 0:
                        break
                    self.fail("Expected identifier in nested name.")
                identifier = self.matched_text
                # make sure there isn't a keyword
                if identifier in _keywords:
                    self.fail("Expected identifier in nested name, "
                              "got keyword: %s" % identifier)
                templateArgs = self._parse_template_argument_list()
                identifier = ASTIdentifier(identifier)
                names.append(ASTNestedNameElement(identifier, templateArgs))

            self.skip_ws()
            if not self.skip_string('::'):
                if memberPointer:
                    self.fail("Expected '::' in pointer to member (function).")
                break
        return ASTNestedName(names, rooted)

    def _parse_trailing_type_spec(self):
        # fundemental types
        self.skip_ws()
        for t in self._simple_fundemental_types:
            if self.skip_word(t):
                return ASTTrailingTypeSpecFundamental(t)

        # TODO: this could/should be more strict
        elements = []
        if self.skip_word_and_ws('signed'):
            elements.append('signed')
        elif self.skip_word_and_ws('unsigned'):
            elements.append('unsigned')
        while 1:
            if self.skip_word_and_ws('short'):
                elements.append('short')
            elif self.skip_word_and_ws('long'):
                elements.append('long')
            else:
                break
        if self.skip_word_and_ws('char'):
            elements.append('char')
        elif self.skip_word_and_ws('int'):
            elements.append('int')
        elif self.skip_word_and_ws('double'):
            elements.append('double')
        if len(elements) > 0:
            return ASTTrailingTypeSpecFundamental(u' '.join(elements))

        # decltype
        self.skip_ws()
        if self.skip_word_and_ws('decltype'):
            self.fail('"decltype(.)" in trailing_type_spec not implemented')

        # prefixed
        prefix = None
        self.skip_ws()
        for k in self._prefix_keys:
            if self.skip_word_and_ws(k):
                prefix = k
                break

        nestedName = self._parse_nested_name()
        return ASTTrailingTypeSpecName(prefix, nestedName)

    def _parse_parameters_and_qualifiers(self, paramMode):
        self.skip_ws()
        if not self.skip_string('('):
            if paramMode == 'function':
                self.fail('Expecting "(" in parameters_and_qualifiers.')
            else:
                return None
        args = []
        self.skip_ws()
        if not self.skip_string(')'):
            while 1:
                self.skip_ws()
                if self.skip_string('...'):
                    args.append(ASTFunctinoParameter(None, True))
                    self.skip_ws()
                    if not self.skip_string(')'):
                        self.fail('Expected ")" after "..." in '
                                  'parameters_and_qualifiers.')
                    break
                if paramMode == 'function':
                    arg = self._parse_type_with_init(outer=None, named='single')
                else:
                    arg = self._parse_type(named=False)
                # TODO: parse default parameters # TODO: didn't we just do that?
                args.append(ASTFunctinoParameter(arg))

                self.skip_ws()
                if self.skip_string(','):
                    continue
                elif self.skip_string(')'):
                    break
                else:
                    self.fail(
                        'Expecting "," or ")" in parameters_and_qualifiers, '
                        'got "%s".' % self.current_char)

        # TODO: why did we have this bail-out?
        # does it hurt to parse the extra stuff?
        # it's needed for pointer to member functions
        if paramMode != 'function' and False:
            return ASTParametersQualifiers(
                args, None, None, None, None, None, None, None)

        self.skip_ws()
        const = self.skip_word_and_ws('const')
        volatile = self.skip_word_and_ws('volatile')
        if not const:  # the can be permuted
            const = self.skip_word_and_ws('const')

        refQual = None
        if self.skip_string('&&'):
            refQual = '&&'
        if not refQual and self.skip_string('&'):
            refQual = '&'

        exceptionSpec = None
        override = None
        final = None
        initializer = None
        self.skip_ws()
        if self.skip_string('noexcept'):
            exceptionSpec = 'noexcept'
            self.skip_ws()
            if self.skip_string('('):
                self.fail('Parameterised "noexcept" not implemented.')

        self.skip_ws()
        override = self.skip_word_and_ws('override')
        final = self.skip_word_and_ws('final')
        if not override:
            override = self.skip_word_and_ws(
                'override')  # they can be permuted

        self.skip_ws()
        if self.skip_string('='):
            self.skip_ws()
            valid = ('0', 'delete', 'default')
            for w in valid:
                if self.skip_word_and_ws(w):
                    initializer = w
                    break
            if not initializer:
                self.fail(
                    'Expected "%s" in initializer-specifier.'
                    % u'" or "'.join(valid))

        return ASTParametersQualifiers(
            args, volatile, const, refQual, exceptionSpec, override, final,
            initializer)

    def _parse_decl_specs_simple(self, outer, typed):
        """Just parse the simple ones."""
        storage = None
        inline = None
        virtual = None
        explicit = None
        constexpr = None
        volatile = None
        const = None
        friend = None
        while 1:  # accept any permutation of a subset of some decl-specs
            self.skip_ws()
            if not storage:
                if outer in ('member', 'function'):
                    if self.skip_word('static'):
                        storage = 'static'
                        continue
                if outer == 'member':
                    if self.skip_word('mutable'):
                        storage = 'mutable'
                        continue
                if self.skip_word('register'):
                    storage = 'register'
                    continue

            if outer == 'function':
                # function-specifiers
                if not inline:
                    inline = self.skip_word('inline')
                    if inline:
                        continue
                if not friend:
                    friend = self.skip_word('friend')
                    if friend:
                        continue
                if not virtual:
                    virtual = self.skip_word('virtual')
                    if virtual:
                        continue
                if not explicit:
                    explicit = self.skip_word('explicit')
                    if explicit:
                        continue

            if not constexpr and outer in ('member', 'function'):
                constexpr = self.skip_word("constexpr")
                if constexpr:
                    continue
            if not volatile and typed:
                volatile = self.skip_word('volatile')
                if volatile:
                    continue
            if not const and typed:
                const = self.skip_word('const')
                if const:
                    continue
            break
        return ASTDeclSpecsSimple(storage, inline, virtual, explicit, constexpr,
                                  volatile, const, friend)

    def _parse_decl_specs(self, outer, typed=True):
        if outer:
            if outer not in ('type', 'member', 'function', 'templateParam'):
                raise Exception('Internal error, unknown outer "%s".' % outer)
        """
        storage-class-specifier function-specifier "constexpr"
        "volatile" "const" trailing-type-specifier

        storage-class-specifier ->
              "static" (only for member_object and function_object)
            | "register"

        function-specifier -> "inline" | "virtual" | "explicit" (only for
        function_object)

        "constexpr" (only for member_object and function_object)
        """
        leftSpecs = self._parse_decl_specs_simple(outer, typed)
        rightSpecs = None

        if typed:
            trailing = self._parse_trailing_type_spec()
            rightSpecs = self._parse_decl_specs_simple(outer, typed)
        else:
            trailing = None
        return ASTDeclSpecs(outer, leftSpecs, rightSpecs, trailing)

    def _parse_declarator_name_param_qual(self, named, paramMode, typed):
        # now we should parse the name, and then suffixes
        if named == 'maybe':
            pos = self.pos
            try:
                declId = self._parse_nested_name()
            except DefinitionError:
                self.pos = pos
                declId = None
        elif named == 'single':
            if self.match(_identifier_re):
                identifier = ASTIdentifier(self.matched_text)
                nne = ASTNestedNameElement(identifier, None)
                declId = ASTNestedName([nne], rooted=False)
                # if it's a member pointer, we may have '::', which should be an error
                self.skip_ws()
                if self.current_char == ':':
                    self.fail("Unexpected ':' after identifier.")
            else:
                declId = None
        elif named:
            declId = self._parse_nested_name()
        else:
            declId = None
        arrayOps = []
        while 1:
            self.skip_ws()
            if typed and self.skip_string('['):
                value = self._parse_expression(end=[']'])
                res = self.skip_string(']')
                assert res
                arrayOps.append(ASTArray(value))
                continue
            else:
                break
        paramQual = self._parse_parameters_and_qualifiers(paramMode)
        return ASTDecleratorNameParamQual(declId=declId, arrayOps=arrayOps,
                                          paramQual=paramQual)

    def _parse_declerator(self, named, paramMode, typed=True):
        # 'typed' here means 'parse return type stuff'
        if paramMode not in ('type', 'function', 'operatorCast'):
            raise Exception(
                "Internal error, unknown paramMode '%s'." % paramMode)
        prevErrors = []
        self.skip_ws()
        if typed and self.skip_string('*'):
            self.skip_ws()
            volatile = False
            const = False
            while 1:
                if not volatile:
                    volatile = self.skip_word_and_ws('volatile')
                    if volatile:
                        continue
                if not const:
                    const = self.skip_word_and_ws('const')
                    if const:
                        continue
                break
            next = self._parse_declerator(named, paramMode, typed)
            return ASTDeclaratorPtr(next=next, volatile=volatile, const=const)
        # TODO: shouldn't we parse an R-value ref here first?
        if typed and self.skip_string("&"):
            next = self._parse_declerator(named, paramMode, typed)
            return ASTDeclaratorRef(next=next)
        if typed and self.skip_string("..."):
            next = self._parse_declerator(named, paramMode, False)
            return ASTDeclaratorParamPack(next=next)
        if typed:  # pointer to member
            pos = self.pos
            try:
                name = self._parse_nested_name(memberPointer=True)
                self.skip_ws()
                if not self.skip_string('*'):
                    self.fail("Expected '*' in pointer to member declarator.")
                self.skip_ws()
            except DefinitionError as e:
                self.pos = pos
                prevErrors.append((e, "If pointer to member declarator"))
            else:
                volatile = False
                const = False
                while 1:
                    if not volatile:
                        volatile = self.skip_word_and_ws('volatile')
                        if volatile:
                            continue
                    if not const:
                        const = self.skip_word_and_ws('const')
                        if const:
                            continue
                    break
                next = self._parse_declerator(named, paramMode, typed)
                return ASTDeclaratorMemPtr(name, const, volatile, next=next)
        if typed and self.current_char == '(':  # note: peeking, not skipping
            if paramMode == "operatorCast":
                # TODO: we should be able to parse cast operators which return
                # function pointers. For now, just hax it and ignore.
                return ASTDecleratorNameParamQual(declId=None, arrayOps=[],
                                                  paramQual=None)
            # maybe this is the beginning of params and quals,try that first,
            # otherwise assume it's noptr->declarator > ( ptr-declarator )
            pos = self.pos
            try:
                # assume this is params and quals
                res = self._parse_declarator_name_param_qual(named, paramMode,
                                                             typed)
                return res
            except DefinitionError as exParamQual:
                prevErrors.append((exParamQual, "If declId, parameters, and qualifiers"))
                self.pos = pos
                try:
                    assert self.current_char == '('
                    self.skip_string('(')
                    # TODO: hmm, if there is a name, it must be in inner, right?
                    # TODO: hmm, if there must be parameters, they must b
                    # inside, right?
                    inner = self._parse_declerator(named, paramMode, typed)
                    if not self.skip_string(')'):
                        self.fail("Expected ')' in \"( ptr-declarator )\"")
                    next = self._parse_declerator(named=False,
                                                  paramMode="type",
                                                  typed=typed)
                    return ASTDeclaratorParen(inner=inner, next=next)
                except DefinitionError as exNoPtrParen:
                    self.pos = pos
                    prevErrors.append((exNoPtrParen, "If parenthesis in noptr-declarator"))
                    header = "Error in declarator"
                    raise self._make_multi_error(prevErrors, header)
        pos = self.pos
        try:
            return self._parse_declarator_name_param_qual(named, paramMode, typed)
        except DefinitionError as e:
            self.pos = pos
            prevErrors.append((e, "If declarator-id"))
            header = "Error in declarator or parameters and qualifiers"
            raise self._make_multi_error(prevErrors, header)

    def _parse_initializer(self, outer=None):
        self.skip_ws()
        # TODO: support paren and brace initialization for memberObject
        if not self.skip_string('='):
            return None
        else:
            if outer == 'member':
                value = self.read_rest().strip()
            elif outer == 'templateParam':
                value = self._parse_expression(end=[',', '>'])
            elif outer is None:  # function parameter
                value = self._parse_expression(end=[',', ')'])
            else:
                self.fail("Internal error, initializer for outer '%s' not "
                          "implemented." % outer)
            return ASTInitializer(value)

    def _parse_type(self, named, outer=None):
        """
        named=False|'maybe'|True: 'maybe' is e.g., for function objects which
        doesn't need to name the arguments

        outer == operatorCast: annoying case, we should not take the params
        """
        if outer:  # always named
            if outer not in ('type', 'member', 'function',
                             'operatorCast', 'templateParam'):
                raise Exception('Internal error, unknown outer "%s".' % outer)
            if outer != 'operatorCast':
                assert named

        if outer in ('type', 'function'):
            # We allow type objects to just be a name.
            # Some functions don't have normal return types: constructors,
            # destrutors, cast operators
            prevErrors = []
            startPos = self.pos
            # first try without the type
            try:
                declSpecs = self._parse_decl_specs(outer=outer, typed=False)
                decl = self._parse_declerator(named=True, paramMode=outer,
                                              typed=False)
                self.assert_end()
            except DefinitionError as exUntyped:
                if outer == 'type':
                    desc = "If just a name"
                elif outer == 'function':
                    desc = "If the function has no return type"
                else:
                    assert False
                prevErrors.append((exUntyped, desc))
                self.pos = startPos
                try:
                    declSpecs = self._parse_decl_specs(outer=outer)
                    decl = self._parse_declerator(named=True, paramMode=outer)
                except DefinitionError as exTyped:
                    self.pos = startPos
                    if outer == 'type':
                        desc = "If typedef-like declaration"
                    elif outer == 'function':
                        desc = "If the function has a return type"
                    else:
                        assert False
                    prevErrors.append((exTyped, desc))
                    # Retain the else branch for easier debugging.
                    # TODO: it would be nice to save the previous stacktrace
                    #       and output it here.
                    if True:
                        if outer == 'type':
                            header = "Type must be either just a name or a "
                            header += "typedef-like declaration."
                        elif outer == 'function':
                            header = "Error when parsing function declaration."
                        else:
                            assert False
                        raise self._make_multi_error(prevErrors, header)
                    else:
                        # For testing purposes.
                        # do it again to get the proper traceback (how do you
                        # relieable save a traceback when an exception is
                        # constructed?)
                        pass
                        self.pos = startPos
                        typed = True
                        declSpecs = self._parse_decl_specs(outer=outer, typed=typed)
                        decl = self._parse_declerator(named=True, paramMode=outer,
                                                      typed=typed)
        else:
            paramMode = 'type'
            if outer == 'member':  # i.e., member
                named = True
            elif outer == 'operatorCast':
                paramMode = 'operatorCast'
                outer = None
            elif outer == 'templateParam':
                named = 'single'
            declSpecs = self._parse_decl_specs(outer=outer)
            decl = self._parse_declerator(named=named, paramMode=paramMode)
        return ASTType(declSpecs, decl)

    def _parse_type_with_init(self, named, outer):
        if outer:
            assert outer in ('type', 'member', 'function', 'templateParam')
        type = self._parse_type(outer=outer, named=named)
        init = self._parse_initializer(outer=outer)
        return ASTTypeWithInit(type, init)

    def _parse_type_using(self):
        name = self._parse_nested_name()
        self.skip_ws()
        if not self.skip_string('='):
            return ASTTypeUsing(name, None)
        type = self._parse_type(False, None)
        return ASTTypeUsing(name, type)

    def _parse_class(self):
        name = self._parse_nested_name()
        self.skip_ws()
        final = self.skip_word_and_ws('final')
        bases = []
        self.skip_ws()
        if self.skip_string(':'):
            while 1:
                self.skip_ws()
                visibility = 'private'
                virtual = False
                pack = False
                if self.skip_word_and_ws('virtual'):
                    virtual = True
                if self.match(_visibility_re):
                    visibility = self.matched_text
                    self.skip_ws()
                if not virtual and self.skip_word_and_ws('virtual'):
                    virtual = True
                baseName = self._parse_nested_name()
                self.skip_ws()
                pack = self.skip_string('...')
                bases.append(ASTBaseClass(baseName, visibility, virtual, pack))
                self.skip_ws()
                if self.skip_string(','):
                    continue
                else:
                    break
        return ASTClass(name, final, bases)

    def _parse_enum(self):
        scoped = None  # is set by CPPEnumObject
        self.skip_ws()
        name = self._parse_nested_name()
        self.skip_ws()
        underlyingType = None
        if self.skip_string(':'):
            underlyingType = self._parse_type(named=False)
        return ASTEnum(name, scoped, underlyingType)

    def _parse_enumerator(self):
        name = self._parse_nested_name()
        self.skip_ws()
        init = None
        if self.skip_string('='):
            self.skip_ws()
            init = ASTInitializer(self.read_rest())
        return ASTEnumerator(name, init)

    def _parse_template_parameter_list(self):
        # only: '<' parameter-list '>'
        # we assume that 'template' has just been parsed
        templateParams = []
        self.skip_ws()
        if not self.skip_string("<"):
            self.fail("Expected '<' after 'template'")
        while 1:
            prevErrors = []
            self.skip_ws()
            if self.skip_word('template'):
                # declare a tenplate template parameter
                nestedParams = self._parse_template_parameter_list()
            else:
                nestedParams = None
            self.skip_ws()
            key = None
            if self.skip_word_and_ws('typename'):
                key = 'typename'
            elif self.skip_word_and_ws('class'):
                key = 'class'
            elif nestedParams:
                self.fail("Expected 'typename' or 'class' after "
                          "template template parameter list.")
            if key:
                # declare a type or template type parameter
                self.skip_ws()
                parameterPack = self.skip_string('...')
                self.skip_ws()
                if self.match(_identifier_re):
                    identifier = ASTIdentifier(self.matched_text)
                else:
                    identifier = None
                self.skip_ws()
                if not parameterPack and self.skip_string('='):
                    default = self._parse_type(named=False, outer=None)
                else:
                    default = None
                data = ASTTemplateKeyParamPackIdDefault(key, identifier,
                                                        parameterPack, default)
                if nestedParams:
                    # template type
                    param = ASTTemplateParamTemplateType(nestedParams, data)
                else:
                    # type
                    param = ASTTemplateParamType(data)
                templateParams.append(param)
            else:
                # declare a non-type parameter
                pos = self.pos
                try:
                    param = self._parse_type_with_init('maybe', 'templateParam')
                    templateParams.append(ASTTemplateParamNonType(param))
                except DefinitionError as e:
                    prevErrors.append((e, "If non-type template parameter"))
                    self.pos = pos
            self.skip_ws()
            if self.skip_string('>'):
                return ASTTemplateParams(templateParams)
            elif self.skip_string(','):
                continue
            else:
                header = "Error in template parameter list."
                try:
                    self.fail('Expected "=", ",", or ">".')
                except DefinitionError as e:
                    prevErrors.append((e, ""))
                raise self._make_multi_error(prevErrors, header)

    def _parse_template_declaration_prefix(self):
        templates = []
        while 1:
            self.skip_ws()
            if not self.skip_word("template"):
                break
            params = self._parse_template_parameter_list()
            templates.append(params)
        if len(templates) == 0:
            return None
        else:
            return ASTTemplateDeclarationPrefix(templates)

    def _check_template_consistency(self, nestedName, templatePrefix,
                                    fullSpecShorthand):
        numArgs = nestedName.num_templates()
        if not templatePrefix:
            numParams = 0
        else:
            numParams = len(templatePrefix.templates)
        if numArgs + 1 < numParams:
            self.fail("Too few template argument lists comapred to parameter"
                      " lists. Argument lists: %d, Parameter lists: %d."
                      % (numArgs, numParams))
        if numArgs > numParams:
            numExtra = numArgs - numParams
            if not fullSpecShorthand:
                msg = "Too many template argument lists compared to parameter" \
                    " lists. Argument lists: %d, Parameter lists: %d," \
                    " Extra empty parameters lists prepended: %d." \
                    % (numArgs, numParams, numExtra)
                msg += " Declaration:\n\t"
                if templatePrefix:
                    msg += "%s\n\t" % text_type(templatePrefix)
                msg += text_type(nestedName)
                self.warn(msg)

            newTemplates = []
            for i in range(numExtra):
                newTemplates.append(ASTTemplateParams([]))
            if templatePrefix:
                newTemplates.extend(templatePrefix.templates)
            templatePrefix = ASTTemplateDeclarationPrefix(newTemplates)
        return templatePrefix

    def parse_declaration(self, objectType):
        if objectType not in ('type', 'member',
                              'function', 'class', 'enum', 'enumerator'):
            raise Exception('Internal error, unknown objectType "%s".' % objectType)
        visibility = None
        templatePrefix = None
        declaration = None

        self.skip_ws()
        if self.match(_visibility_re):
            visibility = self.matched_text

        if objectType in ('type', 'member', 'function', 'class'):
            templatePrefix = self._parse_template_declaration_prefix()

        if objectType == 'type':
            prevErrors = []
            pos = self.pos
            try:
                if not templatePrefix:
                    declaration = self._parse_type(named=True, outer='type')
            except DefinitionError as e:
                prevErrors.append((e, "If typedef-like declaration"))
                self.pos = pos
            pos = self.pos
            try:
                if not declaration:
                    declaration = self._parse_type_using()
            except DefinitionError as e:
                self.pos = pos
                prevErrors.append((e, "If type alias or template alias"))
                header = "Error in type declaration."
                raise self._make_multi_error(prevErrors, header)
        elif objectType == 'member':
            declaration = self._parse_type_with_init(named=True, outer='member')
        elif objectType == 'function':
            declaration = self._parse_type(named=True, outer='function')
        elif objectType == 'class':
            declaration = self._parse_class()
        elif objectType == 'enum':
            declaration = self._parse_enum()
        elif objectType == 'enumerator':
            declaration = self._parse_enumerator()
        else:
            assert False
        templatePrefix = self._check_template_consistency(declaration.name,
                                                          templatePrefix,
                                                          fullSpecShorthand=False)
        return ASTDeclaration(objectType, visibility,
                              templatePrefix, declaration)

    def parse_namespace_object(self):
        templatePrefix = self._parse_template_declaration_prefix()
        name = self._parse_nested_name()
        templatePrefix = self._check_template_consistency(name, templatePrefix,
                                                          fullSpecShorthand=False)
        res = ASTNamespace(name, templatePrefix)
        res.objectType = 'namespace'
        return res

    def parse_xref_object(self):
        templatePrefix = self._parse_template_declaration_prefix()
        name = self._parse_nested_name()
        templatePrefix = self._check_template_consistency(name, templatePrefix,
                                                          fullSpecShorthand=True)
        res = ASTNamespace(name, templatePrefix)
        res.objectType = 'xref'
        return res


def _make_phony_error_name():
    nne = ASTNestedNameElement(ASTIdentifier("PhonyNameDueToError"), None)
    return ASTNestedName([nne], rooted=False)


class CPPObject(ObjectDescription):
    """Description of a C++ language object."""

    doc_field_types = [
        GroupedField('parameter', label=l_('Parameters'),
                     names=('param', 'parameter', 'arg', 'argument'),
                     can_collapse=True),
        GroupedField('template parameter', label=l_('Template Parameters'),
                     names=('tparam', 'template parameter'),
                     can_collapse=True),
        GroupedField('exceptions', label=l_('Throws'), rolename='cpp:class',
                     names=('throws', 'throw', 'exception'),
                     can_collapse=True),
        Field('returnvalue', label=l_('Returns'), has_arg=False,
              names=('returns', 'return')),
    ]

    def warn(self, msg):
        self.state_machine.reporter.warning(msg, line=self.lineno)

    def _add_enumerator_to_parent(self, ast):
        assert ast.objectType == 'enumerator'
        # find the parent, if it exists && is an enum
        #                     && it's unscoped,
        #                  then add the name to the parent scope
        symbol = ast.symbol
        assert symbol
        assert symbol.identifier is not None
        assert symbol.templateParams is None
        assert symbol.templateArgs is None
        parentSymbol = symbol.parent
        assert parentSymbol
        if parentSymbol.parent is None:
            # TODO: we could warn, but it is somewhat equivalent to unscoped
            # enums, without the enum
            return  # no parent
        parentDecl = parentSymbol.declaration
        if parentDecl is None:
            # the parent is not explicitly declared
            # TODO: we could warn, but it could be a style to just assume
            # enumerator parnets to be scoped
            return
        if parentDecl.objectType != 'enum':
            # TODO: maybe issue a warning, enumerators in non-enums is weird,
            # but it is somewhat equivalent to unscoped enums, without the enum
            return
        if parentDecl.scoped:
            return

        targetSymbol = parentSymbol.parent
        s = targetSymbol.find_identifier(symbol.identifier, matchSelf=False)
        if s is not None:
            # something is already declared with that name
            return
        declClone = symbol.declaration.clone()
        declClone.enumeratorScopedSymbol = symbol
        Symbol(parent=targetSymbol, identifier=symbol.identifier,
               templateParams=None, templateArgs=None,
               declaration=declClone,
               docname=self.env.docname)

    def add_target_and_index(self, ast, sig, signode):
        # general note: name must be lstrip(':')'ed, to remove "::"
        try:
            id_v1 = ast.get_id_v1()
        except NoOldIdError:
            id_v1 = None
        id_v2 = ast.get_id_v2()
        # store them in reverse order, so the newest is first
        ids  = [id_v2, id_v1]

        newestId = ids[0]
        assert newestId  # shouldn't be None
        if not re.compile(r'^[a-zA-Z0-9_]*$').match(newestId):
            self.warn('Index id generation for C++ object "%s" failed, please '
                      'report as bug (id=%s).' % (text_type(ast), newestId))

        name = text_type(ast.symbol.get_full_nested_name()).lstrip(':')
        indexText = self.get_index_text(name)
        self.indexnode['entries'].append(('single', indexText, newestId, '', None))

        if newestId not in self.state.document.ids:
            # if the name is not unique, the first one will win
            names = self.env.domaindata['cpp']['names']
            if name not in names:
                names[name] = ast.symbol.docname
                signode['names'].append(name)
            else:
                # print("[CPP] non-unique name:", name)
                pass
            for id in ids:
                if id:  # is None when the element didn't exist in that version
                    signode['ids'].append(id)
            signode['first'] = (not self.names)  # hmm, what is this abound?
            self.state.document.note_explicit_target(signode)

    def parse_definition(self, parser):
        raise NotImplementedError()

    def describe_signature(self, signode, ast, parentScope):
        raise NotImplementedError()

    def handle_signature(self, sig, signode):
        if 'cpp:parentSymbol' not in self.env.ref_context:
            root = self.env.domaindata['cpp']['rootSymbol']
            self.env.ref_context['cpp:parentSymbol'] = root
        parentSymbol = self.env.ref_context['cpp:parentSymbol']

        parser = DefinitionParser(sig, self)
        try:
            ast = self.parse_definition(parser)
            parser.assert_end()
        except DefinitionError as e:
            self.warn(e.description)
            # It is easier to assume some phony name than handling the error in
            # the possibly inner declarations.
            name = _make_phony_error_name()
            symbol = parentSymbol.add_name(name)
            self.env.ref_context['cpp:lastSymbol'] = symbol
            raise ValueError
        symbol = parentSymbol.add_declaration(ast, docname=self.env.docname)
        self.env.ref_context['cpp:lastSymbol'] = symbol

        if ast.objectType == 'enumerator':
            self._add_enumerator_to_parent(ast)

        self.describe_signature(signode, ast)
        return ast


class CPPTypeObject(CPPObject):
    def get_index_text(self, name):
        return _('%s (C++ type)') % name

    def parse_definition(self, parser):
        return parser.parse_declaration("type")

    def describe_signature(self, signode, ast):
        ast.describe_signature(signode, 'lastIsName', self.env)


class CPPMemberObject(CPPObject):
    def get_index_text(self, name):
        return _('%s (C++ member)') % name

    def parse_definition(self, parser):
        return parser.parse_declaration("member")

    def describe_signature(self, signode, ast):
        ast.describe_signature(signode, 'lastIsName', self.env)


class CPPFunctionObject(CPPObject):
    def get_index_text(self, name):
        return _('%s (C++ function)') % name

    def parse_definition(self, parser):
        return parser.parse_declaration("function")

    def describe_signature(self, signode, ast):
        ast.describe_signature(signode, 'lastIsName', self.env)


class CPPClassObject(CPPObject):
    def get_index_text(self, name):
        return _('%s (C++ class)') % name

    def before_content(self):
        lastSymbol = self.env.ref_context['cpp:lastSymbol']
        assert lastSymbol
        self.oldParentSymbol = self.env.ref_context['cpp:parentSymbol']
        self.env.ref_context['cpp:parentSymbol'] = lastSymbol

    def after_content(self):
        self.env.ref_context['cpp:parentSymbol'] = self.oldParentSymbol

    def parse_definition(self, parser):
        return parser.parse_declaration("class")

    def describe_signature(self, signode, ast):
        ast.describe_signature(signode, 'lastIsName', self.env)


class CPPEnumObject(CPPObject):
    def get_index_text(self, name):
        return _('%s (C++ enum)') % name

    def before_content(self):
        lastSymbol = self.env.ref_context['cpp:lastSymbol']
        assert lastSymbol
        self.oldParentSymbol = self.env.ref_context['cpp:parentSymbol']
        self.env.ref_context['cpp:parentSymbol'] = lastSymbol

    def after_content(self):
        self.env.ref_context['cpp:parentSymbol'] = self.oldParentSymbol

    def parse_definition(self, parser):
        ast = parser.parse_declaration("enum")
        # self.objtype is set by ObjectDescription in run()
        if self.objtype == "enum":
            ast.scoped = None
        elif self.objtype == "enum-struct":
            ast.scoped = "struct"
        elif self.objtype == "enum-class":
            ast.scoped = "class"
        else:
            assert False
        return ast

    def describe_signature(self, signode, ast):
        ast.describe_signature(signode, 'lastIsName', self.env)


class CPPEnumeratorObject(CPPObject):
    def get_index_text(self, name):
        return _('%s (C++ enumerator)') % name

    def parse_definition(self, parser):
        return parser.parse_declaration("enumerator")

    def describe_signature(self, signode, ast):
        ast.describe_signature(signode, 'lastIsName', self.env)


class CPPNamespaceObject(Directive):
    """
    This directive is just to tell Sphinx that we're documenting stuff in
    namespace foo.
    """

    has_content = False
    required_arguments = 1
    optional_arguments = 0
    final_argument_whitespace = True
    option_spec = {}

    def warn(self, msg):
        self.state_machine.reporter.warning(msg, line=self.lineno)

    def run(self):
        env = self.state.document.settings.env
        rootSymbol = env.domaindata['cpp']['rootSymbol']
        if self.arguments[0].strip() in ('NULL', '0', 'nullptr'):
            symbol = rootSymbol
            stack = []
        else:
            parser = DefinitionParser(self.arguments[0], self)
            try:
                ast = parser.parse_namespace_object()
                parser.assert_end()
            except DefinitionError as e:
                self.warn(e.description)
                name = _make_phony_error_name()
                ast = ASTNamespace(name, None)
            symbol = rootSymbol.add_name(ast.nestedName, ast.templatePrefix)
            stack = [symbol]
        env.ref_context['cpp:parentSymbol'] = symbol
        env.temp_data['cpp:namespaceStack'] = stack
        return []


class CPPNamespacePushObject(Directive):
    has_content = False
    required_arguments = 1
    optional_arguments = 0
    final_argument_whitespace = True
    option_spec = {}

    def warn(self, msg):
        self.state_machine.reporter.warning(msg, line=self.lineno)

    def run(self):
        env = self.state.document.settings.env
        if self.arguments[0].strip() in ('NULL', '0', 'nullptr'):
            return
        parser = DefinitionParser(self.arguments[0], self)
        try:
            ast = parser.parse_namespace_object()
            parser.assert_end()
        except DefinitionError as e:
            self.warn(e.description)
            name = _make_phony_error_name()
            ast = ASTNamespace(name, None)
        oldParent = env.ref_context.get('cpp:parentSymbol', None)
        if not oldParent:
            oldParent = env.domaindata['cpp']['rootSymbol']
        symbol = oldParent.add_name(ast.nestedName, ast.templatePrefix)
        stack = env.temp_data.get('cpp:namespaceStack', [])
        stack.append(symbol)
        env.ref_context['cpp:parentSymbol'] = symbol
        env.temp_data['cpp:namespaceStack'] = stack
        return []


class CPPNamespacePopObject(Directive):
    has_content = False
    required_arguments = 0
    optional_arguments = 0
    final_argument_whitespace = True
    option_spec = {}

    def warn(self, msg):
        self.state_machine.reporter.warning(msg, line=self.lineno)

    def run(self):
        env = self.state.document.settings.env
        stack = env.temp_data.get('cpp:namespaceStack', None)
        if not stack or len(stack) == 0:
            self.warn("C++ namespace pop on empty stack. Defaulting to gobal scope.")
            stack = []
        else:
            stack.pop()
        if len(stack) > 0:
            symbol = stack[-1]
        else:
            symbol = env.domaindata['cpp']['rootSymbol']
        env.ref_context['cpp:parentSymbol'] = symbol
        env.temp_data['cpp:namespaceStack'] = stack
        return []


class CPPXRefRole(XRefRole):
    def process_link(self, env, refnode, has_explicit_title, title, target):
        parent = env.ref_context.get('cpp:parentSymbol', None)
        if parent:
            refnode['cpp:parentKey'] = parent.get_lookup_key()
        if refnode['reftype'] == 'any':
            # Assume the removal part of fix_parens for :any: refs.
            # The addition part is done with the reference is resolved.
            if not has_explicit_title and title.endswith('()'):
                title = title[:-2]
            if target.endswith('()'):
                target = target[:-2]
        # TODO: should this really be here?
        if not has_explicit_title:
            target = target.lstrip('~')  # only has a meaning for the title
            # if the first character is a tilde, don't display the module/class
            # parts of the contents
            if title[:1] == '~':
                title = title[1:]
                dcolon = title.rfind('::')
                if dcolon != -1:
                    title = title[dcolon + 2:]
        return title, target


class CPPDomain(Domain):
    """C++ language domain."""
    name = 'cpp'
    label = 'C++'
    object_types = {
        'class': ObjType(l_('class'), 'class'),
        'function': ObjType(l_('function'), 'func'),
        'member': ObjType(l_('member'), 'member'),
        'type': ObjType(l_('type'), 'type'),
        'enum': ObjType(l_('enum'), 'enum'),
        'enumerator': ObjType(l_('enumerator'), 'enumerator')
    }

    directives = {
        'class': CPPClassObject,
        'function': CPPFunctionObject,
        'member': CPPMemberObject,
        'var': CPPMemberObject,
        'type': CPPTypeObject,
        'enum': CPPEnumObject,
        'enum-struct': CPPEnumObject,
        'enum-class': CPPEnumObject,
        'enumerator': CPPEnumeratorObject,
        'namespace': CPPNamespaceObject,
        'namespace-push': CPPNamespacePushObject,
        'namespace-pop': CPPNamespacePopObject
    }
    roles = {
        'any': CPPXRefRole(),
        'class': CPPXRefRole(),
        'func': CPPXRefRole(fix_parens=True),
        'member': CPPXRefRole(),
        'var': CPPXRefRole(),
        'type': CPPXRefRole(),
        'enum': CPPXRefRole(),
        'enumerator': CPPXRefRole()
    }
    initial_data = {
        'rootSymbol': Symbol(None, None, None, None, None, None),
        'names': {}  # full name for indexing -> docname
    }

    def clear_doc(self, docname):
        rootSymbol = self.data['rootSymbol']
        rootSymbol.clear_doc(docname)
        for name, nDocname in list(self.data['names'].items()):
            if nDocname == docname:
                del self.data['names'][name]

    def process_doc(self, env, docname, document):
        # just for debugging
        # print(docname)
        # print(self.data['rootSymbol'].dump(0))
        pass

    def merge_domaindata(self, docnames, otherdata):
        self.data['rootSymbol'].merge_with(otherdata['rootSymbol'],
                                           docnames, self.env)
        ourNames = self.data['names']
        for name, docname in otherdata['names'].items():
            if docname in docnames:
                if name in ourNames:
                    msg = "Duplicate declaration, also defined in '%s'.\n"
                    msg += "Name of declaration is '%s'."
                    msg = msg % (ourNames[name], name)
                    self.env.warn(docname, msg)
                else:
                    ourNames[name] = docname

    def _resolve_xref_inner(self, env, fromdocname, builder, typ,
                            target, node, contnode, emitWarnings=True):
        class Warner(object):
            def warn(self, msg):
                if emitWarnings:
                    env.warn_node(msg, node)
        warner = Warner()
        parser = DefinitionParser(target, warner)
        try:
            ast = parser.parse_xref_object()
            parser.skip_ws()
            parser.assert_end()
        except DefinitionError as e:
            warner.warn('Unparseable C++ cross-reference: %r\n%s'
                        % (target, str(e.description)))
            return None, None
        parentKey = node.get("cpp:parentKey", None)
        rootSymbol = self.data['rootSymbol']
        if parentKey:
            parentSymbol = rootSymbol.direct_lookup(parentKey)
            if not parentSymbol:
                print("Target: ", target)
                print("ParentKey: ", parentKey)
            assert parentSymbol  # should be there
        else:
            parentSymbol = rootSymbol

        name = ast.nestedName
        if ast.templatePrefix:
            templateDecls = ast.templatePrefix.templates
        else:
            templateDecls = []
        s = parentSymbol.find_name(name, templateDecls,
                                   templateShorthand=True,
                                   matchSelf=True)
        if s is None or s.declaration is None:
            return None, None
        declaration = s.declaration
        fullNestedName = s.get_full_nested_name()
        name = text_type(fullNestedName).lstrip(':')
        docname = s.docname
        assert docname
        if typ == 'any' and declaration.objectType == 'function':
            if env.config.add_function_parentheses:
                if not node['refexplicit']:
                    title = contnode.pop(0).astext()
                    contnode += nodes.Text(title + '()')
        return make_refnode(builder, fromdocname, docname,
                            declaration.get_newest_id(), contnode, name
                            ), declaration.objectType

    def resolve_xref(self, env, fromdocname, builder,
                     typ, target, node, contnode):
        return self._resolve_xref_inner(env, fromdocname, builder, typ,
                                        target, node, contnode)[0]

    def resolve_any_xref(self, env, fromdocname, builder, target,
                         node, contnode):
        node, objtype = self._resolve_xref_inner(env, fromdocname, builder,
                                                 'any', target, node, contnode,
                                                 emitWarnings=False)
        if node:
            return [('cpp:' + self.role_for_objtype(objtype), node)]
        return []

    def get_objects(self):
        rootSymbol = self.data['rootSymbol']
        for symbol in rootSymbol.get_all_symbols():
            if symbol.declaration is None:
                continue
            assert symbol.docname
            name = text_type(symbol.get_full_nested_name()).lstrip(':')
            objectType = symbol.declaration.objectType
            docname = symbol.docname
            newestId = symbol.declaration.get_newest_id()
            yield (name, name, objectType, docname, newestId, 1)
