summaryrefslogtreecommitdiffstats
path: root/Tools/c-analyzer/c_analyzer/common
diff options
context:
space:
mode:
Diffstat (limited to 'Tools/c-analyzer/c_analyzer/common')
-rw-r--r--Tools/c-analyzer/c_analyzer/common/__init__.py0
-rw-r--r--Tools/c-analyzer/c_analyzer/common/files.py124
-rw-r--r--Tools/c-analyzer/c_analyzer/common/info.py138
-rw-r--r--Tools/c-analyzer/c_analyzer/common/show.py11
-rw-r--r--Tools/c-analyzer/c_analyzer/common/util.py243
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