Author: Amaury Forgeot d'Arc <[email protected]>
Branch: py3k
Changeset: r48001:d6d9ea7ba306
Date: 2011-10-12 22:55 +0200
http://bitbucket.org/pypy/pypy/changeset/d6d9ea7ba306/
Log: Implement the nonlocal statement
diff --git a/pypy/interpreter/astcompiler/ast.py
b/pypy/interpreter/astcompiler/ast.py
--- a/pypy/interpreter/astcompiler/ast.py
+++ b/pypy/interpreter/astcompiler/ast.py
@@ -1016,6 +1016,37 @@
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
@@ -1758,7 +1789,6 @@
_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)
@@ -2516,6 +2546,8 @@
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):
@@ -2684,6 +2716,9 @@
def visit_Global(self, node):
pass
+ def visit_Nonlocal(self, node):
+ pass
+
def visit_Expr(self, node):
node.value.walkabout(self)
@@ -4441,6 +4476,49 @@
__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')
diff --git a/pypy/interpreter/astcompiler/astbuilder.py
b/pypy/interpreter/astcompiler/astbuilder.py
--- a/pypy/interpreter/astcompiler/astbuilder.py
+++ b/pypy/interpreter/astcompiler/astbuilder.py
@@ -255,6 +255,11 @@
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])
@@ -620,6 +625,8 @@
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:
diff --git a/pypy/interpreter/astcompiler/codegen.py
b/pypy/interpreter/astcompiler/codegen.py
--- a/pypy/interpreter/astcompiler/codegen.py
+++ b/pypy/interpreter/astcompiler/codegen.py
@@ -761,6 +761,10 @@
# 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)
diff --git a/pypy/interpreter/astcompiler/symtable.py
b/pypy/interpreter/astcompiler/symtable.py
--- a/pypy/interpreter/astcompiler/symtable.py
+++ b/pypy/interpreter/astcompiler/symtable.py
@@ -10,7 +10,8 @@
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:
@@ -107,7 +108,10 @@
"""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
@@ -116,6 +120,18 @@
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
@@ -444,6 +460,21 @@
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)
diff --git a/pypy/interpreter/astcompiler/test/test_compiler.py
b/pypy/interpreter/astcompiler/test/test_compiler.py
--- a/pypy/interpreter/astcompiler/test/test_compiler.py
+++ b/pypy/interpreter/astcompiler/test/test_compiler.py
@@ -786,6 +786,17 @@
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:
diff --git a/pypy/interpreter/astcompiler/tools/Python.asdl
b/pypy/interpreter/astcompiler/tools/Python.asdl
--- a/pypy/interpreter/astcompiler/tools/Python.asdl
+++ b/pypy/interpreter/astcompiler/tools/Python.asdl
@@ -40,6 +40,7 @@
| ImportFrom(identifier? module, alias* names, int? level)
| Global(identifier* names)
+ | Nonlocal(identifier* names)
| Expr(expr value)
| Pass | Break | Continue
diff --git a/pypy/interpreter/pyparser/data/Grammar3.2
b/pypy/interpreter/pyparser/data/Grammar3.2
--- a/pypy/interpreter/pyparser/data/Grammar3.2
+++ b/pypy/interpreter/pyparser/data/Grammar3.2
@@ -36,7 +36,7 @@
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: ('+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' |
@@ -60,6 +60,7 @@
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
_______________________________________________
pypy-commit mailing list
[email protected]
http://mail.python.org/mailman/listinfo/pypy-commit