1 | # Copyright Bruno da Silva de Oliveira 2003. Use, modification and |
---|
2 | # distribution is subject to the Boost Software License, Version 1.0. |
---|
3 | # (See accompanying file LICENSE_1_0.txt or copy at |
---|
4 | # http:#www.boost.org/LICENSE_1_0.txt) |
---|
5 | |
---|
6 | from declarations import * |
---|
7 | # try to use cElementTree if avaiable |
---|
8 | try: |
---|
9 | from cElementTree import ElementTree |
---|
10 | except ImportError: |
---|
11 | # fall back to the normal elementtree |
---|
12 | from elementtree.ElementTree import ElementTree |
---|
13 | from xml.parsers.expat import ExpatError |
---|
14 | from copy import deepcopy |
---|
15 | from utils import enumerate |
---|
16 | |
---|
17 | |
---|
18 | #============================================================================== |
---|
19 | # Exceptions |
---|
20 | #============================================================================== |
---|
21 | class InvalidXMLError(Exception): pass |
---|
22 | |
---|
23 | class ParserError(Exception): pass |
---|
24 | |
---|
25 | class InvalidContextError(ParserError): pass |
---|
26 | |
---|
27 | |
---|
28 | #============================================================================== |
---|
29 | # GCCXMLParser |
---|
30 | #============================================================================== |
---|
31 | class GCCXMLParser(object): |
---|
32 | 'Parse a GCC_XML file and extract the top-level declarations.' |
---|
33 | |
---|
34 | interested_tags = {'Class':0, 'Function':0, 'Variable':0, 'Enumeration':0} |
---|
35 | |
---|
36 | def Parse(self, filename): |
---|
37 | self.elements = self.GetElementsFromXML(filename) |
---|
38 | # high level declarations |
---|
39 | self.declarations = [] |
---|
40 | self._names = {} |
---|
41 | # parse the elements |
---|
42 | for id in self.elements: |
---|
43 | element, decl = self.elements[id] |
---|
44 | if decl is None: |
---|
45 | try: |
---|
46 | self.ParseElement(id, element) |
---|
47 | except InvalidContextError: |
---|
48 | pass # ignore those nodes with invalid context |
---|
49 | # (workaround gccxml bug) |
---|
50 | |
---|
51 | |
---|
52 | def Declarations(self): |
---|
53 | return self.declarations |
---|
54 | |
---|
55 | |
---|
56 | def AddDecl(self, decl): |
---|
57 | if decl.FullName() in self._names: |
---|
58 | decl.is_unique= False |
---|
59 | for d in self.declarations: |
---|
60 | if d.FullName() == decl.FullName(): |
---|
61 | d.is_unique = False |
---|
62 | self._names[decl.FullName()] = 0 |
---|
63 | self.declarations.append(decl) |
---|
64 | |
---|
65 | |
---|
66 | def ParseElement(self, id, element): |
---|
67 | method = 'Parse' + element.tag |
---|
68 | if hasattr(self, method): |
---|
69 | func = getattr(self, method) |
---|
70 | func(id, element) |
---|
71 | else: |
---|
72 | self.ParseUnknown(id, element) |
---|
73 | |
---|
74 | |
---|
75 | def GetElementsFromXML(self,filename): |
---|
76 | 'Extracts a dictionary of elements from the gcc_xml file.' |
---|
77 | |
---|
78 | tree = ElementTree() |
---|
79 | try: |
---|
80 | tree.parse(filename) |
---|
81 | except ExpatError: |
---|
82 | raise InvalidXMLError, 'Not a XML file: %s' % filename |
---|
83 | |
---|
84 | root = tree.getroot() |
---|
85 | if root.tag != 'GCC_XML': |
---|
86 | raise InvalidXMLError, 'Not a valid GCC_XML file' |
---|
87 | |
---|
88 | # build a dictionary of id -> element, None |
---|
89 | elementlist = root.getchildren() |
---|
90 | elements = {} |
---|
91 | for element in elementlist: |
---|
92 | id = element.get('id') |
---|
93 | if id: |
---|
94 | elements[id] = element, None |
---|
95 | return elements |
---|
96 | |
---|
97 | |
---|
98 | def GetDecl(self, id): |
---|
99 | if id not in self.elements: |
---|
100 | if id == '_0': |
---|
101 | raise InvalidContextError, 'Invalid context found in the xml file.' |
---|
102 | else: |
---|
103 | msg = 'ID not found in elements: %s' % id |
---|
104 | raise ParserError, msg |
---|
105 | |
---|
106 | elem, decl = self.elements[id] |
---|
107 | if decl is None: |
---|
108 | self.ParseElement(id, elem) |
---|
109 | elem, decl = self.elements[id] |
---|
110 | if decl is None: |
---|
111 | raise ParserError, 'Could not parse element: %s' % elem.tag |
---|
112 | return decl |
---|
113 | |
---|
114 | |
---|
115 | def GetType(self, id): |
---|
116 | def Check(id, feature): |
---|
117 | pos = id.find(feature) |
---|
118 | if pos != -1: |
---|
119 | id = id[:pos] + id[pos+1:] |
---|
120 | return True, id |
---|
121 | else: |
---|
122 | return False, id |
---|
123 | const, id = Check(id, 'c') |
---|
124 | volatile, id = Check(id, 'v') |
---|
125 | restricted, id = Check(id, 'r') |
---|
126 | decl = self.GetDecl(id) |
---|
127 | if isinstance(decl, Type): |
---|
128 | res = deepcopy(decl) |
---|
129 | if const: |
---|
130 | res.const = const |
---|
131 | if volatile: |
---|
132 | res.volatile = volatile |
---|
133 | if restricted: |
---|
134 | res.restricted = restricted |
---|
135 | else: |
---|
136 | res = Type(decl.FullName(), const) |
---|
137 | res.volatile = volatile |
---|
138 | res.restricted = restricted |
---|
139 | return res |
---|
140 | |
---|
141 | |
---|
142 | def GetLocation(self, location): |
---|
143 | file, line = location.split(':') |
---|
144 | file = self.GetDecl(file) |
---|
145 | return file, int(line) |
---|
146 | |
---|
147 | |
---|
148 | def Update(self, id, decl): |
---|
149 | element, _ = self.elements[id] |
---|
150 | self.elements[id] = element, decl |
---|
151 | |
---|
152 | |
---|
153 | def ParseUnknown(self, id, element): |
---|
154 | name = '__Unknown_Element_%s' % id |
---|
155 | decl = Unknown(name) |
---|
156 | self.Update(id, decl) |
---|
157 | |
---|
158 | |
---|
159 | def ParseNamespace(self, id, element): |
---|
160 | namespace = element.get('name') |
---|
161 | context = element.get('context') |
---|
162 | if context: |
---|
163 | outer = self.GetDecl(context) |
---|
164 | if not outer.endswith('::'): |
---|
165 | outer += '::' |
---|
166 | namespace = outer + namespace |
---|
167 | if namespace.startswith('::'): |
---|
168 | namespace = namespace[2:] |
---|
169 | self.Update(id, namespace) |
---|
170 | |
---|
171 | |
---|
172 | def ParseFile(self, id, element): |
---|
173 | filename = element.get('name') |
---|
174 | self.Update(id, filename) |
---|
175 | |
---|
176 | |
---|
177 | def ParseVariable(self, id, element): |
---|
178 | # in gcc_xml, a static Field is declared as a Variable, so we check |
---|
179 | # this and call the Field parser. |
---|
180 | context = self.GetDecl(element.get('context')) |
---|
181 | if isinstance(context, Class): |
---|
182 | self.ParseField(id, element) |
---|
183 | elem, decl = self.elements[id] |
---|
184 | decl.static = True |
---|
185 | else: |
---|
186 | namespace = context |
---|
187 | name = element.get('name') |
---|
188 | type_ = self.GetType(element.get('type')) |
---|
189 | location = self.GetLocation(element.get('location')) |
---|
190 | variable = Variable(type_, name, namespace) |
---|
191 | variable.location = location |
---|
192 | self.AddDecl(variable) |
---|
193 | self.Update(id, variable) |
---|
194 | |
---|
195 | |
---|
196 | def GetArguments(self, element): |
---|
197 | args = [] |
---|
198 | for child in element: |
---|
199 | if child.tag == 'Argument': |
---|
200 | type = self.GetType(child.get('type')) |
---|
201 | type.default = child.get('default') |
---|
202 | args.append(type) |
---|
203 | return args |
---|
204 | |
---|
205 | |
---|
206 | def GetExceptions(self, exception_list): |
---|
207 | if exception_list is None: |
---|
208 | return None |
---|
209 | |
---|
210 | exceptions = [] |
---|
211 | for t in exception_list.split(): |
---|
212 | exceptions.append(self.GetType(t)) |
---|
213 | |
---|
214 | return exceptions |
---|
215 | |
---|
216 | |
---|
217 | def ParseFunction(self, id, element, functionType=Function): |
---|
218 | '''functionType is used because a Operator is identical to a normal |
---|
219 | function, only the type of the function changes.''' |
---|
220 | name = element.get('name') |
---|
221 | returns = self.GetType(element.get('returns')) |
---|
222 | namespace = self.GetDecl(element.get('context')) |
---|
223 | location = self.GetLocation(element.get('location')) |
---|
224 | params = self.GetArguments(element) |
---|
225 | incomplete = bool(int(element.get('incomplete', 0))) |
---|
226 | throws = self.GetExceptions(element.get('throw', None)) |
---|
227 | function = functionType(name, namespace, returns, params, throws) |
---|
228 | function.location = location |
---|
229 | self.AddDecl(function) |
---|
230 | self.Update(id, function) |
---|
231 | |
---|
232 | |
---|
233 | def ParseOperatorFunction(self, id, element): |
---|
234 | self.ParseFunction(id, element, Operator) |
---|
235 | |
---|
236 | |
---|
237 | def GetHierarchy(self, bases): |
---|
238 | '''Parses the string "bases" from the xml into a list of tuples of Base |
---|
239 | instances. The first tuple is the most direct inheritance, and then it |
---|
240 | goes up in the hierarchy. |
---|
241 | ''' |
---|
242 | |
---|
243 | if bases is None: |
---|
244 | return [] |
---|
245 | base_names = bases.split() |
---|
246 | this_level = [] |
---|
247 | next_levels = [] |
---|
248 | for base in base_names: |
---|
249 | # get the visibility |
---|
250 | split = base.split(':') |
---|
251 | if len(split) == 2: |
---|
252 | visib = split[0] |
---|
253 | base = split[1] |
---|
254 | else: |
---|
255 | visib = Scope.public |
---|
256 | decl = self.GetDecl(base) |
---|
257 | if not isinstance(decl, Class): |
---|
258 | # on windows, there are some classes which "bases" points to an |
---|
259 | # "Unimplemented" tag, but we are not interested in this classes |
---|
260 | # anyway |
---|
261 | continue |
---|
262 | base = Base(decl.FullName(), visib) |
---|
263 | this_level.append(base) |
---|
264 | # normalize with the other levels |
---|
265 | for index, level in enumerate(decl.hierarchy): |
---|
266 | if index < len(next_levels): |
---|
267 | next_levels[index] = next_levels[index] + level |
---|
268 | else: |
---|
269 | next_levels.append(level) |
---|
270 | hierarchy = [] |
---|
271 | if this_level: |
---|
272 | hierarchy.append(tuple(this_level)) |
---|
273 | if next_levels: |
---|
274 | hierarchy.extend(next_levels) |
---|
275 | return hierarchy |
---|
276 | |
---|
277 | |
---|
278 | def GetMembers(self, member_list): |
---|
279 | # members must be a string with the ids of the members |
---|
280 | if member_list is None: |
---|
281 | return [] |
---|
282 | members = [] |
---|
283 | for member in member_list.split(): |
---|
284 | decl = self.GetDecl(member) |
---|
285 | if type(decl) in Class.ValidMemberTypes(): |
---|
286 | members.append(decl) |
---|
287 | return members |
---|
288 | |
---|
289 | |
---|
290 | def ParseClass(self, id, element): |
---|
291 | name = element.get('name') |
---|
292 | abstract = bool(int(element.get('abstract', '0'))) |
---|
293 | location = self.GetLocation(element.get('location')) |
---|
294 | context = self.GetDecl(element.get('context')) |
---|
295 | incomplete = bool(int(element.get('incomplete', 0))) |
---|
296 | if isinstance(context, str): |
---|
297 | class_ = Class(name, context, [], abstract) |
---|
298 | else: |
---|
299 | # a nested class |
---|
300 | visib = element.get('access', Scope.public) |
---|
301 | class_ = NestedClass( |
---|
302 | name, context.FullName(), visib, [], abstract) |
---|
303 | class_.incomplete = incomplete |
---|
304 | # we have to add the declaration of the class before trying |
---|
305 | # to parse its members and bases, to avoid recursion. |
---|
306 | self.AddDecl(class_) |
---|
307 | class_.location = location |
---|
308 | self.Update(id, class_) |
---|
309 | # now we can get the members and the bases |
---|
310 | class_.hierarchy = self.GetHierarchy(element.get('bases')) |
---|
311 | if class_.hierarchy: |
---|
312 | class_.bases = class_.hierarchy[0] |
---|
313 | members = self.GetMembers(element.get('members')) |
---|
314 | for member in members: |
---|
315 | class_.AddMember(member) |
---|
316 | |
---|
317 | |
---|
318 | def ParseStruct(self, id, element): |
---|
319 | self.ParseClass(id, element) |
---|
320 | |
---|
321 | |
---|
322 | FUNDAMENTAL_RENAME = { |
---|
323 | 'long long int' : 'boost::int64_t', |
---|
324 | 'long long unsigned int' : 'boost::uint64_t', |
---|
325 | } |
---|
326 | |
---|
327 | def ParseFundamentalType(self, id, element): |
---|
328 | name = element.get('name') |
---|
329 | name = self.FUNDAMENTAL_RENAME.get(name, name) |
---|
330 | type_ = FundamentalType(name) |
---|
331 | self.Update(id, type_) |
---|
332 | |
---|
333 | |
---|
334 | def ParseArrayType(self, id, element): |
---|
335 | type = self.GetType(element.get('type')) |
---|
336 | min = element.get('min') |
---|
337 | max = element.get('max') |
---|
338 | array = ArrayType(type.name, type.const, min, max) |
---|
339 | self.Update(id, array) |
---|
340 | |
---|
341 | |
---|
342 | def ParseReferenceType(self, id, element): |
---|
343 | type = self.GetType(element.get('type')) |
---|
344 | expand = not isinstance(type, FunctionType) |
---|
345 | ref = ReferenceType(type.name, type.const, None, expand, type.suffix) |
---|
346 | self.Update(id, ref) |
---|
347 | |
---|
348 | |
---|
349 | def ParsePointerType(self, id, element): |
---|
350 | type = self.GetType(element.get('type')) |
---|
351 | expand = not isinstance(type, FunctionType) |
---|
352 | ref = PointerType(type.name, type.const, None, expand, type.suffix) |
---|
353 | self.Update(id, ref) |
---|
354 | |
---|
355 | |
---|
356 | def ParseFunctionType(self, id, element): |
---|
357 | result = self.GetType(element.get('returns')) |
---|
358 | args = self.GetArguments(element) |
---|
359 | func = FunctionType(result, args) |
---|
360 | self.Update(id, func) |
---|
361 | |
---|
362 | |
---|
363 | def ParseMethodType(self, id, element): |
---|
364 | class_ = self.GetDecl(element.get('basetype')).FullName() |
---|
365 | result = self.GetType(element.get('returns')) |
---|
366 | args = self.GetArguments(element) |
---|
367 | method = MethodType(result, args, class_) |
---|
368 | self.Update(id, method) |
---|
369 | |
---|
370 | |
---|
371 | def ParseField(self, id, element): |
---|
372 | name = element.get('name') |
---|
373 | visib = element.get('access', Scope.public) |
---|
374 | classname = self.GetDecl(element.get('context')).FullName() |
---|
375 | type_ = self.GetType(element.get('type')) |
---|
376 | static = bool(int(element.get('extern', '0'))) |
---|
377 | location = self.GetLocation(element.get('location')) |
---|
378 | var = ClassVariable(type_, name, classname, visib, static) |
---|
379 | var.location = location |
---|
380 | self.Update(id, var) |
---|
381 | |
---|
382 | |
---|
383 | def ParseMethod(self, id, element, methodType=Method): |
---|
384 | name = element.get('name') |
---|
385 | result = self.GetType(element.get('returns')) |
---|
386 | classname = self.GetDecl(element.get('context')).FullName() |
---|
387 | visib = element.get('access', Scope.public) |
---|
388 | static = bool(int(element.get('static', '0'))) |
---|
389 | virtual = bool(int(element.get('virtual', '0'))) |
---|
390 | abstract = bool(int(element.get('pure_virtual', '0'))) |
---|
391 | const = bool(int(element.get('const', '0'))) |
---|
392 | location = self.GetLocation(element.get('location')) |
---|
393 | throws = self.GetExceptions(element.get('throw', None)) |
---|
394 | params = self.GetArguments(element) |
---|
395 | method = methodType( |
---|
396 | name, classname, result, params, visib, virtual, abstract, static, const, throws) |
---|
397 | method.location = location |
---|
398 | self.Update(id, method) |
---|
399 | |
---|
400 | |
---|
401 | def ParseOperatorMethod(self, id, element): |
---|
402 | self.ParseMethod(id, element, ClassOperator) |
---|
403 | |
---|
404 | |
---|
405 | def ParseConstructor(self, id, element): |
---|
406 | name = element.get('name') |
---|
407 | visib = element.get('access', Scope.public) |
---|
408 | classname = self.GetDecl(element.get('context')).FullName() |
---|
409 | location = self.GetLocation(element.get('location')) |
---|
410 | params = self.GetArguments(element) |
---|
411 | artificial = element.get('artificial', False) |
---|
412 | ctor = Constructor(name, classname, params, visib) |
---|
413 | ctor.location = location |
---|
414 | self.Update(id, ctor) |
---|
415 | |
---|
416 | |
---|
417 | def ParseDestructor(self, id, element): |
---|
418 | name = element.get('name') |
---|
419 | visib = element.get('access', Scope.public) |
---|
420 | classname = self.GetDecl(element.get('context')).FullName() |
---|
421 | virtual = bool(int(element.get('virtual', '0'))) |
---|
422 | location = self.GetLocation(element.get('location')) |
---|
423 | des = Destructor(name, classname, visib, virtual) |
---|
424 | des.location = location |
---|
425 | self.Update(id, des) |
---|
426 | |
---|
427 | |
---|
428 | def ParseConverter(self, id, element): |
---|
429 | self.ParseMethod(id, element, ConverterOperator) |
---|
430 | |
---|
431 | |
---|
432 | def ParseTypedef(self, id, element): |
---|
433 | name = element.get('name') |
---|
434 | type = self.GetType(element.get('type')) |
---|
435 | context = self.GetDecl(element.get('context')) |
---|
436 | if isinstance(context, Class): |
---|
437 | context = context.FullName() |
---|
438 | typedef = Typedef(type, name, context) |
---|
439 | self.Update(id, typedef) |
---|
440 | self.AddDecl(typedef) |
---|
441 | |
---|
442 | |
---|
443 | def ParseEnumeration(self, id, element): |
---|
444 | name = element.get('name') |
---|
445 | location = self.GetLocation(element.get('location')) |
---|
446 | context = self.GetDecl(element.get('context')) |
---|
447 | incomplete = bool(int(element.get('incomplete', 0))) |
---|
448 | if isinstance(context, str): |
---|
449 | enum = Enumeration(name, context) |
---|
450 | else: |
---|
451 | visib = element.get('access', Scope.public) |
---|
452 | enum = ClassEnumeration(name, context.FullName(), visib) |
---|
453 | self.AddDecl(enum) |
---|
454 | enum.location = location |
---|
455 | for child in element: |
---|
456 | if child.tag == 'EnumValue': |
---|
457 | name = child.get('name') |
---|
458 | value = int(child.get('init')) |
---|
459 | enum.values[name] = value |
---|
460 | enum.incomplete = incomplete |
---|
461 | self.Update(id, enum) |
---|
462 | |
---|
463 | |
---|
464 | |
---|
465 | def ParseDeclarations(filename): |
---|
466 | 'Returns a list of the top declarations found in the gcc_xml file.' |
---|
467 | |
---|
468 | parser = GCCXMLParser() |
---|
469 | parser.Parse(filename) |
---|
470 | return parser.Declarations() |
---|
471 | |
---|
472 | |
---|
473 | if __name__ == '__main__': |
---|
474 | ParseDeclarations(r'D:\Programming\Libraries\boost-cvs\boost\libs\python\pyste\example\test.xml') |
---|