Amaury Forgeot d'Arc avatar Amaury Forgeot d'Arc committed d6d9ea7

Implement the nonlocal statement

Comments (0)

Files changed (7)

pypy/interpreter/astcompiler/ast.py

                 self.names = None
 
 
+class Nonlocal(stmt):
+
+    _lineno_mask = 2
+    _col_offset_mask = 4
+
+    def __init__(self, names, lineno, col_offset):
+        self.names = names
+        self.w_names = None
+        stmt.__init__(self, lineno, col_offset)
+        self.initialization_state = 7
+
+    def walkabout(self, visitor):
+        visitor.visit_Nonlocal(self)
+
+    def mutate_over(self, visitor):
+        return visitor.visit_Nonlocal(self)
+
+    def sync_app_attrs(self, space):
+        if (self.initialization_state & ~0) ^ 7:
+            missing_field(space, self.initialization_state, ['names', 'lineno', 'col_offset'], 'Nonlocal')
+        else:
+            pass
+        w_list = self.w_names
+        if w_list is not None:
+            list_w = space.listview(w_list)
+            if list_w:
+                self.names = [space.str_w(w_obj) for w_obj in list_w]
+            else:
+                self.names = None
+
+
 class Expr(stmt):
 
     _lineno_mask = 2
     _col_offset_mask = 8
 
     def __init__(self, id, ctx, lineno, col_offset):
-        assert isinstance(id, str)
         self.id = id
         self.ctx = ctx
         expr.__init__(self, lineno, col_offset)
         return self.default_visitor(node)
     def visit_Global(self, node):
         return self.default_visitor(node)
+    def visit_Nonlocal(self, node):
+        return self.default_visitor(node)
     def visit_Expr(self, node):
         return self.default_visitor(node)
     def visit_Pass(self, node):
     def visit_Global(self, node):
         pass
 
+    def visit_Nonlocal(self, node):
+        pass
+
     def visit_Expr(self, node):
         node.value.walkabout(self)
 
     __init__=interp2app(Global_init),
 )
 
+def Nonlocal_get_names(space, w_self):
+    if not w_self.initialization_state & 1:
+        typename = space.type(w_self).getname(space)
+        w_err = space.wrap("'%s' object has no attribute 'names'" % typename)
+        raise OperationError(space.w_AttributeError, w_err)
+    if w_self.w_names is None:
+        if w_self.names is None:
+            w_list = space.newlist([])
+        else:
+            list_w = [space.wrap(node) for node in w_self.names]
+            w_list = space.newlist(list_w)
+        w_self.w_names = w_list
+    return w_self.w_names
+
+def Nonlocal_set_names(space, w_self, w_new_value):
+    w_self.w_names = w_new_value
+    w_self.initialization_state |= 1
+
+_Nonlocal_field_unroller = unrolling_iterable(['names'])
+def Nonlocal_init(space, w_self, __args__):
+    w_self = space.descr_self_interp_w(Nonlocal, w_self)
+    w_self.w_names = None
+    args_w, kwargs_w = __args__.unpack()
+    if args_w:
+        if len(args_w) != 1:
+            w_err = space.wrap("Nonlocal constructor takes either 0 or 1 positional argument")
+            raise OperationError(space.w_TypeError, w_err)
+        i = 0
+        for field in _Nonlocal_field_unroller:
+            space.setattr(w_self, space.wrap(field), args_w[i])
+            i += 1
+    for field, w_value in kwargs_w.iteritems():
+        space.setattr(w_self, space.wrap(field), w_value)
+
+Nonlocal.typedef = typedef.TypeDef("Nonlocal",
+    stmt.typedef,
+    __module__='_ast',
+    _fields=_FieldsWrapper(['names']),
+    names=typedef.GetSetProperty(Nonlocal_get_names, Nonlocal_set_names, cls=Nonlocal),
+    __new__=interp2app(get_AST_new(Nonlocal)),
+    __init__=interp2app(Nonlocal_init),
+)
+
 def Expr_get_value(space, w_self):
     if w_self.w_dict is not None:
         w_obj = w_self.getdictvalue(space, 'value')

pypy/interpreter/astcompiler/astbuilder.py

                  for i in range(1, len(global_node.children), 2)]
         return ast.Global(names, global_node.lineno, global_node.column)
 
+    def handle_nonlocal_stmt(self, nonlocal_node):
+        names = [nonlocal_node.children[i].value
+                 for i in range(1, len(nonlocal_node.children), 2)]
+        return ast.Nonlocal(names, nonlocal_node.lineno, nonlocal_node.column)
+
     def handle_assert_stmt(self, assert_node):
         child_count = len(assert_node.children)
         expr = self.handle_expr(assert_node.children[1])
                 return self.handle_import_stmt(stmt)
             elif stmt_type == syms.global_stmt:
                 return self.handle_global_stmt(stmt)
