import ExprNodes
import Nodes
import Options
+import Builtin
from Cython.Compiler.Visitor import VisitorTransform, TreeVisitor
from Cython.Compiler.Visitor import CythonTransform, EnvTransform, ScopeTrackingTransform
from Cython.Compiler.ModuleNode import ModuleNode
-from Cython.Compiler.UtilNodes import LetNode, LetRefNode
+from Cython.Compiler.UtilNodes import LetNode, LetRefNode, ResultRefNode
from Cython.Compiler.TreeFragment import TreeFragment, TemplateTransform
from Cython.Compiler.StringEncoding import EncodedString
from Cython.Compiler.Errors import error, warning, CompileError, InternalError
def visit_CStructOrUnionDefNode(self, node):
return node
-
class NormalizeTree(CythonTransform):
"""
This transform fixes up a few things after parsing
collector = YieldNodeCollector()
collector.visitchildren(node.result_expr)
if collector.yields or isinstance(node.result_expr, ExprNodes.YieldExprNode):
- body = ExprNodes.YieldExprNode(
- node.result_expr.pos, arg=node.result_expr)
- body = Nodes.ExprStatNode(node.result_expr.pos, expr=body)
+ body = Nodes.ExprStatNode(
+ node.result_expr.pos, expr=node.result_expr)
else:
body = Nodes.ReturnStatNode(
node.result_expr.pos, value=node.result_expr)
return assign_node
+ def _flatten_sequence(self, seq, result):
+ for arg in seq.args:
+ if arg.is_sequence_constructor:
+ self._flatten_sequence(arg, result)
+ else:
+ result.append(arg)
+ return result
+
+ def visit_DelStatNode(self, node):
+ self.visitchildren(node)
+ node.args = self._flatten_sequence(node, [])
+ return node
+
+
def eliminate_rhs_duplicates(expr_list_list, ref_node_sequence):
"""Replace rhs items by LetRefNodes if they appear more than once.
Creates a sequence of LetRefNodes that set up the required temps
# right side of the starred target
for i, (targets, expr) in enumerate(zip(lhs_targets[-lhs_remaining:],
- lhs_args[-lhs_remaining:])):
+ lhs_args[starred + 1:])):
targets.append(expr)
# the starred target itself, must be assigned a (potentially empty) list
if isinstance(node, Nodes.CFuncDefNode):
if u'inline' in node.modifiers and self.scope_type == 'pxd':
node.inline_in_pxd = True
- if node.visibility != 'private':
+ if node.c_visibility != 'private':
err = self.ERR_NOGO_WITH_INLINE % node.visibility
elif node.api:
err = self.ERR_NOGO_WITH_INLINE % 'api'
super(InterpretCompilerDirectives, self).__init__(context)
self.compilation_directive_defaults = {}
for key, value in compilation_directive_defaults.items():
- self.compilation_directive_defaults[unicode(key)] = value
+ self.compilation_directive_defaults[unicode(key)] = copy.deepcopy(value)
self.cython_module_names = cython.set()
self.directive_names = {}
self.wrong_scope_error(node.pos, key, 'module')
del node.directive_comments[key]
- directives = copy.copy(Options.directive_defaults)
- directives.update(self.compilation_directive_defaults)
+ directives = copy.deepcopy(Options.directive_defaults)
+ directives.update(copy.deepcopy(self.compilation_directive_defaults))
directives.update(node.directive_comments)
self.directives = directives
node.directives = directives
return self.visit_Node(node)
class WithTransform(CythonTransform, SkipDeclarations):
-
- # EXCINFO is manually set to a variable that contains
- # the exc_info() tuple that can be generated by the enclosing except
- # statement.
- template_without_target = TreeFragment(u"""
- MGR = EXPR
- EXIT = MGR.__exit__
- MGR.__enter__()
- EXC = True
- try:
- try:
- EXCINFO = None
- BODY
- except:
- EXC = False
- if not EXIT(*EXCINFO):
- raise
- finally:
- if EXC:
- EXIT(None, None, None)
- """, temps=[u'MGR', u'EXC', u"EXIT"],
- pipeline=[NormalizeTree(None)])
-
- template_with_target = TreeFragment(u"""
- MGR = EXPR
- EXIT = MGR.__exit__
- VALUE = MGR.__enter__()
- EXC = True
- try:
- try:
- EXCINFO = None
- TARGET = VALUE
- BODY
- except:
- EXC = False
- if not EXIT(*EXCINFO):
- raise
- finally:
- if EXC:
- EXIT(None, None, None)
- MGR = EXIT = VALUE = EXC = None
-
- """, temps=[u'MGR', u'EXC', u"EXIT", u"VALUE"],
- pipeline=[NormalizeTree(None)])
-
def visit_WithStatNode(self, node):
- # TODO: Cleanup badly needed
- TemplateTransform.temp_name_counter += 1
- handle = "__tmpvar_%d" % TemplateTransform.temp_name_counter
-
- self.visitchildren(node, ['body'])
- excinfo_temp = ExprNodes.NameNode(node.pos, name=handle)#TempHandle(Builtin.tuple_type)
- if node.target is not None:
- result = self.template_with_target.substitute({
- u'EXPR' : node.manager,
- u'BODY' : node.body,
- u'TARGET' : node.target,
- u'EXCINFO' : excinfo_temp
- }, pos=node.pos)
- else:
- result = self.template_without_target.substitute({
- u'EXPR' : node.manager,
- u'BODY' : node.body,
- u'EXCINFO' : excinfo_temp
- }, pos=node.pos)
-
- # Set except excinfo target to EXCINFO
- try_except = result.stats[-1].body.stats[-1]
- try_except.except_clauses[0].excinfo_target = ExprNodes.NameNode(node.pos, name=handle)
-# excinfo_temp.ref(node.pos))
-
-# result.stats[-1].body.stats[-1] = TempsBlockNode(
-# node.pos, temps=[excinfo_temp], body=try_except)
-
- return result
+ self.visitchildren(node, 'body')
+ pos = node.pos
+ body, target, manager = node.body, node.target, node.manager
+ node.target_temp = ExprNodes.TempNode(pos, type=PyrexTypes.py_object_type)
+ if target is not None:
+ node.has_target = True
+ body = Nodes.StatListNode(
+ pos, stats = [
+ Nodes.WithTargetAssignmentStatNode(
+ pos, lhs = target, rhs = node.target_temp),
+ body
+ ])
+ node.target = None
+
+ excinfo_target = ResultRefNode(
+ pos=pos, type=Builtin.tuple_type, may_hold_none=False)
+ except_clause = Nodes.ExceptClauseNode(
+ pos, body = Nodes.IfStatNode(
+ pos, if_clauses = [
+ Nodes.IfClauseNode(
+ pos, condition = ExprNodes.NotNode(
+ pos, operand = ExprNodes.WithExitCallNode(
+ pos, with_stat = node,
+ args = excinfo_target)),
+ body = Nodes.ReraiseStatNode(pos),
+ ),
+ ],
+ else_clause = None),
+ pattern = None,
+ target = None,
+ excinfo_target = excinfo_target,
+ )
+
+ node.body = Nodes.TryFinallyStatNode(
+ pos, body = Nodes.TryExceptStatNode(
+ pos, body = body,
+ except_clauses = [except_clause],
+ else_clause = None,
+ ),
+ finally_clause = Nodes.ExprStatNode(
+ pos, expr = ExprNodes.WithExitCallNode(
+ pos, with_stat = node,
+ args = ExprNodes.TupleNode(
+ pos, args = [ExprNodes.NoneNode(pos) for _ in range(3)]
+ ))),
+ handle_error_case = False,
+ )
+ return node
def visit_ExprNode(self, node):
# With statements are never inside expressions.
return ATTR
""", level='c_class')
+ struct_or_union_wrapper = TreeFragment(u"""
+cdef class NAME:
+ cdef TYPE value
+ def __init__(self, MEMBER=None):
+ cdef int count
+ count = 0
+ INIT_ASSIGNMENTS
+ if IS_UNION and count > 1:
+ raise ValueError, "At most one union member should be specified."
+ def __str__(self):
+ return STR_FORMAT % MEMBER_TUPLE
+ def __repr__(self):
+ return REPR_FORMAT % MEMBER_TUPLE
+ """)
+
+ init_assignment = TreeFragment(u"""
+if VALUE is not None:
+ ATTR = VALUE
+ count += 1
+ """)
+
def __call__(self, root):
self.env_stack = [root.scope]
# needed to determine if a cdef var is declared after it's used.
if not lenv.lookup_here(var): # don't redeclare args
type = type_node.analyse_as_type(lenv)
if type:
- lenv.declare_var(var, type, type_node.pos)
+ lenv.declare_var(name = var, type = type, pos = type_node.pos)
else:
error(type_node.pos, "Not a type")
node.body.analyse_declarations(lenv)
node.analyse_declarations(self.env_stack[-1])
return node
+ def visit_CStructOrUnionDefNode(self, node):
+ # Create a wrapper node if needed.
+ # We want to use the struct type information (so it can't happen
+ # before this phase) but also create new objects to be declared
+ # (so it can't happen later).
+ # Note that we don't return the original node, as it is
+ # never used after this phase.
+ if True: # private (default)
+ return None
+
+ self_value = ExprNodes.AttributeNode(
+ pos = node.pos,
+ obj = ExprNodes.NameNode(pos=node.pos, name=u"self"),
+ attribute = EncodedString(u"value"))
+ var_entries = node.entry.type.scope.var_entries
+ attributes = []
+ for entry in var_entries:
+ attributes.append(ExprNodes.AttributeNode(pos = entry.pos,
+ obj = self_value,
+ attribute = entry.name))
+ # __init__ assignments
+ init_assignments = []
+ for entry, attr in zip(var_entries, attributes):
+ # TODO: branch on visibility
+ init_assignments.append(self.init_assignment.substitute({
+ u"VALUE": ExprNodes.NameNode(entry.pos, name = entry.name),
+ u"ATTR": attr,
+ }, pos = entry.pos))
+
+ # create the class
+ str_format = u"%s(%s)" % (node.entry.type.name, ("%s, " * len(attributes))[:-2])
+ wrapper_class = self.struct_or_union_wrapper.substitute({
+ u"INIT_ASSIGNMENTS": Nodes.StatListNode(node.pos, stats = init_assignments),
+ u"IS_UNION": ExprNodes.BoolNode(node.pos, value = not node.entry.type.is_struct),
+ u"MEMBER_TUPLE": ExprNodes.TupleNode(node.pos, args=attributes),
+ u"STR_FORMAT": ExprNodes.StringNode(node.pos, value = EncodedString(str_format)),
+ u"REPR_FORMAT": ExprNodes.StringNode(node.pos, value = EncodedString(str_format.replace("%s", "%r"))),
+ }, pos = node.pos).stats[0]
+ wrapper_class.class_name = node.name
+ wrapper_class.shadow = True
+ class_body = wrapper_class.body.stats
+
+ # fix value type
+ assert isinstance(class_body[0].base_type, Nodes.CSimpleBaseTypeNode)
+ class_body[0].base_type.name = node.name
+
+ # fix __init__ arguments
+ init_method = class_body[1]
+ assert isinstance(init_method, Nodes.DefNode) and init_method.name == '__init__'
+ arg_template = init_method.args[1]
+ if not node.entry.type.is_struct:
+ arg_template.kw_only = True
+ del init_method.args[1]
+ for entry, attr in zip(var_entries, attributes):
+ arg = copy.deepcopy(arg_template)
+ arg.declarator.name = entry.name
+ init_method.args.append(arg)
+
+ # setters/getters
+ for entry, attr in zip(var_entries, attributes):
+ # TODO: branch on visibility
+ if entry.type.is_pyobject:
+ template = self.basic_pyobject_property
+ else:
+ template = self.basic_property
+ property = template.substitute({
+ u"ATTR": attr,
+ }, pos = entry.pos).stats[0]
+ property.name = entry.name
+ wrapper_class.body.stats.append(property)
+
+ wrapper_class.analyse_declarations(self.env_stack[-1])
+ return self.visit_CClassDefNode(wrapper_class)
+
# Some nodes are no longer needed after declaration
# analysis and can be dropped. The analysis was performed
# on these nodes in a seperate recursive process from the
else:
return None
- def visit_CStructOrUnionDefNode(self, node):
- return None
-
def visit_CNameDeclaratorNode(self, node):
if node.name in self.seen_vars_stack[-1]:
entry = self.env_stack[-1].lookup(node.name)
- if entry is None or entry.visibility != 'extern':
+ if (entry is None or entry.c_visibility != 'extern'
+ and not entry.scope.is_c_class_scope):
warning(node.pos, "cdef variable '%s' declared after it is used" % node.name, 2)
self.visitchildren(node)
return node
template = self.basic_property
elif entry.visibility == 'readonly':
template = self.basic_property_ro
+ else:
+ error(entry.pos,
+ "python methods may not have '%s' Python visibility" %
+ entry.visibility)
property = template.substitute({
u"ATTR": ExprNodes.AttributeNode(pos=entry.pos,
obj=ExprNodes.NameNode(pos=entry.pos, name="self"),
return self.visit_CClassDefNode(node.as_cclass(), pxd_def)
else:
error(node.pos, "'%s' redeclared" % node.name)
- error(pxd_def.pos, "previous declaration here")
+ if pxd_def.pos:
+ error(pxd_def.pos, "previous declaration here")
return None
else:
return node
def visit_DefNode(self, node):
pxd_def = self.scope.lookup(node.name)
- if pxd_def:
+ if pxd_def and (not pxd_def.scope or not pxd_def.scope.is_builtin_scope):
if not pxd_def.is_cfunction:
error(node.pos, "'%s' redeclared" % node.name)
- error(pxd_def.pos, "previous declaration here")
+ if pxd_def.pos:
+ error(pxd_def.pos, "previous declaration here")
return None
node = node.as_cfunction(pxd_def)
- elif self.scope.is_module_scope and self.directives['auto_cpdef']:
+ elif (self.scope.is_module_scope and self.directives['auto_cpdef']
+ and node.is_cdef_func_compatible()):
node = node.as_cfunction(scope=self.scope)
- # Enable this when internal def functions are allowed.
+ # Enable this when nested cdef functions are allowed.
# self.visitchildren(node)
return node
def visit_ClassDefNode(self, node):
pass
- def visit_DefNode(self, node):
+ def visit_FuncDefNode(self, node):
pass
def visit_LambdaNode(self, node):
PyrexTypes.c_void_ptr_type,
'__pyx_generator_body_t')
klass.declare_var(pos=pos, name='body', cname='body',
- type=body_type, is_cdef=True)
+ visibility='private', type=body_type, is_cdef=True)
klass.declare_var(pos=pos, name='is_running', cname='is_running', type=PyrexTypes.c_int_type,
is_cdef=True)
klass.declare_var(pos=pos, name='resume_label', cname='resume_label', type=PyrexTypes.c_int_type,
is_cdef=True)
+ klass.declare_var(pos=pos, name='exc_type', cname='exc_type',
+ type=PyrexTypes.py_object_type, is_cdef=True)
+ klass.declare_var(pos=pos, name='exc_value', cname='exc_value',
+ type=PyrexTypes.py_object_type, is_cdef=True)
+ klass.declare_var(pos=pos, name='exc_traceback', cname='exc_traceback',
+ type=PyrexTypes.py_object_type, is_cdef=True)
import TypeSlots
- e = klass.declare_pyfunction('send', pos)
+ e = klass.declare_pyfunction(name='send', pos=pos)
e.func_cname = '__Pyx_Generator_Send'
e.signature = TypeSlots.binaryfunc
- e = klass.declare_pyfunction('close', pos)
+ e = klass.declare_pyfunction(name='close', pos=pos)
e.func_cname = '__Pyx_Generator_Close'
e.signature = TypeSlots.unaryfunc
- e = klass.declare_pyfunction('throw', pos)
+ e = klass.declare_pyfunction(name='throw', pos=pos)
e.func_cname = '__Pyx_Generator_Throw'
e.signature = TypeSlots.pyfunction_signature
- e = klass.declare_var('__iter__', PyrexTypes.py_object_type, pos, visibility='public')
+ e = klass.declare_var(
+ name='__iter__', type=PyrexTypes.py_object_type,
+ c_visibility='public', pos=pos)
e.func_cname = 'PyObject_SelfIter'
- e = klass.declare_var('__next__', PyrexTypes.py_object_type, pos, visibility='public')
+ e = klass.declare_var(
+ name='__next__', type=PyrexTypes.py_object_type,
+ c_visibility='public', pos=pos)
e.func_cname = '__Pyx_Generator_Next'
self.generator_class = entry.type
return self.generator_class
- def get_scope_use(self, node):
+ def find_entries_used_in_closures(self, node):
from_closure = []
in_closure = []
for name, entry in node.local_scope.entries.items():
if entry.from_closure:
from_closure.append((name, entry))
- elif entry.in_closure and not entry.from_closure:
+ elif entry.in_closure:
in_closure.append((name, entry))
return from_closure, in_closure
if not entry.from_closure:
entry.in_closure = True
- from_closure, in_closure = self.get_scope_use(node)
+ from_closure, in_closure = self.find_entries_used_in_closures(node)
in_closure.sort()
# Now from the begining
inner_node.needs_self_code = False
node.needs_outer_scope = False
+ base_type = None
if node.is_generator:
- generator_class = self.create_generator_class(target_module_scope, node.pos)
+ base_type = self.create_generator_class(target_module_scope, node.pos)
elif not in_closure and not from_closure:
return
elif not in_closure:
as_name = '%s_%s' % (target_module_scope.next_id(Naming.closure_class_prefix), node.entry.cname)
- if node.is_generator:
- entry = target_module_scope.declare_c_class(name = as_name,
- pos = node.pos, defining = True, implementing = True, base_type=generator_class)
- else:
- entry = target_module_scope.declare_c_class(name = as_name,
- pos = node.pos, defining = True, implementing = True)
+ entry = target_module_scope.declare_c_class(
+ name=as_name, pos=node.pos, defining=True,
+ implementing=True, base_type=base_type)
+
func_scope.scope_class = entry
class_scope = entry.type.scope
class_scope.is_internal = True
is_cdef=True)
node.needs_outer_scope = True
for name, entry in in_closure:
- class_scope.declare_var(pos=entry.pos,
+ closure_entry = class_scope.declare_var(pos=entry.pos,
name=entry.name,
cname=entry.cname,
type=entry.type,
is_cdef=True)
+ if entry.is_declared_generic:
+ closure_entry.is_declared_generic = 1
node.needs_closure = True
# Do it here because other classes are already checked
target_module_scope.check_c_class(func_scope.scope_class)
error(node.pos, u"'%s' not a valid cython attribute or is being used incorrectly" % attribute)
return node
- def visit_SimpleCallNode(self, node):
+ def _inject_locals(self, node, func_name):
+ # locals()/dir() builtins
+ lenv = self.current_env()
+ entry = lenv.lookup_here(func_name)
+ if entry:
+ # not the builtin
+ return node
+ pos = node.pos
+ if func_name == 'locals':
+ if len(node.args) > 0:
+ error(self.pos, "Builtin 'locals()' called with wrong number of args, expected 0, got %d"
+ % len(node.args))
+ return node
+ items = [ ExprNodes.DictItemNode(pos,
+ key=ExprNodes.StringNode(pos, value=var),
+ value=ExprNodes.NameNode(pos, name=var))
+ for var in lenv.entries ]
+ return ExprNodes.DictNode(pos, key_value_pairs=items)
+ else:
+ if len(node.args) > 1:
+ error(self.pos, "Builtin 'dir()' called with wrong number of args, expected 0-1, got %d"
+ % len(node.args))
+ return node
+ elif len(node.args) == 1:
+ # optimised in Builtin.py
+ return node
+ items = [ ExprNodes.StringNode(pos, value=var) for var in lenv.entries ]
+ return ExprNodes.ListNode(pos, args=items)
- # locals builtin
+ def visit_SimpleCallNode(self, node):
if isinstance(node.function, ExprNodes.NameNode):
- if node.function.name == 'locals':
- lenv = self.current_env()
- entry = lenv.lookup_here('locals')
- if entry:
- # not the builtin 'locals'
- return node
- if len(node.args) > 0:
- error(self.pos, "Builtin 'locals()' called with wrong number of args, expected 0, got %d" % len(node.args))
- return node
- pos = node.pos
- items = [ ExprNodes.DictItemNode(pos,
- key=ExprNodes.StringNode(pos, value=var),
- value=ExprNodes.NameNode(pos, name=var))
- for var in lenv.entries ]
- return ExprNodes.DictNode(pos, key_value_pairs=items)
+ func_name = node.function.name
+ if func_name in ('dir', 'locals'):
+ return self._inject_locals(node, func_name)
# cython.foo
function = node.function.as_cython_attribute()
#self.c_output_file = options.output_file
self.c_output_file = result.c_file
+ # Closure support, basically treat nested functions as if the AST were
+ # never nested
+ self.nested_funcdefs = []
+
# tells visit_NameNode whether it should register step-into functions
self.register_stepinto = False
# serialize functions
self.tb.start('Functions')
+ # First, serialize functions normally...
self.visitchildren(node)
+
+ # ... then, serialize nested functions
+ for nested_funcdef in self.nested_funcdefs:
+ self.visit_FuncDefNode(nested_funcdef)
+
+ self.register_stepinto = True
+ self.serialize_modulenode_as_function(node)
+ self.register_stepinto = False
self.tb.end('Functions')
# 2.3 compatibility. Serialize global variables
def visit_FuncDefNode(self, node):
self.visited.add(node.local_scope.qualified_name)
+
+ if getattr(node, 'is_wrapper', False):
+ return node
+
+ if self.register_stepinto:
+ self.nested_funcdefs.append(node)
+ return node
+
# node.entry.visibility = 'extern'
if node.py_func is None:
pf_cname = ''
self.visitchildren(node)
return node
+ def serialize_modulenode_as_function(self, node):
+ """
+ Serialize the module-level code as a function so the debugger will know
+ it's a "relevant frame" and it will know where to set the breakpoint
+ for 'break modulename'.
+ """
+ name = node.full_module_name.rpartition('.')[-1]
+
+ cname_py2 = 'init' + name
+ cname_py3 = 'PyInit_' + name
+
+ py2_attrs = dict(
+ name=name,
+ cname=cname_py2,
+ pf_cname='',
+ # Ignore the qualified_name, breakpoints should be set using
+ # `cy break modulename:lineno` for module-level breakpoints.
+ qualified_name='',
+ lineno='1',
+ is_initmodule_function="True",
+ )
+
+ py3_attrs = dict(py2_attrs, cname=cname_py3)
+
+ self._serialize_modulenode_as_function(node, py2_attrs)
+ self._serialize_modulenode_as_function(node, py3_attrs)
+
+ def _serialize_modulenode_as_function(self, node, attrs):
+ self.tb.start('Function', attrs=attrs)
+
+ self.tb.start('Locals')
+ self.serialize_local_variables(node.scope.entries)
+ self.tb.end('Locals')
+
+ self.tb.start('Arguments')
+ self.tb.end('Arguments')
+
+ self.tb.start('StepIntoFunctions')
+ self.register_stepinto = True
+ self.visitchildren(node)
+ self.register_stepinto = False
+ self.tb.end('StepIntoFunctions')
+
+ self.tb.end('Function')
+
def serialize_local_variables(self, entries):
for entry in entries.values():
if entry.type.is_pyobject:
else:
vartype = 'CObject'
- cname = entry.cname
- # if entry.type.is_extension_type:
- # cname = entry.type.typeptr_cname
+ if entry.from_closure:
+ # We're dealing with a closure where a variable from an outer
+ # scope is accessed, get it from the scope object.
+ cname = '%s->%s' % (Naming.cur_scope_cname,
+ entry.outer_entry.cname)
+
+ qname = '%s.%s.%s' % (entry.scope.outer_scope.qualified_name,
+ entry.scope.name,
+ entry.name)
+ elif entry.in_closure:
+ cname = '%s->%s' % (Naming.cur_scope_cname,
+ entry.cname)
+ qname = entry.qualified_name
+ else:
+ cname = entry.cname
+ qname = entry.qualified_name
if not entry.pos:
# this happens for variables that are not in the user's code,
attrs = dict(
name=entry.name,
cname=cname,
- qualified_name=entry.qualified_name,
+ qualified_name=qname,
type=vartype,
lineno=lineno)