1from __future__ import absolute_import 2 3from .TreeFragment import parse_from_strings, StringParseContext 4from . import Symtab 5from . import Naming 6from . import Code 7 8 9class NonManglingModuleScope(Symtab.ModuleScope): 10 11 def __init__(self, prefix, *args, **kw): 12 self.prefix = prefix 13 self.cython_scope = None 14 self.cpp = kw.pop('cpp', False) 15 Symtab.ModuleScope.__init__(self, *args, **kw) 16 17 def add_imported_entry(self, name, entry, pos): 18 entry.used = True 19 return super(NonManglingModuleScope, self).add_imported_entry(name, entry, pos) 20 21 def mangle(self, prefix, name=None): 22 if name: 23 if prefix in (Naming.typeobj_prefix, Naming.func_prefix, Naming.var_prefix, Naming.pyfunc_prefix): 24 # Functions, classes etc. gets a manually defined prefix easily 25 # manually callable instead (the one passed to CythonUtilityCode) 26 prefix = self.prefix 27 return "%s%s" % (prefix, name) 28 else: 29 return Symtab.ModuleScope.mangle(self, prefix) 30 31 32class CythonUtilityCodeContext(StringParseContext): 33 scope = None 34 35 def find_module(self, module_name, relative_to=None, pos=None, need_pxd=True, absolute_fallback=True): 36 if relative_to: 37 raise AssertionError("Relative imports not supported in utility code.") 38 if module_name != self.module_name: 39 if module_name not in self.modules: 40 raise AssertionError("Only the cython cimport is supported.") 41 else: 42 return self.modules[module_name] 43 44 if self.scope is None: 45 self.scope = NonManglingModuleScope( 46 self.prefix, module_name, parent_module=None, context=self, cpp=self.cpp) 47 48 return self.scope 49 50 51class CythonUtilityCode(Code.UtilityCodeBase): 52 """ 53 Utility code written in the Cython language itself. 54 55 The @cname decorator can set the cname for a function, method of cdef class. 56 Functions decorated with @cname('c_func_name') get the given cname. 57 58 For cdef classes the rules are as follows: 59 obj struct -> <cname>_obj 60 obj type ptr -> <cname>_type 61 methods -> <class_cname>_<method_cname> 62 63 For methods the cname decorator is optional, but without the decorator the 64 methods will not be prototyped. See Cython.Compiler.CythonScope and 65 tests/run/cythonscope.pyx for examples. 66 """ 67 68 is_cython_utility = True 69 70 def __init__(self, impl, name="__pyxutil", prefix="", requires=None, 71 file=None, from_scope=None, context=None, compiler_directives=None, 72 outer_module_scope=None): 73 # 1) We need to delay the parsing/processing, so that all modules can be 74 # imported without import loops 75 # 2) The same utility code object can be used for multiple source files; 76 # while the generated node trees can be altered in the compilation of a 77 # single file. 78 # Hence, delay any processing until later. 79 context_types = {} 80 if context is not None: 81 from .PyrexTypes import BaseType 82 for key, value in context.items(): 83 if isinstance(value, BaseType): 84 context[key] = key 85 context_types[key] = value 86 impl = Code.sub_tempita(impl, context, file, name) 87 self.impl = impl 88 self.name = name 89 self.file = file 90 self.prefix = prefix 91 self.requires = requires or [] 92 self.from_scope = from_scope 93 self.outer_module_scope = outer_module_scope 94 self.compiler_directives = compiler_directives 95 self.context_types = context_types 96 97 def __eq__(self, other): 98 if isinstance(other, CythonUtilityCode): 99 return self._equality_params() == other._equality_params() 100 else: 101 return False 102 103 def _equality_params(self): 104 outer_scope = self.outer_module_scope 105 while isinstance(outer_scope, NonManglingModuleScope): 106 outer_scope = outer_scope.outer_scope 107 return self.impl, outer_scope, self.compiler_directives 108 109 def __hash__(self): 110 return hash(self.impl) 111 112 def get_tree(self, entries_only=False, cython_scope=None): 113 from .AnalysedTreeTransforms import AutoTestDictTransform 114 # The AutoTestDictTransform creates the statement "__test__ = {}", 115 # which when copied into the main ModuleNode overwrites 116 # any __test__ in user code; not desired 117 excludes = [AutoTestDictTransform] 118 119 from . import Pipeline, ParseTreeTransforms 120 context = CythonUtilityCodeContext( 121 self.name, compiler_directives=self.compiler_directives, 122 cpp=cython_scope.is_cpp() if cython_scope else False) 123 context.prefix = self.prefix 124 context.cython_scope = cython_scope 125 #context = StringParseContext(self.name) 126 tree = parse_from_strings( 127 self.name, self.impl, context=context, allow_struct_enum_decorator=True) 128 pipeline = Pipeline.create_pipeline(context, 'pyx', exclude_classes=excludes) 129 130 if entries_only: 131 p = [] 132 for t in pipeline: 133 p.append(t) 134 if isinstance(t, ParseTreeTransforms.AnalyseDeclarationsTransform): 135 break 136 137 pipeline = p 138 139 transform = ParseTreeTransforms.CnameDirectivesTransform(context) 140 # InterpretCompilerDirectives already does a cdef declarator check 141 #before = ParseTreeTransforms.DecoratorTransform 142 before = ParseTreeTransforms.InterpretCompilerDirectives 143 pipeline = Pipeline.insert_into_pipeline(pipeline, transform, 144 before=before) 145 146 def merge_scope(scope): 147 def merge_scope_transform(module_node): 148 module_node.scope.merge_in(scope) 149 return module_node 150 return merge_scope_transform 151 152 if self.from_scope: 153 pipeline = Pipeline.insert_into_pipeline( 154 pipeline, merge_scope(self.from_scope), 155 before=ParseTreeTransforms.AnalyseDeclarationsTransform) 156 157 for dep in self.requires: 158 if isinstance(dep, CythonUtilityCode) and hasattr(dep, 'tree') and not cython_scope: 159 pipeline = Pipeline.insert_into_pipeline( 160 pipeline, merge_scope(dep.tree.scope), 161 before=ParseTreeTransforms.AnalyseDeclarationsTransform) 162 163 if self.outer_module_scope: 164 # inject outer module between utility code module and builtin module 165 def scope_transform(module_node): 166 module_node.scope.outer_scope = self.outer_module_scope 167 return module_node 168 169 pipeline = Pipeline.insert_into_pipeline( 170 pipeline, scope_transform, 171 before=ParseTreeTransforms.AnalyseDeclarationsTransform) 172 173 if self.context_types: 174 # inject types into module scope 175 def scope_transform(module_node): 176 for name, type in self.context_types.items(): 177 entry = module_node.scope.declare_type(name, type, None, visibility='extern') 178 entry.in_cinclude = True 179 return module_node 180 181 pipeline = Pipeline.insert_into_pipeline( 182 pipeline, scope_transform, 183 before=ParseTreeTransforms.AnalyseDeclarationsTransform) 184 185 (err, tree) = Pipeline.run_pipeline(pipeline, tree, printtree=False) 186 assert not err, err 187 self.tree = tree 188 return tree 189 190 def put_code(self, output): 191 pass 192 193 @classmethod 194 def load_as_string(cls, util_code_name, from_file=None, **kwargs): 195 """ 196 Load a utility code as a string. Returns (proto, implementation) 197 """ 198 util = cls.load(util_code_name, from_file, **kwargs) 199 return util.proto, util.impl # keep line numbers => no lstrip() 200 201 def declare_in_scope(self, dest_scope, used=False, cython_scope=None, 202 allowlist=None): 203 """ 204 Declare all entries from the utility code in dest_scope. Code will only 205 be included for used entries. If module_name is given, declare the 206 type entries with that name. 207 """ 208 tree = self.get_tree(entries_only=True, cython_scope=cython_scope) 209 210 entries = tree.scope.entries 211 entries.pop('__name__') 212 entries.pop('__file__') 213 entries.pop('__builtins__') 214 entries.pop('__doc__') 215 216 for entry in entries.values(): 217 entry.utility_code_definition = self 218 entry.used = used 219 220 original_scope = tree.scope 221 dest_scope.merge_in(original_scope, merge_unused=True, allowlist=allowlist) 222 tree.scope = dest_scope 223 224 for dep in self.requires: 225 if dep.is_cython_utility: 226 dep.declare_in_scope(dest_scope, cython_scope=cython_scope) 227 228 return original_scope 229 230 @staticmethod 231 def filter_inherited_directives(current_directives): 232 """ 233 Cython utility code should usually only pick up a few directives from the 234 environment (those that intentionally control its function) and ignore most 235 other compiler directives. This function provides a sensible default list 236 of directives to copy. 237 """ 238 from .Options import _directive_defaults 239 utility_code_directives = dict(_directive_defaults) 240 inherited_directive_names = ( 241 'binding', 'always_allow_keywords', 'allow_none_for_extension_args', 242 'auto_pickle', 'ccomplex', 243 'c_string_type', 'c_string_encoding', 244 'optimize.inline_defnode_calls', 'optimize.unpack_method_calls', 245 'optimize.unpack_method_calls_in_pyinit', 'optimize.use_switch') 246 for name in inherited_directive_names: 247 if name in current_directives: 248 utility_code_directives[name] = current_directives[name] 249 return utility_code_directives 250 251 252def declare_declarations_in_scope(declaration_string, env, private_type=True, 253 *args, **kwargs): 254 """ 255 Declare some declarations given as Cython code in declaration_string 256 in scope env. 257 """ 258 CythonUtilityCode(declaration_string, *args, **kwargs).declare_in_scope(env) 259