1  from collections import namedtuple
       2  import os.path
       3  
       4  from c_common import fsutil
       5  from c_common.clsutil import classonly
       6  import c_common.misc as _misc
       7  from c_parser.info import (
       8      KIND,
       9      HighlevelParsedItem,
      10      Declaration,
      11      TypeDeclaration,
      12  )
      13  from c_parser.match import (
      14      is_type_decl,
      15  )
      16  from .match import (
      17      is_process_global,
      18  )
      19  
      20  
      21  IGNORED = _misc.Labeled('IGNORED')
      22  UNKNOWN = _misc.Labeled('UNKNOWN')
      23  
      24  
      25  class ESC[4;38;5;81mSystemType(ESC[4;38;5;149mTypeDeclaration):
      26  
      27      def __init__(self, name):
      28          super().__init__(None, name, None, None, _shortkey=name)
      29  
      30  
      31  class ESC[4;38;5;81mAnalyzed:
      32      _locked = False
      33  
      34      @classonly
      35      def is_target(cls, raw):
      36          if isinstance(raw, HighlevelParsedItem):
      37              return True
      38          else:
      39              return False
      40  
      41      @classonly
      42      def from_raw(cls, raw, **extra):
      43          if isinstance(raw, cls):
      44              if extra:
      45                  # XXX ?
      46                  raise NotImplementedError((raw, extra))
      47                  #return cls(raw.item, raw.typedecl, **raw._extra, **extra)
      48              else:
      49                  return info
      50          elif cls.is_target(raw):
      51              return cls(raw, **extra)
      52          else:
      53              raise NotImplementedError((raw, extra))
      54  
      55      @classonly
      56      def from_resolved(cls, item, resolved, **extra):
      57          if isinstance(resolved, TypeDeclaration):
      58              return cls(item, typedecl=resolved, **extra)
      59          else:
      60              typedeps, extra = cls._parse_raw_resolved(item, resolved, extra)
      61              if item.kind is KIND.ENUM:
      62                  if typedeps:
      63                      raise NotImplementedError((item, resolved, extra))
      64              elif not typedeps:
      65                  raise NotImplementedError((item, resolved, extra))
      66              return cls(item, typedeps, **extra or {})
      67  
      68      @classonly
      69      def _parse_raw_resolved(cls, item, resolved, extra_extra):
      70          if resolved in (UNKNOWN, IGNORED):
      71              return resolved, None
      72          try:
      73              typedeps, extra = resolved
      74          except (TypeError, ValueError):
      75              typedeps = extra = None
      76          if extra:
      77              # The resolved data takes precedence.
      78              extra = dict(extra_extra, **extra)
      79          if isinstance(typedeps, TypeDeclaration):
      80              return typedeps, extra
      81          elif typedeps in (None, UNKNOWN):
      82              # It is still effectively unresolved.
      83              return UNKNOWN, extra
      84          elif None in typedeps or UNKNOWN in typedeps:
      85              # It is still effectively unresolved.
      86              return typedeps, extra
      87          elif any(not isinstance(td, TypeDeclaration) for td in typedeps):
      88              raise NotImplementedError((item, typedeps, extra))
      89          return typedeps, extra
      90  
      91      def __init__(self, item, typedecl=None, **extra):
      92          assert item is not None
      93          self.item = item
      94          if typedecl in (UNKNOWN, IGNORED):
      95              pass
      96          elif item.kind is KIND.STRUCT or item.kind is KIND.UNION:
      97              if isinstance(typedecl, TypeDeclaration):
      98                  raise NotImplementedError(item, typedecl)
      99              elif typedecl is None:
     100                  typedecl = UNKNOWN
     101              else:
     102                  typedecl = [UNKNOWN if d is None else d for d in typedecl]
     103          elif typedecl is None:
     104              typedecl = UNKNOWN
     105          elif typedecl and not isinstance(typedecl, TypeDeclaration):
     106              # All the other decls have a single type decl.
     107              typedecl, = typedecl
     108              if typedecl is None:
     109                  typedecl = UNKNOWN
     110          self.typedecl = typedecl
     111          self._extra = extra
     112          self._locked = True
     113  
     114          self._validate()
     115  
     116      def _validate(self):
     117          item = self.item
     118          extra = self._extra
     119          # Check item.
     120          if not isinstance(item, HighlevelParsedItem):
     121              raise ValueError(f'"item" must be a high-level parsed item, got {item!r}')
     122          # Check extra.
     123          for key, value in extra.items():
     124              if key.startswith('_'):
     125                  raise ValueError(f'extra items starting with {"_"!r} not allowed, got {extra!r}')
     126              if hasattr(item, key) and not callable(getattr(item, key)):
     127                  raise ValueError(f'extra cannot override item, got {value!r} for key {key!r}')
     128  
     129      def __repr__(self):
     130          kwargs = [
     131              f'item={self.item!r}',
     132              f'typedecl={self.typedecl!r}',
     133              *(f'{k}={v!r}' for k, v in self._extra.items())
     134          ]
     135          return f'{type(self).__name__}({", ".join(kwargs)})'
     136  
     137      def __str__(self):
     138          try:
     139              return self._str
     140          except AttributeError:
     141              self._str, = self.render('line')
     142              return self._str
     143  
     144      def __hash__(self):
     145          return hash(self.item)
     146  
     147      def __eq__(self, other):
     148          if isinstance(other, Analyzed):
     149              return self.item == other.item
     150          elif isinstance(other, HighlevelParsedItem):
     151              return self.item == other
     152          elif type(other) is tuple:
     153              return self.item == other
     154          else:
     155              return NotImplemented
     156  
     157      def __gt__(self, other):
     158          if isinstance(other, Analyzed):
     159              return self.item > other.item
     160          elif isinstance(other, HighlevelParsedItem):
     161              return self.item > other
     162          elif type(other) is tuple:
     163              return self.item > other
     164          else:
     165              return NotImplemented
     166  
     167      def __dir__(self):
     168          names = set(super().__dir__())
     169          names.update(self._extra)
     170          names.remove('_locked')
     171          return sorted(names)
     172  
     173      def __getattr__(self, name):
     174          if name.startswith('_'):
     175              raise AttributeError(name)
     176          # The item takes precedence over the extra data (except if callable).
     177          try:
     178              value = getattr(self.item, name)
     179              if callable(value):
     180                  raise AttributeError(name)
     181          except AttributeError:
     182              try:
     183                  value = self._extra[name]
     184              except KeyError:
     185                  pass
     186              else:
     187                  # Speed things up the next time.
     188                  self.__dict__[name] = value
     189                  return value
     190              raise  # re-raise
     191          else:
     192              return value
     193  
     194      def __setattr__(self, name, value):
     195          if self._locked and name != '_str':
     196              raise AttributeError(f'readonly ({name})')
     197          super().__setattr__(name, value)
     198  
     199      def __delattr__(self, name):
     200          if self._locked:
     201              raise AttributeError(f'readonly ({name})')
     202          super().__delattr__(name)
     203  
     204      @property
     205      def decl(self):
     206          if not isinstance(self.item, Declaration):
     207              raise AttributeError('decl')
     208          return self.item
     209  
     210      @property
     211      def signature(self):
     212          # XXX vartype...
     213          ...
     214  
     215      @property
     216      def istype(self):
     217          return is_type_decl(self.item.kind)
     218  
     219      @property
     220      def is_known(self):
     221          if self.typedecl in (UNKNOWN, IGNORED):
     222              return False
     223          elif isinstance(self.typedecl, TypeDeclaration):
     224              return True
     225          else:
     226              return UNKNOWN not in self.typedecl
     227  
     228      def fix_filename(self, relroot=fsutil.USE_CWD, **kwargs):
     229          self.item.fix_filename(relroot, **kwargs)
     230          return self
     231  
     232      def as_rowdata(self, columns=None):
     233          # XXX finish!
     234          return self.item.as_rowdata(columns)
     235  
     236      def render_rowdata(self, columns=None):
     237          # XXX finish!
     238          return self.item.render_rowdata(columns)
     239  
     240      def render(self, fmt='line', *, itemonly=False):
     241          if fmt == 'raw':
     242              yield repr(self)
     243              return
     244          rendered = self.item.render(fmt)
     245          if itemonly or not self._extra:
     246              yield from rendered
     247              return
     248          extra = self._render_extra(fmt)
     249          if not extra:
     250              yield from rendered
     251          elif fmt in ('brief', 'line'):
     252              rendered, = rendered
     253              extra, = extra
     254              yield f'{rendered}\t{extra}'
     255          elif fmt == 'summary':
     256              raise NotImplementedError(fmt)
     257          elif fmt == 'full':
     258              yield from rendered
     259              for line in extra:
     260                  yield f'\t{line}'
     261          else:
     262              raise NotImplementedError(fmt)
     263  
     264      def _render_extra(self, fmt):
     265          if fmt in ('brief', 'line'):
     266              yield str(self._extra)
     267          else:
     268              raise NotImplementedError(fmt)
     269  
     270  
     271  class ESC[4;38;5;81mAnalysis:
     272  
     273      _item_class = Analyzed
     274  
     275      @classonly
     276      def build_item(cls, info, resolved=None, **extra):
     277          if resolved is None:
     278              return cls._item_class.from_raw(info, **extra)
     279          else:
     280              return cls._item_class.from_resolved(info, resolved, **extra)
     281  
     282      @classmethod
     283      def from_results(cls, results):
     284          self = cls()
     285          for info, resolved in results:
     286              self._add_result(info, resolved)
     287          return self
     288  
     289      def __init__(self, items=None):
     290          self._analyzed = {type(self).build_item(item): None
     291                            for item in items or ()}
     292  
     293      def __repr__(self):
     294          return f'{type(self).__name__}({list(self._analyzed.keys())})'
     295  
     296      def __iter__(self):
     297          #yield from self.types
     298          #yield from self.functions
     299          #yield from self.variables
     300          yield from self._analyzed
     301  
     302      def __len__(self):
     303          return len(self._analyzed)
     304  
     305      def __getitem__(self, key):
     306          if type(key) is int:
     307              for i, val in enumerate(self._analyzed):
     308                  if i == key:
     309                      return val
     310              else:
     311                  raise IndexError(key)
     312          else:
     313              return self._analyzed[key]
     314  
     315      def fix_filenames(self, relroot=fsutil.USE_CWD, **kwargs):
     316          if relroot and relroot is not fsutil.USE_CWD:
     317              relroot = os.path.abspath(relroot)
     318          for item in self._analyzed:
     319              item.fix_filename(relroot, fixroot=False, **kwargs)
     320  
     321      def _add_result(self, info, resolved):
     322          analyzed = type(self).build_item(info, resolved)
     323          self._analyzed[analyzed] = None
     324          return analyzed