UtilityCode.py 9.2 KB

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