aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/Tools/c-analyzer/c_parser/parser/_common.py
blob: 2eacace2c001df9b0755ae41e6b4291d3e4ae95d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import re

from ._regexes import (
    _ind,
    STRING_LITERAL,
    VAR_DECL as _VAR_DECL,
)


def log_match(group, m, depth_before=None, depth_after=None):
    from . import _logger

    if m is not None:
        text = m.group(0)
        if text.startswith(('(', ')')) or text.endswith(('(', ')')):
            _logger.debug(f'matched <{group}> ({text!r})')
        else:
            _logger.debug(f'matched <{group}> ({text})')

    elif depth_before is not None or depth_after is not None:
        if depth_before is None:
            depth_before = '???'
        elif depth_after is None:
            depth_after = '???'
        _logger.log(1, f'depth: %s -> %s', depth_before, depth_after)

    else:
        raise NotImplementedError('this should not have been hit')


#############################
# regex utils

def set_capture_group(pattern, group, *, strict=True):
    old = f'(?:  # <{group}>'
    if strict and f'(?:  # <{group}>' not in pattern:
        raise ValueError(f'{old!r} not found in pattern')
    return pattern.replace(old, f'(  # <{group}>', 1)


def set_capture_groups(pattern, groups, *, strict=True):
    for group in groups:
        pattern = set_capture_group(pattern, group, strict=strict)
    return pattern


#############################
# syntax-related utils

_PAREN_RE = re.compile(rf'''
    (?:
        (?:
            [^'"()]*
            {_ind(STRING_LITERAL, 3)}
         )*
        [^'"()]*
        (?:
            ( [(] )
            |
            ( [)] )
         )
     )
    ''', re.VERBOSE)


def match_paren(text, depth=0):
    pos = 0
    while (m := _PAREN_RE.match(text, pos)):
        pos = m.end()
        _open, _close = m.groups()
        if _open:
            depth += 1
        else:  # _close
            depth -= 1
            if depth == 0:
                return pos
    else:
        raise ValueError(f'could not find matching parens for {text!r}')


VAR_DECL = set_capture_groups(_VAR_DECL, (
    'STORAGE',
    'TYPE_QUAL',
    'TYPE_SPEC',
    'DECLARATOR',
    'IDENTIFIER',
    'WRAPPED_IDENTIFIER',
    'FUNC_IDENTIFIER',
))


def parse_var_decl(decl):
    m = re.match(VAR_DECL, decl, re.VERBOSE)
    (storage, typequal, typespec, declarator,
     name,
     wrappedname,
     funcptrname,
     ) = m.groups()
    if name:
        kind = 'simple'
    elif wrappedname:
        kind = 'wrapped'
        name = wrappedname
    elif funcptrname:
        kind = 'funcptr'
        name = funcptrname
    else:
        raise NotImplementedError
    abstract = declarator.replace(name, '')
    vartype = {
        'storage': storage,
        'typequal': typequal,
        'typespec': typespec,
        'abstract': abstract,
    }
    return (kind, name, vartype)


#############################
# parser state utils

# XXX Drop this or use it!
def iter_results(results):
    if not results:
        return
    if callable(results):
        results = results()

    for result, text in results():
        if result:
            yield result, text