xxx return clsraw item raw typedecl raw extra extra the resolved data takes precedence it is still effectively unresolved it is still effectively unresolved all the other decls have a single type decl check item check extra the item takes precedence over the extra data except if callable speed things up the next time xxx vartype xxx finish xxx finish yield from self types yield from self functions yield from self variables xxx return cls raw item raw typedecl raw _extra extra the resolved data takes precedence it is still effectively unresolved it is still effectively unresolved all the other decls have a single type decl check item check extra the item takes precedence over the extra data except if callable speed things up the next time re raise xxx vartype xxx finish xxx finish yield from self types yield from self functions yield from self variables
import os.path

from c_common import fsutil
from c_common.clsutil import classonly
import c_common.misc as _misc
from c_parser.info import (
    KIND,
    HighlevelParsedItem,
    Declaration,
    TypeDeclaration,
)
from c_parser.match import (
    is_type_decl,
)


IGNORED = _misc.Labeled('IGNORED')
UNKNOWN = _misc.Labeled('UNKNOWN')


class SystemType(TypeDeclaration):

    def __init__(self, name):
        super().__init__(None, name, None, None, _shortkey=name)


class Analyzed:
    _locked = False

    @classonly
    def is_target(cls, raw):
        if isinstance(raw, HighlevelParsedItem):
            return True
        else:
            return False

    @classonly
    def from_raw(cls, raw, **extra):
        if isinstance(raw, cls):
            if extra:
                
                raise NotImplementedError((raw, 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:
            
            extra = dict(extra_extra, **extra)
        if isinstance(typedeps, TypeDeclaration):
            return typedeps, extra
        elif typedeps in (None, UNKNOWN):
            
            return UNKNOWN, extra
        elif None in typedeps or UNKNOWN in typedeps:
            
            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):
            
            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
        
        if not isinstance(item, HighlevelParsedItem):
            raise ValueError(f'"item" must be a high-level parsed item, got {item!r}')
        
        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)
        
        try:
            value = getattr(self.item, name)
            if callable(value):
                raise AttributeError(name)
        except AttributeError:
            try:
                value = self._extra[name]
            except KeyError:
                pass
            else:
                
                self.__dict__[name] = value
                return value
            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):
        
        ...

    @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):
        
        return self.item.as_rowdata(columns)

    def render_rowdata(self, columns=None):
        
        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._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