text: #
  # * The source code in this file is based on the soure code of numpydoc.
  #
  # # NLCPy License #
  #
  #     Copyright (c) 2020-2021 NEC Corporation
  #     All rights reserved.
  #
  #     Redistribution and use in source and binary forms, with or without
  #     modification, are permitted provided that the following conditions are met:
  #     * Redistributions of source code must retain the above copyright notice,
  #       this list of conditions and the following disclaimer.
  #     * Redistributions in binary form must reproduce the above copyright notice,
  #       this list of conditions and the following disclaimer in the documentation
  #       and/or other materials provided with the distribution.
  #     * Neither NEC Corporation nor the names of its contributors may be
  #       used to endorse or promote products derived from this software
  #       without specific prior written permission.
  #
  #     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  #     ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  #     WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  #     DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  #     FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  #     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  #     LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  #     ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  #     (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  #     SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  #
  # ## numpydoc License ##
  #
  #  Copyright (C) 2008 Stefan van der Walt <stefan@mentat.za.net>,
  #  Pauli Virtanen <pav@iki.fi>
  #
  #  Redistribution and use in source and binary forms, with or without
  #  modification, are permitted provided that the following conditions are
  #  met:
  #
  #   1. Redistributions of source code must retain the above copyright
  #      notice, this list of conditions and the following disclaimer.
  #   2. Redistributions in binary form must reproduce the above copyright
  #      notice, this list of conditions and the following disclaimer in
  #      the documentation and/or other materials provided with the
  #      distribution.
  #
  #  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
  #  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  #  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  #  DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
  #  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  
  import re
  
  # When sphinx (including the napoleon extension) parses the parameters
  # section of a docstring, it converts the information into field lists.
  # Some items in the list are for the parameter type. When the type fields
  # are processed, the text is split and some tokens are turned into
  # pending_xref nodes. These nodes are responsible for creating links.
  #
  # numpydoc does not create field lists, so the type information is
  # not placed into fields that can be processed to make links. Instead,
  # when parsing the type information we identify tokens that are link
  # worthy and wrap them around a :obj: role.
  
  # Note: we never split on commas that are not followed by a space
  # You risk creating bad rst markup if you do so.
  
  QUALIFIED_NAME_RE = re.compile(
      # e.g int, numpy.array, ~numpy.array, .class_in_current_module
      r'^'
      r'[~\.]?'
      r'[a-zA-Z_]\w*'
      r'(?:\.[a-zA-Z_]\w*)*'
      r'$'
  )
  
  CONTAINER_SPLIT_RE = re.compile(
      # splits dict(str, int) into
      #    ['dict', '[', 'str', ', ', 'int', ']', '']
      r'(\s*[\[\]\(\)\{\}]\s*|,\s+)'
  )
  
  CONTAINER_SPLIT_REJECT_RE = re.compile(
      # Leads to bad markup e.g.
      # {int}qualified_name
      r'[\]\)\}]\w'
  )
  
  DOUBLE_QUOTE_SPLIT_RE = re.compile(
      # splits 'callable ``f(x0, *args)`` or ``f(x0, y0, *args)``' into
      #    ['callable ', '``f(x0, *args)``', ' or ', '``f(x0, y0, *args)``', '']
      r'(``.+?``)'
  )
  
  ROLE_SPLIT_RE = re.compile(
      # splits to preserve ReST roles
      r'(:\w+:`.+?(?<!\\)`)'
  )
  
  SINGLE_QUOTE_SPLIT_RE = re.compile(
      # splits to preserve quoted expressions roles
      r'(`.+?`)'
  )
  
  TEXT_SPLIT_RE = re.compile(
      # splits on ' or ', ' | ', ', ' and ' '
      r'(\s+or\s+|\s+\|\s+|,\s+|\s+)'
  )
  
  CONTAINER_CHARS = set('[](){}')
  
  # Save people some time and add some common standard aliases
  DEFAULT_LINKS = {
      # Python
      'None': ':data:`python:None`',
      'bool': ':ref:`bool <python:bltin-boolean-values>`',
      'boolean': ':ref:`bool <python:bltin-boolean-values>`',
      'True': ':data:`python:True`',
      'False': ':data:`python:False`',
      'list': ':class:`python:list`',
      'tuple': ':class:`python:tuple`',
      'str': ':class:`python:str`',
      'string': ':class:`python:str`',
      'dict': ':class:`python:dict`',
      'float': ':class:`python:float`',
      'int': ':class:`python:int`',
      'callable': ':func:`python:callable`',
      'iterable': ':term:`python:iterable`',
      'sequence': ':term:`python:sequence`',
      'contextmanager': ':func:`python:contextlib.contextmanager`',
      'namedtuple': ':func:`python:collections.namedtuple`',
      'generator': ':term:`python:generator`',
      # NumPy
      'array': 'numpy.ndarray',
      'ndarray': 'numpy.ndarray',
      'np.ndarray': 'numpy.ndarray',
      'array-like': ':term:`numpy:array_like`',
      'array_like': ':term:`numpy:array_like`',
      'scalar': ':ref:`scalar <numpy:arrays.scalars>`',
      'RandomState': 'numpy.random.RandomState',
      'np.random.RandomState': 'numpy.random.RandomState',
      'np.inf': ':data:`numpy.inf`',
      'np.nan': ':data:`numpy.nan`',
      'numpy': ':mod:`numpy`',
  }
  
  
  def make_xref(param_type, xref_aliases, xref_ignore):
      """Parse and apply appropriate sphinx role(s) to `param_type`.
  
      The :obj: role is the default.
  
      Parameters
      ----------
      param_type : str
          text
      xref_aliases : dict
          Mapping used to resolve common abbreviations and aliases
          to fully qualified names that can be cross-referenced.
      xref_ignore : set or "all"
          A set containing words not to cross-reference. Instead of a set, the
          string 'all' can be given to ignore all unrecognized terms.
          Unrecognized terms include those that are not in `xref_aliases` and
          are not already wrapped in a reST role.
  
      Returns
      -------
      out : str
          Text with fully-qualified names and terms that may be wrapped in a
          ``:obj:`` role.
      """
      ignore_set = xref_ignore
      wrap_unknown = True
      if isinstance(xref_ignore, str):
          if xref_ignore.lower() == "all":
              wrap_unknown = False
              ignore_set = set()
          else:
              raise TypeError(
                  "xref_ignore must be a set or 'all', got {}".format(xref_ignore)
              )
  
      if param_type in xref_aliases:
          link, title = xref_aliases[param_type], param_type
          param_type = link
      else:
          link = title = param_type
  
      if QUALIFIED_NAME_RE.match(link) and link not in ignore_set:
          if link != title:
              return ':obj:`%s <%s>`' % (title, link)
          if wrap_unknown:
              return ':obj:`%s`' % link
          return link
  
      def _split_and_apply_re(s, pattern):
          """
          Split string using the regex pattern,
          apply main function to the parts that do not match the pattern,
          combine the results
          """
          results = []
          tokens = pattern.split(s)
          n = len(tokens)
          if n > 1:
              for i, tok in enumerate(tokens):
                  if pattern.match(tok):
                      results.append(tok)
                  else:
                      res = make_xref(tok, xref_aliases, xref_ignore)
                      # Opening brackets immediately after a role is
                      # bad markup. Detect that and add backslash.
                      # :role:`type`( to :role:`type`\(
                      if res and res[-1] == '`' and i < n - 1:
                          next_char = tokens[i + 1][0]
                          if next_char in '([{':
                              res += '\\'
                      results.append(res)
  
              return ''.join(results)
          return s
  
      # The cases are dealt with in an order the prevents
      # conflict.
      # Then the strategy is:
      #   - Identify a pattern we are not interested in
      #   - split off the pattern
      #   - re-apply the function to the other parts
      #   - join the results with the pattern
  
      # Unsplittable literal
      if '``' in param_type:
          return _split_and_apply_re(param_type, DOUBLE_QUOTE_SPLIT_RE)
  
      # Any roles
      if ':`' in param_type:
          return _split_and_apply_re(param_type, ROLE_SPLIT_RE)
  
      # Any quoted expressions
      if '`' in param_type:
          return _split_and_apply_re(param_type, SINGLE_QUOTE_SPLIT_RE)
  
      # Any sort of bracket '[](){}'
      if any(c in CONTAINER_CHARS for c in param_type):
          if CONTAINER_SPLIT_REJECT_RE.search(param_type):
              return param_type
          return _split_and_apply_re(param_type, CONTAINER_SPLIT_RE)
  
      # Common splitter tokens
      return _split_and_apply_re(param_type, TEXT_SPLIT_RE)
  