aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/tools/cython/Cython/Compiler/UtilityCode.py
blob: 401422222f71926ac7e545f04f81b504ac9d175c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
from __future__ import absolute_import 
 
from .TreeFragment import parse_from_strings, StringParseContext 
from . import Symtab 
from . import Naming 
from . import Code 
 
 
class NonManglingModuleScope(Symtab.ModuleScope): 
 
    def __init__(self, prefix, *args, **kw): 
        self.prefix = prefix 
        self.cython_scope = None 
        self.cpp = kw.pop('cpp', False)
        Symtab.ModuleScope.__init__(self, *args, **kw) 
 
    def add_imported_entry(self, name, entry, pos): 
        entry.used = True 
        return super(NonManglingModuleScope, self).add_imported_entry(name, entry, pos)
 
    def mangle(self, prefix, name=None): 
        if name: 
            if prefix in (Naming.typeobj_prefix, Naming.func_prefix, Naming.var_prefix, Naming.pyfunc_prefix): 
                # Functions, classes etc. gets a manually defined prefix easily 
                # manually callable instead (the one passed to CythonUtilityCode) 
                prefix = self.prefix 
            return "%s%s" % (prefix, name) 
        else: 
            return Symtab.ModuleScope.mangle(self, prefix) 
 

class CythonUtilityCodeContext(StringParseContext): 
    scope = None 
 
    def find_module(self, module_name, relative_to=None, pos=None, need_pxd=True, absolute_fallback=True):
        if relative_to:
            raise AssertionError("Relative imports not supported in utility code.")
        if module_name != self.module_name: 
            if module_name not in self.modules: 
                raise AssertionError("Only the cython cimport is supported.") 
            else: 
                return self.modules[module_name] 
 
        if self.scope is None: 
            self.scope = NonManglingModuleScope(
                self.prefix, module_name, parent_module=None, context=self, cpp=self.cpp)
 
        return self.scope 
 
 
