1from collections import namedtuple 2import os.path 3 4from c_common import fsutil 5from c_common.clsutil import classonly 6import c_common.misc as _misc 7from c_parser.info import ( 8 KIND, 9 HighlevelParsedItem, 10 Declaration, 11 TypeDeclaration, 12) 13from c_parser.match import ( 14 is_type_decl, 15) 16from .match import ( 17 is_process_global, 18) 19 20 21IGNORED = _misc.Labeled('IGNORED') 22UNKNOWN = _misc.Labeled('UNKNOWN') 23 24 25class SystemType(TypeDeclaration): 26 27 def __init__(self, name): 28 super().__init__(None, name, None, None, _shortkey=name) 29 30 31class Analyzed: 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 271class Analysis: 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 325