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