| from collections import namedtuple |
| import os.path |
| |
| from c_common import fsutil |
| from c_common.clsutil import classonly |
| import c_common.misc as _misc |
| from c_parser.info import ( |
| KIND, |
| HighlevelParsedItem, |
| Declaration, |
| TypeDeclaration, |
| ) |
| from c_parser.match import ( |
| is_type_decl, |
| ) |
| from .match import ( |
| is_process_global, |
| ) |
| |
| |
| IGNORED = _misc.Labeled('IGNORED') |
| UNKNOWN = _misc.Labeled('UNKNOWN') |
| |
| |
| class SystemType(TypeDeclaration): |
| |
| def __init__(self, name): |
| super().__init__(None, name, None, None, _shortkey=name) |
| |
| |
| class Analyzed: |
| _locked = False |
| |
| @classonly |
| def is_target(cls, raw): |
| if isinstance(raw, HighlevelParsedItem): |
| return True |
| else: |
| return False |
| |
| @classonly |
| def from_raw(cls, raw, **extra): |
| if isinstance(raw, cls): |
| if extra: |
| # XXX ? |
| raise NotImplementedError((raw, extra)) |
| #return cls(raw.item, raw.typedecl, **raw._extra, **extra) |
| else: |
| return info |
| elif cls.is_target(raw): |
| return cls(raw, **extra) |
| else: |
| raise NotImplementedError((raw, extra)) |
| |
| @classonly |
| def from_resolved(cls, item, resolved, **extra): |
| if isinstance(resolved, TypeDeclaration): |
| return cls(item, typedecl=resolved, **extra) |
| else: |
| typedeps, extra = cls._parse_raw_resolved(item, resolved, extra) |
| if item.kind is KIND.ENUM: |
| if typedeps: |
| raise NotImplementedError((item, resolved, extra)) |
| elif not typedeps: |
| raise NotImplementedError((item, resolved, extra)) |
| return cls(item, typedeps, **extra or {}) |
| |
| @classonly |
| def _parse_raw_resolved(cls, item, resolved, extra_extra): |
| if resolved in (UNKNOWN, IGNORED): |
| return resolved, None |
| try: |
| typedeps, extra = resolved |
| except (TypeError, ValueError): |
| typedeps = extra = None |
| if extra: |
| # The resolved data takes precedence. |
| extra = dict(extra_extra, **extra) |
| if isinstance(typedeps, TypeDeclaration): |
| return typedeps, extra |
| elif typedeps in (None, UNKNOWN): |
| # It is still effectively unresolved. |
| return UNKNOWN, extra |
| elif None in typedeps or UNKNOWN in typedeps: |
| # It is still effectively unresolved. |
| return typedeps, extra |
| elif any(not isinstance(td, TypeDeclaration) for td in typedeps): |
| raise NotImplementedError((item, typedeps, extra)) |
| return typedeps, extra |
| |
| def __init__(self, item, typedecl=None, **extra): |
| assert item is not None |
| self.item = item |
| if typedecl in (UNKNOWN, IGNORED): |
| pass |
| elif item.kind is KIND.STRUCT or item.kind is KIND.UNION: |
| if isinstance(typedecl, TypeDeclaration): |
| raise NotImplementedError(item, typedecl) |
| elif typedecl is None: |
| typedecl = UNKNOWN |
| else: |
| typedecl = [UNKNOWN if d is None else d for d in typedecl] |
| elif typedecl is None: |
| typedecl = UNKNOWN |
| elif typedecl and not isinstance(typedecl, TypeDeclaration): |
| # All the other decls have a single type decl. |
| typedecl, = typedecl |
| if typedecl is None: |
| typedecl = UNKNOWN |
| self.typedecl = typedecl |
| self._extra = extra |
| self._locked = True |
| |
| self._validate() |
| |
| def _validate(self): |
| item = self.item |
| extra = self._extra |
| # Check item. |
| if not isinstance(item, HighlevelParsedItem): |
| raise ValueError(f'"item" must be a high-level parsed item, got {item!r}') |
| # Check extra. |
| for key, value in extra.items(): |
| if key.startswith('_'): |
| raise ValueError(f'extra items starting with {"_"!r} not allowed, got {extra!r}') |
| if hasattr(item, key) and not callable(getattr(item, key)): |
| raise ValueError(f'extra cannot override item, got {value!r} for key {key!r}') |
| |
| def __repr__(self): |
| kwargs = [ |
| f'item={self.item!r}', |
| f'typedecl={self.typedecl!r}', |
| *(f'{k}={v!r}' for k, v in self._extra.items()) |
| ] |
| return f'{type(self).__name__}({", ".join(kwargs)})' |
| |
| def __str__(self): |
| try: |
| return self._str |
| except AttributeError: |
| self._str, = self.render('line') |
| return self._str |
| |
| def __hash__(self): |
| return hash(self.item) |
| |
| def __eq__(self, other): |
| if isinstance(other, Analyzed): |
| return self.item == other.item |
| elif isinstance(other, HighlevelParsedItem): |
| return self.item == other |
| elif type(other) is tuple: |
| return self.item == other |
| else: |
| return NotImplemented |
| |
| def __gt__(self, other): |
| if isinstance(other, Analyzed): |
| return self.item > other.item |
| elif isinstance(other, HighlevelParsedItem): |
| return self.item > other |
| elif type(other) is tuple: |
| return self.item > other |
| else: |
| return NotImplemented |
| |
| def __dir__(self): |
| names = set(super().__dir__()) |
| names.update(self._extra) |
| names.remove('_locked') |
| return sorted(names) |
| |
| def __getattr__(self, name): |
| if name.startswith('_'): |
| raise AttributeError(name) |
| # The item takes precedence over the extra data (except if callable). |
| try: |
| value = getattr(self.item, name) |
| if callable(value): |
| raise AttributeError(name) |
| except AttributeError: |
| try: |
| value = self._extra[name] |
| except KeyError: |
| pass |
| else: |
| # Speed things up the next time. |
| self.__dict__[name] = value |
| return value |
| raise # re-raise |
| else: |
| return value |
| |
| def __setattr__(self, name, value): |
| if self._locked and name != '_str': |
| raise AttributeError(f'readonly ({name})') |
| super().__setattr__(name, value) |
| |
| def __delattr__(self, name): |
| if self._locked: |
| raise AttributeError(f'readonly ({name})') |
| super().__delattr__(name) |
| |
| @property |
| def decl(self): |
| if not isinstance(self.item, Declaration): |
| raise AttributeError('decl') |
| return self.item |
| |
| @property |
| def signature(self): |
| # XXX vartype... |
| ... |
| |
| @property |
| def istype(self): |
| return is_type_decl(self.item.kind) |
| |
| @property |
| def is_known(self): |
| if self.typedecl in (UNKNOWN, IGNORED): |
| return False |
| elif isinstance(self.typedecl, TypeDeclaration): |
| return True |
| else: |
| return UNKNOWN not in self.typedecl |
| |
| def fix_filename(self, relroot=fsutil.USE_CWD, **kwargs): |
| self.item.fix_filename(relroot, **kwargs) |
| return self |
| |
| def as_rowdata(self, columns=None): |
| # XXX finsih! |
| return self.item.as_rowdata(columns) |
| |
| def render_rowdata(self, columns=None): |
| # XXX finsih! |
| return self.item.render_rowdata(columns) |
| |
| def render(self, fmt='line', *, itemonly=False): |
| if fmt == 'raw': |
| yield repr(self) |
| return |
| rendered = self.item.render(fmt) |
| if itemonly or not self._extra: |
| yield from rendered |
| return |
| extra = self._render_extra(fmt) |
| if not extra: |
| yield from rendered |
| elif fmt in ('brief', 'line'): |
| rendered, = rendered |
| extra, = extra |
| yield f'{rendered}\t{extra}' |
| elif fmt == 'summary': |
| raise NotImplementedError(fmt) |
| elif fmt == 'full': |
| yield from rendered |
| for line in extra: |
| yield f'\t{line}' |
| else: |
| raise NotImplementedError(fmt) |
| |
| def _render_extra(self, fmt): |
| if fmt in ('brief', 'line'): |
| yield str(self._extra) |
| else: |
| raise NotImplementedError(fmt) |
| |
| |
| class Analysis: |
| |
| _item_class = Analyzed |
| |
| @classonly |
| def build_item(cls, info, resolved=None, **extra): |
| if resolved is None: |
| return cls._item_class.from_raw(info, **extra) |
| else: |
| return cls._item_class.from_resolved(info, resolved, **extra) |
| |
| @classmethod |
| def from_results(cls, results): |
| self = cls() |
| for info, resolved in results: |
| self._add_result(info, resolved) |
| return self |
| |
| def __init__(self, items=None): |
| self._analyzed = {type(self).build_item(item): None |
| for item in items or ()} |
| |
| def __repr__(self): |
| return f'{type(self).__name__}({list(self._analyzed.keys())})' |
| |
| def __iter__(self): |
| #yield from self.types |
| #yield from self.functions |
| #yield from self.variables |
| yield from self._analyzed |
| |
| def __len__(self): |
| return len(self._analyzed) |
| |
| def __getitem__(self, key): |
| if type(key) is int: |
| for i, val in enumerate(self._analyzed): |
| if i == key: |
| return val |
| else: |
| raise IndexError(key) |
| else: |
| return self._analyzed[key] |
| |
| def fix_filenames(self, relroot=fsutil.USE_CWD, **kwargs): |
| if relroot and relroot is not fsutil.USE_CWD: |
| relroot = os.path.abspath(relroot) |
| for item in self._analyzed: |
| item.fix_filename(relroot, fixroot=False, **kwargs) |
| |
| def _add_result(self, info, resolved): |
| analyzed = type(self).build_item(info, resolved) |
| self._analyzed[analyzed] = None |
| return analyzed |