OLD | NEW |
(Empty) | |
| 1 # |
| 2 # TreeFragments - parsing of strings to trees |
| 3 # |
| 4 |
| 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 |
| 16 |
| 17 """ |
| 18 Support for parsing strings into code trees. |
| 19 """ |
| 20 |
| 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 |
| 27 |
| 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) |
| 32 |
| 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). |
| 39 |
| 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.
) |
| 42 |
| 43 RETURNS |
| 44 |
| 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" |
| 56 |
| 57 module_name = name |
| 58 if initial_pos is None: |
| 59 initial_pos = (name, 1, 0) |
| 60 code_source = StringSourceDescriptor(name, code) |
| 61 |
| 62 scope = context.find_module(module_name, pos = initial_pos, need_pxd = 0) |
| 63 |
| 64 buf = StringIO(code) |
| 65 |
| 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) |
| 69 |
| 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) |
| 76 |
| 77 tree.scope = scope |
| 78 return tree |
| 79 |
| 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 |
| 88 |
| 89 class ApplyPositionAndCopy(TreeCopier): |
| 90 def __init__(self, pos): |
| 91 super(ApplyPositionAndCopy, self).__init__() |
| 92 self.pos = pos |
| 93 |
| 94 def visit_Node(self, node): |
| 95 copy = super(ApplyPositionAndCopy, self).visit_Node(node) |
| 96 copy.pos = self.pos |
| 97 return copy |
| 98 |
| 99 class TemplateTransform(VisitorTransform): |
| 100 """ |
| 101 Makes a copy of a template tree while doing substitutions. |
| 102 |
| 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. |
| 115 |
| 116 Also a list "temps" should be passed. Any names listed will |
| 117 be transformed into anonymous, temporary names. |
| 118 |
| 119 Currently supported for tempnames is: |
| 120 NameNode |
| 121 (various function and class definition nodes etc. should be added to this) |
| 122 |
| 123 Each replacement node gets the position of the substituted node |
| 124 recursively applied to every member node. |
| 125 """ |
| 126 |
| 127 temp_name_counter = 0 |
| 128 |
| 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 |
| 146 |
| 147 def get_pos(self, node): |
| 148 if self.pos: |
| 149 return self.pos |
| 150 else: |
| 151 return node.pos |
| 152 |
| 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 |
| 162 |
| 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 |
| 171 |
| 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) |
| 179 |
| 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) |
| 187 |
| 188 def copy_code_tree(node): |
| 189 return TreeCopier()(node) |
| 190 |
| 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 |
| 199 |
| 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"))) |
| 204 |
| 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 |
| 225 |
| 226 def copy(self): |
| 227 return copy_code_tree(self.root) |
| 228 |
| 229 def substitute(self, nodes={}, temps=[], pos = None): |
| 230 return TemplateTransform()(self.root, |
| 231 substitutions = nodes, |
| 232 temps = self.temps + temps, pos = pos) |
| 233 |
| 234 class SetPosTransform(VisitorTransform): |
| 235 def __init__(self, pos): |
| 236 super(SetPosTransform, self).__init__() |
| 237 self.pos = pos |
| 238 |
| 239 def visit_Node(self, node): |
| 240 node.pos = self.pos |
| 241 self.visitchildren(node) |
| 242 return node |
OLD | NEW |