• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

trolldbois / ctypeslib / 4769048898

pending completion
4769048898

push

github

Loic Jaquemet
Improve macro parsing a bit. fixes #107. This encovers that the API works better that the testunit hacks for macro value ans stringization. Macro concat doesnt work (#77)

58 of 59 new or added lines in 1 file covered. (98.31%)

1 existing line in 1 file now uncovered.

2026 of 2412 relevant lines covered (84.0%)

25.11 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.14
/ctypeslib/codegen/codegenerator.py
1
"""Create ctypes wrapper code for abstract type descriptions.
2
Type descriptions are collections of typedesc instances.
3
"""
4

5
from __future__ import print_function
30✔
6
from __future__ import unicode_literals
30✔
7

8
import collections
30✔
9
import ctypes
30✔
10
import logging
30✔
11
import os
30✔
12
import pkgutil
30✔
13
import sys
30✔
14
import textwrap
30✔
15
import io
30✔
16
from io import StringIO
30✔
17

18
from clang.cindex import TypeKind
30✔
19

20
from ctypeslib.codegen import clangparser
30✔
21
from ctypeslib.codegen import config
30✔
22
from ctypeslib.codegen import typedesc
30✔
23
from ctypeslib.codegen import util
30✔
24
from ctypeslib.library import Library
30✔
25

26
log = logging.getLogger("codegen")
30✔
27

28

29
class Generator:
30✔
30
    def __init__(self, output, cfg):
30✔
31
        self.output = output
30✔
32
        self.stream = StringIO()
30✔
33
        self.imports = StringIO()
30✔
34
        self.cfg = cfg
30✔
35
        self.generate_locations = cfg.generate_locations
30✔
36
        self.generate_comments = cfg.generate_comments
30✔
37
        self.generate_docstrings = cfg.generate_docstrings
30✔
38
        self.known_symbols = cfg.known_symbols or {}
30✔
39
        self.preloaded_dlls = cfg.preloaded_dlls or []
30✔
40
        if cfg.searched_dlls is None:
30✔
41
            self.searched_dlls = []
×
42
        else:
43
            self.searched_dlls = cfg.searched_dlls
30✔
44

45
        # we use collections.OrderedDict() to keep ordering
46
        self.done = collections.OrderedDict()  # type descriptions that have been generated
30✔
47
        self.names = list()  # names that have been generated
30✔
48
        self.more = collections.OrderedDict()
30✔
49
        self.macros = 0
30✔
50
        self.cross_arch_code_generation = cfg.cross_arch
30✔
51
        # what record dependency were generated
52
        self.head_generated = set()
30✔
53
        self.body_generated = set()
30✔
54

55
    # pylint: disable=method-hidden
56
    def enable_fundamental_type_wrappers(self):
30✔
57
        """
58
        If a type is a int128, a long_double_t or a void, some placeholders need
59
        to be in the generated code to be valid.
60
        """
61
        self.enable_fundamental_type_wrappers = lambda: True
30✔
62
        headers = pkgutil.get_data("ctypeslib", "data/fundamental_type_name.tpl").decode()
