diff options
Diffstat (limited to 'Tools/c-analyzer/c_analyzer/common')
-rw-r--r-- | Tools/c-analyzer/c_analyzer/common/__init__.py | 0 | ||||
-rw-r--r-- | Tools/c-analyzer/c_analyzer/common/files.py | 124 | ||||
-rw-r--r-- | Tools/c-analyzer/c_analyzer/common/info.py | 138 | ||||
-rw-r--r-- | Tools/c-analyzer/c_analyzer/common/show.py | 11 | ||||
-rw-r--r-- | Tools/c-analyzer/c_analyzer/common/util.py | 243 |
5 files changed, 0 insertions, 516 deletions
diff --git a/Tools/c-analyzer/c_analyzer/common/__init__.py b/Tools/c-analyzer/c_analyzer/common/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/Tools/c-analyzer/c_analyzer/common/__init__.py +++ /dev/null diff --git a/Tools/c-analyzer/c_analyzer/common/files.py b/Tools/c-analyzer/c_analyzer/common/files.py deleted file mode 100644 index a8a0447..0000000 --- a/Tools/c-analyzer/c_analyzer/common/files.py +++ /dev/null @@ -1,124 +0,0 @@ -import glob -import os -import os.path - -# XXX need tests: -# * walk_tree() -# * glob_tree() -# * iter_files_by_suffix() - - -C_SOURCE_SUFFIXES = ('.c', '.h') - - -def _walk_tree(root, *, - _walk=os.walk, - ): - # A wrapper around os.walk that resolves the filenames. - for parent, _, names in _walk(root): - for name in names: - yield os.path.join(parent, name) - - -def walk_tree(root, *, - suffix=None, - walk=_walk_tree, - ): - """Yield each file in the tree under the given directory name. - - If "suffix" is provided then only files with that suffix will - be included. - """ - if suffix and not isinstance(suffix, str): - raise ValueError('suffix must be a string') - - for filename in walk(root): - if suffix and not filename.endswith(suffix): - continue - yield filename - - -def glob_tree(root, *, - suffix=None, - _glob=glob.iglob, - _escape=glob.escape, - _join=os.path.join, - ): - """Yield each file in the tree under the given directory name. - - If "suffix" is provided then only files with that suffix will - be included. - """ - suffix = suffix or '' - if not isinstance(suffix, str): - raise ValueError('suffix must be a string') - - for filename in _glob(_join(_escape(root), f'*{suffix}')): - yield filename - for filename in _glob(_join(_escape(root), f'**/*{suffix}')): - yield filename - - -def iter_files(root, suffix=None, relparent=None, *, - get_files=None, - _glob=glob_tree, - _walk=walk_tree, - ): - """Yield each file in the tree under the given directory name. - - If "root" is a non-string iterable then do the same for each of - those trees. - - If "suffix" is provided then only files with that suffix will - be included. - - if "relparent" is provided then it is used to resolve each - filename as a relative path. - """ - if get_files is None: - get_files = os.walk - if not isinstance(root, str): - roots = root - for root in roots: - yield from iter_files(root, suffix, relparent, - get_files=get_files, - _glob=_glob, _walk=_walk) - return - - # Use the right "walk" function. - if get_files in (glob.glob, glob.iglob, glob_tree): - get_files = _glob - else: - _files = _walk_tree if get_files in (os.walk, walk_tree) else get_files - get_files = (lambda *a, **k: _walk(*a, walk=_files, **k)) - - # Handle a single suffix. - if suffix and not isinstance(suffix, str): - filenames = get_files(root) - suffix = tuple(suffix) - else: - filenames = get_files(root, suffix=suffix) - suffix = None - - for filename in filenames: - if suffix and not isinstance(suffix, str): # multiple suffixes - if not filename.endswith(suffix): - continue - if relparent: - filename = os.path.relpath(filename, relparent) - yield filename - - -def iter_files_by_suffix(root, suffixes, relparent=None, *, - walk=walk_tree, - _iter_files=iter_files, - ): - """Yield each file in the tree that has the given suffixes. - - Unlike iter_files(), the results are in the original suffix order. - """ - if isinstance(suffixes, str): - suffixes = [suffixes] - # XXX Ignore repeated suffixes? - for suffix in suffixes: - yield from _iter_files(root, suffix, relparent) diff --git a/Tools/c-analyzer/c_analyzer/common/info.py b/Tools/c-analyzer/c_analyzer/common/info.py deleted file mode 100644 index 1a853a4..0000000 --- a/Tools/c-analyzer/c_analyzer/common/info.py +++ /dev/null @@ -1,138 +0,0 @@ -from collections import namedtuple -import re - -from .util import classonly, _NTBase - -# XXX need tests: -# * ID.match() - - -UNKNOWN = '???' - -# Does not start with digit and contains at least one letter. -NAME_RE = re.compile(r'(?!\d)(?=.*?[A-Za-z])\w+', re.ASCII) - - -class ID(_NTBase, namedtuple('ID', 'filename funcname name')): - """A unique ID for a single symbol or declaration.""" - - __slots__ = () - # XXX Add optional conditions (tuple of strings) field. - #conditions = Slot() - - @classonly - def from_raw(cls, raw): - if not raw: - return None - if isinstance(raw, str): - return cls(None, None, raw) - try: - name, = raw - filename = None - except ValueError: - try: - filename, name = raw - except ValueError: - return super().from_raw(raw) - return cls(filename, None, name) - - 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, - ) - #cls.conditions.set(self, tuple(str(s) if s else None - # for s in conditions or ())) - return self - - def validate(self): - """Fail if the object is invalid (i.e. init with bad data).""" - if not self.name: - raise TypeError('missing name') - if not NAME_RE.fullmatch(self.name): - raise ValueError( - f'name must be an identifier, got {self.name!r}') - - # Symbols from a binary might not have filename/funcname info. - - if self.funcname: - if not self.filename: - raise TypeError('missing filename') - if not NAME_RE.fullmatch(self.funcname) and self.funcname != UNKNOWN: - raise ValueError( - f'name must be an identifier, got {self.funcname!r}') - - # XXX Require the filename (at least UNKONWN)? - # XXX Check the filename? - - @property - def islocal(self): - return self.funcname is not None - - def match(self, other, *, - match_files=(lambda f1, f2: f1 == f2), - ): - """Return True if the two match. - - At least one of the two must be completely valid (no UNKNOWN - anywhere). Otherwise False is returned. The remaining one - *may* have UNKNOWN for both funcname and filename. It must - have a valid name though. - - The caller is responsible for knowing which of the two is valid - (and which to use if both are valid). - """ - # First check the name. - if self.name is None: - return False - if other.name != self.name: - return False - - # Then check the filename. - if self.filename is None: - return False - if other.filename is None: - return False - if self.filename == UNKNOWN: - # "other" must be the valid one. - if other.funcname == UNKNOWN: - return False - elif self.funcname != UNKNOWN: - # XXX Try matching funcname even though we don't - # know the filename? - raise NotImplementedError - else: - return True - elif other.filename == UNKNOWN: - # "self" must be the valid one. - if self.funcname == UNKNOWN: - return False - elif other.funcname != UNKNOWN: - # XXX Try matching funcname even though we don't - # know the filename? - raise NotImplementedError - else: - return True - elif not match_files(self.filename, other.filename): - return False - - # Finally, check the funcname. - if self.funcname == UNKNOWN: - # "other" must be the valid one. - if other.funcname == UNKNOWN: - return False - else: - return other.funcname is not None - elif other.funcname == UNKNOWN: - # "self" must be the valid one. - if self.funcname == UNKNOWN: - return False - else: - return self.funcname is not None - elif self.funcname == other.funcname: - # Both are valid. - return True - - return False diff --git a/Tools/c-analyzer/c_analyzer/common/show.py b/Tools/c-analyzer/c_analyzer/common/show.py deleted file mode 100644 index 5f3cb1c..0000000 --- a/Tools/c-analyzer/c_analyzer/common/show.py +++ /dev/null @@ -1,11 +0,0 @@ - -def basic(variables, *, - _print=print): - """Print each row simply.""" - for var in variables: - if var.funcname: - line = f'{var.filename}:{var.funcname}():{var.name}' - else: - line = f'{var.filename}:{var.name}' - line = f'{line:<64} {var.vartype}' - _print(line) diff --git a/Tools/c-analyzer/c_analyzer/common/util.py b/Tools/c-analyzer/c_analyzer/common/util.py deleted file mode 100644 index 43d0bb6..0000000 --- a/Tools/c-analyzer/c_analyzer/common/util.py +++ /dev/null @@ -1,243 +0,0 @@ -import csv -import subprocess - - -_NOT_SET = object() - - -def run_cmd(argv, **kwargs): - proc = subprocess.run( - argv, - #capture_output=True, - #stderr=subprocess.STDOUT, - stdout=subprocess.PIPE, - text=True, - check=True, - **kwargs - ) - return proc.stdout - - -def read_tsv(infile, header, *, - _open=open, - _get_reader=csv.reader, - ): - """Yield each row of the given TSV (tab-separated) file.""" - if isinstance(infile, str): - with _open(infile, newline='') as infile: - yield from read_tsv(infile, header, - _open=_open, - _get_reader=_get_reader, - ) - return - lines = iter(infile) - - # Validate the header. - try: - actualheader = next(lines).strip() - except StopIteration: - actualheader = '' - if actualheader != header: - raise ValueError(f'bad header {actualheader!r}') - - for row in _get_reader(lines, delimiter='\t'): - yield tuple(v.strip() for v in row) - - -def write_tsv(outfile, header, rows, *, - _open=open, - _get_writer=csv.writer, - ): - """Write each of the rows to the given TSV (tab-separated) file.""" - if isinstance(outfile, str): - with _open(outfile, 'w', newline='') as outfile: - return write_tsv(outfile, header, rows, - _open=_open, - _get_writer=_get_writer, - ) - - if isinstance(header, str): - header = header.split('\t') - writer = _get_writer(outfile, delimiter='\t') - writer.writerow(header) - for row in rows: - writer.writerow('' if v is None else str(v) - for v in row) - - -class Slot: - """A descriptor that provides a slot. - - This is useful for types that can't have slots via __slots__, - e.g. tuple subclasses. - """ - - __slots__ = ('initial', 'default', 'readonly', 'instances', 'name') - - def __init__(self, initial=_NOT_SET, *, - default=_NOT_SET, - readonly=False, - ): - self.initial = initial - self.default = default - self.readonly = readonly - - # The instance cache is not inherently tied to the normal - # lifetime of the instances. So must do something in order to - # avoid keeping the instances alive by holding a reference here. - # Ideally we would use weakref.WeakValueDictionary to do this. - # However, most builtin types do not support weakrefs. So - # instead we monkey-patch __del__ on the attached class to clear - # the instance. - self.instances = {} - self.name = None - - def __set_name__(self, cls, name): - if self.name is not None: - raise TypeError('already used') - self.name = name - try: - slotnames = cls.__slot_names__ - except AttributeError: - slotnames = cls.__slot_names__ = [] - slotnames.append(name) - self._ensure___del__(cls, slotnames) - - def __get__(self, obj, cls): - if obj is None: # called on the class - return self - try: - value = self.instances[id(obj)] - except KeyError: - if self.initial is _NOT_SET: - value = self.default - else: - value = self.initial - self.instances[id(obj)] = value - if value is _NOT_SET: - raise AttributeError(self.name) - # XXX Optionally make a copy? - return value - - def __set__(self, obj, value): - if self.readonly: - raise AttributeError(f'{self.name} is readonly') - # XXX Optionally coerce? - self.instances[id(obj)] = value - - def __delete__(self, obj): - if self.readonly: - raise AttributeError(f'{self.name} is readonly') - self.instances[id(obj)] = self.default # XXX refleak? - - def _ensure___del__(self, cls, slotnames): # See the comment in __init__(). - try: - old___del__ = cls.__del__ - except AttributeError: - old___del__ = (lambda s: None) - else: - if getattr(old___del__, '_slotted', False): - return - - def __del__(_self): - for name in slotnames: - delattr(_self, name) - old___del__(_self) - __del__._slotted = True - cls.__del__ = __del__ - - def set(self, obj, value): - """Update the cached value for an object. - - This works even if the descriptor is read-only. This is - particularly useful when initializing the object (e.g. in - its __new__ or __init__). - """ - self.instances[id(obj)] = value - - -class classonly: - """A non-data descriptor that makes a value only visible on the class. - - This is like the "classmethod" builtin, but does not show up on - instances of the class. It may be used as a decorator. - """ - - def __init__(self, value): - self.value = value - self.getter = classmethod(value).__get__ - self.name = None - - def __set_name__(self, cls, name): - if self.name is not None: - raise TypeError('already used') - self.name = name - - def __get__(self, obj, cls): - if obj is not None: - raise AttributeError(self.name) - # called on the class - return self.getter(None, cls) - - -class _NTBase: - - __slots__ = () - - @classonly - def from_raw(cls, raw): - if not raw: - return None - elif isinstance(raw, cls): - return raw - elif isinstance(raw, str): - return cls.from_string(raw) - else: - if hasattr(raw, 'items'): - return cls(**raw) - try: - args = tuple(raw) - except TypeError: - pass - else: - return cls(*args) - raise NotImplementedError - - @classonly - def from_string(cls, value): - """Return a new instance based on the given string.""" - raise NotImplementedError - - @classmethod - def _make(cls, iterable): # The default _make() is not subclass-friendly. - return cls.__new__(cls, *iterable) - - # XXX Always validate? - #def __init__(self, *args, **kwargs): - # self.validate() - - # XXX The default __repr__() is not subclass-friendly (where the name changes). - #def __repr__(self): - # _, _, sig = super().__repr__().partition('(') - # return f'{self.__class__.__name__}({sig}' - - # To make sorting work with None: - def __lt__(self, other): - try: - return super().__lt__(other) - except TypeError: - if None in self: - return True - elif None in other: - return False - else: - raise - - def validate(self): - return - - # XXX Always validate? - #def _replace(self, **kwargs): - # obj = super()._replace(**kwargs) - # obj.validate() - # return obj |