class CythonUtilityCode(Code.UtilityCodeBase): 
    """ 
    Utility code written in the Cython language itself. 
 
    The @cname decorator can set the cname for a function, method of cdef class. 
    Functions decorated with @cname('c_func_name') get the given cname. 
 
    For cdef classes the rules are as follows: 
        obj struct      -> <cname>_obj 
        obj type ptr    -> <cname>_type 
        methods         -> <class_cname>_<method_cname> 
 
    For methods the cname decorator is optional, but without the decorator the 
    methods will not be prototyped. See Cython.Compiler.CythonScope and 
    tests/run/cythonscope.pyx for examples. 
    """ 
 
    is_cython_utility = True 
 
    def __init__(self, impl, name="__pyxutil", prefix="", requires=None, 
                 file=None, from_scope=None, context=None, compiler_directives=None,
                 outer_module_scope=None):
        # 1) We need to delay the parsing/processing, so that all modules can be 
        #    imported without import loops 
        # 2) The same utility code object can be used for multiple source files; 
        #    while the generated node trees can be altered in the compilation of a 
        #    single file. 
        # Hence, delay any processing until later. 
        context_types = {}
        if context is not None: 
            from .PyrexTypes import BaseType
            for key, value in context.items():
                if isinstance(value, BaseType):
                    context[key] = key
                    context_types[key] = value
            impl = Code.sub_tempita(impl, context, file, name) 
        self.impl = impl 
        self.name = name 
        self.file = file 
        self.prefix = prefix 
        self.requires = requires or [] 
        self.from_scope = from_scope 
        self.outer_module_scope = outer_module_scope
        self.compiler_directives = compiler_directives
        self.context_types = context_types
 
    def __eq__(self, other):
        if isinstance(other, CythonUtilityCode):
            return self._equality_params() == other._equality_params()
        else:
            return False

    def _equality_params(self):
        outer_scope = self.outer_module_scope
        while isinstance(outer_scope, NonManglingModuleScope):
            outer_scope = outer_scope.outer_scope
        return self.impl, outer_scope, self.compiler_directives

    def __hash__(self):
        return hash(self.impl)

    def get_tree(self, entries_only=False, cython_scope=None): 
        from .AnalysedTreeTransforms import AutoTestDictTransform 
        # The AutoTestDictTransform creates the statement "__test__ = {}", 
        # which when copied into the main ModuleNode overwrites 
        # any __test__ in user code; not desired 
        excludes = [AutoTestDictTransform] 
 
        from . import Pipeline, ParseTreeTransforms 
        context = CythonUtilityCodeContext(
            self.name, compiler_directives=self.compiler_directives,
            cpp=cython_scope.is_cpp() if cython_scope else False)
        context.prefix = self.prefix 
        context.cython_scope = cython_scope 
        #context = StringParseContext(self.name) 
        tree = parse_from_strings(
            self.name, self.impl, context=context, allow_struct_enum_decorator=True)
        pipeline = Pipeline.create_pipeline(context, 'pyx', exclude_classes=excludes) 
 
        if entries_only: 
            p = [] 
            for t in pipeline: 
                p.append(t) 
                if isinstance(p, ParseTreeTransforms.AnalyseDeclarationsTransform): 
                    break 
 
            pipeline = p 
 
        transform = ParseTreeTransforms.CnameDirectivesTransform(context) 
        # InterpretCompilerDirectives already does a cdef declarator check 
        #before = ParseTreeTransforms.DecoratorTransform 
        before = ParseTreeTransforms.InterpretCompilerDirectives 
        pipeline = Pipeline.insert_into_pipeline(pipeline, transform, 
                                                 before=before) 
 
        def merge_scope(scope):
            def merge_scope_transform(module_node):
                module_node.scope.merge_in(scope)
                return module_node 
            return merge_scope_transform
 
        if self.from_scope:
            pipeline = Pipeline.insert_into_pipeline(
                pipeline, merge_scope(self.from_scope),
                before=ParseTreeTransforms.AnalyseDeclarationsTransform)
 
        for dep in self.requires:
            if isinstance(dep, CythonUtilityCode) and hasattr(dep, 'tree') and not cython_scope:
                pipeline = Pipeline.insert_into_pipeline(
                    pipeline, merge_scope(dep.tree.scope),
                    before=ParseTreeTransforms.AnalyseDeclarationsTransform)

        if self.outer_module_scope:
            # inject outer module between utility code module and builtin module
            def scope_transform(module_node):
                module_node.scope.outer_scope = self.outer_module_scope
                return module_node

            pipeline = Pipeline.insert_into_pipeline(
                pipeline, scope_transform,
                before=ParseTreeTransforms.AnalyseDeclarationsTransform)

        if self.context_types:
            # inject types into module scope
            def scope_transform(module_node):
                for name, type in self.context_types.items():
                    entry = module_node.scope.declare_type(name, type, None, visibility='extern')
                    entry.in_cinclude = True
                return module_node

            pipeline = Pipeline.insert_into_pipeline(
                pipeline, scope_transform,
                before=ParseTreeTransforms.AnalyseDeclarationsTransform)

        (err, tree) = Pipeline.run_pipeline(pipeline, tree, printtree=False) 
        assert not err, err 
        self.tree = tree
        return tree 
 
    def put_code(self, output): 
        pass 
 
    @classmethod 
    def load_as_string(cls, util_code_name, from_file=None, **kwargs): 
        """ 
        Load a utility code as a string. Returns (proto, implementation) 
        """ 
        util = cls.load(util_code_name, from_file, **kwargs) 
        return util.proto, util.impl # keep line numbers => no lstrip() 
 
    def declare_in_scope(self, dest_scope, used=False, cython_scope=None, 
                         whitelist=None): 
        """ 
        Declare all entries from the utility code in dest_scope. Code will only 
        be included for used entries. If module_name is given, declare the 
        type entries with that name. 
        """ 
        tree = self.get_tree(entries_only=True, cython_scope=cython_scope) 
 
        entries = tree.scope.entries 
        entries.pop('__name__') 
        entries.pop('__file__') 
        entries.pop('__builtins__') 
        entries.pop('__doc__') 
 
        for entry in entries.values():
            entry.utility_code_definition = self 
            entry.used = used 
 
        original_scope = tree.scope 
        dest_scope.merge_in(original_scope, merge_unused=True, whitelist=whitelist)
        tree.scope = dest_scope 
 
        for dep in self.requires: 
            if dep.is_cython_utility: 
                dep.declare_in_scope(dest_scope, cython_scope=cython_scope)
 
        return original_scope 
 

def declare_declarations_in_scope(declaration_string, env, private_type=True, 
                                  *args, **kwargs): 
    """ 
    Declare some declarations given as Cython code in declaration_string 
    in scope env. 
    """ 
    CythonUtilityCode(declaration_string, *args, **kwargs).declare_in_scope(env)