Update mojo sdk to rev 1dc8a9a5db73d3718d99917fadf31f5fb2ebad4f
[chromium-blink-merge.git] / third_party / cython / src / Cython / Compiler / TreeFragment.py
blob983295983333cf882a9dc53736055635444103d5
2 # TreeFragments - parsing of strings to trees
5 import re
6 from StringIO import StringIO
7 from Scanning import PyrexScanner, StringSourceDescriptor
8 from Symtab import ModuleScope
9 import PyrexTypes
10 from Visitor import VisitorTransform
11 from Nodes import Node, StatListNode
12 from ExprNodes import NameNode
13 import Parsing
14 import Main
15 import UtilNodes
17 """
18 Support for parsing strings into code trees.
19 """
21 class StringParseContext(Main.Context):
22 def __init__(self, name, include_directories=None):
23 if include_directories is None: include_directories = []
24 Main.Context.__init__(self, include_directories, {},
25 create_testscope=False)
26 self.module_name = name
28 def find_module(self, module_name, relative_to = None, pos = None, need_pxd = 1):
29 if module_name not in (self.module_name, 'cython'):
30 raise AssertionError("Not yet supporting any cimports/includes from string code snippets")
31 return ModuleScope(module_name, parent_module = None, context = self)
33 def parse_from_strings(name, code, pxds={}, level=None, initial_pos=None,
34 context=None, allow_struct_enum_decorator=False):
35 """
36 Utility method to parse a (unicode) string of code. This is mostly
37 used for internal Cython compiler purposes (creating code snippets
38 that transforms should emit, as well as unit testing).
40 code - a unicode string containing Cython (module-level) code
41 name - a descriptive name for the code source (to use in error messages etc.)
43 RETURNS
45 The tree, i.e. a ModuleNode. The ModuleNode's scope attribute is
46 set to the scope used when parsing.
47 """
48 if context is None:
49 context = StringParseContext(name)
50 # Since source files carry an encoding, it makes sense in this context
51 # to use a unicode string so that code fragments don't have to bother
52 # with encoding. This means that test code passed in should not have an
53 # encoding header.
54 assert isinstance(code, unicode), "unicode code snippets only please"
55 encoding = "UTF-8"
57 module_name = name
58 if initial_pos is None:
59 initial_pos = (name, 1, 0)
60 code_source = StringSourceDescriptor(name, code)
62 scope = context.find_module(module_name, pos = initial_pos, need_pxd = 0)
64 buf = StringIO(code)
66 scanner = PyrexScanner(buf, code_source, source_encoding = encoding,
67 scope = scope, context = context, initial_pos = initial_pos)
68 ctx = Parsing.Ctx(allow_struct_enum_decorator=allow_struct_enum_decorator)
70 if level is None:
71 tree = Parsing.p_module(scanner, 0, module_name, ctx=ctx)
72 tree.scope = scope
73 tree.is_pxd = False
74 else:
75 tree = Parsing.p_code(scanner, level=level, ctx=ctx)
77 tree.scope = scope
78 return tree
80 class TreeCopier(VisitorTransform):
81 def visit_Node(self, node):
82 if node is None:
83 return node
84 else:
85 c = node.clone_node()
86 self.visitchildren(c)
87 return c
89 class ApplyPositionAndCopy(TreeCopier):
90 def __init__(self, pos):
91 super(ApplyPositionAndCopy, self).__init__()
92 self.pos = pos
94 def visit_Node(self, node):
95 copy = super(ApplyPositionAndCopy, self).visit_Node(node)
96 copy.pos = self.pos
97 return copy
99 class TemplateTransform(VisitorTransform):
101 Makes a copy of a template tree while doing substitutions.
103 A dictionary "substitutions" should be passed in when calling
104 the transform; mapping names to replacement nodes. Then replacement
105 happens like this:
106 - If an ExprStatNode contains a single NameNode, whose name is
107 a key in the substitutions dictionary, the ExprStatNode is
108 replaced with a copy of the tree given in the dictionary.
109 It is the responsibility of the caller that the replacement
110 node is a valid statement.
111 - If a single NameNode is otherwise encountered, it is replaced
112 if its name is listed in the substitutions dictionary in the
113 same way. It is the responsibility of the caller to make sure
114 that the replacement nodes is a valid expression.
116 Also a list "temps" should be passed. Any names listed will
117 be transformed into anonymous, temporary names.
119 Currently supported for tempnames is:
120 NameNode
121 (various function and class definition nodes etc. should be added to this)
123 Each replacement node gets the position of the substituted node
124 recursively applied to every member node.
127 temp_name_counter = 0
129 def __call__(self, node, substitutions, temps, pos):
130 self.substitutions = substitutions
131 self.pos = pos
132 tempmap = {}
133 temphandles = []
134 for temp in temps:
135 TemplateTransform.temp_name_counter += 1
136 handle = UtilNodes.TempHandle(PyrexTypes.py_object_type)
137 tempmap[temp] = handle
138 temphandles.append(handle)
139 self.tempmap = tempmap
140 result = super(TemplateTransform, self).__call__(node)
141 if temps:
142 result = UtilNodes.TempsBlockNode(self.get_pos(node),
143 temps=temphandles,
144 body=result)
145 return result
147 def get_pos(self, node):
148 if self.pos:
149 return self.pos
150 else:
151 return node.pos
153 def visit_Node(self, node):
154 if node is None:
155 return None
156 else:
157 c = node.clone_node()
158 if self.pos is not None:
159 c.pos = self.pos
160 self.visitchildren(c)
161 return c
163 def try_substitution(self, node, key):
164 sub = self.substitutions.get(key)
165 if sub is not None:
166 pos = self.pos
167 if pos is None: pos = node.pos
168 return ApplyPositionAndCopy(pos)(sub)
169 else:
170 return self.visit_Node(node) # make copy as usual
172 def visit_NameNode(self, node):
173 temphandle = self.tempmap.get(node.name)
174 if temphandle:
175 # Replace name with temporary
176 return temphandle.ref(self.get_pos(node))
177 else:
178 return self.try_substitution(node, node.name)
180 def visit_ExprStatNode(self, node):
181 # If an expression-as-statement consists of only a replaceable
182 # NameNode, we replace the entire statement, not only the NameNode
183 if isinstance(node.expr, NameNode):
184 return self.try_substitution(node, node.expr.name)
185 else:
186 return self.visit_Node(node)
188 def copy_code_tree(node):
189 return TreeCopier()(node)
191 INDENT_RE = re.compile(ur"^ *")
192 def strip_common_indent(lines):
193 "Strips empty lines and common indentation from the list of strings given in lines"
194 # TODO: Facilitate textwrap.indent instead
195 lines = [x for x in lines if x.strip() != u""]
196 minindent = min([len(INDENT_RE.match(x).group(0)) for x in lines])
197 lines = [x[minindent:] for x in lines]
198 return lines
200 class TreeFragment(object):
201 def __init__(self, code, name="(tree fragment)", pxds={}, temps=[], pipeline=[], level=None, initial_pos=None):
202 if isinstance(code, unicode):
203 def fmt(x): return u"\n".join(strip_common_indent(x.split(u"\n")))
205 fmt_code = fmt(code)
206 fmt_pxds = {}
207 for key, value in pxds.iteritems():
208 fmt_pxds[key] = fmt(value)
209 mod = t = parse_from_strings(name, fmt_code, fmt_pxds, level=level, initial_pos=initial_pos)
210 if level is None:
211 t = t.body # Make sure a StatListNode is at the top
212 if not isinstance(t, StatListNode):
213 t = StatListNode(pos=mod.pos, stats=[t])
214 for transform in pipeline:
215 if transform is None:
216 continue
217 t = transform(t)
218 self.root = t
219 elif isinstance(code, Node):
220 if pxds != {}: raise NotImplementedError()
221 self.root = code
222 else:
223 raise ValueError("Unrecognized code format (accepts unicode and Node)")
224 self.temps = temps
226 def copy(self):
227 return copy_code_tree(self.root)
229 def substitute(self, nodes={}, temps=[], pos = None):
230 return TemplateTransform()(self.root,
231 substitutions = nodes,
232 temps = self.temps + temps, pos = pos)
234 class SetPosTransform(VisitorTransform):
235 def __init__(self, pos):
236 super(SetPosTransform, self).__init__()
237 self.pos = pos
239 def visit_Node(self, node):
240 node.pos = self.pos
241 self.visitchildren(node)
242 return node