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