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