• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from collections import namedtuple
2import os.path
3
4from c_common import fsutil
5from c_common.clsutil import classonly
6import c_common.misc as _misc
7from c_parser.info import (
8    KIND,
9    HighlevelParsedItem,
10    Declaration,
11    TypeDeclaration,
12)
13from c_parser.match import (
14    is_type_decl,
15)
16from .match import (
17    is_process_global,
18)
19
20
21IGNORED = _misc.Labeled('IGNORED')
22UNKNOWN = _misc.Labeled('UNKNOWN')
23
24
25class SystemType(TypeDeclaration):
26
27    def __init__(self, name):
28        super().__init__(None, name, None, None, _shortkey=name)
29
30
31class Analyzed:
32    _locked = False
33
34    @classonly
35    def is_target(cls, raw):
36        if isinstance(raw, HighlevelParsedItem):
37            return True
38        else:
39            return False
40
41    @classonly
42    def from_raw(cls, raw, **extra):
43        if isinstance(raw, cls):
44            if extra:
45                # XXX ?
46                raise NotImplementedError((raw, extra))
47                #return cls(raw.item, raw.typedecl, **raw._extra, **extra)
48            else:
49                return info
50        elif cls.is_target(raw):
51            return cls(raw, **extra)
52        else:
53            raise NotImplementedError((raw, extra))
54
55    @classonly
56    def from_resolved(cls, item, resolved, **extra):
57        if isinstance(resolved, TypeDeclaration):
58            return cls(item, typedecl=resolved, **extra)
59        else:
60            typedeps, extra = cls._parse_raw_resolved(item, resolved, extra)
61            if item.kind is KIND.ENUM:
62                if typedeps:
63                    raise NotImplementedError((item, resolved, extra))
64            elif not typedeps:
65                raise NotImplementedError((item, resolved, extra))
66            return cls(item, typedeps, **extra or {})
67
68    @classonly
69    def _parse_raw_resolved(cls, item, resolved, extra_extra):
70        if resolved in (UNKNOWN, IGNORED):
71            return resolved, None
72        try:
73            typedeps, extra = resolved
74        except (TypeError, ValueError):
75            typedeps = extra = None
76        if extra:
77            # The resolved data takes precedence.
78            extra = dict(extra_extra, **extra)
79        if isinstance(typedeps, TypeDeclaration):
80            return typedeps, extra
81        elif typedeps in (None, UNKNOWN):
82            # It is still effectively unresolved.
83            return UNKNOWN, extra
84        elif None in typedeps or UNKNOWN in typedeps:
85            # It is still effectively unresolved.
86            return typedeps, extra
87        elif any(not isinstance(td, TypeDeclaration) for td in typedeps):
88            raise NotImplementedError((item, typedeps, extra))
89        return typedeps, extra
90
91    def __init__(self, item, typedecl=None, **extra):
92        assert item is not None
93        self.item = item
94        if typedecl in (UNKNOWN, IGNORED):
95            pass
96        elif item.kind is KIND.STRUCT or item.kind is KIND.UNION:
97            if isinstance(typedecl, TypeDeclaration):
98                raise NotImplementedError(item, typedecl)
99            elif typedecl is None:
100                typedecl = UNKNOWN
101            else:
102                typedecl = [UNKNOWN if d is None else d for d in typedecl]
103        elif typedecl is None:
104            typedecl = UNKNOWN
105        elif typedecl and not isinstance(typedecl, TypeDeclaration):
106            # All the other decls have a single type decl.
107            typedecl, = typedecl
108            if typedecl is None:
109                typedecl = UNKNOWN
110        self.typedecl = typedecl
111        self._extra = extra
112        self._locked = True
113
114        self._validate()
115
116    def _validate(self):
117        item = self.item
118        extra = self._extra
119        # Check item.
120        if not isinstance(item, HighlevelParsedItem):
121            raise ValueError(f'"item" must be a high-level parsed item, got {item!r}')
122        # Check extra.
123        for key, value in extra.items():
124            if key.startswith('_'):
125                raise ValueError(f'extra items starting with {"_"!r} not allowed, got {extra!r}')
126            if hasattr(item, key) and not callable(getattr(item, key)):
127                raise ValueError(f'extra cannot override item, got {value!r} for key {key!r}')
128
129    def __repr__(self):
130        kwargs = [
131            f'item={self.item!r}',
132            f'typedecl={self.typedecl!r}',
133            *(f'{k}={v!r}' for k, v in self._extra.items())
134        ]
135        return f'{type(self).__name__}({", ".join(kwargs)})'
136
137    def __str__(self):
138        try:
139            return self._str
140        except AttributeError:
141            self._str, = self.render('line')
142            return self._str
143
144    def __hash__(self):
145        return hash(self.item)
146
147    def __eq__(self, other):
148        if isinstance(other, Analyzed):
149            return self.item == other.item
150        elif isinstance(other, HighlevelParsedItem):
151            return self.item == other
152        elif type(other) is tuple:
153            return self.item == other
154        else:
155            return NotImplemented
156
157    def __gt__(self, other):
158        if isinstance(other, Analyzed):
159            return self.item > other.item
160        elif isinstance(other, HighlevelParsedItem):
161            return self.item > other
162        elif type(other) is tuple:
163            return self.item > other
164        else:
165            return NotImplemented
166
167    def __dir__(self):
168        names = set(super().__dir__())
169        names.update(self._extra)
170        names.remove('_locked')
171        return sorted(names)
172
173    def __getattr__(self, name):
174        if name.startswith('_'):
175            raise AttributeError(name)
176        # The item takes precedence over the extra data (except if callable).
177        try:
178            value = getattr(self.item, name)
179            if callable(value):
180                raise AttributeError(name)
181        except AttributeError:
182            try:
183                value = self._extra[name]
184            except KeyError:
185                pass
186            else:
187                # Speed things up the next time.
188                self.__dict__[name] = value
189                return value
190            raise  # re-raise
191        else:
192            return value
193
194    def __setattr__(self, name, value):
195        if self._locked and name != '_str':
196            raise AttributeError(f'readonly ({name})')
197        super().__setattr__(name, value)
198
199    def __delattr__(self, name):
200        if self._locked:
201            raise AttributeError(f'readonly ({name})')
202        super().__delattr__(name)
203
204    @property
205    def decl(self):
206        if not isinstance(self.item, Declaration):
207            raise AttributeError('decl')
208        return self.item
209
210    @property
211    def signature(self):
212        # XXX vartype...
213        ...
214
215    @property
216    def istype(self):
217        return is_type_decl(self.item.kind)
218
219    @property
220    def is_known(self):
221        if self.typedecl in (UNKNOWN, IGNORED):
222            return False
223        elif isinstance(self.typedecl, TypeDeclaration):
224            return True
225        else:
226            return UNKNOWN not in self.typedecl
227
228    def fix_filename(self, relroot=fsutil.USE_CWD, **kwargs):
229        self.item.fix_filename(relroot, **kwargs)
230        return self
231
232    def as_rowdata(self, columns=None):
233        # XXX finish!
234        return self.item.as_rowdata(columns)
235
236    def render_rowdata(self, columns=None):
237        # XXX finish!
238        return self.item.render_rowdata(columns)
239
240    def render(self, fmt='line', *, itemonly=False):
241        if fmt == 'raw':
242            yield repr(self)
243            return
244        rendered = self.item.render(fmt)
245        if itemonly or not self._extra:
246            yield from rendered
247            return
248        extra = self._render_extra(fmt)
249        if not extra:
250            yield from rendered
251        elif fmt in ('brief', 'line'):
252            rendered, = rendered
253            extra, = extra
254            yield f'{rendered}\t{extra}'
255        elif fmt == 'summary':
256            raise NotImplementedError(fmt)
257        elif fmt == 'full':
258            yield from rendered
259            for line in extra:
260                yield f'\t{line}'
261        else:
262            raise NotImplementedError(fmt)
263
264    def _render_extra(self, fmt):
265        if fmt in ('brief', 'line'):
266            yield str(self._extra)
267        else:
268            raise NotImplementedError(fmt)
269
270
271class Analysis:
272
273    _item_class = Analyzed
274
275    @classonly
276    def build_item(cls, info, resolved=None, **extra):
277        if resolved is None:
278            return cls._item_class.from_raw(info, **extra)
279        else:
280            return cls._item_class.from_resolved(info, resolved, **extra)
281
282    @classmethod
283    def from_results(cls, results):
284        self = cls()
285        for info, resolved in results:
286            self._add_result(info, resolved)
287        return self
288
289    def __init__(self, items=None):
290        self._analyzed = {type(self).build_item(item): None
291                          for item in items or ()}
292
293    def __repr__(self):
294        return f'{type(self).__name__}({list(self._analyzed.keys())})'
295
296    def __iter__(self):
297        #yield from self.types
298        #yield from self.functions
299        #yield from self.variables
300        yield from self._analyzed
301
302    def __len__(self):
303        return len(self._analyzed)
304
305    def __getitem__(self, key):
306        if type(key) is int:
307            for i, val in enumerate(self._analyzed):
308                if i == key:
309                    return val
310            else:
311                raise IndexError(key)
312        else:
313            return self._analyzed[key]
314
315    def fix_filenames(self, relroot=fsutil.USE_CWD, **kwargs):
316        if relroot and relroot is not fsutil.USE_CWD:
317            relroot = os.path.abspath(relroot)
318        for item in self._analyzed:
319            item.fix_filename(relroot, fixroot=False, **kwargs)
320
321    def _add_result(self, info, resolved):
322        analyzed = type(self).build_item(info, resolved)
323        self._analyzed[analyzed] = None
324        return analyzed
325