1from c_parser.info import (
2    KIND,
3    TypeDeclaration,
4    POTSType,
5    FuncPtr,
6)
7from c_parser.match import (
8    is_pots,
9    is_funcptr,
10)
11from .info import (
12    IGNORED,
13    UNKNOWN,
14    SystemType,
15)
16from .match import (
17    is_system_type,
18)
19
20
21def get_typespecs(typedecls):
22    typespecs = {}
23    for decl in typedecls:
24        if decl.shortkey not in typespecs:
25            typespecs[decl.shortkey] = [decl]
26        else:
27            typespecs[decl.shortkey].append(decl)
28    return typespecs
29
30
31def analyze_decl(decl, typespecs, knowntypespecs, types, knowntypes, *,
32                 analyze_resolved=None):
33    resolved = resolve_decl(decl, typespecs, knowntypespecs, types)
34    if resolved is None:
35        # The decl is supposed to be skipped or ignored.
36        return None
37    if analyze_resolved is None:
38        return resolved, None
39    return analyze_resolved(resolved, decl, types, knowntypes)
40
41# This alias helps us avoid name collisions.
42_analyze_decl = analyze_decl
43
44
45def analyze_type_decls(types, analyze_decl, handle_unresolved=True):
46    unresolved = set(types)
47    while unresolved:
48        updated = []
49        for decl in unresolved:
50            resolved = analyze_decl(decl)
51            if resolved is None:
52                # The decl should be skipped or ignored.
53                types[decl] = IGNORED
54                updated.append(decl)
55                continue
56            typedeps, _ = resolved
57            if typedeps is None:
58                raise NotImplementedError(decl)
59            if UNKNOWN in typedeps:
60                # At least one dependency is unknown, so this decl
61                # is not resolvable.
62                types[decl] = UNKNOWN
63                updated.append(decl)
64                continue
65            if None in typedeps:
66                # XXX
67                # Handle direct recursive types first.
68                nonrecursive = 1
69                if decl.kind is KIND.STRUCT or decl.kind is KIND.UNION:
70                    nonrecursive = 0
71                    i = 0
72                    for member, dep in zip(decl.members, typedeps):
73                        if dep is None:
74                            if member.vartype.typespec != decl.shortkey:
75                                nonrecursive += 1
76                            else:
77                                typedeps[i] = decl
78                        i += 1
79                if nonrecursive:
80                    # We don't have all dependencies resolved yet.
81                    continue
82            types[decl] = resolved
83            updated.append(decl)
84        if updated:
85            for decl in updated:
86                unresolved.remove(decl)
87        else:
88            # XXX
89            # Handle indirect recursive types.
90            ...
91            # We couldn't resolve the rest.
92            # Let the caller deal with it!
93            break
94    if unresolved and handle_unresolved:
95        if handle_unresolved is True:
96            handle_unresolved = _handle_unresolved
97        handle_unresolved(unresolved, types, analyze_decl)
98
99
100def resolve_decl(decl, typespecs, knowntypespecs, types):
101    if decl.kind is KIND.ENUM:
102        typedeps = []
103    else:
104        if decl.kind is KIND.VARIABLE:
105            vartypes = [decl.vartype]
106        elif decl.kind is KIND.FUNCTION:
107            vartypes = [decl.signature.returntype]
108        elif decl.kind is KIND.TYPEDEF:
109            vartypes = [decl.vartype]
110        elif decl.kind is KIND.STRUCT or decl.kind is KIND.UNION:
111            vartypes = [m.vartype for m in decl.members]
112        else:
113            # Skip this one!
114            return None
115
116        typedeps = []
117        for vartype in vartypes:
118            typespec = vartype.typespec
119            if is_pots(typespec):
120                typedecl = POTSType(typespec)
121            elif is_system_type(typespec):
122                typedecl = SystemType(typespec)
123            elif is_funcptr(vartype):
124                typedecl = FuncPtr(vartype)
125            else:
126                typedecl = find_typedecl(decl, typespec, typespecs)
127                if typedecl is None:
128                    typedecl = find_typedecl(decl, typespec, knowntypespecs)
129                elif not isinstance(typedecl, TypeDeclaration):
130                    raise NotImplementedError(repr(typedecl))
131                if typedecl is None:
132                    # We couldn't find it!
133                    typedecl = UNKNOWN
134                elif typedecl not in types:
135                    # XXX How can this happen?
136                    typedecl = UNKNOWN
137                elif types[typedecl] is UNKNOWN:
138                    typedecl = UNKNOWN
139                elif types[typedecl] is IGNORED:
140                    # We don't care if it didn't resolve.
141                    pass
142                elif types[typedecl] is None:
143                    # The typedecl for the typespec hasn't been resolved yet.
144                    typedecl = None
145            typedeps.append(typedecl)
146    return typedeps
147
148
149def find_typedecl(decl, typespec, typespecs):
150    specdecls = typespecs.get(typespec)
151    if not specdecls:
152        return None
153
154    filename = decl.filename
155
156    if len(specdecls) == 1:
157        typedecl, = specdecls
158        if '-' in typespec and typedecl.filename != filename:
159            # Inlined types are always in the same file.
160            return None
161        return typedecl
162
163    # Decide which one to return.
164    candidates = []
165    samefile = None
166    for typedecl in specdecls:
167        type_filename = typedecl.filename
168        if type_filename == filename:
169            if samefile is not None:
170                # We expect type names to be unique in a file.
171                raise NotImplementedError((decl, samefile, typedecl))
172            samefile = typedecl
173        elif filename.endswith('.c') and not type_filename.endswith('.h'):
174            # If the decl is in a source file then we expect the
175            # type to be in the same file or in a header file.
176            continue
177        candidates.append(typedecl)
178    if not candidates:
179        return None
180    elif len(candidates) == 1:
181        winner, = candidates
182        # XXX Check for inline?
183    elif '-' in typespec:
184        # Inlined types are always in the same file.
185        winner = samefile
186    elif samefile is not None:
187        # Favor types in the same file.
188        winner = samefile
189    else:
190        # We don't know which to return.
191        raise NotImplementedError((decl, candidates))
192
193    return winner
194
195
196#############################
197# handling unresolved decls
198
199class Skipped(TypeDeclaration):
200    def __init__(self):
201        _file = _name = _data = _parent = None
202        super().__init__(_file, _name, _data, _parent, _shortkey='<skipped>')
203_SKIPPED = Skipped()
204del Skipped
205
206
207def _handle_unresolved(unresolved, types, analyze_decl):
208    #raise NotImplementedError(unresolved)
209
210    dump = True
211    dump = False
212    if dump:
213        print()
214    for decl in types:  # Preserve the original order.
215        if decl not in unresolved:
216            assert types[decl] is not None, decl
217            if types[decl] in (UNKNOWN, IGNORED):
218                unresolved.add(decl)
219                if dump:
220                    _dump_unresolved(decl, types, analyze_decl)
221                    print()
222            else:
223                assert types[decl][0] is not None, (decl, types[decl])
224                assert None not in types[decl][0], (decl, types[decl])
225        else:
226            assert types[decl] is None
227            if dump:
228                _dump_unresolved(decl, types, analyze_decl)
229                print()
230    #raise NotImplementedError
231
232    for decl in unresolved:
233        types[decl] = ([_SKIPPED], None)
234
235    for decl in types:
236        assert types[decl]
237
238
239def _dump_unresolved(decl, types, analyze_decl):
240    if isinstance(decl, str):
241        typespec = decl
242        decl, = (d for d in types if d.shortkey == typespec)
243    elif type(decl) is tuple:
244        filename, typespec = decl
245        if '-' in typespec:
246            found = [d for d in types
247                     if d.shortkey == typespec and d.filename == filename]
248            #if not found:
249            #    raise NotImplementedError(decl)
250            decl, = found
251        else:
252            found = [d for d in types if d.shortkey == typespec]
253            if not found:
254                print(f'*** {typespec} ???')
255                return
256                #raise NotImplementedError(decl)
257            else:
258                decl, = found
259    resolved = analyze_decl(decl)
260    if resolved:
261        typedeps, _ = resolved or (None, None)
262
263    if decl.kind is KIND.STRUCT or decl.kind is KIND.UNION:
264        print(f'*** {decl.shortkey} {decl.filename}')
265        for member, mtype in zip(decl.members, typedeps):
266            typespec = member.vartype.typespec
267            if typespec == decl.shortkey:
268                print(f'     ~~~~: {typespec:20} - {member!r}')
269                continue
270            status = None
271            if is_pots(typespec):
272                mtype = typespec
273                status = 'okay'
274            elif is_system_type(typespec):
275                mtype = typespec
276                status = 'okay'
277            elif mtype is None:
278                if '-' in member.vartype.typespec:
279                    mtype, = [d for d in types
280                              if d.shortkey == member.vartype.typespec
281                              and d.filename == decl.filename]
282                else:
283                    found = [d for d in types
284                             if d.shortkey == typespec]
285                    if not found:
286                        print(f' ???: {typespec:20}')
287                        continue
288                    mtype, = found
289            if status is None:
290                status = 'okay' if types.get(mtype) else 'oops'
291            if mtype is _SKIPPED:
292                status = 'okay'
293                mtype = '<skipped>'
294            elif isinstance(mtype, FuncPtr):
295                status = 'okay'
296                mtype = str(mtype.vartype)
297            elif not isinstance(mtype, str):
298                if hasattr(mtype, 'vartype'):
299                    if is_funcptr(mtype.vartype):
300                        status = 'okay'
301                mtype = str(mtype).rpartition('(')[0].rstrip()
302            status = '    okay' if status == 'okay' else f'--> {status}'
303            print(f' {status}: {typespec:20} - {member!r} ({mtype})')
304    else:
305        print(f'*** {decl} ({decl.vartype!r})')
306        if decl.vartype.typespec.startswith('struct ') or is_funcptr(decl):
307            _dump_unresolved(
308                (decl.filename, decl.vartype.typespec),
309                types,
310                analyze_decl,
311            )
312