summaryrefslogtreecommitdiff
blob: dc876ae0b7596663ba0741fff7d29a28d5723adb (plain)
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import os.path

from c_analyzer_common import files
from c_analyzer_common.info import UNKNOWN
from c_parser import declarations, info
from .info import Symbol
from .source import _find_symbol


# XXX need tests:
# * look_up_known_symbol()
# * symbol_from_source()
# * get_resolver()
# * symbols_to_variables()

def look_up_known_symbol(symbol, knownvars, *,
                         match_files=(lambda f1, f2: f1 == f2),
                         ):
    """Return the known variable matching the given symbol.

    "knownvars" is a mapping of common.ID to parser.Variable.

    "match_files" is used to verify if two filenames point to
    the same file.
    """
    if not knownvars:
        return None

    if symbol.funcname == UNKNOWN:
        if not symbol.filename or symbol.filename == UNKNOWN:
            for varid in knownvars:
                if not varid.funcname:
                    continue
                if varid.name == symbol.name:
                    return knownvars[varid]
            else:
                return None
        else:
            for varid in knownvars:
                if not varid.funcname:
                    continue
                if not match_files(varid.filename, symbol.filename):
                    continue
                if varid.name == symbol.name:
                    return knownvars[varid]
            else:
                return None
    elif not symbol.filename or symbol.filename == UNKNOWN:
        raise NotImplementedError
    else:
        return knownvars.get(symbol.id)


def find_in_source(symbol, dirnames, *,
                   _perfilecache={},
                   _find_symbol=_find_symbol,
                   _iter_files=files.iter_files_by_suffix,
                   ):
    """Return the Variable matching the given Symbol.

    If there is no match then return None.
    """
    if symbol.filename and symbol.filename != UNKNOWN:
        filenames = [symbol.filename]
    else:
        filenames = _iter_files(dirnames, ('.c', '.h'))

    if symbol.funcname and symbol.funcname != UNKNOWN:
        raise NotImplementedError

    (filename, funcname, vartype
     ) = _find_symbol(symbol.name, filenames, _perfilecache)
    if filename == UNKNOWN:
        return None
    return info.Variable(
            id=(filename, funcname, symbol.name),
            vartype=vartype,
            )


def get_resolver(knownvars=None, dirnames=None, *,
                 _look_up_known=look_up_known_symbol,
                 _from_source=find_in_source,
                 ):
    """Return a "resolver" func for the given known vars and dirnames.

    The func takes a single Symbol and returns a corresponding Variable.
    If the symbol was located then the variable will be valid, populated
    with the corresponding information.  Otherwise None is returned.
    """
    if knownvars:
        knownvars = dict(knownvars)  # a copy
        def resolve_known(symbol):
            found = _look_up_known(symbol, knownvars)
            if found is None:
                return None
            elif symbol.funcname == UNKNOWN:
                knownvars.pop(found.id)
            elif not symbol.filename or symbol.filename == UNKNOWN:
                knownvars.pop(found.id)
            return found
        if dirnames:
            def resolve(symbol):
                found = resolve_known(symbol)
                if found is None:
                    return None
                    #return _from_source(symbol, dirnames)
                else:
                    for dirname in dirnames:
                        if not dirname.endswith(os.path.sep):
                            dirname += os.path.sep
                        if found.filename.startswith(dirname):
                            break
                    else:
                        return None
                    return found
        else:
            resolve = resolve_known
    elif dirnames:
        def resolve(symbol):
            return _from_source(symbol, dirnames)
    else:
        def resolve(symbol):
            return None
    return resolve


def symbols_to_variables(symbols, *,
                         resolve=(lambda s: look_up_known_symbol(s, None)),
                         ):
    """Yield the variable the matches each given symbol.

    Use get_resolver() for a "resolve" func to use.
    """
    for symbol in symbols:
        if isinstance(symbol, info.Variable):
            # XXX validate?
            yield symbol
            continue
        if symbol.kind != Symbol.KIND.VARIABLE:
            continue
        resolved = resolve(symbol)
        if resolved is None:
            #raise NotImplementedError(symbol)
            resolved = info.Variable(
                    id=symbol.id,
                    vartype=UNKNOWN,
                    )
        yield resolved