2 # TreeFragments - parsing of strings to trees
6 from StringIO import StringIO
7 from Scanning import PyrexScanner, StringSourceDescriptor
8 from Symtab import ModuleScope
11 from Visitor import VisitorTransform
12 from Nodes import Node, StatListNode
13 from ExprNodes import NameNode
19 Support for parsing strings into code trees.
22 class StringParseContext(Main.Context):
23 def __init__(self, include_directories, name):
24 Main.Context.__init__(self, include_directories, {})
25 self.module_name = name
27 def find_module(self, module_name, relative_to = None, pos = None, need_pxd = 1):
28 if module_name != self.module_name:
29 raise AssertionError("Not yet supporting any cimports/includes from string code snippets")
30 return ModuleScope(module_name, parent_module = None, context = self)
32 def parse_from_strings(name, code, pxds={}, level=None, initial_pos=None):
34 Utility method to parse a (unicode) string of code. This is mostly
35 used for internal Cython compiler purposes (creating code snippets
36 that transforms should emit, as well as unit testing).
38 code - a unicode string containing Cython (module-level) code
39 name - a descriptive name for the code source (to use in error messages etc.)
42 # Since source files carry an encoding, it makes sense in this context
43 # to use a unicode string so that code fragments don't have to bother
44 # with encoding. This means that test code passed in should not have an
46 assert isinstance(code, unicode), "unicode code snippets only please"
50 if initial_pos is None:
51 initial_pos = (name, 1, 0)
52 code_source = StringSourceDescriptor(name, code)
54 context = StringParseContext([], name)
55 scope = context.find_module(module_name, pos = initial_pos, need_pxd = 0)
59 scanner = PyrexScanner(buf, code_source, source_encoding = encoding,
60 scope = scope, context = context, initial_pos = initial_pos)
62 tree = Parsing.p_module(scanner, 0, module_name)
65 tree = Parsing.p_code(scanner, level=level)
68 class TreeCopier(VisitorTransform):
69 def visit_Node(self, node):
77 class ApplyPositionAndCopy(TreeCopier):
78 def __init__(self, pos):
79 super(ApplyPositionAndCopy, self).__init__()
82 def visit_Node(self, node):
83 copy = super(ApplyPositionAndCopy, self).visit_Node(node)
87 class TemplateTransform(VisitorTransform):
89 Makes a copy of a template tree while doing substitutions.
91 A dictionary "substitutions" should be passed in when calling
92 the transform; mapping names to replacement nodes. Then replacement
94 - If an ExprStatNode contains a single NameNode, whose name is
95 a key in the substitutions dictionary, the ExprStatNode is
96 replaced with a copy of the tree given in the dictionary.
97 It is the responsibility of the caller that the replacement
98 node is a valid statement.
99 - If a single NameNode is otherwise encountered, it is replaced
100 if its name is listed in the substitutions dictionary in the
101 same way. It is the responsibility of the caller to make sure
102 that the replacement nodes is a valid expression.
104 Also a list "temps" should be passed. Any names listed will
105 be transformed into anonymous, temporary names.
107 Currently supported for tempnames is:
109 (various function and class definition nodes etc. should be added to this)
111 Each replacement node gets the position of the substituted node
112 recursively applied to every member node.
115 temp_name_counter = 0
117 def __call__(self, node, substitutions, temps, pos):
118 self.substitutions = substitutions
123 TemplateTransform.temp_name_counter += 1
124 handle = UtilNodes.TempHandle(PyrexTypes.py_object_type)
125 tempmap[temp] = handle
126 temphandles.append(handle)
127 self.tempmap = tempmap
128 result = super(TemplateTransform, self).__call__(node)
130 result = UtilNodes.TempsBlockNode(self.get_pos(node),
135 def get_pos(self, node):
141 def visit_Node(self, node):
145 c = node.clone_node()
146 if self.pos is not None:
148 self.visitchildren(c)
151 def try_substitution(self, node, key):
152 sub = self.substitutions.get(key)
155 if pos is None: pos = node.pos
156 return ApplyPositionAndCopy(pos)(sub)
158 return self.visit_Node(node) # make copy as usual
160 def visit_NameNode(self, node):
161 temphandle = self.tempmap.get(node.name)
163 # Replace name with temporary
164 return temphandle.ref(self.get_pos(node))
166 return self.try_substitution(node, node.name)
168 def visit_ExprStatNode(self, node):
169 # If an expression-as-statement consists of only a replaceable
170 # NameNode, we replace the entire statement, not only the NameNode
171 if isinstance(node.expr, NameNode):
172 return self.try_substitution(node, node.expr.name)
174 return self.visit_Node(node)
176 def copy_code_tree(node):
177 return TreeCopier()(node)
179 INDENT_RE = re.compile(ur"^ *")
180 def strip_common_indent(lines):
181 "Strips empty lines and common indentation from the list of strings given in lines"
182 # TODO: Facilitate textwrap.indent instead
183 lines = [x for x in lines if x.strip() != u""]
184 minindent = min([len(INDENT_RE.match(x).group(0)) for x in lines])
185 lines = [x[minindent:] for x in lines]
188 class TreeFragment(object):
189 def __init__(self, code, name="(tree fragment)", pxds={}, temps=[], pipeline=[], level=None, initial_pos=None):
190 if isinstance(code, unicode):
191 def fmt(x): return u"\n".join(strip_common_indent(x.split(u"\n")))
195 for key, value in pxds.iteritems():
196 fmt_pxds[key] = fmt(value)
197 mod = t = parse_from_strings(name, fmt_code, fmt_pxds, level=level, initial_pos=initial_pos)
199 t = t.body # Make sure a StatListNode is at the top
200 if not isinstance(t, StatListNode):
201 t = StatListNode(pos=mod.pos, stats=[t])
202 for transform in pipeline:
203 if transform is None:
207 elif isinstance(code, Node):
208 if pxds != {}: raise NotImplementedError()
211 raise ValueError("Unrecognized code format (accepts unicode and Node)")
215 return copy_code_tree(self.root)
217 def substitute(self, nodes={}, temps=[], pos = None):
218 return TemplateTransform()(self.root,
219 substitutions = nodes,
220 temps = self.temps + temps, pos = pos)