1#!/usr/bin/env python
2
3"""
4DataCollectingParser subclasses ctypesparser.CtypesParser and builds Description
5objects from the CtypesType objects and other information from CtypesParser.
6After parsing is complete, a DescriptionCollection object can be retrieved by
7calling DataCollectingParser.data().
8"""
9
10from . import ctypesparser
11from ..descriptions import *
12from ..ctypedescs import *
13from ..expressions import *
14from ..messages import *
15from tempfile import mkstemp
16import os
17
18
19class DataCollectingParser(ctypesparser.CtypesParser, ctypesparser.CtypesTypeVisitor):
20    """Main class for the Parser component. Steps for use:
21    p=DataCollectingParser(names_of_header_files,options)
22    p.parse()
23    data=p.data() #A dictionary of constants, enums, structs, functions, etc.
24    """
25
26    def __init__(self, headers, options):
27        super(DataCollectingParser, self).__init__(options)
28        self.headers = headers
29        self.options = options
30
31        self.constants = []
32        self.typedefs = []
33        self.structs = []
34        self.enums = []
35        self.functions = []
36        self.variables = []
37        self.macros = []
38
39        self.all = []
40        self.output_order = []
41
42        # NULL is a useful macro to have defined
43        null = ConstantExpressionNode(None)
44        nullmacro = ConstantDescription("NULL", null, ("<built-in>", 1))
45        self.constants.append(nullmacro)
46        self.all.append(nullmacro)
47        self.output_order.append(("constant", nullmacro))
48
49        # A list of tuples describing macros; saved to be processed after
50        # everything else has been parsed
51        self.saved_macros = []
52        # A set of structs that are already known
53        self.already_seen_structs = set()
54        # A dict of structs that have only been seen in opaque form
55        self.already_seen_opaque_structs = {}
56        # A set of enums that are already known
57        self.already_seen_enums = set()
58        # A dict of enums that have only been seen in opaque form
59        self.already_seen_opaque_enums = {}
60
61    def parse(self):
62        fd, fname = mkstemp(suffix=".h")
63        with os.fdopen(fd, "w") as f:
64            for header in self.options.other_headers:
65                f.write("#include <%s>\n" % header)
66            for header in self.headers:
67                f.write('#include "%s"\n' % os.path.abspath(header))
68            f.flush()
69        try:
70            super(DataCollectingParser, self).parse(fname, self.options.debug_level)
71        finally:
72            os.unlink(fname)
73
74        for name, params, expr, (filename, lineno) in self.saved_macros:
75            self.handle_macro(name, params, expr, filename, lineno)
76
77    def handle_define_constant(self, name, expr, filename, lineno):
78        # Called by CParser
79        # Save to handle later
80        self.saved_macros.append((name, None, expr, (filename, lineno)))
81
82    def handle_define_unparseable(self, name, params, value, filename, lineno):
83        # Called by CParser
84        if params:
85            original_string = "#define %s(%s) %s" % (name, ",".join(params), " ".join(value))
86        else:
87            original_string = "#define %s %s" % (name, " ".join(value))
88        macro = MacroDescription(name, params, None, src=(filename, lineno))
89        macro.error('Could not parse macro "%s"' % original_string, cls="macro")
90        macro.original_string = original_string
91        self.macros.append(macro)
92        self.all.append(macro)
93        self.output_order.append(("macro", macro))
94
95    def handle_define_macro(self, name, params, expr, filename, lineno):
96        # Called by CParser
97        # Save to handle later
98        self.saved_macros.append((name, params, expr, (filename, lineno)))
99
100    def handle_undefine(self, macro, filename, lineno):
101        # save to handle later to get order correct
102        self.saved_macros.append(("#undef", None, macro, (filename, lineno)))
103
104    def handle_ctypes_typedef(self, name, ctype, filename, lineno):
105        # Called by CtypesParser
106        ctype.visit(self)
107
108        typedef = TypedefDescription(name, ctype, src=(filename, repr(lineno)))
109
110        self.typedefs.append(typedef)
111        self.all.append(typedef)
112        self.output_order.append(("typedef", typedef))
113
114    def handle_ctypes_new_type(self, ctype, filename, lineno):
115        # Called by CtypesParser
116        if isinstance(ctype, ctypesparser.CtypesEnum):
117            self.handle_enum(ctype, filename, lineno)
118        else:
119            self.handle_struct(ctype, filename, lineno)
120
121    def handle_ctypes_function(
122        self, name, restype, argtypes, errcheck, variadic, attrib, filename, lineno
123    ):
124        # Called by CtypesParser
125        restype.visit(self)
126        for argtype in argtypes:
127            argtype.visit(self)
128
129        function = FunctionDescription(
130            name, restype, argtypes, errcheck, variadic, attrib, src=(filename, repr(lineno))
131        )
132
133        self.functions.append(function)
134        self.all.append(function)
135        self.output_order.append(("function", function))
136
137    def handle_ctypes_variable(self, name, ctype, filename, lineno):
138        # Called by CtypesParser
139        ctype.visit(self)
140
141        variable = VariableDescription(name, ctype, src=(filename, repr(lineno)))
142
143        self.variables.append(variable)
144        self.all.append(variable)
145        self.output_order.append(("variable", variable))
146
147    def handle_struct(self, ctypestruct, filename, lineno):
148        # Called from within DataCollectingParser
149
150        # When we find an opaque struct, we make a StructDescription for it
151        # and record it in self.already_seen_opaque_structs. If we later
152        # find a transparent struct with the same tag, we fill in the
153        # opaque struct with the information from the transparent struct and
154        # move the opaque struct to the end of the struct list.
155
156        name = "%s %s" % (ctypestruct.variety, ctypestruct.tag)
157
158        if name in self.already_seen_structs:
159            return
160
161        if ctypestruct.opaque:
162            if name not in self.already_seen_opaque_structs:
163                struct = StructDescription(
164                    ctypestruct.tag,
165                    ctypestruct.attrib,
166                    ctypestruct.variety,
167                    None,  # No members
168                    True,  # Opaque
169                    ctypestruct,
170                    src=(filename, str(lineno)),
171                )
172
173                self.already_seen_opaque_structs[name] = struct
174                self.structs.append(struct)
175                self.all.append(struct)
176                self.output_order.append(("struct", struct))
177
178        else:
179            for (membername, ctype) in ctypestruct.members:
180                ctype.visit(self)
181
182            if name in self.already_seen_opaque_structs:
183                # Fill in older version
184                struct = self.already_seen_opaque_structs[name]
185                struct.opaque = False
186                struct.members = ctypestruct.members
187                struct.ctype = ctypestruct
188                struct.src = ctypestruct.src
189
190                self.output_order.append(("struct-body", struct))
191
192                del self.already_seen_opaque_structs[name]
193
194            else:
195                struct = StructDescription(
196                    ctypestruct.tag,
197                    ctypestruct.attrib,
198                    ctypestruct.variety,
199                    ctypestruct.members,
200                    False,  # Not opaque
201                    src=(filename, str(lineno)),
202                    ctype=ctypestruct,
203                )
204                self.structs.append(struct)
205                self.all.append(struct)
206                self.output_order.append(("struct", struct))
207                self.output_order.append(("struct-body", struct))
208
209            self.already_seen_structs.add(name)
210
211    def handle_enum(self, ctypeenum, filename, lineno):
212        # Called from within DataCollectingParser.
213
214        # Process for handling opaque enums is the same as process for opaque
215        # structs. See handle_struct() for more details.
216
217        tag = ctypeenum.tag
218        if tag in self.already_seen_enums:
219            return
220
221        if ctypeenum.opaque:
222            if tag not in self.already_seen_opaque_enums:
223                enum = EnumDescription(ctypeenum.tag, None, ctypeenum, src=(filename, str(lineno)))
224                enum.opaque = True
225
226                self.already_seen_opaque_enums[tag] = enum
227                self.enums.append(enum)
228                self.all.append(enum)
229                self.output_order.append(("enum", enum))
230
231        else:
232            if tag in self.already_seen_opaque_enums:
233                # Fill in older opaque version
234                enum = self.already_seen_opaque_enums[tag]
235                enum.opaque = False
236                enum.ctype = ctypeenum
237                enum.src = ctypeenum.src
238                enum.members = ctypeenum.enumerators
239
240                del self.already_seen_opaque_enums[tag]
241
242            else:
243                enum = EnumDescription(
244                    ctypeenum.tag,
245                    ctypeenum.enumerators,
246                    src=(filename, str(lineno)),
247                    ctype=ctypeenum,
248                )
249                enum.opaque = False
250
251                self.enums.append(enum)
252                self.all.append(enum)
253                self.output_order.append(("enum", enum))
254
255            self.already_seen_enums.add(tag)
256
257            for (enumname, expr) in ctypeenum.enumerators:
258                constant = ConstantDescription(enumname, expr, src=(filename, lineno))
259
260                self.constants.append(constant)
261                self.all.append(constant)
262                self.output_order.append(("constant", constant))
263
264    def handle_macro(self, name, params, expr, filename, lineno):
265        # Called from within DataCollectingParser
266        src = (filename, lineno)
267
268        if expr == None:
269            expr = ConstantExpressionNode(True)
270            constant = ConstantDescription(name, expr, src)
271            self.constants.append(constant)
272            self.all.append(constant)
273            return
274
275        expr.visit(self)
276
277        if isinstance(expr, CtypesType):
278            if params:
279                macro = MacroDescription(name, "", src)
280                macro.error(
281                    "%s has parameters but evaluates to a type. "
282                    "Ctypesgen does not support it." % macro.casual_name(),
283                    cls="macro",
284                )
285                self.macros.append(macro)
286                self.all.append(macro)
287                self.output_order.append(("macro", macro))
288
289            else:
290                typedef = TypedefDescription(name, expr, src)
291                self.typedefs.append(typedef)
292                self.all.append(typedef)
293                self.output_order.append(("typedef", typedef))
294
295        elif name == "#undef":
296            undef = UndefDescription(expr, src)
297            self.all.append(undef)
298            self.output_order.append(("undef", undef))
299        else:
300            macro = MacroDescription(name, params, expr, src)
301            self.macros.append(macro)
302            self.all.append(macro)
303            self.output_order.append(("macro", macro))
304
305        # Macros could possibly contain things like __FILE__, __LINE__, etc...
306        # This could be supported, but it would be a lot of work. It would
307        # probably also bloat the Preamble considerably.
308
309    def handle_error(self, message, filename, lineno):
310        # Called by CParser
311        error_message("%s:%d: %s" % (filename, lineno, message), cls="cparser")
312
313    def handle_pp_error(self, message):
314        # Called by PreprocessorParser
315        error_message("%s: %s" % (self.options.cpp, message), cls="cparser")
316
317    def handle_status(self, message):
318        # Called by CParser
319        status_message(message)
320
321    def visit_struct(self, struct):
322        self.handle_struct(struct, struct.src[0], struct.src[1])
323
324    def visit_enum(self, enum):
325        self.handle_enum(enum, enum.src[0], enum.src[1])
326
327    def data(self):
328        return DescriptionCollection(
329            self.constants,
330            self.typedefs,
331            self.structs,
332            self.enums,
333            self.functions,
334            self.variables,
335            self.macros,
336            self.all,
337            self.output_order,
338        )
339