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