| Index: third_party/cython/src/Cython/Compiler/TreeFragment.py
|
| diff --git a/third_party/cython/src/Cython/Compiler/TreeFragment.py b/third_party/cython/src/Cython/Compiler/TreeFragment.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..983295983333cf882a9dc53736055635444103d5
|
| --- /dev/null
|
| +++ b/third_party/cython/src/Cython/Compiler/TreeFragment.py
|
| @@ -0,0 +1,242 @@
|
| +#
|
| +# TreeFragments - parsing of strings to trees
|
| +#
|
| +
|
| +import re
|
| +from StringIO import StringIO
|
| +from Scanning import PyrexScanner, StringSourceDescriptor
|
| +from Symtab import ModuleScope
|
| +import PyrexTypes
|
| +from Visitor import VisitorTransform
|
| +from Nodes import Node, StatListNode
|
| +from ExprNodes import NameNode
|
| +import Parsing
|
| +import Main
|
| +import UtilNodes
|
| +
|
| +"""
|
| +Support for parsing strings into code trees.
|
| +"""
|
| +
|
| +class StringParseContext(Main.Context):
|
| + def __init__(self, name, include_directories=None):
|
| + if include_directories is None: include_directories = []
|
| + Main.Context.__init__(self, include_directories, {},
|
| + create_testscope=False)
|
| + self.module_name = name
|
| +
|
| + def find_module(self, module_name, relative_to = None, pos = None, need_pxd = 1):
|
| + if module_name not in (self.module_name, 'cython'):
|
| + raise AssertionError("Not yet supporting any cimports/includes from string code snippets")
|
| + return ModuleScope(module_name, parent_module = None, context = self)
|
| +
|
| +def parse_from_strings(name, code, pxds={}, level=None, initial_pos=None,
|
| + context=None, allow_struct_enum_decorator=False):
|
| + """
|
| + Utility method to parse a (unicode) string of code. This is mostly
|
| + used for internal Cython compiler purposes (creating code snippets
|
| + that transforms should emit, as well as unit testing).
|
| +
|
| + code - a unicode string containing Cython (module-level) code
|
| + name - a descriptive name for the code source (to use in error messages etc.)
|
| +
|
| + RETURNS
|
| +
|
| + The tree, i.e. a ModuleNode. The ModuleNode's scope attribute is
|
| + set to the scope used when parsing.
|
| + """
|
| + if context is None:
|
| + context = StringParseContext(name)
|
| + # Since source files carry an encoding, it makes sense in this context
|
| + # to use a unicode string so that code fragments don't have to bother
|
| + # with encoding. This means that test code passed in should not have an
|
| + # encoding header.
|
| + assert isinstance(code, unicode), "unicode code snippets only please"
|
| + encoding = "UTF-8"
|
| +
|
| + module_name = name
|
| + if initial_pos is None:
|
| + initial_pos = (name, 1, 0)
|
| + code_source = StringSourceDescriptor(name, code)
|
| +
|
| + scope = context.find_module(module_name, pos = initial_pos, need_pxd = 0)
|
| +
|
| + buf = StringIO(code)
|
| +
|
| + scanner = PyrexScanner(buf, code_source, source_encoding = encoding,
|
| + scope = scope, context = context, initial_pos = initial_pos)
|
| + ctx = Parsing.Ctx(allow_struct_enum_decorator=allow_struct_enum_decorator)
|
| +
|
| + if level is None:
|
| + tree = Parsing.p_module(scanner, 0, module_name, ctx=ctx)
|
| + tree.scope = scope
|
| + tree.is_pxd = False
|
| + else:
|
| + tree = Parsing.p_code(scanner, level=level, ctx=ctx)
|
| +
|
| + tree.scope = scope
|
| + return tree
|
| +
|
| +class TreeCopier(VisitorTransform):
|
| + def visit_Node(self, node):
|
| + if node is None:
|
| + return node
|
| + else:
|
| + c = node.clone_node()
|
| + self.visitchildren(c)
|
| + return c
|
| +
|
| +class ApplyPositionAndCopy(TreeCopier):
|
| + def __init__(self, pos):
|
| + super(ApplyPositionAndCopy, self).__init__()
|
| + self.pos = pos
|
| +
|
| + def visit_Node(self, node):
|
| + copy = super(ApplyPositionAndCopy, self).visit_Node(node)
|
| + copy.pos = self.pos
|
| + return copy
|
| +
|
| +class TemplateTransform(VisitorTransform):
|
| + """
|
| + Makes a copy of a template tree while doing substitutions.
|
| +
|
| + A dictionary "substitutions" should be passed in when calling
|
| + the transform; mapping names to replacement nodes. Then replacement
|
| + happens like this:
|
| + - If an ExprStatNode contains a single NameNode, whose name is
|
| + a key in the substitutions dictionary, the ExprStatNode is
|
| + replaced with a copy of the tree given in the dictionary.
|
| + It is the responsibility of the caller that the replacement
|
| + node is a valid statement.
|
| + - If a single NameNode is otherwise encountered, it is replaced
|
| + if its name is listed in the substitutions dictionary in the
|
| + same way. It is the responsibility of the caller to make sure
|
| + that the replacement nodes is a valid expression.
|
| +
|
| + Also a list "temps" should be passed. Any names listed will
|
| + be transformed into anonymous, temporary names.
|
| +
|
| + Currently supported for tempnames is:
|
| + NameNode
|
| + (various function and class definition nodes etc. should be added to this)
|
| +
|
| + Each replacement node gets the position of the substituted node
|
| + recursively applied to every member node.
|
| + """
|
| +
|
| + temp_name_counter = 0
|
| +
|
| + def __call__(self, node, substitutions, temps, pos):
|
| + self.substitutions = substitutions
|
| + self.pos = pos
|
| + tempmap = {}
|
| + temphandles = []
|
| + for temp in temps:
|
| + TemplateTransform.temp_name_counter += 1
|
| + handle = UtilNodes.TempHandle(PyrexTypes.py_object_type)
|
| + tempmap[temp] = handle
|
| + temphandles.append(handle)
|
| + self.tempmap = tempmap
|
| + result = super(TemplateTransform, self).__call__(node)
|
| + if temps:
|
| + result = UtilNodes.TempsBlockNode(self.get_pos(node),
|
| + temps=temphandles,
|
| + body=result)
|
| + return result
|
| +
|
| + def get_pos(self, node):
|
| + if self.pos:
|
| + return self.pos
|
| + else:
|
| + return node.pos
|
| +
|
| + def visit_Node(self, node):
|
| + if node is None:
|
| + return None
|
| + else:
|
| + c = node.clone_node()
|
| + if self.pos is not None:
|
| + c.pos = self.pos
|
| + self.visitchildren(c)
|
| + return c
|
| +
|
| + def try_substitution(self, node, key):
|
| + sub = self.substitutions.get(key)
|
| + if sub is not None:
|
| + pos = self.pos
|
| + if pos is None: pos = node.pos
|
| + return ApplyPositionAndCopy(pos)(sub)
|
| + else:
|
| + return self.visit_Node(node) # make copy as usual
|
| +
|
| + def visit_NameNode(self, node):
|
| + temphandle = self.tempmap.get(node.name)
|
| + if temphandle:
|
| + # Replace name with temporary
|
| + return temphandle.ref(self.get_pos(node))
|
| + else:
|
| + return self.try_substitution(node, node.name)
|
| +
|
| + def visit_ExprStatNode(self, node):
|
| + # If an expression-as-statement consists of only a replaceable
|
| + # NameNode, we replace the entire statement, not only the NameNode
|
| + if isinstance(node.expr, NameNode):
|
| + return self.try_substitution(node, node.expr.name)
|
| + else:
|
| + return self.visit_Node(node)
|
| +
|
| +def copy_code_tree(node):
|
| + return TreeCopier()(node)
|
| +
|
| +INDENT_RE = re.compile(ur"^ *")
|
| +def strip_common_indent(lines):
|
| + "Strips empty lines and common indentation from the list of strings given in lines"
|
| + # TODO: Facilitate textwrap.indent instead
|
| + lines = [x for x in lines if x.strip() != u""]
|
| + minindent = min([len(INDENT_RE.match(x).group(0)) for x in lines])
|
| + lines = [x[minindent:] for x in lines]
|
| + return lines
|
| +
|
| +class TreeFragment(object):
|
| + def __init__(self, code, name="(tree fragment)", pxds={}, temps=[], pipeline=[], level=None, initial_pos=None):
|
| + if isinstance(code, unicode):
|
| + def fmt(x): return u"\n".join(strip_common_indent(x.split(u"\n")))
|
| +
|
| + fmt_code = fmt(code)
|
| + fmt_pxds = {}
|
| + for key, value in pxds.iteritems():
|
| + fmt_pxds[key] = fmt(value)
|
| + mod = t = parse_from_strings(name, fmt_code, fmt_pxds, level=level, initial_pos=initial_pos)
|
| + if level is None:
|
| + t = t.body # Make sure a StatListNode is at the top
|
| + if not isinstance(t, StatListNode):
|
| + t = StatListNode(pos=mod.pos, stats=[t])
|
| + for transform in pipeline:
|
| + if transform is None:
|
| + continue
|
| + t = transform(t)
|
| + self.root = t
|
| + elif isinstance(code, Node):
|
| + if pxds != {}: raise NotImplementedError()
|
| + self.root = code
|
| + else:
|
| + raise ValueError("Unrecognized code format (accepts unicode and Node)")
|
| + self.temps = temps
|
| +
|
| + def copy(self):
|
| + return copy_code_tree(self.root)
|
| +
|
| + def substitute(self, nodes={}, temps=[], pos = None):
|
| + return TemplateTransform()(self.root,
|
| + substitutions = nodes,
|
| + temps = self.temps + temps, pos = pos)
|
| +
|
| +class SetPosTransform(VisitorTransform):
|
| + def __init__(self, pos):
|
| + super(SetPosTransform, self).__init__()
|
| + self.pos = pos
|
| +
|
| + def visit_Node(self, node):
|
| + node.pos = self.pos
|
| + self.visitchildren(node)
|
| + return node
|
|
|