cpython/Tools/c-analyzer/c_parser/match.py

import re

from . import info as _info
from .parser._regexes import SIMPLE_TYPE


_KIND = _info.KIND


def match_storage(decl, expected):
    default = _info.get_default_storage(decl)
    #assert default
    if expected is None:
        expected = {default}
    elif isinstance(expected, str):
        expected = {expected or default}
    elif not expected:
        expected = _info.STORAGE
    else:
        expected = {v or default for v in expected}
    storage = _info.get_effective_storage(decl, default=default)
    return storage in expected


##################################
# decl matchers

def is_type_decl(item):
    return _KIND.is_type_decl(item.kind)


def is_decl(item):
    return _KIND.is_decl(item.kind)


def is_pots(typespec, *,
            _regex=re.compile(rf'^{SIMPLE_TYPE}$', re.VERBOSE),
            ):

    if not typespec:
        return None
    if type(typespec) is not str:
        _, _, _, typespec, _ = _info.get_parsed_vartype(typespec)
    return _regex.match(typespec) is not None


def is_funcptr(vartype):
    if not vartype:
        return None
    _, _, _, _, abstract = _info.get_parsed_vartype(vartype)
    return _is_funcptr(abstract)


def _is_funcptr(declstr):
    if not declstr:
        return None
    # XXX Support "(<name>*)(".
    return '(*)(' in declstr.replace(' ', '')


def is_forward_decl(decl):
    if decl.kind is _KIND.TYPEDEF:
        return False
    elif is_type_decl(decl):
        return not decl.data
    elif decl.kind is _KIND.FUNCTION:
        # XXX This doesn't work with ParsedItem.
        return decl.signature.isforward
    elif decl.kind is _KIND.VARIABLE:
        # No var decls are considered forward (or all are...).
        return False
    else:
        raise NotImplementedError(decl)


def can_have_symbol(decl):
    return decl.kind in (_KIND.VARIABLE, _KIND.FUNCTION)


def has_external_symbol(decl):
    if not can_have_symbol(decl):
        return False
    if _info.get_effective_storage(decl) != 'extern':
        return False
    if decl.kind is _KIND.FUNCTION:
        return not decl.signature.isforward
    else:
        # It must be a variable, which can only be implicitly extern here.
        return decl.storage != 'extern'


def has_internal_symbol(decl):
    if not can_have_symbol(decl):
        return False
    return _info.get_actual_storage(decl) == 'static'


def is_external_reference(decl):
    if not can_have_symbol(decl):
        return False
    # We have to check the declared storage rather tnan the effective.
    if decl.storage != 'extern':
        return False
    if decl.kind is _KIND.FUNCTION:
        return decl.signature.isforward
    # Otherwise it's a variable.
    return True


def is_local_var(decl):
    if not decl.kind is _KIND.VARIABLE:
        return False
    return True if decl.parent else False


def is_global_var(decl):
    if not decl.kind is _KIND.VARIABLE:
        return False
    return False if decl.parent else True


##################################
# filtering with matchers

def filter_by_kind(items, kind):
    if kind == 'type':
        kinds = _KIND._TYPE_DECLS
    elif kind == 'decl':
        kinds = _KIND._TYPE_DECLS
    try:
        okay = kind in _KIND
    except TypeError:
        kinds = set(kind)
    else:
        kinds = {kind} if okay else set(kind)
    for item in items:
        if item.kind in kinds:
            yield item


##################################
# grouping with matchers

def group_by_category(decls, categories, *, ignore_non_match=True):
    collated = {}
    for decl in decls:
        # Matchers should be mutually exclusive.  (First match wins.)
        for category, match in categories.items():
            if match(decl):
                if category not in collated:
                    collated[category] = [decl]
                else:
                    collated[category].append(decl)
                break
        else:
            if not ignore_non_match:
                raise Exception(f'no match for {decl!r}')
    return collated


def group_by_kind(items):
    collated = {kind: [] for kind in _KIND}
    for item in items:
        try:
            collated[item.kind].append(item)
        except KeyError:
            raise ValueError(f'unsupported kind in {item!r}')
    return collated


def group_by_kinds(items):
    # Collate into kind groups (decl, type, etc.).
    collated = {_KIND.get_group(k): [] for k in _KIND}
    for item in items:
        group = _KIND.get_group(item.kind)
        collated[group].append(item)
    return collated