| 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, | |
| ) | |
| 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 finish! | |
| return self.item.as_rowdata(columns) | |
| def render_rowdata(self, columns=None): | |
| # XXX finish! | |
| 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 |