30✔
63
        size = str(self.parser.get_ctypes_size(TypeKind.LONGDOUBLE) // 8)
30✔
64
        headers = headers.replace("__LONG_DOUBLE_SIZE__", size)
30✔
65
        print(headers, file=self.imports)
30✔
66

67
    def enable_pointer_type(self):
30✔
68
        """
69
        If a type is a pointer, a platform-independent POINTER_T type needs
70
        to be in the generated code.
71
        """
72
        # only enable if cross arch mode is on
73
        if not self.cross_arch_code_generation:
30✔
74
            return "ctypes.POINTER"
30✔
75
        self.enable_pointer_type = lambda: "POINTER_T"
30✔
76
        headers = pkgutil.get_data("ctypeslib", "data/pointer_type.tpl").decode()
30✔
77
        # assuming a LONG also has the same sizeof than a pointer.
78
        word_size = self.parser.get_ctypes_size(TypeKind.POINTER) // 8
30✔
79
        word_type = self.parser.get_ctypes_name(TypeKind.ULONG)
30✔
80
        # pylint: disable=protected-access
81
        word_char = getattr(ctypes, word_type)._type_
30✔
82
        # replacing template values
83
        headers = headers.replace("__POINTER_SIZE__", str(word_size))
30✔
84
        headers = headers.replace("__REPLACEMENT_TYPE__", word_type)
30✔
85
        headers = headers.replace("__REPLACEMENT_TYPE_CHAR__", word_char)
30✔
86
        print(headers, file=self.imports)
30✔
87
        return "POINTER_T"
30✔
88

89
    def enable_structure_type(self):
30✔
90
        """
91
        If a structure type is used, declare our ctypes.Structure extension type
92
        """
93
        self.enable_structure_type = lambda: True
30✔
94
        headers = pkgutil.get_data("ctypeslib", "data/structure_type.tpl").decode()
30✔
95
        print(headers, file=self.imports)
30✔
96

97
    def enable_string_cast(self):
30✔
98
        """
99
        If a structure type is used, declare our ctypes.Structure extension type
100
        """
101
        self.enable_string_cast = lambda: True
30✔
102
        headers = pkgutil.get_data("ctypeslib", "data/string_cast.tpl").decode()
30✔
103
        headers = headers.replace("__POINTER_TYPE__", self.enable_pointer_type())
30✔
104
        print(headers, file=self.imports)
30✔
105

106
    def generate_headers(self, parser):
30✔
107
        # fix parser in self for later use
108
        self.parser = parser
30✔
109
        headers = pkgutil.get_data("ctypeslib", "data/headers.tpl").decode()
30✔
110
        # get sizes from clang library
111
        word_size = self.parser.get_ctypes_size(TypeKind.LONG) // 8
30✔
112
        pointer_size = self.parser.get_ctypes_size(TypeKind.POINTER) // 8
30✔
113
        longdouble_size = self.parser.get_ctypes_size(TypeKind.LONGDOUBLE) // 8
30✔
114
        # replacing template values
115
        headers = headers.replace("__FLAGS__", str(self.parser.flags))
30✔
116
        headers = headers.replace("__WORD_SIZE__", str(word_size))
30✔
117
        headers = headers.replace("__POINTER_SIZE__", str(pointer_size))
30✔
118
        headers = headers.replace("__LONGDOUBLE_SIZE__", str(longdouble_size))
30✔
119
        print(headers, file=self.imports)
30✔
120

121
    def type_name(self, t, generate=True):
30✔
122
        """
123
        Returns a string containing an expression that can be used to
124
        refer to the type. Assumes the 'from ctypes import *'
125
        namespace is available.
126
        """
127
        # no Test case for these
128
        # elif isinstance(t, typedesc.Argument):
129
        # elif isinstance(t, typedesc.CvQualifiedType):
130
        # elif isinstance(t, typedesc.Variable):
131
        #   return "%s" % self.type_name(t.typ, generate)
132
        # elif isinstance(t, typedesc.Enumeration):
133
        #   return t.name
134

135
        if isinstance(t, typedesc.FundamentalType):
30✔
136
            return self.FundamentalType(t)
30✔
137
        if isinstance(t, typedesc.ArrayType):
30✔
138
            # C99 feature called the flexible array member feature.
139
            # changing this to a pointer is incorrect.
140
            # if t.size == 0:
141
            #     pointer_class = self.enable_pointer_type()
142
            #     return "%s(%s)" % (pointer_class, self.type_name(t.typ, generate))
143
            # else:
144
            return "%s * %s" % (self.type_name(t.typ, generate), t.size)
30✔
145
        if isinstance(t, typedesc.PointerType) and isinstance(t.typ, typedesc.FunctionType):
30✔
146
            return self.type_name(t.typ, generate)
30✔
147
        if isinstance(t, typedesc.PointerType):
30✔
148
            pointer_class = self.enable_pointer_type()
30✔
149
            if t.typ.name in ["c_ubyte", "c_char"]:
30✔
150
                self.enable_string_cast()
30✔
151
            return "%s(%s)" % (pointer_class, self.type_name(t.typ, generate))
30✔
152
        if isinstance(t, typedesc.FunctionType):
30✔
153
            args = [self.type_name(x, generate) for x in [t.returns] + list(t.iterArgTypes())]
30✔
154
            if "__stdcall__" in t.attributes:
30✔
155
                return "ctypes.WINFUNCTYPE(%s)" % ", ".join(args)
×
156
            else:
157
                return "ctypes.CFUNCTYPE(%s)" % ", ".join(args)
30✔
158
        # elif isinstance(t, typedesc.Structure):
159
        # elif isinstance(t, typedesc.Typedef):
160
        # elif isinstance(t, typedesc.Union):
161
        return t.name
30✔
162
        # All typedesc typedefs should be handled
163
        # raise TypeError('This typedesc should be handled %s'%(t))
164

165
    ################################################################
166

167
    _aliases = 0
30✔
168

169
    def Alias(self, alias):
30✔
170
        """Handles Aliases. No test cases yet"""
171
        # FIXME
172
        if self.generate_comments:
×
173
            self.print_comment(alias)
×
174
        print("%s = %s # alias" % (alias.name, alias.alias), file=self.stream)
×
175
        self._aliases += 1
×
176
        return
×
177

178
    _macros = 0
30✔
179

180
    def Macro(self, macro):
30✔
181
        """
182
        Handles macro. No test cases else that #defines.
183

184
        Clang will first give us the macro definition,
185
        and then later, the macro reference in code will be replaced by teh macro body.
186
        So really, there is nothing to actually generate.
187
        Just push the macro in comment, and let the rest work away
188

189
        """
190
        if macro.location is None:
30✔
191
            log.info("Ignoring %s with no location", macro.name)
×
192
            return
×
193
        if self.generate_locations:
30✔
194
            print("# %s:%s" % macro.location, file=self.stream)
×
195
        if self.generate_comments:
30✔
196
            self.print_comment(macro)
×
197

198
        # get tokens types all the way to here ?
199
        # 1. clang makes the decision on type casting and validity of data.
200
        # let's not try to be clever.
201
        # only ignore, undefined references, macro functions...
202
        # 2. or get a flag in macro that tells us if something contains undefinedIdentifier
203
        # is not code-generable ?
204
        # codegen should decide what codegen can do.
205
        if macro.args:
30✔
206
            print("# def %s%s:  # macro" % (macro.name, macro.args), file=self.stream)
30✔
207
            print("#    return %s  " % macro.body, file=self.stream)
30✔
208
        elif util.contains_undefined_identifier(macro):
30✔
209
            # we can't handle that, we comment it out
210
            if isinstance(macro.body, typedesc.UndefinedIdentifier):
30✔
211
                print("# %s = %s # macro" % (macro.name, macro.body.name), file=self.stream)
30✔
212
            else:  # we assume it's a list
213
                print("# %s = %s # macro" % (macro.name, " ".join([str(_) for _ in macro.body])), file=self.stream)
30✔
214
        elif isinstance(macro.body, bool):
30✔
215
            print("%s = %s # macro" % (macro.name, macro.body), file=self.stream)
30✔
216
            self.macros += 1
30✔
217
            self.names.append(macro.name)
30✔
218
        elif isinstance(macro.body, str):
30✔
219
            if macro.body == "":
30✔
220
                print("# %s = %s # macro" % (macro.name, macro.body), file=self.stream)
30✔
221
            else:
222
                body = macro.body
30✔
223
                float_value = util.from_c_float_literal(body)
30✔
224
                if float_value is not None:
30✔
225
                    body = float_value
30✔
226
                # what about integers you ask ? body token that represents token are Integer here.
227
                # either it's just a thing we gonna print, or we need to have a registered item
228
                print("%s = %s # macro" % (macro.name, body), file=self.stream)
30✔
229
                self.macros += 1
30✔
230
                self.names.append(macro.name)
30✔
231
        # This is why we need to have token types all the way here.
232
        # but at the same time, clang does not type tokens. So we might as well guess them here too
233
        elif util.body_is_all_string_tokens(macro.body):
30✔
234
            print("%s = %s # macro" % (macro.name, " ".join([str(_) for _ in macro.body])), file=self.stream)
30✔
235
            self.macros += 1
30✔
236
            self.names.append(macro.name)
30✔
237
        else:
238
            # this might be a token list of float literal
239
            body = macro.body
30✔
240
            float_value = util.from_c_float_literal(body)
30✔
241
            if float_value is not None:
30✔
242
                body = float_value
30✔
243
            # or anything else that might be a valid python literal...
244
            print("%s = %s # macro" % (macro.name, body), file=self.stream)
30✔
245
            self.macros += 1
30✔
246
            self.names.append(macro.name)
30✔
247
        return
30✔
248

249
    _typedefs = 0
30✔
250

251
    def Typedef(self, tp):
30✔
252
        if self.generate_comments:
30✔
253
            self.print_comment(tp)
×
254
        sized_types = {
30✔
255
            "uint8_t": "c_uint8",
256
            "uint16_t": "c_uint16",
257
            "uint32_t": "c_uint32",
258
            "uint64_t": "c_uint64",
259
            "int8_t": "c_int8",
260
            "int16_t": "c_int16",
261
            "int32_t": "c_int32",
262
            "int64_t": "c_int64",
263
        }
264
        name = self.type_name(tp)  # tp.name
30✔
265
        if isinstance(tp.typ, typedesc.FundamentalType) and tp.name in sized_types:
30✔
266
            print("%s = ctypes.%s" % (name, sized_types[tp.name]), file=self.stream)
30✔
267
            self.names.append(tp.name)
30✔
268
            return
30✔
269
        if tp.typ not in self.done:
30✔
270
            # generate only declaration code for records ?
271
            # if type(tp.typ) in (typedesc.Structure, typedesc.Union):
272
            #    self._generate(tp.typ.get_head())
273
            #    self.more.add(tp.typ)
274
            # else:
275
            #    self._generate(tp.typ)
276
            self._generate(tp.typ)
30✔
277
        # generate actual typedef code.
278
        if tp.name != self.type_name(tp.typ):
30✔
279
            print("%s = %s" % (name, self.type_name(tp.typ)), file=self.stream)
30✔
280

281
            if isinstance(tp.typ, typedesc.Enumeration):
30✔
282
                print("%s__enumvalues = %s__enumvalues" % (name, self.type_name(tp.typ)), file=self.stream)
30✔
283
                self.names.append("%s__enumvalues" % name)
30✔
284

285
        self.names.append(tp.name)
30✔
286
        self._typedefs += 1
30✔
287
        return
30✔
288

289
    def _get_real_type(self, tp):
30✔
290
        # FIXME, kinda useless really.
291
        if isinstance(tp, typedesc.Typedef):
30✔
292
            if isinstance(tp.typ, typedesc.Typedef):
×
293
                raise TypeError("Nested loop in Typedef %s" % tp.name)
×
294
            return self._get_real_type(tp.typ)
×
295
        elif isinstance(tp, typedesc.CvQualifiedType):
30✔
296
            return self._get_real_type(tp.typ)
×
297
        return tp
30✔
298

299
    _arraytypes = 0
30✔
300

301
    def ArrayType(self, tp):
30✔
302
        self._generate(self._get_real_type(tp.typ))
30✔
303
        self._generate(tp.typ)
30✔
304
        self._arraytypes += 1
30✔
305

306
    _functiontypes = 0
30✔
307
    _notfound_functiontypes = 0
30✔
308

309
    def FunctionType(self, tp):
30✔
310
        self._generate(tp.returns)
30✔
311
        self.generate_all(tp.arguments)
30✔
312
        # print >> self.stream, "%s = %s # Functiontype " % (
313
        # self.type_name(tp), [self.type_name(a) for a in tp.arguments])
314
        self._functiontypes += 1
30✔
315

316
    def Argument(self, tp):
30✔
317
        self._generate(tp.typ)
30✔
318

319
    _pointertypes = 0
30✔
320

321
    def PointerType(self, tp):
30✔
322
        # print 'generate', tp.typ
323
        if isinstance(tp.typ, typedesc.PointerType):
30✔
324
            self._generate(tp.typ)
30✔
325
        elif type(tp.typ) in (typedesc.Union, typedesc.Structure):
30✔
326
            self._generate(tp.typ.get_head())
30✔
327
            self.more[tp.typ] = True
30✔
328
        elif isinstance(tp.typ, typedesc.Typedef):
30✔
329
            self._generate(tp.typ)
×
330
        else:
331
            self._generate(tp.typ)
30✔
332
        self._pointertypes += 1
30✔
333

334
    def CvQualifiedType(self, tp):
30✔
335
        self._generate(tp.typ)
×
336

337
    _variables = 0
30✔
338
    _notfound_variables = 0
30✔
339

340
    def Variable(self, tp):
30✔
341
        self._variables += 1
30✔
342
        if self.generate_comments:
30✔
343
            self.print_comment(tp)
×
344

345
        # 2021-02 give me a test case for this. it breaks all extern variables otherwise.
346
        if tp.extern and self.find_library_with_func(tp):
30✔
347
            dll_library = self.find_library_with_func(tp)
30✔
348
            self._generate(tp.typ)
30✔
349
            # calling convention does not matter for in_dll...
350
            libname = self.get_sharedlib(dll_library, "cdecl")
30✔
351
            print("%s = (%s).in_dll(%s, '%s')" % (tp.name, self.type_name(tp.typ), libname, tp.name), file=self.stream)
30✔
352
            self.names.append(tp.name)
30✔
353
            # wtypes.h contains IID_IProcessInitControl, for example
354
            return
30✔
355

356
        # Hm.  The variable MAY be a #define'd symbol that we have
357
        # artifically created, or it may be an exported variable that
358
        # is not in the libraries that we search.  Anyway, if it has
359
        # no tp.init value we can't generate code for it anyway, so we
360
        # drop it.
361
        # if tp.init is None:
362
        #    self._notfound_variables += 1
363
        #    return
364
        # el
365
        if isinstance(tp.init, typedesc.FunctionType):
30✔
366
            _args = [x for x in tp.typ.iterArgNames()]
30✔
367
            print("%s = %s # args: %s" % (tp.name, self.type_name(tp.init), _args), file=self.stream)
30✔
368
            self.names.append(tp.name)
30✔
369
            return
30✔
370
        elif isinstance(tp.typ, typedesc.PointerType) or isinstance(tp.typ, typedesc.ArrayType):
30✔
371
            if isinstance(tp.typ.typ, typedesc.FundamentalType) and (
30✔
372
                tp.typ.typ.name in ["c_ubyte", "c_char", "c_wchar"]
373
            ):
374
                # string
375
                # FIXME a char * is not a python string.
376
                # we should output a cstring() construct.
377
                init_value = repr(tp.init)
30✔
378
            elif isinstance(tp.typ.typ, typedesc.FundamentalType) and (
30✔
379
                "int" in tp.typ.typ.name or "long" in tp.typ.typ.name
380
            ):
381
                # array of number
382
                # CARE: size of elements must match size of array
383
                # init_value = repr(tp.init)
384
                init_value = "[%s]" % ",".join([str(x) for x in tp.init])
30✔
385
                # we do NOT want Variable to be described as ctypes object
386
                # when we can have a python abstraction for them.
387
                # init_value_type = self.type_name(tp.typ, False)
388
                # init_value = "(%s)(%s)"%(init_value_type,init_value)
389
            elif isinstance(tp.typ.typ, typedesc.Structure):
30✔
390
                self._generate(tp.typ.typ)
30✔
391
                init_value = self.type_name(tp.typ, False) + "()"
30✔
392
            else:
393
                if tp.init is not None:
30✔
394
                    init_value = tp.init
30✔
395
                else:
396
                    init_value = self.type_name(tp.typ, False) + "()"
×
397

398
        elif isinstance(tp.typ, typedesc.Structure):
30✔
399
            init_value = self.type_name(tp.typ, False)
30✔
400
        elif isinstance(tp.typ, typedesc.FundamentalType) and tp.typ.name in [
30✔
401
            "c_ubyte",
402
            "c_char",
403
            "c_wchar",
404
        ]:
405
            if tp.init is not None:
30✔
406
                init_value = repr(tp.init)
30✔
407
            else:
408
                init_value = "'\\x00'"
30✔
409
        else:
410
            # we want to have FundamentalType variable use the actual
411
            # type default, and not be a python ctypes object
412
            # if init_value is None:
413
            #    init_value = ''; # use default ctypes object constructor
414
            # init_value = "%s(%s)"%(self.type_name(tp.typ, False), init_value)
415
            if tp.init is not None:
30✔
416
                # TODO, check that if tp.init is a string literal
417
                #  and that there is a definition for it ?
418
                init_value = tp.init
30✔
419
            elif tp.typ.name in ["c_float", "c_double", "c_longdouble"]:
30✔
420
                init_value = 0.0
30✔
421
            else:
422
                # integers
423
                init_value = 0
30✔
424
        #
425
        # print it out
426
        print("%s = %s # Variable %s" % (tp.name, init_value, self.type_name(tp.typ, False)), file=self.stream)
30✔
427
        #
428
        self.names.append(tp.name)
30✔
429

430
    _enumvalues = 0
30✔
431

432
    def EnumValue(self, tp):
30✔
433
        # FIXME should be in parser
434
        value = int(tp.value)
30✔
435
        print("%s = %d" % (tp.name, value), file=self.stream)
30✔
436
        self.names.append(tp.name)
30✔
437
        self._enumvalues += 1
30✔
438

439
    _enumtypes = 0
30✔
440

441
    def Enumeration(self, tp):
30✔
442
        if self.generate_comments:
30✔
443
            self.print_comment(tp)
×
444
        print("", file=self.stream)
30✔
445
        if tp.name:
30✔
446
            print("# values for enumeration '%s'" % tp.name, file=self.stream)
30✔
447
        else:
448
            print("# values for unnamed enumeration", file=self.stream)
×
449
        print("%s__enumvalues = {" % tp.name, file=self.stream)
30✔
450
        for item in tp.values:
30✔
451
            print("    %s: '%s'," % (int(item.value), item.name), file=self.stream)
30✔
452
        print("}", file=self.stream)
30✔
453

454
        # Some enumerations have the same name for the enum type
455
        # and an enum value.  Excel's XlDisplayShapes is such an example.
456
        # Since we don't have separate namespaces for the type and the values,
457
        # we generate the TYPE last, overwriting the value. XXX
458
        for item in tp.values:
30✔
459
            self._generate(item)
30✔
460
        if tp.name:
30✔
461
            # Enums can be forced to occupy less space than an int when the compiler flag '-fshort-enums' is set.
462
            # The size adjustment is done when possible, depending on the values of the enum.
463
            # In any case, we should trust the enum size returned by the compiler.
464
            #
465
            # Furthermore, in order to obtain a correct (un)signed representation in Python,
466
            # the signedness of the enum is deduced from the sign of enum values.
467
            # If there is not any negative value in the enum, then the resulting ctype will be unsigned.
468
            # Sources:
469
            #   https://stackoverflow.com/a/54527229/1641819
470
            #   https://stackoverflow.com/a/56432050/1641819
471

472
            # Look for any negative value in enum
473
            has_negative = False
30✔
474
            for item in tp.values:
30✔
475
                if item.value < 0:
30✔
476
                    has_negative = True
30✔
477
                    break
30✔
478

479
            # Determine enum type depending on its size and signedness
480
            if tp.size == 1:
30✔
481
                enum_ctype = 'ctypes.c_int8' if has_negative else 'ctypes.c_uint8'
30✔
482
            elif tp.size == 2:
30✔
483
                enum_ctype = 'ctypes.c_int16' if has_negative else 'ctypes.c_uint16'
30✔
484
            elif tp.size == 4:
30✔
485
                enum_ctype = 'ctypes.c_int32' if has_negative else 'ctypes.c_uint32'
30✔
486
            elif tp.size == 8:
30✔
487
                enum_ctype = 'ctypes.c_int64' if has_negative else 'ctypes.c_uint64'
30✔
488
            else:
489
                enum_ctype = 'ctypes.c_int' if has_negative else 'ctypes.c_uint'
×
490

491
            print("%s = %s # enum" % (tp.name, enum_ctype), file=self.stream)
30✔
492
            self.names.append(tp.name)
30✔
493
        self._enumtypes += 1
30✔
494

495
    def get_undeclared_type(self, item):
30✔
496
        """
497
        Checks if a typed has already been declared in the python output
498
        or is a builtin python type.
499
        """
500
        if item.name in self.head_generated:
30✔
501
            return None
×
502
        if item in self.done:
30✔
503
            return None
30✔
504
        if isinstance(item, typedesc.FundamentalType):
×
505
            return None
×
506
        if isinstance(item, typedesc.PointerType):
×
507
            return self.get_undeclared_type(item.typ)
×
508
        if isinstance(item, typedesc.ArrayType):
×
509
            return self.get_undeclared_type(item.typ)
×
510
        # else its an undeclared structure.
511
        return item
×
512

513
    def _get_undefined_head_dependencies(self, struct):
30✔
514
        """Return head dependencies on other record types.
515
        Head dependencies is exclusive of body dependency. It's one or the other.
516
        """
517
        r = set()
30✔
518
        for m in struct.members:
30✔
519
            if isinstance(m.type, typedesc.PointerType) and typedesc.is_record(m.type.typ):
30✔
520
                r.add(m.type)
30✔
521
        # remove all already defined heads
522
        r = [_ for _ in r if _.name not in self.head_generated]
30✔
523
        return r
30✔
524

525
    def _get_undefined_body_dependencies(self, struct):
30✔
526
        """Return head dependencies on other record types.
527
        Head dependencies is exclusive of body dependency. It's one or the other.
528
        """
529
        r = set()
30✔
530
        for m in struct.members:
30✔
531
            if isinstance(m.type, typedesc.ArrayType) and typedesc.is_record(m.type.typ):
30✔
532
                r.add(m.type.typ)
30✔
533
            elif typedesc.is_record(m.type):
30✔
534
                r.add(m.type)
30✔
535
            elif m.type not in self.done:
30✔
536
                r.add(m.type)
30✔
537
        # remove all already defined bodies
538
        r = [_ for _ in r if _.name not in self.body_generated]
30✔
539
        return r
30✔
540

541
    _structures = 0
30✔
542

543
    def Structure(self, struct):
30✔
544
        if struct.name in self.head_generated and struct.name in self.body_generated:
30✔
545
            self.done[struct] = True
×
546
            return
×
547
        self.enable_structure_type()
30✔
548
        self._structures += 1
30✔
549
        depends = set()
30✔
550
        # We only print a empty struct.
551
        if struct.members is None:
30✔
552
            log.info("No members for: %s", struct.name)
30✔
553
            self._generate(struct.get_head(), False)
30✔
554
            return
30✔
555
        # look in bases class for dependencies
556
        # FIXME - need a real dependency graph maker
557
        # remove myself, just in case.
558
        if struct in self.done:
30✔
559
            del self.done[struct]
30✔
560
        # checks members dependencies in bases
561
        for b in struct.bases:
30✔
562
            depends.update([self.get_undeclared_type(m.type) for m in b.members])
30✔
563
        depends.discard(None)
30✔
564
        if len(depends) > 0:
30✔
565
            log.debug("Generate %s DEPENDS for Bases %s", struct.name, depends)
×
566
            for dep in depends:
×
567
                self._generate(dep)
×
568

569
        # checks members dependencies
570
        # test_record_ordering head does not mean declared. _fields_ mean declared
571
        # CPOINTER members just require a class definition
572
        # whereas members that are non pointers require a full _fields_ declaration
573
        # before this record body is defined fully
574
        # depends.update([self.get_undeclared_type(m.type)
575
        #                 for m in struct.members])
576
        # self.done[struct] = True
577
        # hard dependencies for members types that are not pointer but records
578
        # soft dependencies for members pointers to record
579
        undefined_head_dependencies = self._get_undefined_head_dependencies(struct)
30✔
580
        undefined_body_dependencies = self._get_undefined_body_dependencies(struct)
30✔
581

582
        if len(undefined_body_dependencies) == 0:
30✔
583
            if len(undefined_head_dependencies) == 0:
30✔
584
                # generate this head and body in one go
585
                # if struct.get_head() not in self.done:
586
                if struct.name not in self.head_generated:
30✔
587
                    self._generate(struct.get_head(), True)
30✔
588
                    self._generate(struct.get_body(), True)
30✔
589
                else:
590
                    self._generate(struct.get_body(), False)
30✔
591
            else:
592
                # generate this head first, to avoid recursive issue, then the dep, then this body
593
                self._generate(struct.get_head(), False)
×
594
                for dep in undefined_head_dependencies:
×
595
                    self._generate(dep)
×
596
                self._generate(struct.get_body(), False)
×
597
        else:
598
            # hard dep on defining the body of these dependencies
599
            # generate this head first, to avoid recursive issue, then the dep, then this body
600
            self._generate(struct.get_head(), False)
30✔
601
            for dep in undefined_head_dependencies:
30✔
602
                self._generate(dep)
30✔
603
            for dep in undefined_body_dependencies:
30✔
604
                self._generate(dep)
30✔
605
            for dep in undefined_body_dependencies:
30✔
606
                if isinstance(dep, typedesc.Structure):
30✔
607
                    self._generate(dep.get_body(), False)
30✔
608
            self._generate(struct.get_body(), False)
30✔
609
        # we defined ourselve
610
        self.done[struct] = True
30✔
611

612
    Union = Structure
30✔
613

614
    def StructureHead(self, head, inline=False):
30✔
615
        if head.name in self.head_generated:
30✔
616
            log.debug("Skipping - Head already generated for %s", head.name)
×
617
            return
×
618
        log.debug("Head start for %s inline:%s", head.name, inline)
30✔
619
        for struct in head.struct.bases:
30✔
620
            self._generate(struct.get_head())
30✔
621
            # add dependencies
622
            self.more[struct] = True
30✔
623
        basenames = [self.type_name(b) for b in head.struct.bases]
30✔
624
        if basenames:
30✔
625
            # method_names = [m.name for m in head.struct.members if type(m) is typedesc.Method]
626
            print(
30✔
627
                "class %s(%s):" % (head.struct.name, ", ".join(basenames)),
628
                file=self.stream,
629
            )
630
        else:
631
            # methods = [m for m in head.struct.members if type(m) is typedesc.Method]
632
            if isinstance(head.struct, typedesc.Structure):
30✔
633
                # Inherit from our ctypes.Structure extension
634
                print("class %s(Structure):" % head.struct.name, file=self.stream)
30✔
635
            elif isinstance(head.struct, typedesc.Union):
30✔
636
                print("class %s(Union):" % head.struct.name, file=self.stream)
30✔
637
        if not inline:
30✔
638
            print("    pass\n", file=self.stream)
30✔
639
        # special empty struct
640
        if inline and not head.struct.members:
30✔
641
            print("    pass\n", file=self.stream)
30✔
642
        self.names.append(head.struct.name)
30✔
643
        log.debug("Head finished for %s", head.name)
30✔
644
        self.head_generated.add(head.name)
30✔
645

646
    def StructureBody(self, body, inline=False):
30✔
647
        if body.name in self.body_generated:
30✔
648
            log.debug("Skipping - Body already generated for %s", body.name)
×
649
            return
×
650
        log.debug("Body start for %s", body.name)
30✔
651
        fields = []
30✔
652
        methods = []
30✔
653
        for m in body.struct.members:
30✔
654
            if isinstance(m, typedesc.Field):
30✔
655
                fields.append(m)
30✔
656
                # if type(m.type) is typedesc.Typedef:
657
                #    self._generate(get_real_type(m.type))
658
                # self._generate(m.type)
659
            elif isinstance(m, typedesc.Method):
×
660
                methods.append(m)
×
661
                # self._generate(m.returns)
662
                # self.generate_all(m.iterArgTypes())
663
            elif isinstance(m, typedesc.Ignored):
×
664
                pass
×
665
        # handled inline Vs dependent
666
        log.debug("body inline:%s for structure %s", inline, body.struct.name)
30✔
667
        if not inline:
30✔
668
            prefix = "%s." % body.struct.name
30✔
669
        else:
670
            prefix = "    "
30✔
671
        if methods:
30✔
672
            # XXX we have parsed the COM interface methods but should
673
            # we emit any code for them?
674
            pass
×
675
        # LXJ: we pack all the time, because clang gives a precise field offset
676
        # per target architecture. No need to defer to ctypes logic for that.
677
        if fields:
30✔
678
            print("%s_pack_ = 1 # source:%s" % (prefix, body.struct.packed), file=self.stream)
30✔
679

680
        if body.struct.bases:
30✔
681
            if len(body.struct.bases) == 1:  # its a Struct or a simple Class
30✔
682
                self._generate(body.struct.bases[0].get_body(), inline)
30✔
683
            else:  # we have a multi-parent inheritance
684
                for b in body.struct.bases:
×
685
                    self._generate(b.get_body(), inline)
×
686
        # field definition normally span several lines.
687
        # Before we generate them, we need to 'import' everything they need.
688
        # So, call type_name for each field once,
689
        for f in fields:
30✔
690
            self.type_name(f.type)
30✔
691

692
        # unnamed fields get autogenerated names "_0", "_1", "_2", "_3", ...
693
        unnamed_fields = {}
30✔
694
        for f in fields:
30✔
695
            # _anonymous_ fields are fields of type Structure or Union,
696
            # that have no name.
697
            if f.is_anonymous and isinstance(f.type, (typedesc.Structure, typedesc.Union)):
30✔
698
                # anonymous types can have a member name
699
                # un-named anonymous record come here with a name == ''
700
                if f.name == '':
30✔
701
                    unnamed_fields[f] = "_%d" % len(unnamed_fields)
30✔
702
                # otherwise, we want to keep that field's name
703
        if unnamed_fields:
30✔
704
            unnamed_fields_str = ", ".join("'%s'" % _ for _ in unnamed_fields.values())
30✔
705
            print("%s_anonymous_ = (%s,)" % (prefix, unnamed_fields_str), file=self.stream)
30✔
706
        if len(fields) > 0:
30✔
707
            print("%s_fields_ = [" % prefix, file=self.stream)
30✔
708
            if self.generate_locations and body.struct.location:
30✔
709
                print("    # %s %s" % body.struct.location, file=self.stream)
×
710
            for f in fields:
30✔
711
                fieldname = unnamed_fields.get(f, f.name)
30✔
712
                type_name = self.type_name(f.type)
30✔
713
                # handle "__" prefixed names by using a wrapper
714
                if type_name.startswith("__"):
30✔
715
                    type_name = "globals()['%s']" % type_name
30✔
716
                # a bitfield needs a triplet
717
                if f.is_bitfield is False:
30✔
718
                    print("    ('%s', %s)," % (fieldname, type_name), file=self.stream)
30✔
719
                else:
720
                    # FIXME: Python bitfield is int32 only.
721
                    # from clang.cindex import TypeKind
722
                    # print fieldname
723
                    # import code
724
                    # code.interact(local=locals())
725
                    print("    ('%s', %s, %s)," % (fieldname, self.type_name(f.type), f.bits), file=self.stream)
30✔
726
            if inline:
30✔
727
                print(prefix, end=" ", file=self.stream)
30✔
728
            print("]\n", file=self.stream)
30✔
729
        log.debug("Body finished for %s", body.name)
30✔
730
        self.body_generated.add(body.name)
30✔
731

732
    def find_library_with_func(self, func):
30✔
733
        if hasattr(func, "dllname"):
30✔
734
            return func.dllname
×
735
        name = func.name
30✔
736
        if os.name == "posix" and sys.platform == "darwin":
30✔
737
            name = "_%s" % name
×
738
        for dll in self.searched_dlls:
30✔
739
            try:
30✔
740
                getattr(dll, name)
30✔
741
            except AttributeError:
×
742
                pass
×
743
            else:
744
                return dll
30✔
745
        return None
30✔
746

747
    _c_libraries = None
30✔
748

749
    def need_CLibraries(self):
30✔
750
        # Create a '_libraries' doctionary in the generated code, if
751
        # it not yet exists. Will map library pathnames to loaded libs.
752
        if self._c_libraries is None:
30✔
753
            self._c_libraries = {}
30✔
754
            print("_libraries = {}", file=self.imports)
30✔
755

756
    _stdcall_libraries = None
30✔
757

758
    def need_WinLibraries(self):
30✔
759
        # Create a '_stdcall_libraries' doctionary in the generated code, if
760
        # it not yet exists. Will map library pathnames to loaded libs.
761
        if self._stdcall_libraries is None:
×
762
            self._stdcall_libraries = {}
×
763
            print("_stdcall_libraries = {}", file=self.imports)
×
764

765
    _dll_stub_issued = False
30✔
766

767
    def get_sharedlib(self, library, cc, stub=False):
30✔
768
        # deal with missing -l with a stub
769
        stub_comment = ""
30✔
770
        if stub and not self._dll_stub_issued:
30✔
771
            self._dll_stub_issued = True
30✔
772
            stub_comment = " FunctionFactoryStub() # "
30✔
773
            print("""class FunctionFactoryStub:
30✔
774
    def __getattr__(self, _):
775
      return ctypes.CFUNCTYPE(lambda y:y)
776
""", file=self.imports)
777
            print("# libraries['FIXME_STUB'] explanation", file=self.imports)
30✔
778
            print("# As you did not list (-l libraryname.so) a library that exports this function", file=self.imports)
30✔
779
            print("# This is a non-working stub instead. ", file=self.imports)
30✔
780
            print("# You can either re-run clan2py with -l /path/to/library.so",file=self.imports)
30✔
781
            print("# Or manually fix this by comment the ctypes.CDLL loading", file=self.imports)
30✔
782

783
        # generate windows call
784
        if cc == "stdcall":
30✔
785
            self.need_WinLibraries()
×
786
            if library._name not in self._stdcall_libraries:
×
787
                _ = "_stdcall_libraries[%r] =%s ctypes.WinDLL(%r)" % (library._name, stub_comment, library._filepath)
×
788
                print(_, file=self.imports)
×
789
                self._stdcall_libraries[library._name] = None
×
790
            return "_stdcall_libraries[%r]" % library._name
×
791

792
        # generate clinux call
793
        self.need_CLibraries()
30✔
794
        if self.preloaded_dlls != []:
30✔
795
            global_flag = ", mode=ctypes.RTLD_GLOBAL"
×
796
        else:
797
            global_flag = ""
30✔
798
        if library._name not in self._c_libraries:
30✔
799
            print("_libraries[%r] =%s ctypes.CDLL(%r%s)" % (library._name, stub_comment, library._filepath, global_flag),
30✔
800
                  file=self.imports)
801
            self._c_libraries[library._name] = None
30✔
802
        return "_libraries[%r]" % library._name
30✔
803

804
    _STRING_defined = False
30✔
805

806
    def need_STRING(self):
30✔
807
        if self._STRING_defined:
×
808
            return
×
809
        print("STRING = c_char_p", file=self.imports)
×
810
        self._STRING_defined = True
×
811
        return
×
812

813
    _WSTRING_defined = False
30✔
814

815
    def need_WSTRING(self):
30✔
816
        if self._WSTRING_defined:
×
817
            return
×
818
        print("WSTRING = c_wchar_p", file=self.imports)
×
819
        self._WSTRING_defined = True
×
820
        return
×
821

822
    def Function(self, func):
30✔
823
        # FIXME: why do we call this ? it does nothing
824
        if self.generate_comments:
30✔
825
            self.print_comment(func)
×
826
        self._generate(func.returns)
30✔
827
        self.generate_all(func.iterArgTypes())
30✔
828

829
        # useful code
830
        args = [self.type_name(a) for a in func.iterArgTypes()]
30✔
831
        cc = "cdecl"
30✔
832
        if "__stdcall__" in func.attributes:
30✔
833
            cc = "stdcall"
×
834

835
        #
836
        library = self.find_library_with_func(func)
30✔
837
        if library:
30✔
838
            libname = self.get_sharedlib(library, cc)
30✔
839
        else:
840

841
            class LibraryStub:
30✔
842
                _filepath = "FIXME_STUB"
30✔
843
                _name = "FIXME_STUB"
30✔
844

845
            libname = self.get_sharedlib(LibraryStub(), cc, stub=True)
30✔
846

847
        argnames = [a or "p%d" % (i + 1) for i, a in enumerate(func.iterArgNames())]
30✔
848

849
        if self.generate_locations and func.location:
30✔
850
            print("# %s %s" % func.location, file=self.stream)
×
851
        # Generate the function decl code
852
        print("%s = %s.%s" % (func.name, libname, func.name), file=self.stream)
30✔
853
        print(
30✔
854
            "%s.restype = %s" % (func.name, self.type_name(func.returns)),
855
            file=self.stream,
856
        )
857
        if self.generate_comments:
30✔
858
            print("# %s(%s)" % (func.name, ", ".join(argnames)), file=self.stream)
×
859
        print("%s.argtypes = [%s]" % (func.name, ", ".join(args)), file=self.stream)
30✔
860

861
        if self.generate_docstrings:
30✔
862

863
            def typeString(typ):
×
864
                if hasattr(typ, "name"):
×
865
                    return typ.name
×
866
                elif hasattr(typ, "typ") and isinstance(typ, typedesc.PointerType):
×
867
                    return typeString(typ.typ) + " *"
×
868
                else:
869
                    return "unknown"
×
870

871
            argsAndTypes = zip([typeString(t) for t in func.iterArgTypes()], argnames)
×
872
            print(
×
873
                '{funcname}.__doc__ = """{ret} {funcname}({args})\n'
874
                '    {file}:{line}"""'.format(
875
                    funcname=func.name,
876
                    args=", ".join(["%s %s" % i for i in argsAndTypes]),
877
                    file=func.location[0],
878
                    line=func.location[1],
879
                    ret=typeString(func.returns),
880
                ),
881
                file=self.stream,
882
            )
883

884
        self.names.append(func.name)
30✔
885
        self._functiontypes += 1
30✔
886
        return
30✔
887

888
    def FundamentalType(self, _type):
30✔
889
        """Returns the proper ctypes class name for a fundamental type
890

891
        1) activates generation of appropriate headers for
892
        ## int128_t
893
        ## c_long_double_t
894
        2) return appropriate name for type
895
        """
896
        log.debug("HERE in FundamentalType for %s %s", _type, _type.name)
30✔
897
        if _type.name in ["None", "c_long_double_t", "c_uint128", "c_int128"]:
30✔
898
            self.enable_fundamental_type_wrappers()
30✔
899
            return _type.name
30✔
900
        return "ctypes.%s" % _type.name
30✔
901

902
    ########
903

904
    def _generate(self, item, *args):
30✔
905
        """ wraps execution of specific methods."""
906
        if item in self.done:
30✔
907
            return
30✔
908
        # verbose output with location.
909
        if self.generate_locations and item.location:
30✔
910
            print("# %s:%d" % item.location, file=self.stream)
×
911
        if self.generate_comments:
30✔
912
            self.print_comment(item)
×
913
        log.debug("generate %s, %s", item.__class__.__name__, item.name)
30✔
914
        # to avoid infinite recursion, we have to mark it as done
915
        # before actually generating the code.
916
        self.done[item] = True
30✔
917
        # go to specific treatment
918
        mth = getattr(self, type(item).__name__)
30✔
919
        mth(item, *args)
30✔
920
        return
30✔
921

922
    def print_comment(self, item):
30✔
923
        if item.comment is None:
×
924
            return
×
925
        for _ in textwrap.wrap(item.comment, 78):
×
926
            print("# %s" % _, file=self.stream)
×
927
        return
×
928

929
    def generate_all(self, items):
30✔
930
        for item in items:
30✔
931
            self._generate(item)
30✔
932
        return
30✔
933

934
    def generate_items(self, items):
30✔
935
        # items = set(items)
936
        loops = 0
30✔
937
        while items:
30✔
938
            loops += 1
30✔
939
            self.more = collections.OrderedDict()
30✔
940
            self.generate_all(items)
30✔
941

942
            # items |= self.more , but keeping ordering
943
            _s = set(items)
30✔
944
            [items.append(k) for k in self.more.keys() if k not in _s]
30✔
945

946
            # items -= self.done, but keep ordering
947
            _done = self.done.keys()
30✔
948
            for i in list(items):
30✔
949
                if i in _done:
30✔
950
                    items.remove(i)
30✔
951

952
        return loops
30✔
953

954
    def generate(self, parser, items):
30✔
955
        self.generate_headers(parser)
30✔
956
        return self.generate_code(items)
30✔
957

958
    def generate_code(self, items):
30✔
959
        print(
30✔
960
            "\n".join(
961
                ["ctypes.CDLL('%s', ctypes.RTLD_GLOBAL)" % preloaded_dll for preloaded_dll in self.preloaded_dlls]
962
            ),
963
            file=self.imports,
964
        )
965
        loops = self.generate_items(items)
30✔
966

967
        self.output.write(self.imports.getvalue())
30✔
968
        self.output.write("\n\n")
30✔
969
        self.output.write(self.stream.getvalue())
30✔
970

971
        text = "__all__ = \\"
30✔
972
        # text Wrapper doesn't work for the first line in certain cases.
973
        print(text, file=self.output)
30✔
974
        # doesn't work for the first line in certain cases.
975
        wrapper = textwrap.TextWrapper(break_long_words=False, initial_indent="    ", subsequent_indent="    ")
30✔
976
        text = "[%s]" % ", ".join([repr(str(n)) for n in sorted(self.names)])
30✔
977
        for line in wrapper.wrap(text):
30✔
978
            print(line, file=self.output)
30✔
979

980
        return loops
30✔
981

982
    def print_stats(self, stream):
30✔
983
        total = (
×
984
            self._structures
985
            + self._functiontypes
986
            + self._enumtypes
987
            + self._typedefs
988
            + self._pointertypes
989
            + self._arraytypes
990
        )
991
        print("###########################", file=stream)
×
992
        print("# Symbols defined:", file=stream)
×
993
        print("#", file=stream)
×
994
        print("# Variables:          %5d" % self._variables, file=stream)
×
995
        print("# Struct/Unions:      %5d" % self._structures, file=stream)
×
996
        print("# Functions:          %5d" % self._functiontypes, file=stream)
×
997
        print("# Enums:              %5d" % self._enumtypes, file=stream)
×
998
        print("# Enum values:        %5d" % self._enumvalues, file=stream)
×
999
        print("# Typedefs:           %5d" % self._typedefs, file=stream)
×
1000
        print("# Pointertypes:       %5d" % self._pointertypes, file=stream)
×
1001
        print("# Arraytypes:         %5d" % self._arraytypes, file=stream)
×
1002
        print("# unknown functions:  %5d" % self._notfound_functiontypes, file=stream)
×
1003
        print("# unknown variables:  %5d" % self._notfound_variables, file=stream)
×
1004
        print("#", file=stream)
×
1005
        print("# Total symbols: %5d" % total, file=stream)
×
1006
        print("###########################", file=stream)
×
1007
        return
×
1008

1009

1010
################################################################
1011

1012
class CodeTranslator:
30✔
1013
    """
1014
    Organiser class to take C files in input, and produce python code in a standard fashion
1015
    """
1016
    def __init__(self, cfg: config.CodegenConfig):
30✔
1017
        self.cfg = cfg
30✔
1018
        self.parser = None
30✔
1019
        self.generator = None
30✔
1020
        self.items = []
30✔
1021
        self.filtered_items = []
30✔
1022

1023
    def preload_dlls(self):
30✔
1024
        # FIXME
1025
        self.cfg.preloaded_dlls = [Library(name, nm="nm") for name in self.cfg.preloaded_dlls]
30✔
1026

1027
    def make_clang_parser(self):
30✔
1028
        self.parser = clangparser.Clang_Parser(self.cfg.clang_opts)
30✔
1029
        if typedesc.Macro in self.cfg.types:
30✔
1030
            self.parser.activate_macros_parsing()
×
1031
        if self.cfg.generate_comments:
30✔
1032
            self.parser.activate_comment_parsing()
×
1033
        # FIXME
1034
        # if self.cfg.filter_location:
1035
        #     parser.filter_location(srcfiles)
1036
        return self.parser
30✔
1037

1038
    def parse_input_string(self, input_io):
30✔
1039
        if self.parser is None:
30✔
1040
            self.make_clang_parser()
30✔
1041
        self.parser.parse_string(input_io)
30✔
1042
        # get the typedesc C types items
1043
        self.items.extend(self.parser.get_result())
30✔
1044

1045
    def parse_input_file(self, src_file):
30✔
1046
        if self.parser is None:
30✔
1047
            self.make_clang_parser()
30✔
1048
        self.parser.parse(src_file)
30✔
1049
        # get the typedesc C types items
1050
        self.items.extend(self.parser.get_result())
30✔
1051

1052
    def parse_input_files(self, src_files: list):
30✔
1053
        if self.parser is None:
30✔
1054
            self.make_clang_parser()
30✔
1055
        # filter location with clang.
1056
        if self.cfg.filter_location:
30✔
1057
            self.parser.filter_location(src_files)
30✔
1058
        #
1059
        for srcfile in src_files:
30✔
1060
            # verifying that is really a file we can open
1061
            with open(srcfile):
30✔
1062
                pass
30✔
1063
            log.debug("Parsing input file %s", srcfile)
30✔
1064
            self.parser.parse(srcfile)
30✔
1065
        # get the typedesc C types items
1066
        self.items.extend(self.parser.get_result())
30✔
1067

1068
    def make_code_generator(self, output):
30✔
1069
        self.generator = Generator(output, cfg=self.cfg)
30✔
1070
        return self.generator
30✔
1071

1072
    def generate_code(self, output):
30✔
1073
        if self.generator is None:
30✔
1074
            self.make_code_generator(output)
30✔
1075
        self.filtered_items = list(self.items)
30✔
1076
        log.debug("%d items before filtering", len(self.filtered_items))
30✔
1077
        self.filter_types()
30✔
1078
        self.filter_symbols()
30✔
1079
        self.filter_expressions()
30✔
1080
        log.debug("Left with %d items after filtering", len(self.filtered_items))
30✔
1081
        loops = self.generator.generate(self.parser, self.filtered_items)
30✔
1082
        if self.cfg.verbose:
30✔
1083
            self.generator.print_stats(sys.stderr)
×
1084
            log.info("needed %d loop(s)", loops)
×
1085

1086
    def filter_types(self):
30✔
1087
        self.filtered_items = [i for i in self.filtered_items if i.__class__ in self.cfg.types]
30✔
1088

1089
    def filter_symbols(self):
30✔
1090
        if len(self.cfg.symbols) == 0:
30✔
1091
            return
30✔
1092
        todo = []
×
1093
        syms = set(self.cfg.symbols)
×
1094
        for i in self.filtered_items:
×
1095
            if i.name in syms:
×
1096
                todo.append(i)
×
1097
                syms.remove(i.name)
×
1098
            else:
1099
                log.debug("not generating {}: not a symbol".format(i.name))
×
1100
        if syms:
×
1101
            log.warning("symbols not found %s", [str(x) for x in list(syms)])
×
1102
        self.filtered_items = todo
×
1103

1104
    def filter_expressions(self):
30✔
1105
        if len(self.cfg.expressions) == 0:
30✔
1106
            return
30✔
1107
        todo = []
×
1108
        for s in self.cfg.expressions:
×
1109
            log.debug("regexp: looking for %s", s.pattern)
×
1110
            for i in self.filtered_items:
×
1111
                log.debug("regexp: i.name is %s", i.name)
×
1112
                if i.name is None:
×
1113
                    continue
×
1114
                match = s.match(i.name)
×
1115
                # if we only want complete matches:
1116
                if match and match.group() == i.name:
×
1117
                    todo.append(i)
×
1118
                    continue
×
1119
                # if we follow our own documentation,
1120
                # allow regular expression match of any part of name:
1121
                match = s.search(i.name)
×
1122
                if match:
×
1123
                    todo.append(i)
×
1124
                    continue
×
1125
        self.filtered_items = todo
×
1126

1127

1128
# easy to use API.
1129

1130
def translate(input_io, outfile=None, cfg=None):
30✔
1131
    """
1132
        Take a readable C like input readable and translate it to python.
1133
    """
1134
    cfg = cfg or config.CodegenConfig()
30✔
1135
    translator = CodeTranslator(cfg)
30✔
1136
    translator.preload_dlls()
30✔
1137
    translator.parse_input_string(input_io)
30✔
1138
    # gen python code
1139
    if outfile:
30✔
UNCOV
1140
        return translator.generate_code(outfile)
×
1141
    # otherwise return python
1142
    output = io.StringIO()
30✔
1143
    translator.generate_code(output)
30✔
1144
    output.seek(0)
30✔
1145
    # inject generated code in python namespace
1146
    ignore_coding = output.readline()
30✔
1147
    # exec ofi.getvalue() in namespace
1148
    output = ''.join(output.readlines())
30✔
1149
    namespace = {}
30✔
1150
    exec(output, namespace)
30✔
1151
    return util.ADict(namespace)
30✔
1152

1153

1154
def translate_files(source_files, outfile=None, cfg: config.CodegenConfig=None):
30✔
1155
    """
1156
    Translate the content of source_files in python code in outfile
1157

1158
    source_files: list of filenames or single filename
1159
    """
1160
    cfg = cfg or config.CodegenConfig()
30✔
1161
    translator = CodeTranslator(cfg)
30✔
1162
    translator.preload_dlls()
30✔
1163
    if isinstance(source_files, list):
30✔
1164
        translator.parse_input_files(source_files)
30✔
1165
    else:
1166
        translator.parse_input_file(source_files)
30✔
1167
    log.debug("Input was parsed")
30✔
1168
    if outfile:
30✔
1169
        return translator.generate_code(outfile)
30✔
1170
    # otherwise return python
1171
    output = io.StringIO()
30✔
1172
    translator.generate_code(output)
30✔
1173
    output.seek(0)
30✔
1174
    # inject generated code in python namespace
1175
    ignore_coding = output.readline()
30✔
1176
    # exec ofi.getvalue() in namespace
1177
    output = ''.join(output.readlines())
30✔
1178
    namespace = {}
30✔
1179
    exec(output, namespace)
30✔
1180
    return util.ADict(namespace)
30✔
1181

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc