162306a36Sopenharmony_ci# -*- coding: utf-8; mode: python -*-
262306a36Sopenharmony_ci# pylint: disable=W0141,C0113,C0103,C0325
362306a36Sopenharmony_ciu"""
462306a36Sopenharmony_ci    cdomain
562306a36Sopenharmony_ci    ~~~~~~~
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci    Replacement for the sphinx c-domain.
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci    :copyright:  Copyright (C) 2016  Markus Heiser
1062306a36Sopenharmony_ci    :license:    GPL Version 2, June 1991 see Linux/COPYING for details.
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci    List of customizations:
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci    * Moved the *duplicate C object description* warnings for function
1562306a36Sopenharmony_ci      declarations in the nitpicky mode. See Sphinx documentation for
1662306a36Sopenharmony_ci      the config values for ``nitpick`` and ``nitpick_ignore``.
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci    * Add option 'name' to the "c:function:" directive.  With option 'name' the
1962306a36Sopenharmony_ci      ref-name of a function can be modified. E.g.::
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci          .. c:function:: int ioctl( int fd, int request )
2262306a36Sopenharmony_ci             :name: VIDIOC_LOG_STATUS
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci      The func-name (e.g. ioctl) remains in the output but the ref-name changed
2562306a36Sopenharmony_ci      from 'ioctl' to 'VIDIOC_LOG_STATUS'. The function is referenced by::
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci          * :c:func:`VIDIOC_LOG_STATUS` or
2862306a36Sopenharmony_ci          * :any:`VIDIOC_LOG_STATUS` (``:any:`` needs sphinx 1.3)
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci     * Handle signatures of function-like macros well. Don't try to deduce
3162306a36Sopenharmony_ci       arguments types of function-like macros.
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci"""
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cifrom docutils import nodes
3662306a36Sopenharmony_cifrom docutils.parsers.rst import directives
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ciimport sphinx
3962306a36Sopenharmony_cifrom sphinx import addnodes
4062306a36Sopenharmony_cifrom sphinx.domains.c import c_funcptr_sig_re, c_sig_re
4162306a36Sopenharmony_cifrom sphinx.domains.c import CObject as Base_CObject
4262306a36Sopenharmony_cifrom sphinx.domains.c import CDomain as Base_CDomain
4362306a36Sopenharmony_cifrom itertools import chain
4462306a36Sopenharmony_ciimport re
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci__version__  = '1.1'
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci# Get Sphinx version
4962306a36Sopenharmony_cimajor, minor, patch = sphinx.version_info[:3]
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci# Namespace to be prepended to the full name
5262306a36Sopenharmony_cinamespace = None
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci#
5562306a36Sopenharmony_ci# Handle trivial newer c domain tags that are part of Sphinx 3.1 c domain tags
5662306a36Sopenharmony_ci# - Store the namespace if ".. c:namespace::" tag is found
5762306a36Sopenharmony_ci#
5862306a36Sopenharmony_ciRE_namespace = re.compile(r'^\s*..\s*c:namespace::\s*(\S+)\s*$')
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_cidef markup_namespace(match):
6162306a36Sopenharmony_ci    global namespace
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci    namespace = match.group(1)
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci    return ""
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci#
6862306a36Sopenharmony_ci# Handle c:macro for function-style declaration
6962306a36Sopenharmony_ci#
7062306a36Sopenharmony_ciRE_macro = re.compile(r'^\s*..\s*c:macro::\s*(\S+)\s+(\S.*)\s*$')
7162306a36Sopenharmony_cidef markup_macro(match):
7262306a36Sopenharmony_ci    return ".. c:function:: " + match.group(1) + ' ' + match.group(2)
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci#
7562306a36Sopenharmony_ci# Handle newer c domain tags that are evaluated as .. c:type: for
7662306a36Sopenharmony_ci# backward-compatibility with Sphinx < 3.0
7762306a36Sopenharmony_ci#
7862306a36Sopenharmony_ciRE_ctype = re.compile(r'^\s*..\s*c:(struct|union|enum|enumerator|alias)::\s*(.*)$')
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_cidef markup_ctype(match):
8162306a36Sopenharmony_ci    return ".. c:type:: " + match.group(2)
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci#
8462306a36Sopenharmony_ci# Handle newer c domain tags that are evaluated as :c:type: for
8562306a36Sopenharmony_ci# backward-compatibility with Sphinx < 3.0
8662306a36Sopenharmony_ci#
8762306a36Sopenharmony_ciRE_ctype_refs = re.compile(r':c:(var|struct|union|enum|enumerator)::`([^\`]+)`')
8862306a36Sopenharmony_cidef markup_ctype_refs(match):
8962306a36Sopenharmony_ci    return ":c:type:`" + match.group(2) + '`'
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci#
9262306a36Sopenharmony_ci# Simply convert :c:expr: and :c:texpr: into a literal block.
9362306a36Sopenharmony_ci#
9462306a36Sopenharmony_ciRE_expr = re.compile(r':c:(expr|texpr):`([^\`]+)`')
9562306a36Sopenharmony_cidef markup_c_expr(match):
9662306a36Sopenharmony_ci    return '\\ ``' + match.group(2) + '``\\ '
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci#
9962306a36Sopenharmony_ci# Parse Sphinx 3.x C markups, replacing them by backward-compatible ones
10062306a36Sopenharmony_ci#
10162306a36Sopenharmony_cidef c_markups(app, docname, source):
10262306a36Sopenharmony_ci    result = ""
10362306a36Sopenharmony_ci    markup_func = {
10462306a36Sopenharmony_ci        RE_namespace: markup_namespace,
10562306a36Sopenharmony_ci        RE_expr: markup_c_expr,
10662306a36Sopenharmony_ci        RE_macro: markup_macro,
10762306a36Sopenharmony_ci        RE_ctype: markup_ctype,
10862306a36Sopenharmony_ci        RE_ctype_refs: markup_ctype_refs,
10962306a36Sopenharmony_ci    }
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci    lines = iter(source[0].splitlines(True))
11262306a36Sopenharmony_ci    for n in lines:
11362306a36Sopenharmony_ci        match_iterators = [regex.finditer(n) for regex in markup_func]
11462306a36Sopenharmony_ci        matches = sorted(chain(*match_iterators), key=lambda m: m.start())
11562306a36Sopenharmony_ci        for m in matches:
11662306a36Sopenharmony_ci            n = n[:m.start()] + markup_func[m.re](m) + n[m.end():]
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci        result = result + n
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci    source[0] = result
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci#
12362306a36Sopenharmony_ci# Now implements support for the cdomain namespacing logic
12462306a36Sopenharmony_ci#
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_cidef setup(app):
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci    # Handle easy Sphinx 3.1+ simple new tags: :c:expr and .. c:namespace::
12962306a36Sopenharmony_ci    app.connect('source-read', c_markups)
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci    if (major == 1 and minor < 8):
13262306a36Sopenharmony_ci        app.override_domain(CDomain)
13362306a36Sopenharmony_ci    else:
13462306a36Sopenharmony_ci        app.add_domain(CDomain, override=True)
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci    return dict(
13762306a36Sopenharmony_ci        version = __version__,
13862306a36Sopenharmony_ci        parallel_read_safe = True,
13962306a36Sopenharmony_ci        parallel_write_safe = True
14062306a36Sopenharmony_ci    )
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ciclass CObject(Base_CObject):
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci    """
14562306a36Sopenharmony_ci    Description of a C language object.
14662306a36Sopenharmony_ci    """
14762306a36Sopenharmony_ci    option_spec = {
14862306a36Sopenharmony_ci        "name" : directives.unchanged
14962306a36Sopenharmony_ci    }
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci    def handle_func_like_macro(self, sig, signode):
15262306a36Sopenharmony_ci        u"""Handles signatures of function-like macros.
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci        If the objtype is 'function' and the the signature ``sig`` is a
15562306a36Sopenharmony_ci        function-like macro, the name of the macro is returned. Otherwise
15662306a36Sopenharmony_ci        ``False`` is returned.  """
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci        global namespace
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci        if not self.objtype == 'function':
16162306a36Sopenharmony_ci            return False
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci        m = c_funcptr_sig_re.match(sig)
16462306a36Sopenharmony_ci        if m is None:
16562306a36Sopenharmony_ci            m = c_sig_re.match(sig)
16662306a36Sopenharmony_ci            if m is None:
16762306a36Sopenharmony_ci                raise ValueError('no match')
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci        rettype, fullname, arglist, _const = m.groups()
17062306a36Sopenharmony_ci        arglist = arglist.strip()
17162306a36Sopenharmony_ci        if rettype or not arglist:
17262306a36Sopenharmony_ci            return False
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci        arglist = arglist.replace('`', '').replace('\\ ', '') # remove markup
17562306a36Sopenharmony_ci        arglist = [a.strip() for a in arglist.split(",")]
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci        # has the first argument a type?
17862306a36Sopenharmony_ci        if len(arglist[0].split(" ")) > 1:
17962306a36Sopenharmony_ci            return False
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci        # This is a function-like macro, its arguments are typeless!
18262306a36Sopenharmony_ci        signode  += addnodes.desc_name(fullname, fullname)
18362306a36Sopenharmony_ci        paramlist = addnodes.desc_parameterlist()
18462306a36Sopenharmony_ci        signode  += paramlist
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci        for argname in arglist:
18762306a36Sopenharmony_ci            param = addnodes.desc_parameter('', '', noemph=True)
18862306a36Sopenharmony_ci            # separate by non-breaking space in the output
18962306a36Sopenharmony_ci            param += nodes.emphasis(argname, argname)
19062306a36Sopenharmony_ci            paramlist += param
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci        if namespace:
19362306a36Sopenharmony_ci            fullname = namespace + "." + fullname
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci        return fullname
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci    def handle_signature(self, sig, signode):
19862306a36Sopenharmony_ci        """Transform a C signature into RST nodes."""
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci        global namespace
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci        fullname = self.handle_func_like_macro(sig, signode)
20362306a36Sopenharmony_ci        if not fullname:
20462306a36Sopenharmony_ci            fullname = super(CObject, self).handle_signature(sig, signode)
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci        if "name" in self.options:
20762306a36Sopenharmony_ci            if self.objtype == 'function':
20862306a36Sopenharmony_ci                fullname = self.options["name"]
20962306a36Sopenharmony_ci            else:
21062306a36Sopenharmony_ci                # FIXME: handle :name: value of other declaration types?
21162306a36Sopenharmony_ci                pass
21262306a36Sopenharmony_ci        else:
21362306a36Sopenharmony_ci            if namespace:
21462306a36Sopenharmony_ci                fullname = namespace + "." + fullname
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci        return fullname
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci    def add_target_and_index(self, name, sig, signode):
21962306a36Sopenharmony_ci        # for C API items we add a prefix since names are usually not qualified
22062306a36Sopenharmony_ci        # by a module name and so easily clash with e.g. section titles
22162306a36Sopenharmony_ci        targetname = 'c.' + name
22262306a36Sopenharmony_ci        if targetname not in self.state.document.ids:
22362306a36Sopenharmony_ci            signode['names'].append(targetname)
22462306a36Sopenharmony_ci            signode['ids'].append(targetname)
22562306a36Sopenharmony_ci            signode['first'] = (not self.names)
22662306a36Sopenharmony_ci            self.state.document.note_explicit_target(signode)
22762306a36Sopenharmony_ci            inv = self.env.domaindata['c']['objects']
22862306a36Sopenharmony_ci            if (name in inv and self.env.config.nitpicky):
22962306a36Sopenharmony_ci                if self.objtype == 'function':
23062306a36Sopenharmony_ci                    if ('c:func', name) not in self.env.config.nitpick_ignore:
23162306a36Sopenharmony_ci                        self.state_machine.reporter.warning(
23262306a36Sopenharmony_ci                            'duplicate C object description of %s, ' % name +
23362306a36Sopenharmony_ci                            'other instance in ' + self.env.doc2path(inv[name][0]),
23462306a36Sopenharmony_ci                            line=self.lineno)
23562306a36Sopenharmony_ci            inv[name] = (self.env.docname, self.objtype)
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci        indextext = self.get_index_text(name)
23862306a36Sopenharmony_ci        if indextext:
23962306a36Sopenharmony_ci            self.indexnode['entries'].append(
24062306a36Sopenharmony_ci                    ('single', indextext, targetname, '', None))
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ciclass CDomain(Base_CDomain):
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci    """C language domain."""
24562306a36Sopenharmony_ci    name = 'c'
24662306a36Sopenharmony_ci    label = 'C'
24762306a36Sopenharmony_ci    directives = {
24862306a36Sopenharmony_ci        'function': CObject,
24962306a36Sopenharmony_ci        'member':   CObject,
25062306a36Sopenharmony_ci        'macro':    CObject,
25162306a36Sopenharmony_ci        'type':     CObject,
25262306a36Sopenharmony_ci        'var':      CObject,
25362306a36Sopenharmony_ci    }
254