+            elif stmt_type == syms.nonlocal_stmt:
+                return self.handle_nonlocal_stmt(stmt)
             elif stmt_type == syms.assert_stmt:
                 return self.handle_assert_stmt(stmt)
             else:

pypy/interpreter/astcompiler/codegen.py

         # Handled in symbol table building.
         pass
 
+    def visit_Nonlocal(self, glob):
+        # Handled in symbol table building.
+        pass
+
     def visit_Pass(self, pas):
         self.update_position(pas.lineno, True)
 

pypy/interpreter/astcompiler/symtable.py

 SYM_GLOBAL = 1
 SYM_ASSIGNED = 2 # Or deleted actually.
 SYM_PARAM = 2 << 1
-SYM_USED = 2 << 2
+SYM_NONLOCAL = 2 << 2
+SYM_USED = 2 << 3
 SYM_BOUND = (SYM_PARAM | SYM_ASSIGNED)
 
 # codegen.py actually deals with these:
         """Decide on the scope of a name."""
         if flags & SYM_GLOBAL:
             if flags & SYM_PARAM:
-                err = "name '%s' is local and global" % (name,)
+                err = "name '%s' is parameter and global" % (name,)
+                raise SyntaxError(err, self.lineno, self.col_offset)
+            if flags & SYM_NONLOCAL:
+                err = "name '%s' is nonlocal and global" % (name,)
                 raise SyntaxError(err, self.lineno, self.col_offset)
             self.symbols[name] = SCOPE_GLOBAL_EXPLICIT
             globs[name] = None
                     del bound[name]
                 except KeyError:
                     pass
+        elif flags & SYM_NONLOCAL:
+            if flags & SYM_PARAM:
+                err = "name '%s' is parameter and nonlocal" % (name,)
+                raise SyntaxError(err, self.lineno, self.col_offset)
+            if bound is None:
+                err = "nonlocal declaration not allowed at module level"
+                raise SyntaxError(err, self.lineno, self.col_offset)
+            if name not in bound:
+                err = "no binding for nonlocal '%s' found" % (name,)
+                raise SyntaxError(err, self.lineno, self.col_offset)
+            self.symbols[name] = SCOPE_FREE
+            free[name] = None
         elif flags & SYM_BOUND:
             self.symbols[name] = SCOPE_LOCAL
             local[name] = None
                                     glob.lineno, glob.col_offset)
             self.note_symbol(name, SYM_GLOBAL)
 
+    def visit_Nonlocal(self, nonl):
+        for name in nonl.names:
+            old_role = self.scope.lookup_role(name)
+            if old_role & (SYM_USED | SYM_ASSIGNED):
+                if old_role & SYM_ASSIGNED:
+                    msg = "name '%s' is assigned to before nonlocal declaration" \
+                        % (name,)
+                else:
+                    msg = "name '%s' is used prior to nonlocal declaration" % \
+                        (name,)
+                misc.syntax_warning(self.space, msg, self.compile_info.filename,
+                                    nonl.lineno, nonl.col_offset)
+            self.note_symbol(name, SYM_NONLOCAL)
+            
+
     def visit_Lambda(self, lamb):
         args = lamb.args
         assert isinstance(args, ast.arguments)

pypy/interpreter/astcompiler/test/test_compiler.py

             raise AssertionError("attribute not removed")"""
         yield self.st, test, "X.__name__", "X"
 
+    def test_nonlocal(self):
+        test = """if 1:
+        def f():
+            y = 0
+            def g(x):
+                nonlocal y
+                y = x + 1
+            g(3)
+            return y"""
+        yield self.st, test, "f()", 4
+
 
 class AppTestCompiler:
 

pypy/interpreter/astcompiler/tools/Python.asdl

 	      | ImportFrom(identifier? module, alias* names, int? level)
 
 	      | Global(identifier* names)
+	      | Nonlocal(identifier* names)
 	      | Expr(expr value)
 	      | Pass | Break | Continue
 

pypy/interpreter/pyparser/data/Grammar3.2

 stmt: simple_stmt | compound_stmt
 simple_stmt: small_stmt (';' small_stmt)* [';'] NEWLINE
 small_stmt: (expr_stmt | del_stmt | pass_stmt | flow_stmt |
-             import_stmt | global_stmt | assert_stmt)
+             import_stmt | global_stmt | nonlocal_stmt | assert_stmt)
 expr_stmt: testlist (augassign (yield_expr|testlist) |
                      ('=' (yield_expr|testlist))*)
 augassign: ('+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' |
 dotted_as_names: dotted_as_name (',' dotted_as_name)*
 dotted_name: NAME ('.' NAME)*
 global_stmt: 'global' NAME (',' NAME)*
+nonlocal_stmt: 'nonlocal' NAME (',' NAME)*
 assert_stmt: 'assert' test [',' test]
 
 compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | with_stmt | funcdef | classdef | decorated
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.