1 import os.path
2
3 from c_common import fsutil
4 from c_common.clsutil import classonly
5 import c_common.misc as _misc
6 from c_parser.info import (
7 KIND,
8 HighlevelParsedItem,
9 Declaration,
10 TypeDeclaration,
11 )
12 from c_parser.match import (
13 is_type_decl,
14 )
15
16
17 IGNORED = _misc.Labeled('IGNORED')
18 UNKNOWN = _misc.Labeled('UNKNOWN')
19
20
21 class ESC[4;38;5;81mSystemType(ESC[4;38;5;149mTypeDeclaration):
22
23 def __init__(self, name):
24 super().__init__(None, name, None, None, _shortkey=name)
25
26
27 class ESC[4;38;5;81mAnalyzed:
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
267 class ESC[4;38;5;81mAnalysis:
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