|
|
|
|
|
|
|
|
|
|
|
|
|
|
""" |
|
|
Support for parsing strings into code trees. |
|
|
""" |
|
|
|
|
|
from __future__ import absolute_import |
|
|
|
|
|
import re |
|
|
from io import StringIO |
|
|
|
|
|
from .Scanning import PyrexScanner, StringSourceDescriptor |
|
|
from .Symtab import ModuleScope |
|
|
from . import PyrexTypes |
|
|
from .Visitor import VisitorTransform |
|
|
from .Nodes import Node, StatListNode |
|
|
from .ExprNodes import NameNode |
|
|
from .StringEncoding import _unicode |
|
|
from . import Parsing |
|
|
from . import Main |
|
|
from . import UtilNodes |
|
|
|
|
|
|
|
|
class StringParseContext(Main.Context): |
|
|
def __init__(self, name, include_directories=None, compiler_directives=None, cpp=False): |
|
|
if include_directories is None: |
|
|
include_directories = [] |
|
|
if compiler_directives is None: |
|
|
compiler_directives = {} |
|
|
|
|
|
Main.Context.__init__(self, include_directories, compiler_directives, cpp=cpp, language_level=2) |
|
|
self.module_name = name |
|
|
|
|
|
def find_module(self, module_name, relative_to=None, pos=None, need_pxd=1, absolute_fallback=True): |
|
|
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=None, 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) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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=False) |
|
|
|
|
|
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) |
|
|
|
|
|
def visit_NameNode(self, node): |
|
|
temphandle = self.tempmap.get(node.name) |
|
|
if temphandle: |
|
|
|
|
|
return temphandle.ref(self.get_pos(node)) |
|
|
else: |
|
|
return self.try_substitution(node, node.name) |
|
|
|
|
|
def visit_ExprStatNode(self, node): |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
_match_indent = re.compile(u"^ *").match |
|
|
|
|
|
|
|
|
def strip_common_indent(lines): |
|
|
"""Strips empty lines and common indentation from the list of strings given in lines""" |
|
|
|
|
|
lines = [x for x in lines if x.strip() != u""] |
|
|
if lines: |
|
|
minindent = min([len(_match_indent(x).group(0)) for x in lines]) |
|
|
lines = [x[minindent:] for x in lines] |
|
|
return lines |
|
|
|
|
|
|
|
|
class TreeFragment(object): |
|
|
def __init__(self, code, name=None, pxds=None, temps=None, pipeline=None, level=None, initial_pos=None): |
|
|
if pxds is None: |
|
|
pxds = {} |
|
|
if temps is None: |
|
|
temps = [] |
|
|
if pipeline is None: |
|
|
pipeline = [] |
|
|
if not name: |
|
|
name = "(tree fragment)" |
|
|
|
|
|
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.items(): |
|
|
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 |
|
|
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=None, temps=None, pos = None): |
|
|
if nodes is None: |
|
|
nodes = {} |
|
|
if temps is None: |
|
|
temps = [] |
|
|
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 |
|
|
|