2
# TreeFragments - parsing of strings to trees
6
Support for parsing strings into code trees.
11
from io import StringIO
13
from .Scanning import PyrexScanner, StringSourceDescriptor
14
from .Symtab import ModuleScope
15
from . import PyrexTypes
16
from .Visitor import VisitorTransform
17
from .Nodes import Node, StatListNode
18
from .ExprNodes import NameNode
21
from . import UtilNodes
24
class StringParseContext(Main.Context):
25
def __init__(self, name, include_directories=None, compiler_directives=None, cpp=False):
26
if include_directories is None:
27
include_directories = []
28
if compiler_directives is None:
29
compiler_directives = {}
30
Main.Context.__init__(self, include_directories, compiler_directives, cpp=cpp, language_level='3')
31
self.module_name = name
33
def find_module(self, module_name, from_module=None, pos=None, need_pxd=1, absolute_fallback=True, relative_import=False):
34
if module_name not in (self.module_name, 'cython'):
35
raise AssertionError("Not yet supporting any cimports/includes from string code snippets")
36
return ModuleScope(module_name, parent_module=None, context=self)
39
def parse_from_strings(name, code, pxds=None, level=None, initial_pos=None,
40
context=None, allow_struct_enum_decorator=False,
41
in_utility_code=False):
43
Utility method to parse a (unicode) string of code. This is mostly
44
used for internal Cython compiler purposes (creating code snippets
45
that transforms should emit, as well as unit testing).
47
code - a unicode string containing Cython (module-level) code
48
name - a descriptive name for the code source (to use in error messages etc.)
49
in_utility_code - used to suppress some messages from utility code. False by default
50
because some generated code snippets like properties and dataclasses
51
probably want to see those messages.
55
The tree, i.e. a ModuleNode. The ModuleNode's scope attribute is
56
set to the scope used when parsing.
59
context = StringParseContext(name)
60
# Since source files carry an encoding, it makes sense in this context
61
# to use a unicode string so that code fragments don't have to bother
62
# with encoding. This means that test code passed in should not have an
64
assert isinstance(code, str), "unicode code snippets only please"
68
if initial_pos is None:
69
initial_pos = (name, 1, 0)
70
code_source = StringSourceDescriptor(name, code)
72
code_source.in_utility_code = True
74
scope = context.find_module(module_name, pos=initial_pos, need_pxd=False)
78
scanner = PyrexScanner(buf, code_source, source_encoding = encoding,
79
scope = scope, context = context, initial_pos = initial_pos)
80
ctx = Parsing.Ctx(allow_struct_enum_decorator=allow_struct_enum_decorator)
83
tree = Parsing.p_module(scanner, 0, module_name, ctx=ctx)
87
tree = Parsing.p_code(scanner, level=level, ctx=ctx)
93
class TreeCopier(VisitorTransform):
94
def visit_Node(self, node):
103
class ApplyPositionAndCopy(TreeCopier):
104
def __init__(self, pos):
108
def visit_Node(self, node):
109
copy = super().visit_Node(node)
114
class TemplateTransform(VisitorTransform):
116
Makes a copy of a template tree while doing substitutions.
118
A dictionary "substitutions" should be passed in when calling
119
the transform; mapping names to replacement nodes. Then replacement
121
- If an ExprStatNode contains a single NameNode, whose name is
122
a key in the substitutions dictionary, the ExprStatNode is
123
replaced with a copy of the tree given in the dictionary.
124
It is the responsibility of the caller that the replacement
125
node is a valid statement.
126
- If a single NameNode is otherwise encountered, it is replaced
127
if its name is listed in the substitutions dictionary in the
128
same way. It is the responsibility of the caller to make sure
129
that the replacement nodes is a valid expression.
131
Also a list "temps" should be passed. Any names listed will
132
be transformed into anonymous, temporary names.
134
Currently supported for tempnames is:
136
(various function and class definition nodes etc. should be added to this)
138
Each replacement node gets the position of the substituted node
139
recursively applied to every member node.
142
temp_name_counter = 0
144
def __call__(self, node, substitutions, temps, pos):
145
self.substitutions = substitutions
150
TemplateTransform.temp_name_counter += 1
151
handle = UtilNodes.TempHandle(PyrexTypes.py_object_type)
152
tempmap[temp] = handle
153
temphandles.append(handle)
154
self.tempmap = tempmap
155
result = super().__call__(node)
157
result = UtilNodes.TempsBlockNode(self.get_pos(node),
162
def get_pos(self, node):
168
def visit_Node(self, node):
172
c = node.clone_node()
173
if self.pos is not None:
175
self.visitchildren(c)
178
def try_substitution(self, node, key):
179
sub = self.substitutions.get(key)
182
if pos is None: pos = node.pos
183
return ApplyPositionAndCopy(pos)(sub)
185
return self.visit_Node(node) # make copy as usual
187
def visit_NameNode(self, node):
188
temphandle = self.tempmap.get(node.name)
190
# Replace name with temporary
191
return temphandle.ref(self.get_pos(node))
193
return self.try_substitution(node, node.name)
195
def visit_ExprStatNode(self, node):
196
# If an expression-as-statement consists of only a replaceable
197
# NameNode, we replace the entire statement, not only the NameNode
198
if isinstance(node.expr, NameNode):
199
return self.try_substitution(node, node.expr.name)
201
return self.visit_Node(node)
204
def copy_code_tree(node):
205
return TreeCopier()(node)
208
_match_indent = re.compile("^ *").match
211
def strip_common_indent(lines):
212
"""Strips empty lines and common indentation from the list of strings given in lines"""
213
# TODO: Facilitate textwrap.indent instead
214
lines = [x for x in lines if x.strip() != ""]
216
minindent = min([len(_match_indent(x).group(0)) for x in lines])
217
lines = [x[minindent:] for x in lines]
222
def __init__(self, code, name=None, pxds=None, temps=None, pipeline=None, level=None, initial_pos=None):
230
name = "(tree fragment)"
232
if isinstance(code, str):
233
def fmt(x): return u"\n".join(strip_common_indent(x.split(u"\n")))
237
for key, value in pxds.items():
238
fmt_pxds[key] = fmt(value)
239
mod = t = parse_from_strings(name, fmt_code, fmt_pxds, level=level, initial_pos=initial_pos)
241
t = t.body # Make sure a StatListNode is at the top
242
if not isinstance(t, StatListNode):
243
t = StatListNode(pos=mod.pos, stats=[t])
244
for transform in pipeline:
245
if transform is None:
249
elif isinstance(code, Node):
251
raise NotImplementedError()
254
raise ValueError("Unrecognized code format (accepts unicode and Node)")
258
return copy_code_tree(self.root)
260
def substitute(self, nodes=None, temps=None, pos = None):
265
return TemplateTransform()(self.root,
266
substitutions = nodes,
267
temps = self.temps + temps, pos = pos)
270
class SetPosTransform(VisitorTransform):
271
def __init__(self, pos):
275
def visit_Node(self, node):
277
self.visitchildren(node)