| 
									
										
										
										
											2020-10-22 18:42:51 -06:00
										 |  |  | from collections import namedtuple | 
					
						
							|  |  |  | import enum | 
					
						
							|  |  |  | import os.path | 
					
						
							|  |  |  | import re | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-20 15:39:28 -07:00
										 |  |  | from c_common import fsutil | 
					
						
							| 
									
										
										
										
											2020-10-22 18:42:51 -06:00
										 |  |  | from c_common.clsutil import classonly | 
					
						
							|  |  |  | import c_common.misc as _misc | 
					
						
							|  |  |  | import c_common.strutil as _strutil | 
					
						
							|  |  |  | import c_common.tables as _tables | 
					
						
							| 
									
										
										
										
											2020-10-30 15:46:52 -06:00
										 |  |  | from .parser._regexes import SIMPLE_TYPE, _STORAGE | 
					
						
							| 
									
										
										
										
											2020-10-22 18:42:51 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | FIXED_TYPE = _misc.Labeled('FIXED_TYPE') | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-30 15:46:52 -06:00
										 |  |  | STORAGE = frozenset(_STORAGE) | 
					
						
							| 
									
										
										
										
											2020-10-22 18:42:51 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ############################# | 
					
						
							|  |  |  | # kinds | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @enum.unique | 
					
						
							|  |  |  | class KIND(enum.Enum): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # XXX Use these in the raw parser code. | 
					
						
							|  |  |  |     TYPEDEF = 'typedef' | 
					
						
							|  |  |  |     STRUCT = 'struct' | 
					
						
							|  |  |  |     UNION = 'union' | 
					
						
							|  |  |  |     ENUM = 'enum' | 
					
						
							|  |  |  |     FUNCTION = 'function' | 
					
						
							|  |  |  |     VARIABLE = 'variable' | 
					
						
							|  |  |  |     STATEMENT = 'statement' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classonly | 
					
						
							|  |  |  |     def _from_raw(cls, raw): | 
					
						
							|  |  |  |         if raw is None: | 
					
						
							|  |  |  |             return None | 
					
						
							|  |  |  |         elif isinstance(raw, cls): | 
					
						
							|  |  |  |             return raw | 
					
						
							|  |  |  |         elif type(raw) is str: | 
					
						
							|  |  |  |             # We could use cls[raw] for the upper-case form, | 
					
						
							|  |  |  |             # but there's no need to go to the trouble. | 
					
						
							|  |  |  |             return cls(raw.lower()) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             raise NotImplementedError(raw) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classonly | 
					
						
							|  |  |  |     def by_priority(cls, group=None): | 
					
						
							|  |  |  |         if group is None: | 
					
						
							|  |  |  |             return cls._ALL_BY_PRIORITY.copy() | 
					
						
							|  |  |  |         elif group == 'type': | 
					
						
							|  |  |  |             return cls._TYPE_DECLS_BY_PRIORITY.copy() | 
					
						
							|  |  |  |         elif group == 'decl': | 
					
						
							|  |  |  |             return cls._ALL_DECLS_BY_PRIORITY.copy() | 
					
						
							|  |  |  |         elif isinstance(group, str): | 
					
						
							|  |  |  |             raise NotImplementedError(group) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             # XXX Treat group as a set of kinds & return in priority order? | 
					
						
							|  |  |  |             raise NotImplementedError(group) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classonly | 
					
						
							|  |  |  |     def is_type_decl(cls, kind): | 
					
						
							|  |  |  |         if kind in cls.TYPES: | 
					
						
							|  |  |  |             return True | 
					
						
							|  |  |  |         if not isinstance(kind, cls): | 
					
						
							|  |  |  |             raise TypeError(f'expected KIND, got {kind!r}') | 
					
						
							|  |  |  |         return False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classonly | 
					
						
							|  |  |  |     def is_decl(cls, kind): | 
					
						
							|  |  |  |         if kind in cls.DECLS: | 
					
						
							|  |  |  |             return True | 
					
						
							|  |  |  |         if not isinstance(kind, cls): | 
					
						
							|  |  |  |             raise TypeError(f'expected KIND, got {kind!r}') | 
					
						
							|  |  |  |         return False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classonly | 
					
						
							|  |  |  |     def get_group(cls, kind, *, groups=None): | 
					
						
							|  |  |  |         if not isinstance(kind, cls): | 
					
						
							|  |  |  |             raise TypeError(f'expected KIND, got {kind!r}') | 
					
						
							|  |  |  |         if groups is None: | 
					
						
							|  |  |  |             groups = ['type'] | 
					
						
							|  |  |  |         elif not groups: | 
					
						
							|  |  |  |             groups = () | 
					
						
							|  |  |  |         elif isinstance(groups, str): | 
					
						
							|  |  |  |             group = groups | 
					
						
							|  |  |  |             if group not in cls._GROUPS: | 
					
						
							|  |  |  |                 raise ValueError(f'unsupported group {group!r}') | 
					
						
							|  |  |  |             groups = [group] | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             unsupported = [g for g in groups if g not in cls._GROUPS] | 
					
						
							|  |  |  |             if unsupported: | 
					
						
							|  |  |  |                 raise ValueError(f'unsupported groups {", ".join(repr(unsupported))}') | 
					
						
							|  |  |  |         for group in groups: | 
					
						
							|  |  |  |             if kind in cls._GROUPS[group]: | 
					
						
							|  |  |  |                 return group | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             return kind.value | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classonly | 
					
						
							|  |  |  |     def resolve_group(cls, group): | 
					
						
							|  |  |  |         if isinstance(group, cls): | 
					
						
							|  |  |  |             return {group} | 
					
						
							|  |  |  |         elif isinstance(group, str): | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 return cls._GROUPS[group].copy() | 
					
						
							|  |  |  |             except KeyError: | 
					
						
							|  |  |  |                 raise ValueError(f'unsupported group {group!r}') | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             resolved = set() | 
					
						
							|  |  |  |             for gr in group: | 
					
						
							|  |  |  |                 resolve.update(cls.resolve_group(gr)) | 
					
						
							|  |  |  |             return resolved | 
					
						
							|  |  |  |             #return {*cls.resolve_group(g) for g in group} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | KIND._TYPE_DECLS_BY_PRIORITY = [ | 
					
						
							|  |  |  |     # These are in preferred order. | 
					
						
							|  |  |  |     KIND.TYPEDEF, | 
					
						
							|  |  |  |     KIND.STRUCT, | 
					
						
							|  |  |  |     KIND.UNION, | 
					
						
							|  |  |  |     KIND.ENUM, | 
					
						
							|  |  |  | ] | 
					
						
							|  |  |  | KIND._ALL_DECLS_BY_PRIORITY = [ | 
					
						
							|  |  |  |     # These are in preferred order. | 
					
						
							|  |  |  |     *KIND._TYPE_DECLS_BY_PRIORITY, | 
					
						
							|  |  |  |     KIND.FUNCTION, | 
					
						
							|  |  |  |     KIND.VARIABLE, | 
					
						
							|  |  |  | ] | 
					
						
							|  |  |  | KIND._ALL_BY_PRIORITY = [ | 
					
						
							|  |  |  |     # These are in preferred order. | 
					
						
							|  |  |  |     *KIND._ALL_DECLS_BY_PRIORITY, | 
					
						
							|  |  |  |     KIND.STATEMENT, | 
					
						
							|  |  |  | ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | KIND.TYPES = frozenset(KIND._TYPE_DECLS_BY_PRIORITY) | 
					
						
							|  |  |  | KIND.DECLS = frozenset(KIND._ALL_DECLS_BY_PRIORITY) | 
					
						
							|  |  |  | KIND._GROUPS = { | 
					
						
							|  |  |  |     'type': KIND.TYPES, | 
					
						
							|  |  |  |     'decl': KIND.DECLS, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | KIND._GROUPS.update((k.value, {k}) for k in KIND) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-30 15:46:52 -06:00
										 |  |  | def get_kind_group(item): | 
					
						
							|  |  |  |     return KIND.get_group(item.kind) | 
					
						
							| 
									
										
										
										
											2020-10-22 18:42:51 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ############################# | 
					
						
							|  |  |  | # low-level | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-20 15:39:28 -07:00
										 |  |  | def _fix_filename(filename, relroot, *, | 
					
						
							|  |  |  |                   formatted=True, | 
					
						
							|  |  |  |                   **kwargs): | 
					
						
							|  |  |  |     if formatted: | 
					
						
							|  |  |  |         fix = fsutil.format_filename | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         fix = fsutil.fix_filename | 
					
						
							|  |  |  |     return fix(filename, relroot=relroot, **kwargs) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-22 18:42:51 -06:00
										 |  |  | class FileInfo(namedtuple('FileInfo', 'filename lno')): | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def from_raw(cls, raw): | 
					
						
							|  |  |  |         if isinstance(raw, cls): | 
					
						
							|  |  |  |             return raw | 
					
						
							|  |  |  |         elif isinstance(raw, tuple): | 
					
						
							|  |  |  |             return cls(*raw) | 
					
						
							|  |  |  |         elif not raw: | 
					
						
							|  |  |  |             return None | 
					
						
							|  |  |  |         elif isinstance(raw, str): | 
					
						
							|  |  |  |             return cls(raw, -1) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             raise TypeError(f'unsupported "raw": {raw:!r}') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __str__(self): | 
					
						
							|  |  |  |         return self.filename | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-20 15:39:28 -07:00
										 |  |  |     def fix_filename(self, relroot=fsutil.USE_CWD, **kwargs): | 
					
						
							|  |  |  |         filename = _fix_filename(self.filename, relroot, **kwargs) | 
					
						
							|  |  |  |         if filename == self.filename: | 
					
						
							|  |  |  |             return self | 
					
						
							| 
									
										
										
										
											2020-10-22 18:42:51 -06:00
										 |  |  |         return self._replace(filename=filename) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class SourceLine(namedtuple('Line', 'file kind data conditions')): | 
					
						
							|  |  |  |     KINDS = ( | 
					
						
							|  |  |  |         #'directive',  # data is ... | 
					
						
							|  |  |  |         'source',  # "data" is the line | 
					
						
							|  |  |  |         #'comment',  # "data" is the text, including comment markers | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def filename(self): | 
					
						
							|  |  |  |         return self.file.filename | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def lno(self): | 
					
						
							|  |  |  |         return self.file.lno | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class DeclID(namedtuple('DeclID', 'filename funcname name')): | 
					
						
							|  |  |  |     """The globally-unique identifier for a declaration.""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def from_row(cls, row, **markers): | 
					
						
							|  |  |  |         row = _tables.fix_row(row, **markers) | 
					
						
							|  |  |  |         return cls(*row) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-20 15:39:28 -07:00
										 |  |  |     # We have to provde _make() becaose we implemented __new__(). | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _make(cls, iterable): | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             return cls(*iterable) | 
					
						
							|  |  |  |         except Exception: | 
					
						
							|  |  |  |             super()._make(iterable) | 
					
						
							|  |  |  |             raise  # re-raise | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-22 18:42:51 -06:00
										 |  |  |     def __new__(cls, filename, funcname, name): | 
					
						
							|  |  |  |         self = super().__new__( | 
					
						
							|  |  |  |             cls, | 
					
						
							|  |  |  |             filename=str(filename) if filename else None, | 
					
						
							|  |  |  |             funcname=str(funcname) if funcname else None, | 
					
						
							|  |  |  |             name=str(name) if name else None, | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         self._compare = tuple(v or '' for v in self) | 
					
						
							|  |  |  |         return self | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __hash__(self): | 
					
						
							|  |  |  |         return super().__hash__() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __eq__(self, other): | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             other = tuple(v or '' for v in other) | 
					
						
							|  |  |  |         except TypeError: | 
					
						
							|  |  |  |             return NotImplemented | 
					
						
							|  |  |  |         return self._compare == other | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __gt__(self, other): | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             other = tuple(v or '' for v in other) | 
					
						
							|  |  |  |         except TypeError: | 
					
						
							|  |  |  |             return NotImplemented | 
					
						
							|  |  |  |         return self._compare > other | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-20 15:39:28 -07:00
										 |  |  |     def fix_filename(self, relroot=fsutil.USE_CWD, **kwargs): | 
					
						
							|  |  |  |         filename = _fix_filename(self.filename, relroot, **kwargs) | 
					
						
							|  |  |  |         if filename == self.filename: | 
					
						
							|  |  |  |             return self | 
					
						
							|  |  |  |         return self._replace(filename=filename) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-22 18:42:51 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | class ParsedItem(namedtuple('ParsedItem', 'file kind parent name data')): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def from_raw(cls, raw): | 
					
						
							|  |  |  |         if isinstance(raw, cls): | 
					
						
							|  |  |  |             return raw | 
					
						
							|  |  |  |         elif isinstance(raw, tuple): | 
					
						
							|  |  |  |             return cls(*raw) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             raise TypeError(f'unsupported "raw": {raw:!r}') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def from_row(cls, row, columns=None): | 
					
						
							|  |  |  |         if not columns: | 
					
						
							|  |  |  |             colnames = 'filename funcname name kind data'.split() | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             colnames = list(columns) | 
					
						
							|  |  |  |             for i, column in enumerate(colnames): | 
					
						
							|  |  |  |                 if column == 'file': | 
					
						
							|  |  |  |                     colnames[i] = 'filename' | 
					
						
							|  |  |  |                 elif column == 'funcname': | 
					
						
							|  |  |  |                     colnames[i] = 'parent' | 
					
						
							|  |  |  |         if len(row) != len(set(colnames)): | 
					
						
							|  |  |  |             raise NotImplementedError(columns, row) | 
					
						
							|  |  |  |         kwargs = {} | 
					
						
							|  |  |  |         for column, value in zip(colnames, row): | 
					
						
							|  |  |  |             if column == 'filename': | 
					
						
							|  |  |  |                 kwargs['file'] = FileInfo.from_raw(value) | 
					
						
							|  |  |  |             elif column == 'kind': | 
					
						
							|  |  |  |                 kwargs['kind'] = KIND(value) | 
					
						
							|  |  |  |             elif column in cls._fields: | 
					
						
							|  |  |  |                 kwargs[column] = value | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 raise NotImplementedError(column) | 
					
						
							|  |  |  |         return cls(**kwargs) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def id(self): | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             return self._id | 
					
						
							|  |  |  |         except AttributeError: | 
					
						
							|  |  |  |             if self.kind is KIND.STATEMENT: | 
					
						
							|  |  |  |                 self._id = None | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 self._id = DeclID(str(self.file), self.funcname, self.name) | 
					
						
							|  |  |  |             return self._id | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def filename(self): | 
					
						
							|  |  |  |         if not self.file: | 
					
						
							|  |  |  |             return None | 
					
						
							|  |  |  |         return self.file.filename | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def lno(self): | 
					
						
							|  |  |  |         if not self.file: | 
					
						
							|  |  |  |             return -1 | 
					
						
							|  |  |  |         return self.file.lno | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def funcname(self): | 
					
						
							|  |  |  |         if not self.parent: | 
					
						
							|  |  |  |             return None | 
					
						
							|  |  |  |         if type(self.parent) is str: | 
					
						
							|  |  |  |             return self.parent | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             return self.parent.name | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-20 15:39:28 -07:00
										 |  |  |     def fix_filename(self, relroot=fsutil.USE_CWD, **kwargs): | 
					
						
							|  |  |  |         fixed = self.file.fix_filename(relroot, **kwargs) | 
					
						
							|  |  |  |         if fixed == self.file: | 
					
						
							|  |  |  |             return self | 
					
						
							|  |  |  |         return self._replace(file=fixed) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-22 18:42:51 -06:00
										 |  |  |     def as_row(self, columns=None): | 
					
						
							|  |  |  |         if not columns: | 
					
						
							|  |  |  |             columns = self._fields | 
					
						
							|  |  |  |         row = [] | 
					
						
							|  |  |  |         for column in columns: | 
					
						
							|  |  |  |             if column == 'file': | 
					
						
							|  |  |  |                 value = self.filename | 
					
						
							|  |  |  |             elif column == 'kind': | 
					
						
							|  |  |  |                 value = self.kind.value | 
					
						
							|  |  |  |             elif column == 'data': | 
					
						
							|  |  |  |                 value = self._render_data() | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 value = getattr(self, column) | 
					
						
							|  |  |  |             row.append(value) | 
					
						
							|  |  |  |         return row | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _render_data(self): | 
					
						
							|  |  |  |         if not self.data: | 
					
						
							|  |  |  |             return None | 
					
						
							|  |  |  |         elif isinstance(self.data, str): | 
					
						
							|  |  |  |             return self.data | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             # XXX | 
					
						
							|  |  |  |             raise NotImplementedError | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def _get_vartype(data): | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         vartype = dict(data['vartype']) | 
					
						
							|  |  |  |     except KeyError: | 
					
						
							|  |  |  |         vartype = dict(data) | 
					
						
							|  |  |  |         storage = data.get('storage') | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         storage = data.get('storage') or vartype.get('storage') | 
					
						
							|  |  |  |     del vartype['storage'] | 
					
						
							|  |  |  |     return storage, vartype | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_parsed_vartype(decl): | 
					
						
							|  |  |  |     kind = getattr(decl, 'kind', None) | 
					
						
							|  |  |  |     if isinstance(decl, ParsedItem): | 
					
						
							|  |  |  |         storage, vartype = _get_vartype(decl.data) | 
					
						
							|  |  |  |         typequal = vartype['typequal'] | 
					
						
							|  |  |  |         typespec = vartype['typespec'] | 
					
						
							|  |  |  |         abstract = vartype['abstract'] | 
					
						
							|  |  |  |     elif isinstance(decl, dict): | 
					
						
							|  |  |  |         kind = decl.get('kind') | 
					
						
							|  |  |  |         storage, vartype = _get_vartype(decl) | 
					
						
							|  |  |  |         typequal = vartype['typequal'] | 
					
						
							|  |  |  |         typespec = vartype['typespec'] | 
					
						
							|  |  |  |         abstract = vartype['abstract'] | 
					
						
							|  |  |  |     elif isinstance(decl, VarType): | 
					
						
							|  |  |  |         storage = None | 
					
						
							|  |  |  |         typequal, typespec, abstract = decl | 
					
						
							|  |  |  |     elif isinstance(decl, TypeDef): | 
					
						
							|  |  |  |         storage = None | 
					
						
							|  |  |  |         typequal, typespec, abstract = decl.vartype | 
					
						
							|  |  |  |     elif isinstance(decl, Variable): | 
					
						
							|  |  |  |         storage = decl.storage | 
					
						
							|  |  |  |         typequal, typespec, abstract = decl.vartype | 
					
						
							| 
									
										
										
										
											2022-08-17 16:54:59 -06:00
										 |  |  |     elif isinstance(decl, Signature): | 
					
						
							|  |  |  |         storage = None | 
					
						
							|  |  |  |         typequal, typespec, abstract = decl.returntype | 
					
						
							| 
									
										
										
										
											2020-10-22 18:42:51 -06:00
										 |  |  |     elif isinstance(decl, Function): | 
					
						
							|  |  |  |         storage = decl.storage | 
					
						
							|  |  |  |         typequal, typespec, abstract = decl.signature.returntype | 
					
						
							|  |  |  |     elif isinstance(decl, str): | 
					
						
							|  |  |  |         vartype, storage = VarType.from_str(decl) | 
					
						
							|  |  |  |         typequal, typespec, abstract = vartype | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         raise NotImplementedError(decl) | 
					
						
							|  |  |  |     return kind, storage, typequal, typespec, abstract | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-30 15:46:52 -06:00
										 |  |  | def get_default_storage(decl): | 
					
						
							|  |  |  |     if decl.kind not in (KIND.VARIABLE, KIND.FUNCTION): | 
					
						
							|  |  |  |         return None | 
					
						
							|  |  |  |     return 'extern' if decl.parent is None else 'auto' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_effective_storage(decl, *, default=None): | 
					
						
							|  |  |  |     # Note that "static" limits access to just that C module | 
					
						
							|  |  |  |     # and "extern" (the default for module-level) allows access | 
					
						
							|  |  |  |     # outside the C module. | 
					
						
							|  |  |  |     if default is None: | 
					
						
							|  |  |  |         default = get_default_storage(decl) | 
					
						
							|  |  |  |         if default is None: | 
					
						
							|  |  |  |             return None | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         storage = decl.storage | 
					
						
							|  |  |  |     except AttributeError: | 
					
						
							|  |  |  |         storage, _ = _get_vartype(decl.data) | 
					
						
							|  |  |  |     return storage or default | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-22 18:42:51 -06:00
										 |  |  | ############################# | 
					
						
							|  |  |  | # high-level | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class HighlevelParsedItem: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     kind = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     FIELDS = ('file', 'parent', 'name', 'data') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def from_parsed(cls, parsed): | 
					
						
							|  |  |  |         if parsed.kind is not cls.kind: | 
					
						
							|  |  |  |             raise TypeError(f'kind mismatch ({parsed.kind.value} != {cls.kind.value})') | 
					
						
							|  |  |  |         data, extra = cls._resolve_data(parsed.data) | 
					
						
							|  |  |  |         self = cls( | 
					
						
							|  |  |  |             cls._resolve_file(parsed), | 
					
						
							|  |  |  |             parsed.name, | 
					
						
							|  |  |  |             data, | 
					
						
							|  |  |  |             cls._resolve_parent(parsed) if parsed.parent else None, | 
					
						
							|  |  |  |             **extra or {} | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         self._parsed = parsed | 
					
						
							|  |  |  |         return self | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _resolve_file(cls, parsed): | 
					
						
							|  |  |  |         fileinfo = FileInfo.from_raw(parsed.file) | 
					
						
							|  |  |  |         if not fileinfo: | 
					
						
							|  |  |  |             raise NotImplementedError(parsed) | 
					
						
							|  |  |  |         return fileinfo | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _resolve_data(cls, data): | 
					
						
							|  |  |  |         return data, None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _raw_data(cls, data, extra): | 
					
						
							|  |  |  |         if isinstance(data, str): | 
					
						
							|  |  |  |             return data | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             raise NotImplementedError(data) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _data_as_row(cls, data, extra, colnames): | 
					
						
							|  |  |  |         row = {} | 
					
						
							|  |  |  |         for colname in colnames: | 
					
						
							|  |  |  |             if colname in row: | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             rendered = cls._render_data_row_item(colname, data, extra) | 
					
						
							|  |  |  |             if rendered is iter(rendered): | 
					
						
							|  |  |  |                 rendered, = rendered | 
					
						
							|  |  |  |             row[colname] = rendered | 
					
						
							|  |  |  |         return row | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _render_data_row_item(cls, colname, data, extra): | 
					
						
							|  |  |  |         if colname == 'data': | 
					
						
							|  |  |  |             return str(data) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             return None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _render_data_row(cls, fmt, data, extra, colnames): | 
					
						
							|  |  |  |         if fmt != 'row': | 
					
						
							|  |  |  |             raise NotImplementedError | 
					
						
							|  |  |  |         datarow = cls._data_as_row(data, extra, colnames) | 
					
						
							|  |  |  |         unresolved = [c for c, v in datarow.items() if v is None] | 
					
						
							|  |  |  |         if unresolved: | 
					
						
							|  |  |  |             raise NotImplementedError(unresolved) | 
					
						
							|  |  |  |         for colname, value in datarow.items(): | 
					
						
							|  |  |  |             if type(value) != str: | 
					
						
							|  |  |  |                 if colname == 'kind': | 
					
						
							|  |  |  |                     datarow[colname] = value.value | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     datarow[colname] = str(value) | 
					
						
							|  |  |  |         return datarow | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _render_data(cls, fmt, data, extra): | 
					
						
							|  |  |  |         row = cls._render_data_row(fmt, data, extra, ['data']) | 
					
						
							|  |  |  |         yield ' '.join(row.values()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _resolve_parent(cls, parsed, *, _kind=None): | 
					
						
							|  |  |  |         fileinfo = FileInfo(parsed.file.filename, -1) | 
					
						
							|  |  |  |         if isinstance(parsed.parent, str): | 
					
						
							|  |  |  |             if parsed.parent.isidentifier(): | 
					
						
							|  |  |  |                 name = parsed.parent | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 # XXX It could be something like "<kind> <name>". | 
					
						
							|  |  |  |                 raise NotImplementedError(repr(parsed.parent)) | 
					
						
							|  |  |  |             parent = ParsedItem(fileinfo, _kind, None, name, None) | 
					
						
							|  |  |  |         elif type(parsed.parent) is tuple: | 
					
						
							|  |  |  |             # XXX It could be something like (kind, name). | 
					
						
							|  |  |  |             raise NotImplementedError(repr(parsed.parent)) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             return parsed.parent | 
					
						
							|  |  |  |         Parent = KIND_CLASSES.get(_kind, Declaration) | 
					
						
							|  |  |  |         return Parent.from_parsed(parent) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _parse_columns(cls, columns): | 
					
						
							|  |  |  |         colnames = {}  # {requested -> actual} | 
					
						
							|  |  |  |         columns = list(columns or cls.FIELDS) | 
					
						
							|  |  |  |         datacolumns = [] | 
					
						
							|  |  |  |         for i, colname in enumerate(columns): | 
					
						
							|  |  |  |             if colname == 'file': | 
					
						
							|  |  |  |                 columns[i] = 'filename' | 
					
						
							|  |  |  |                 colnames['file'] = 'filename' | 
					
						
							|  |  |  |             elif colname == 'lno': | 
					
						
							|  |  |  |                 columns[i] = 'line' | 
					
						
							|  |  |  |                 colnames['lno'] = 'line' | 
					
						
							|  |  |  |             elif colname in ('filename', 'line'): | 
					
						
							|  |  |  |                 colnames[colname] = colname | 
					
						
							|  |  |  |             elif colname == 'data': | 
					
						
							|  |  |  |                 datacolumns.append(colname) | 
					
						
							|  |  |  |                 colnames[colname] = None | 
					
						
							|  |  |  |             elif colname in cls.FIELDS or colname == 'kind': | 
					
						
							|  |  |  |                 colnames[colname] = colname | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 datacolumns.append(colname) | 
					
						
							|  |  |  |                 colnames[colname] = None | 
					
						
							|  |  |  |         return columns, datacolumns, colnames | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, file, name, data, parent=None, *, | 
					
						
							|  |  |  |                  _extra=None, | 
					
						
							|  |  |  |                  _shortkey=None, | 
					
						
							|  |  |  |                  _key=None, | 
					
						
							|  |  |  |                  ): | 
					
						
							|  |  |  |         self.file = file | 
					
						
							|  |  |  |         self.parent = parent or None | 
					
						
							|  |  |  |         self.name = name | 
					
						
							|  |  |  |         self.data = data | 
					
						
							|  |  |  |         self._extra = _extra or {} | 
					
						
							|  |  |  |         self._shortkey = _shortkey | 
					
						
							|  |  |  |         self._key = _key | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __repr__(self): | 
					
						
							|  |  |  |         args = [f'{n}={getattr(self, n)!r}' | 
					
						
							|  |  |  |                 for n in ['file', 'name', 'data', 'parent', *(self._extra or ())]] | 
					
						
							|  |  |  |         return f'{type(self).__name__}({", ".join(args)})' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __str__(self): | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             return self._str | 
					
						
							|  |  |  |         except AttributeError: | 
					
						
							|  |  |  |             self._str = next(self.render()) | 
					
						
							|  |  |  |             return self._str | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __getattr__(self, name): | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             return self._extra[name] | 
					
						
							|  |  |  |         except KeyError: | 
					
						
							|  |  |  |             raise AttributeError(name) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __hash__(self): | 
					
						
							|  |  |  |         return hash(self._key) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __eq__(self, other): | 
					
						
							|  |  |  |         if isinstance(other, HighlevelParsedItem): | 
					
						
							|  |  |  |             return self._key == other._key | 
					
						
							|  |  |  |         elif type(other) is tuple: | 
					
						
							|  |  |  |             return self._key == other | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             return NotImplemented | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __gt__(self, other): | 
					
						
							|  |  |  |         if isinstance(other, HighlevelParsedItem): | 
					
						
							|  |  |  |             return self._key > other._key | 
					
						
							|  |  |  |         elif type(other) is tuple: | 
					
						
							|  |  |  |             return self._key > other | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             return NotImplemented | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def id(self): | 
					
						
							|  |  |  |         return self.parsed.id | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def shortkey(self): | 
					
						
							|  |  |  |         return self._shortkey | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def key(self): | 
					
						
							|  |  |  |         return self._key | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def filename(self): | 
					
						
							|  |  |  |         if not self.file: | 
					
						
							|  |  |  |             return None | 
					
						
							|  |  |  |         return self.file.filename | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def parsed(self): | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             return self._parsed | 
					
						
							|  |  |  |         except AttributeError: | 
					
						
							|  |  |  |             parent = self.parent | 
					
						
							|  |  |  |             if parent is not None and not isinstance(parent, str): | 
					
						
							|  |  |  |                 parent = parent.name | 
					
						
							|  |  |  |             self._parsed = ParsedItem( | 
					
						
							|  |  |  |                 self.file, | 
					
						
							|  |  |  |                 self.kind, | 
					
						
							|  |  |  |                 parent, | 
					
						
							|  |  |  |                 self.name, | 
					
						
							|  |  |  |                 self._raw_data(), | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |             return self._parsed | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-20 15:39:28 -07:00
										 |  |  |     def fix_filename(self, relroot=fsutil.USE_CWD, **kwargs): | 
					
						
							| 
									
										
										
										
											2020-10-22 18:42:51 -06:00
										 |  |  |         if self.file: | 
					
						
							| 
									
										
										
										
											2020-11-20 15:39:28 -07:00
										 |  |  |             self.file = self.file.fix_filename(relroot, **kwargs) | 
					
						
							|  |  |  |         return self | 
					
						
							| 
									
										
										
										
											2020-10-22 18:42:51 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def as_rowdata(self, columns=None): | 
					
						
							|  |  |  |         columns, datacolumns, colnames = self._parse_columns(columns) | 
					
						
							|  |  |  |         return self._as_row(colnames, datacolumns, self._data_as_row) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def render_rowdata(self, columns=None): | 
					
						
							|  |  |  |         columns, datacolumns, colnames = self._parse_columns(columns) | 
					
						
							|  |  |  |         def data_as_row(data, ext, cols): | 
					
						
							|  |  |  |             return self._render_data_row('row', data, ext, cols) | 
					
						
							|  |  |  |         rowdata = self._as_row(colnames, datacolumns, data_as_row) | 
					
						
							|  |  |  |         for column, value in rowdata.items(): | 
					
						
							|  |  |  |             colname = colnames.get(column) | 
					
						
							|  |  |  |             if not colname: | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             if column == 'kind': | 
					
						
							|  |  |  |                 value = value.value | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 if column == 'parent': | 
					
						
							|  |  |  |                     if self.parent: | 
					
						
							|  |  |  |                         value = f'({self.parent.kind.value} {self.parent.name})' | 
					
						
							|  |  |  |                 if not value: | 
					
						
							|  |  |  |                     value = '-' | 
					
						
							|  |  |  |                 elif type(value) is VarType: | 
					
						
							|  |  |  |                     value = repr(str(value)) | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     value = str(value) | 
					
						
							|  |  |  |             rowdata[column] = value | 
					
						
							|  |  |  |         return rowdata | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _as_row(self, colnames, datacolumns, data_as_row): | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             data = data_as_row(self.data, self._extra, datacolumns) | 
					
						
							|  |  |  |         except NotImplementedError: | 
					
						
							|  |  |  |             data = None | 
					
						
							|  |  |  |         row = data or {} | 
					
						
							|  |  |  |         for column, colname in colnames.items(): | 
					
						
							|  |  |  |             if colname == 'filename': | 
					
						
							|  |  |  |                 value = self.file.filename if self.file else None | 
					
						
							|  |  |  |             elif colname == 'line': | 
					
						
							|  |  |  |                 value = self.file.lno if self.file else None | 
					
						
							|  |  |  |             elif colname is None: | 
					
						
							|  |  |  |                 value = getattr(self, column, None) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 value = getattr(self, colname, None) | 
					
						
							|  |  |  |             row.setdefault(column, value) | 
					
						
							|  |  |  |         return row | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def render(self, fmt='line'): | 
					
						
							|  |  |  |         fmt = fmt or 'line' | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             render = _FORMATS[fmt] | 
					
						
							|  |  |  |         except KeyError: | 
					
						
							|  |  |  |             raise TypeError(f'unsupported fmt {fmt!r}') | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             data = self._render_data(fmt, self.data, self._extra) | 
					
						
							|  |  |  |         except NotImplementedError: | 
					
						
							|  |  |  |             data = '-' | 
					
						
							|  |  |  |         yield from render(self, data) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ### formats ### | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def _fmt_line(parsed, data=None): | 
					
						
							|  |  |  |     parts = [ | 
					
						
							|  |  |  |         f'<{parsed.kind.value}>', | 
					
						
							|  |  |  |     ] | 
					
						
							|  |  |  |     parent = '' | 
					
						
							|  |  |  |     if parsed.parent: | 
					
						
							|  |  |  |         parent = parsed.parent | 
					
						
							|  |  |  |         if not isinstance(parent, str): | 
					
						
							|  |  |  |             if parent.kind is KIND.FUNCTION: | 
					
						
							|  |  |  |                 parent = f'{parent.name}()' | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 parent = parent.name | 
					
						
							|  |  |  |         name = f'<{parent}>.{parsed.name}' | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         name = parsed.name | 
					
						
							|  |  |  |     if data is None: | 
					
						
							|  |  |  |         data = parsed.data | 
					
						
							|  |  |  |     elif data is iter(data): | 
					
						
							|  |  |  |         data, = data | 
					
						
							|  |  |  |     parts.extend([ | 
					
						
							|  |  |  |         name, | 
					
						
							|  |  |  |         f'<{data}>' if data else '-', | 
					
						
							|  |  |  |         f'({str(parsed.file or "<unknown file>")})', | 
					
						
							|  |  |  |     ]) | 
					
						
							|  |  |  |     yield '\t'.join(parts) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def _fmt_full(parsed, data=None): | 
					
						
							|  |  |  |     if parsed.kind is KIND.VARIABLE and parsed.parent: | 
					
						
							|  |  |  |         prefix = 'local ' | 
					
						
							|  |  |  |         suffix = f' ({parsed.parent.name})' | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         # XXX Show other prefixes (e.g. global, public) | 
					
						
							|  |  |  |         prefix = suffix = '' | 
					
						
							|  |  |  |     yield f'{prefix}{parsed.kind.value} {parsed.name!r}{suffix}' | 
					
						
							|  |  |  |     for column, info in parsed.render_rowdata().items(): | 
					
						
							|  |  |  |         if column == 'kind': | 
					
						
							|  |  |  |             continue | 
					
						
							|  |  |  |         if column == 'name': | 
					
						
							|  |  |  |             continue | 
					
						
							|  |  |  |         if column == 'parent' and parsed.kind is not KIND.VARIABLE: | 
					
						
							|  |  |  |             continue | 
					
						
							|  |  |  |         if column == 'data': | 
					
						
							|  |  |  |             if parsed.kind in (KIND.STRUCT, KIND.UNION): | 
					
						
							|  |  |  |                 column = 'members' | 
					
						
							|  |  |  |             elif parsed.kind is KIND.ENUM: | 
					
						
							|  |  |  |                 column = 'enumerators' | 
					
						
							|  |  |  |             elif parsed.kind is KIND.STATEMENT: | 
					
						
							|  |  |  |                 column = 'text' | 
					
						
							|  |  |  |                 data, = data | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 column = 'signature' | 
					
						
							|  |  |  |                 data, = data | 
					
						
							|  |  |  |             if not data: | 
					
						
							|  |  |  | #                yield f'\t{column}:\t-' | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             elif isinstance(data, str): | 
					
						
							|  |  |  |                 yield f'\t{column}:\t{data!r}' | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 yield f'\t{column}:' | 
					
						
							|  |  |  |                 for line in data: | 
					
						
							|  |  |  |                     yield f'\t\t- {line}' | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             yield f'\t{column}:\t{info}' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | _FORMATS = { | 
					
						
							|  |  |  |     'raw': (lambda v, _d: [repr(v)]), | 
					
						
							|  |  |  |     'brief': _fmt_line, | 
					
						
							|  |  |  |     'line': _fmt_line, | 
					
						
							|  |  |  |     'full': _fmt_full, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ### declarations ## | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class Declaration(HighlevelParsedItem): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def from_row(cls, row, **markers): | 
					
						
							|  |  |  |         fixed = tuple(_tables.fix_row(row, **markers)) | 
					
						
							|  |  |  |         if cls is Declaration: | 
					
						
							|  |  |  |             _, _, _, kind, _ = fixed | 
					
						
							|  |  |  |             sub = KIND_CLASSES.get(KIND(kind)) | 
					
						
							|  |  |  |             if not sub or not issubclass(sub, Declaration): | 
					
						
							|  |  |  |                 raise TypeError(f'unsupported kind, got {row!r}') | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             sub = cls | 
					
						
							|  |  |  |         return sub._from_row(fixed) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _from_row(cls, row): | 
					
						
							|  |  |  |         filename, funcname, name, kind, data = row | 
					
						
							|  |  |  |         kind = KIND._from_raw(kind) | 
					
						
							|  |  |  |         if kind is not cls.kind: | 
					
						
							|  |  |  |             raise TypeError(f'expected kind {cls.kind.value!r}, got {row!r}') | 
					
						
							|  |  |  |         fileinfo = FileInfo.from_raw(filename) | 
					
						
							| 
									
										
										
										
											2022-08-25 15:46:08 -06:00
										 |  |  |         extra = None | 
					
						
							| 
									
										
										
										
											2020-10-22 18:42:51 -06:00
										 |  |  |         if isinstance(data, str): | 
					
						
							|  |  |  |             data, extra = cls._parse_data(data, fmt='row') | 
					
						
							|  |  |  |         if extra: | 
					
						
							|  |  |  |             return cls(fileinfo, name, data, funcname, _extra=extra) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             return cls(fileinfo, name, data, funcname) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _resolve_parent(cls, parsed, *, _kind=None): | 
					
						
							|  |  |  |         if _kind is None: | 
					
						
							|  |  |  |             raise TypeError(f'{cls.kind.value} declarations do not have parents ({parsed})') | 
					
						
							|  |  |  |         return super()._resolve_parent(parsed, _kind=_kind) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _render_data(cls, fmt, data, extra): | 
					
						
							|  |  |  |         if not data: | 
					
						
							|  |  |  |             # XXX There should be some!  Forward? | 
					
						
							|  |  |  |             yield '???' | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             yield from cls._format_data(fmt, data, extra) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _render_data_row_item(cls, colname, data, extra): | 
					
						
							|  |  |  |         if colname == 'data': | 
					
						
							|  |  |  |             return cls._format_data('row', data, extra) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             return None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _format_data(cls, fmt, data, extra): | 
					
						
							|  |  |  |         raise NotImplementedError(fmt) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _parse_data(cls, datastr, fmt=None): | 
					
						
							|  |  |  |         """This is the reverse of _render_data.""" | 
					
						
							|  |  |  |         if not datastr or datastr is _tables.UNKNOWN or datastr == '???': | 
					
						
							|  |  |  |             return None, None | 
					
						
							|  |  |  |         elif datastr is _tables.EMPTY or datastr == '-': | 
					
						
							|  |  |  |             # All the kinds have *something* even it is unknown. | 
					
						
							|  |  |  |             raise TypeError('all declarations have data of some sort, got none') | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             return cls._unformat_data(datastr, fmt) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _unformat_data(cls, datastr, fmt=None): | 
					
						
							|  |  |  |         raise NotImplementedError(fmt) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class VarType(namedtuple('VarType', 'typequal typespec abstract')): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def from_str(cls, text): | 
					
						
							|  |  |  |         orig = text | 
					
						
							|  |  |  |         storage, sep, text = text.strip().partition(' ') | 
					
						
							|  |  |  |         if not sep: | 
					
						
							|  |  |  |             text = storage | 
					
						
							|  |  |  |             storage = None | 
					
						
							|  |  |  |         elif storage not in ('auto', 'register', 'static', 'extern'): | 
					
						
							|  |  |  |             text = orig | 
					
						
							|  |  |  |             storage = None | 
					
						
							|  |  |  |         return cls._from_str(text), storage | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _from_str(cls, text): | 
					
						
							|  |  |  |         orig = text | 
					
						
							|  |  |  |         if text.startswith(('const ', 'volatile ')): | 
					
						
							|  |  |  |             typequal, _, text = text.partition(' ') | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             typequal = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Extract a series of identifiers/keywords. | 
					
						
							|  |  |  |         m = re.match(r"^ *'?([a-zA-Z_]\w*(?:\s+[a-zA-Z_]\w*)*)\s*(.*?)'?\s*$", text) | 
					
						
							|  |  |  |         if not m: | 
					
						
							|  |  |  |             raise ValueError(f'invalid vartype text {orig!r}') | 
					
						
							|  |  |  |         typespec, abstract = m.groups() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return cls(typequal, typespec, abstract or None) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __str__(self): | 
					
						
							|  |  |  |         parts = [] | 
					
						
							|  |  |  |         if self.qualifier: | 
					
						
							|  |  |  |             parts.append(self.qualifier) | 
					
						
							|  |  |  |         parts.append(self.spec + (self.abstract or '')) | 
					
						
							|  |  |  |         return ' '.join(parts) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def qualifier(self): | 
					
						
							|  |  |  |         return self.typequal | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def spec(self): | 
					
						
							|  |  |  |         return self.typespec | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class Variable(Declaration): | 
					
						
							|  |  |  |     kind = KIND.VARIABLE | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _resolve_parent(cls, parsed): | 
					
						
							|  |  |  |         return super()._resolve_parent(parsed, _kind=KIND.FUNCTION) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _resolve_data(cls, data): | 
					
						
							|  |  |  |         if not data: | 
					
						
							|  |  |  |             return None, None | 
					
						
							|  |  |  |         storage, vartype = _get_vartype(data) | 
					
						
							|  |  |  |         return VarType(**vartype), {'storage': storage} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _raw_data(self, data, extra): | 
					
						
							|  |  |  |         vartype = data._asdict() | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |             'storage': extra['storage'], | 
					
						
							|  |  |  |             'vartype': vartype, | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _format_data(cls, fmt, data, extra): | 
					
						
							|  |  |  |         storage = extra.get('storage') | 
					
						
							|  |  |  |         text = f'{storage} {data}' if storage else str(data) | 
					
						
							|  |  |  |         if fmt in ('line', 'brief'): | 
					
						
							|  |  |  |             yield text | 
					
						
							|  |  |  |         #elif fmt == 'full': | 
					
						
							|  |  |  |         elif fmt == 'row': | 
					
						
							|  |  |  |             yield text | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             raise NotImplementedError(fmt) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _unformat_data(cls, datastr, fmt=None): | 
					
						
							|  |  |  |         if fmt in ('line', 'brief'): | 
					
						
							|  |  |  |             vartype, storage = VarType.from_str(datastr) | 
					
						
							|  |  |  |             return vartype, {'storage': storage} | 
					
						
							|  |  |  |         #elif fmt == 'full': | 
					
						
							|  |  |  |         elif fmt == 'row': | 
					
						
							|  |  |  |             vartype, storage = VarType.from_str(datastr) | 
					
						
							|  |  |  |             return vartype, {'storage': storage} | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             raise NotImplementedError(fmt) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, file, name, data, parent=None, storage=None): | 
					
						
							|  |  |  |         super().__init__(file, name, data, parent, | 
					
						
							| 
									
										
										
										
											2020-10-30 15:46:52 -06:00
										 |  |  |                          _extra={'storage': storage or None}, | 
					
						
							| 
									
										
										
										
											2020-10-22 18:42:51 -06:00
										 |  |  |                          _shortkey=f'({parent.name}).{name}' if parent else name, | 
					
						
							|  |  |  |                          _key=(str(file), | 
					
						
							|  |  |  |                                # Tilde comes after all other ascii characters. | 
					
						
							|  |  |  |                                f'~{parent or ""}~', | 
					
						
							|  |  |  |                                name, | 
					
						
							|  |  |  |                                ), | 
					
						
							|  |  |  |                          ) | 
					
						
							| 
									
										
										
										
											2020-10-30 15:46:52 -06:00
										 |  |  |         if storage: | 
					
						
							|  |  |  |             if storage not in STORAGE: | 
					
						
							|  |  |  |                 # The parser must need an update. | 
					
						
							|  |  |  |                 raise NotImplementedError(storage) | 
					
						
							|  |  |  |             # Otherwise we trust the compiler to have validated it. | 
					
						
							| 
									
										
										
										
											2020-10-22 18:42:51 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def vartype(self): | 
					
						
							|  |  |  |         return self.data | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class Signature(namedtuple('Signature', 'params returntype inline isforward')): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def from_str(cls, text): | 
					
						
							|  |  |  |         orig = text | 
					
						
							|  |  |  |         storage, sep, text = text.strip().partition(' ') | 
					
						
							|  |  |  |         if not sep: | 
					
						
							|  |  |  |             text = storage | 
					
						
							|  |  |  |             storage = None | 
					
						
							|  |  |  |         elif storage not in ('auto', 'register', 'static', 'extern'): | 
					
						
							|  |  |  |             text = orig | 
					
						
							|  |  |  |             storage = None | 
					
						
							|  |  |  |         return cls._from_str(text), storage | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _from_str(cls, text): | 
					
						
							|  |  |  |         orig = text | 
					
						
							|  |  |  |         inline, sep, text = text.partition('|') | 
					
						
							|  |  |  |         if not sep: | 
					
						
							|  |  |  |             text = inline | 
					
						
							|  |  |  |             inline = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         isforward = False | 
					
						
							|  |  |  |         if text.endswith(';'): | 
					
						
							|  |  |  |             text = text[:-1] | 
					
						
							|  |  |  |             isforward = True | 
					
						
							|  |  |  |         elif text.endswith('{}'): | 
					
						
							|  |  |  |             text = text[:-2] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         index = text.rindex('(') | 
					
						
							|  |  |  |         if index < 0: | 
					
						
							|  |  |  |             raise ValueError(f'bad signature text {orig!r}') | 
					
						
							|  |  |  |         params = text[index:] | 
					
						
							|  |  |  |         while params.count('(') <= params.count(')'): | 
					
						
							|  |  |  |             index = text.rindex('(', 0, index) | 
					
						
							|  |  |  |             if index < 0: | 
					
						
							|  |  |  |                 raise ValueError(f'bad signature text {orig!r}') | 
					
						
							|  |  |  |             params = text[index:] | 
					
						
							|  |  |  |         text = text[:index] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         returntype = VarType._from_str(text.rstrip()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return cls(params, returntype, inline, isforward) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __str__(self): | 
					
						
							|  |  |  |         parts = [] | 
					
						
							|  |  |  |         if self.inline: | 
					
						
							|  |  |  |             parts.extend([ | 
					
						
							|  |  |  |                 self.inline, | 
					
						
							|  |  |  |                 '|', | 
					
						
							|  |  |  |             ]) | 
					
						
							|  |  |  |         parts.extend([ | 
					
						
							|  |  |  |             str(self.returntype), | 
					
						
							|  |  |  |             self.params, | 
					
						
							|  |  |  |             ';' if self.isforward else '{}', | 
					
						
							|  |  |  |         ]) | 
					
						
							|  |  |  |         return ' '.join(parts) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def returns(self): | 
					
						
							|  |  |  |         return self.returntype | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-17 16:54:59 -06:00
										 |  |  |     @property | 
					
						
							|  |  |  |     def typequal(self): | 
					
						
							|  |  |  |         return self.returntype.typequal | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def typespec(self): | 
					
						
							|  |  |  |         return self.returntype.typespec | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def abstract(self): | 
					
						
							|  |  |  |         return self.returntype.abstract | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-22 18:42:51 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | class Function(Declaration): | 
					
						
							|  |  |  |     kind = KIND.FUNCTION | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _resolve_data(cls, data): | 
					
						
							|  |  |  |         if not data: | 
					
						
							|  |  |  |             return None, None | 
					
						
							|  |  |  |         kwargs = dict(data) | 
					
						
							|  |  |  |         returntype = dict(data['returntype']) | 
					
						
							|  |  |  |         del returntype['storage'] | 
					
						
							|  |  |  |         kwargs['returntype'] = VarType(**returntype) | 
					
						
							|  |  |  |         storage = kwargs.pop('storage') | 
					
						
							|  |  |  |         return Signature(**kwargs), {'storage': storage} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _raw_data(self, data): | 
					
						
							| 
									
										
										
										
											2021-10-06 19:55:16 +02:00
										 |  |  |         # XXX finish! | 
					
						
							| 
									
										
										
										
											2020-10-22 18:42:51 -06:00
										 |  |  |         return data | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _format_data(cls, fmt, data, extra): | 
					
						
							|  |  |  |         storage = extra.get('storage') | 
					
						
							|  |  |  |         text = f'{storage} {data}' if storage else str(data) | 
					
						
							|  |  |  |         if fmt in ('line', 'brief'): | 
					
						
							|  |  |  |             yield text | 
					
						
							|  |  |  |         #elif fmt == 'full': | 
					
						
							|  |  |  |         elif fmt == 'row': | 
					
						
							|  |  |  |             yield text | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             raise NotImplementedError(fmt) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _unformat_data(cls, datastr, fmt=None): | 
					
						
							|  |  |  |         if fmt in ('line', 'brief'): | 
					
						
							|  |  |  |             sig, storage = Signature.from_str(sig) | 
					
						
							|  |  |  |             return sig, {'storage': storage} | 
					
						
							|  |  |  |         #elif fmt == 'full': | 
					
						
							|  |  |  |         elif fmt == 'row': | 
					
						
							|  |  |  |             sig, storage = Signature.from_str(sig) | 
					
						
							|  |  |  |             return sig, {'storage': storage} | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             raise NotImplementedError(fmt) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, file, name, data, parent=None, storage=None): | 
					
						
							|  |  |  |         super().__init__(file, name, data, parent, _extra={'storage': storage}) | 
					
						
							|  |  |  |         self._shortkey = f'~{name}~ {self.data}' | 
					
						
							|  |  |  |         self._key = ( | 
					
						
							|  |  |  |             str(file), | 
					
						
							|  |  |  |             self._shortkey, | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def signature(self): | 
					
						
							|  |  |  |         return self.data | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class TypeDeclaration(Declaration): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, file, name, data, parent=None, *, _shortkey=None): | 
					
						
							|  |  |  |         if not _shortkey: | 
					
						
							|  |  |  |             _shortkey = f'{self.kind.value} {name}' | 
					
						
							|  |  |  |         super().__init__(file, name, data, parent, | 
					
						
							|  |  |  |                          _shortkey=_shortkey, | 
					
						
							|  |  |  |                          _key=( | 
					
						
							|  |  |  |                              str(file), | 
					
						
							|  |  |  |                              _shortkey, | 
					
						
							|  |  |  |                              ), | 
					
						
							|  |  |  |                          ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class POTSType(TypeDeclaration): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, name): | 
					
						
							|  |  |  |         _file = _data = _parent = None | 
					
						
							|  |  |  |         super().__init__(_file, name, _data, _parent, _shortkey=name) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class FuncPtr(TypeDeclaration): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, vartype): | 
					
						
							|  |  |  |         _file = _name = _parent = None | 
					
						
							|  |  |  |         data = vartype | 
					
						
							|  |  |  |         self.vartype = vartype | 
					
						
							|  |  |  |         super().__init__(_file, _name, data, _parent, _shortkey=f'<{vartype}>') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class TypeDef(TypeDeclaration): | 
					
						
							|  |  |  |     kind = KIND.TYPEDEF | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _resolve_data(cls, data): | 
					
						
							|  |  |  |         if not data: | 
					
						
							|  |  |  |             raise NotImplementedError(data) | 
					
						
							| 
									
										
										
										
											2022-08-17 16:54:59 -06:00
										 |  |  |         kwargs = dict(data) | 
					
						
							|  |  |  |         del kwargs['storage'] | 
					
						
							|  |  |  |         if 'returntype' in kwargs: | 
					
						
							|  |  |  |             vartype = kwargs['returntype'] | 
					
						
							|  |  |  |             del vartype['storage'] | 
					
						
							|  |  |  |             kwargs['returntype'] = VarType(**vartype) | 
					
						
							|  |  |  |             datacls = Signature | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             datacls = VarType | 
					
						
							|  |  |  |         return datacls(**kwargs), None | 
					
						
							| 
									
										
										
										
											2020-10-22 18:42:51 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _raw_data(self, data): | 
					
						
							|  |  |  |         # XXX finish! | 
					
						
							|  |  |  |         return data | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _format_data(cls, fmt, data, extra): | 
					
						
							|  |  |  |         text = str(data) | 
					
						
							|  |  |  |         if fmt in ('line', 'brief'): | 
					
						
							|  |  |  |             yield text | 
					
						
							|  |  |  |         elif fmt == 'full': | 
					
						
							|  |  |  |             yield text | 
					
						
							|  |  |  |         elif fmt == 'row': | 
					
						
							|  |  |  |             yield text | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             raise NotImplementedError(fmt) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _unformat_data(cls, datastr, fmt=None): | 
					
						
							|  |  |  |         if fmt in ('line', 'brief'): | 
					
						
							|  |  |  |             vartype, _ = VarType.from_str(datastr) | 
					
						
							|  |  |  |             return vartype, None | 
					
						
							|  |  |  |         #elif fmt == 'full': | 
					
						
							|  |  |  |         elif fmt == 'row': | 
					
						
							|  |  |  |             vartype, _ = VarType.from_str(datastr) | 
					
						
							|  |  |  |             return vartype, None | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             raise NotImplementedError(fmt) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, file, name, data, parent=None): | 
					
						
							|  |  |  |         super().__init__(file, name, data, parent, _shortkey=name) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def vartype(self): | 
					
						
							|  |  |  |         return self.data | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class Member(namedtuple('Member', 'name vartype size')): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def from_data(cls, raw, index): | 
					
						
							|  |  |  |         name = raw.name if raw.name else index | 
					
						
							|  |  |  |         vartype = size = None | 
					
						
							|  |  |  |         if type(raw.data) is int: | 
					
						
							|  |  |  |             size = raw.data | 
					
						
							|  |  |  |         elif isinstance(raw.data, str): | 
					
						
							|  |  |  |             size = int(raw.data) | 
					
						
							|  |  |  |         elif raw.data: | 
					
						
							|  |  |  |             vartype = dict(raw.data) | 
					
						
							|  |  |  |             del vartype['storage'] | 
					
						
							|  |  |  |             if 'size' in vartype: | 
					
						
							| 
									
										
										
										
											2022-05-03 13:18:27 -06:00
										 |  |  |                 size = vartype.pop('size') | 
					
						
							|  |  |  |                 if isinstance(size, str) and size.isdigit(): | 
					
						
							|  |  |  |                     size = int(size) | 
					
						
							| 
									
										
										
										
											2020-10-22 18:42:51 -06:00
										 |  |  |             vartype = VarType(**vartype) | 
					
						
							|  |  |  |         return cls(name, vartype, size) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def from_str(cls, text): | 
					
						
							|  |  |  |         name, _, vartype = text.partition(': ') | 
					
						
							|  |  |  |         if name.startswith('#'): | 
					
						
							|  |  |  |             name = int(name[1:]) | 
					
						
							|  |  |  |         if vartype.isdigit(): | 
					
						
							|  |  |  |             size = int(vartype) | 
					
						
							|  |  |  |             vartype = None | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             vartype, _ = VarType.from_str(vartype) | 
					
						
							|  |  |  |             size = None | 
					
						
							|  |  |  |         return cls(name, vartype, size) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __str__(self): | 
					
						
							|  |  |  |         name = self.name if isinstance(self.name, str) else f'#{self.name}' | 
					
						
							|  |  |  |         return f'{name}: {self.vartype or self.size}' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class _StructUnion(TypeDeclaration): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _resolve_data(cls, data): | 
					
						
							|  |  |  |         if not data: | 
					
						
							|  |  |  |             # XXX There should be some!  Forward? | 
					
						
							|  |  |  |             return None, None | 
					
						
							|  |  |  |         return [Member.from_data(v, i) for i, v in enumerate(data)], None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _raw_data(self, data): | 
					
						
							|  |  |  |         # XXX finish! | 
					
						
							|  |  |  |         return data | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _format_data(cls, fmt, data, extra): | 
					
						
							|  |  |  |         if fmt in ('line', 'brief'): | 
					
						
							|  |  |  |             members = ', '.join(f'<{m}>' for m in data) | 
					
						
							|  |  |  |             yield f'[{members}]' | 
					
						
							|  |  |  |         elif fmt == 'full': | 
					
						
							|  |  |  |             for member in data: | 
					
						
							|  |  |  |                 yield f'{member}' | 
					
						
							|  |  |  |         elif fmt == 'row': | 
					
						
							|  |  |  |             members = ', '.join(f'<{m}>' for m in data) | 
					
						
							|  |  |  |             yield f'[{members}]' | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             raise NotImplementedError(fmt) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _unformat_data(cls, datastr, fmt=None): | 
					
						
							|  |  |  |         if fmt in ('line', 'brief'): | 
					
						
							|  |  |  |             members = [Member.from_str(m[1:-1]) | 
					
						
							|  |  |  |                        for m in datastr[1:-1].split(', ')] | 
					
						
							|  |  |  |             return members, None | 
					
						
							|  |  |  |         #elif fmt == 'full': | 
					
						
							|  |  |  |         elif fmt == 'row': | 
					
						
							|  |  |  |             members = [Member.from_str(m.rstrip('>').lstrip('<')) | 
					
						
							|  |  |  |                        for m in datastr[1:-1].split('>, <')] | 
					
						
							|  |  |  |             return members, None | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             raise NotImplementedError(fmt) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, file, name, data, parent=None): | 
					
						
							|  |  |  |         super().__init__(file, name, data, parent) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def members(self): | 
					
						
							|  |  |  |         return self.data | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class Struct(_StructUnion): | 
					
						
							|  |  |  |     kind = KIND.STRUCT | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class Union(_StructUnion): | 
					
						
							|  |  |  |     kind = KIND.UNION | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class Enum(TypeDeclaration): | 
					
						
							|  |  |  |     kind = KIND.ENUM | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _resolve_data(cls, data): | 
					
						
							|  |  |  |         if not data: | 
					
						
							|  |  |  |             # XXX There should be some!  Forward? | 
					
						
							|  |  |  |             return None, None | 
					
						
							|  |  |  |         enumerators = [e if isinstance(e, str) else e.name | 
					
						
							|  |  |  |                        for e in data] | 
					
						
							|  |  |  |         return enumerators, None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _raw_data(self, data): | 
					
						
							| 
									
										
										
										
											2021-10-06 19:55:16 +02:00
										 |  |  |         # XXX finish! | 
					
						
							| 
									
										
										
										
											2020-10-22 18:42:51 -06:00
										 |  |  |         return data | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _format_data(cls, fmt, data, extra): | 
					
						
							|  |  |  |         if fmt in ('line', 'brief'): | 
					
						
							|  |  |  |             yield repr(data) | 
					
						
							|  |  |  |         elif fmt == 'full': | 
					
						
							|  |  |  |             for enumerator in data: | 
					
						
							|  |  |  |                 yield f'{enumerator}' | 
					
						
							|  |  |  |         elif fmt == 'row': | 
					
						
							|  |  |  |             # XXX This won't work with CSV... | 
					
						
							|  |  |  |             yield ','.join(data) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             raise NotImplementedError(fmt) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _unformat_data(cls, datastr, fmt=None): | 
					
						
							|  |  |  |         if fmt in ('line', 'brief'): | 
					
						
							|  |  |  |             return _strutil.unrepr(datastr), None | 
					
						
							|  |  |  |         #elif fmt == 'full': | 
					
						
							|  |  |  |         elif fmt == 'row': | 
					
						
							|  |  |  |             return datastr.split(','), None | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             raise NotImplementedError(fmt) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, file, name, data, parent=None): | 
					
						
							|  |  |  |         super().__init__(file, name, data, parent) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def enumerators(self): | 
					
						
							|  |  |  |         return self.data | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ### statements ### | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class Statement(HighlevelParsedItem): | 
					
						
							|  |  |  |     kind = KIND.STATEMENT | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _resolve_data(cls, data): | 
					
						
							| 
									
										
										
										
											2021-10-06 19:55:16 +02:00
										 |  |  |         # XXX finish! | 
					
						
							| 
									
										
										
										
											2020-10-22 18:42:51 -06:00
										 |  |  |         return data, None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _raw_data(self, data): | 
					
						
							| 
									
										
										
										
											2021-10-06 19:55:16 +02:00
										 |  |  |         # XXX finish! | 
					
						
							| 
									
										
										
										
											2020-10-22 18:42:51 -06:00
										 |  |  |         return data | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _render_data(cls, fmt, data, extra): | 
					
						
							|  |  |  |         # XXX Handle other formats? | 
					
						
							|  |  |  |         return repr(data) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _parse_data(self, datastr, fmt=None): | 
					
						
							|  |  |  |         # XXX Handle other formats? | 
					
						
							|  |  |  |         return _strutil.unrepr(datastr), None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, file, name, data, parent=None): | 
					
						
							|  |  |  |         super().__init__(file, name, data, parent, | 
					
						
							|  |  |  |                          _shortkey=data or '', | 
					
						
							|  |  |  |                          _key=( | 
					
						
							|  |  |  |                              str(file), | 
					
						
							|  |  |  |                              file.lno, | 
					
						
							|  |  |  |                              # XXX Only one stmt per line? | 
					
						
							|  |  |  |                              ), | 
					
						
							|  |  |  |                          ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def text(self): | 
					
						
							|  |  |  |         return self.data | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ### | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | KIND_CLASSES = {cls.kind: cls for cls in [ | 
					
						
							|  |  |  |     Variable, | 
					
						
							|  |  |  |     Function, | 
					
						
							|  |  |  |     TypeDef, | 
					
						
							|  |  |  |     Struct, | 
					
						
							|  |  |  |     Union, | 
					
						
							|  |  |  |     Enum, | 
					
						
							|  |  |  |     Statement, | 
					
						
							|  |  |  | ]} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def resolve_parsed(parsed): | 
					
						
							|  |  |  |     if isinstance(parsed, HighlevelParsedItem): | 
					
						
							|  |  |  |         return parsed | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         cls = KIND_CLASSES[parsed.kind] | 
					
						
							|  |  |  |     except KeyError: | 
					
						
							|  |  |  |         raise ValueError(f'unsupported kind in {parsed!r}') | 
					
						
							|  |  |  |     return cls.from_parsed(parsed) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-30 15:46:52 -06:00
										 |  |  | def set_flag(item, name, value): | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         setattr(item, name, value) | 
					
						
							|  |  |  |     except AttributeError: | 
					
						
							|  |  |  |         object.__setattr__(item, name, value) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-22 18:42:51 -06:00
										 |  |  | ############################# | 
					
						
							|  |  |  | # composite | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class Declarations: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def from_decls(cls, decls): | 
					
						
							|  |  |  |         return cls(decls) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def from_parsed(cls, items): | 
					
						
							|  |  |  |         decls = (resolve_parsed(item) | 
					
						
							|  |  |  |                  for item in items | 
					
						
							|  |  |  |                  if item.kind is not KIND.STATEMENT) | 
					
						
							|  |  |  |         return cls.from_decls(decls) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _resolve_key(cls, raw): | 
					
						
							|  |  |  |         if isinstance(raw, str): | 
					
						
							|  |  |  |             raw = [raw] | 
					
						
							|  |  |  |         elif isinstance(raw, Declaration): | 
					
						
							|  |  |  |             raw = ( | 
					
						
							|  |  |  |                 raw.filename if cls._is_public(raw) else None, | 
					
						
							|  |  |  |                 # `raw.parent` is always None for types and functions. | 
					
						
							|  |  |  |                 raw.parent if raw.kind is KIND.VARIABLE else None, | 
					
						
							|  |  |  |                 raw.name, | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         extra = None | 
					
						
							|  |  |  |         if len(raw) == 1: | 
					
						
							|  |  |  |             name, = raw | 
					
						
							|  |  |  |             if name: | 
					
						
							|  |  |  |                 name = str(name) | 
					
						
							|  |  |  |                 if name.endswith(('.c', '.h')): | 
					
						
							|  |  |  |                     # This is only legit as a query. | 
					
						
							|  |  |  |                     key = (name, None, None) | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     key = (None, None, name) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 key = (None, None, None) | 
					
						
							|  |  |  |         elif len(raw) == 2: | 
					
						
							|  |  |  |             parent, name = raw | 
					
						
							|  |  |  |             name = str(name) | 
					
						
							|  |  |  |             if isinstance(parent, Declaration): | 
					
						
							|  |  |  |                 key = (None, parent.name, name) | 
					
						
							|  |  |  |             elif not parent: | 
					
						
							|  |  |  |                 key = (None, None, name) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 parent = str(parent) | 
					
						
							|  |  |  |                 if parent.endswith(('.c', '.h')): | 
					
						
							|  |  |  |                     key = (parent, None, name) | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     key = (None, parent, name) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             key, extra = raw[:3], raw[3:] | 
					
						
							|  |  |  |             filename, funcname, name = key | 
					
						
							|  |  |  |             filename = str(filename) if filename else None | 
					
						
							|  |  |  |             if isinstance(funcname, Declaration): | 
					
						
							|  |  |  |                 funcname = funcname.name | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 funcname = str(funcname) if funcname else None | 
					
						
							|  |  |  |             name = str(name) if name else None | 
					
						
							|  |  |  |             key = (filename, funcname, name) | 
					
						
							|  |  |  |         return key, extra | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def _is_public(cls, decl): | 
					
						
							|  |  |  |         # For .c files don't we need info from .h files to make this decision? | 
					
						
							|  |  |  |         # XXX Check for "extern". | 
					
						
							|  |  |  |         # For now we treat all decls a "private" (have filename set). | 
					
						
							|  |  |  |         return False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, decls): | 
					
						
							|  |  |  |         # (file, func, name) -> decl | 
					
						
							|  |  |  |         # "public": | 
					
						
							|  |  |  |         #   * (None, None, name) | 
					
						
							|  |  |  |         # "private", "global": | 
					
						
							|  |  |  |         #   * (file, None, name) | 
					
						
							|  |  |  |         # "private", "local": | 
					
						
							|  |  |  |         #   * (file, func, name) | 
					
						
							|  |  |  |         if hasattr(decls, 'items'): | 
					
						
							|  |  |  |             self._decls = decls | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self._decls = {} | 
					
						
							|  |  |  |             self._extend(decls) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # XXX always validate? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def validate(self): | 
					
						
							|  |  |  |         for key, decl in self._decls.items(): | 
					
						
							|  |  |  |             if type(key) is not tuple or len(key) != 3: | 
					
						
							|  |  |  |                 raise ValueError(f'expected 3-tuple key, got {key!r} (for decl {decl!r})') | 
					
						
							|  |  |  |             filename, funcname, name = key | 
					
						
							|  |  |  |             if not name: | 
					
						
							|  |  |  |                 raise ValueError(f'expected name in key, got {key!r} (for decl {decl!r})') | 
					
						
							|  |  |  |             elif type(name) is not str: | 
					
						
							|  |  |  |                 raise ValueError(f'expected name in key to be str, got {key!r} (for decl {decl!r})') | 
					
						
							|  |  |  |             # XXX Check filename type? | 
					
						
							|  |  |  |             # XXX Check funcname type? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if decl.kind is KIND.STATEMENT: | 
					
						
							|  |  |  |                 raise ValueError(f'expected a declaration, got {decl!r}') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __repr__(self): | 
					
						
							|  |  |  |         return f'{type(self).__name__}({list(self)})' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __len__(self): | 
					
						
							|  |  |  |         return len(self._decls) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __iter__(self): | 
					
						
							|  |  |  |         yield from self._decls | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __getitem__(self, key): | 
					
						
							|  |  |  |         # XXX Be more exact for the 3-tuple case? | 
					
						
							|  |  |  |         if type(key) not in (str, tuple): | 
					
						
							|  |  |  |             raise KeyError(f'unsupported key {key!r}') | 
					
						
							|  |  |  |         resolved, extra = self._resolve_key(key) | 
					
						
							|  |  |  |         if extra: | 
					
						
							|  |  |  |             raise KeyError(f'key must have at most 3 parts, got {key!r}') | 
					
						
							|  |  |  |         if not resolved[2]: | 
					
						
							|  |  |  |             raise ValueError(f'expected name in key, got {key!r}') | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             return self._decls[resolved] | 
					
						
							|  |  |  |         except KeyError: | 
					
						
							|  |  |  |             if type(key) is tuple and len(key) == 3: | 
					
						
							|  |  |  |                 filename, funcname, name = key | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 filename, funcname, name = resolved | 
					
						
							|  |  |  |             if filename and not filename.endswith(('.c', '.h')): | 
					
						
							|  |  |  |                 raise KeyError(f'invalid filename in key {key!r}') | 
					
						
							|  |  |  |             elif funcname and funcname.endswith(('.c', '.h')): | 
					
						
							|  |  |  |                 raise KeyError(f'invalid funcname in key {key!r}') | 
					
						
							|  |  |  |             elif name and name.endswith(('.c', '.h')): | 
					
						
							|  |  |  |                 raise KeyError(f'invalid name in key {key!r}') | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 raise  # re-raise | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def types(self): | 
					
						
							|  |  |  |         return self._find(kind=KIND.TYPES) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def functions(self): | 
					
						
							|  |  |  |         return self._find(None, None, None, KIND.FUNCTION) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def variables(self): | 
					
						
							|  |  |  |         return self._find(None, None, None, KIND.VARIABLE) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def iter_all(self): | 
					
						
							|  |  |  |         yield from self._decls.values() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def get(self, key, default=None): | 
					
						
							|  |  |  |         try: | 
					
						
							| 
									
										
										
										
											2022-05-03 15:44:52 -06:00
										 |  |  |             return self[key] | 
					
						
							| 
									
										
										
										
											2020-10-22 18:42:51 -06:00
										 |  |  |         except KeyError: | 
					
						
							|  |  |  |             return default | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     #def add_decl(self, decl, key=None): | 
					
						
							|  |  |  |     #    decl = _resolve_parsed(decl) | 
					
						
							|  |  |  |     #    self._add_decl(decl, key) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def find(self, *key, **explicit): | 
					
						
							|  |  |  |         if not key: | 
					
						
							|  |  |  |             if not explicit: | 
					
						
							|  |  |  |                 return iter(self) | 
					
						
							|  |  |  |             return self._find(**explicit) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         resolved, extra = self._resolve_key(key) | 
					
						
							|  |  |  |         filename, funcname, name = resolved | 
					
						
							|  |  |  |         if not extra: | 
					
						
							|  |  |  |             kind = None | 
					
						
							|  |  |  |         elif len(extra) == 1: | 
					
						
							|  |  |  |             kind, = extra | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             raise KeyError(f'key must have at most 4 parts, got {key!r}') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         implicit= {} | 
					
						
							|  |  |  |         if filename: | 
					
						
							|  |  |  |             implicit['filename'] = filename | 
					
						
							|  |  |  |         if funcname: | 
					
						
							|  |  |  |             implicit['funcname'] = funcname | 
					
						
							|  |  |  |         if name: | 
					
						
							|  |  |  |             implicit['name'] = name | 
					
						
							|  |  |  |         if kind: | 
					
						
							|  |  |  |             implicit['kind'] = kind | 
					
						
							|  |  |  |         return self._find(**implicit, **explicit) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _find(self, filename=None, funcname=None, name=None, kind=None): | 
					
						
							|  |  |  |         for decl in self._decls.values(): | 
					
						
							|  |  |  |             if filename and decl.filename != filename: | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             if funcname: | 
					
						
							|  |  |  |                 if decl.kind is not KIND.VARIABLE: | 
					
						
							|  |  |  |                     continue | 
					
						
							|  |  |  |                 if decl.parent.name != funcname: | 
					
						
							|  |  |  |                     continue | 
					
						
							|  |  |  |             if name and decl.name != name: | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             if kind: | 
					
						
							|  |  |  |                 kinds = KIND.resolve_group(kind) | 
					
						
							|  |  |  |                 if decl.kind not in kinds: | 
					
						
							|  |  |  |                     continue | 
					
						
							|  |  |  |             yield decl | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _add_decl(self, decl, key=None): | 
					
						
							|  |  |  |         if key: | 
					
						
							|  |  |  |             if type(key) not in (str, tuple): | 
					
						
							|  |  |  |                 raise NotImplementedError((key, decl)) | 
					
						
							|  |  |  |             # Any partial key will be turned into a full key, but that | 
					
						
							|  |  |  |             # same partial key will still match a key lookup. | 
					
						
							|  |  |  |             resolved, _ = self._resolve_key(key) | 
					
						
							|  |  |  |             if not resolved[2]: | 
					
						
							|  |  |  |                 raise ValueError(f'expected name in key, got {key!r}') | 
					
						
							|  |  |  |             key = resolved | 
					
						
							|  |  |  |             # XXX Also add with the decl-derived key if not the same? | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             key, _ = self._resolve_key(decl) | 
					
						
							|  |  |  |         self._decls[key] = decl | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _extend(self, decls): | 
					
						
							|  |  |  |         decls = iter(decls) | 
					
						
							|  |  |  |         # Check only the first item. | 
					
						
							|  |  |  |         for decl in decls: | 
					
						
							|  |  |  |             if isinstance(decl, Declaration): | 
					
						
							|  |  |  |                 self._add_decl(decl) | 
					
						
							|  |  |  |                 # Add the rest without checking. | 
					
						
							|  |  |  |                 for decl in decls: | 
					
						
							|  |  |  |                     self._add_decl(decl) | 
					
						
							|  |  |  |             elif isinstance(decl, HighlevelParsedItem): | 
					
						
							|  |  |  |                 raise NotImplementedError(decl) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 try: | 
					
						
							|  |  |  |                     key, decl = decl | 
					
						
							|  |  |  |                 except ValueError: | 
					
						
							|  |  |  |                     raise NotImplementedError(decl) | 
					
						
							|  |  |  |                 if not isinstance(decl, Declaration): | 
					
						
							|  |  |  |                     raise NotImplementedError(decl) | 
					
						
							|  |  |  |                 self._add_decl(decl, key) | 
					
						
							|  |  |  |                 # Add the rest without checking. | 
					
						
							|  |  |  |                 for key, decl in decls: | 
					
						
							|  |  |  |                     self._add_decl(decl, key) | 
					
						
							|  |  |  |             # The iterator will be exhausted at this point. |