diff -r 6478c4259ce3 Doc/library/dis.rst --- a/Doc/library/dis.rst Thu Jan 10 07:46:29 2013 +0200 +++ b/Doc/library/dis.rst Sun Feb 23 00:46:12 2014 +0100 @@ -451,6 +451,14 @@ denoting nested loops, try statements, and such. +.. opcode:: POP_BLOCK_AND_PUSH_VALUE + + Removes one block from the block stack, while preserving the top value. + Per frame, there is a stack of blocks, denoting nested loops, try + statements, and such. The top value is pushed onto the stack after the + rest of the block's values are popped off. + + .. opcode:: POP_EXCEPT Removes one block from the block stack. The popped block must be an exception @@ -459,6 +467,16 @@ last three popped values are used to restore the exception state. +.. opcode:: POP_EXCEPT_AND_PUSH_VALUE + + Removes one block from the block stack, while preserving the top value. + The popped block must be an exception handler block, as implicitly + created when entering an except handler. In addition to popping + extraneous values from the frame stack, the last three popped values are + used to restore the exception state. The previous top is pushed onto the + stack afterwards. + + .. opcode:: END_FINALLY Terminates a :keyword:`finally` clause. The interpreter recalls whether the diff -r 6478c4259ce3 Grammar/Grammar --- a/Grammar/Grammar Thu Jan 10 07:46:29 2013 +0200 +++ b/Grammar/Grammar Sun Feb 23 00:46:12 2014 +0100 @@ -80,10 +80,11 @@ except_clause: 'except' [test ['as' NAME]] suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT -test: or_test ['if' or_test 'else' test] | lambdef -test_nocond: or_test | lambdef_nocond +test: except_test ['if' except_test 'else' test] | lambdef +test_nocond: except_test | lambdef_nocond lambdef: 'lambda' [varargslist] ':' test lambdef_nocond: 'lambda' [varargslist] ':' test_nocond +except_test: or_test ['except' test ':' test] or_test: and_test ('or' and_test)* and_test: not_test ('and' not_test)* not_test: 'not' not_test | comparison @@ -123,7 +124,7 @@ # results in an ambiguity. ast.c makes sure it's a NAME. argument: test [comp_for] | test '=' test # Really [keyword '='] test comp_iter: comp_for | comp_if -comp_for: 'for' exprlist 'in' or_test [comp_iter] +comp_for: 'for' exprlist 'in' except_test [comp_iter] comp_if: 'if' test_nocond [comp_iter] # not used in grammar, but may appear in "node" passed from Parser to Compiler diff -r 6478c4259ce3 Include/opcode.h --- a/Include/opcode.h Thu Jan 10 07:46:29 2013 +0200 +++ b/Include/opcode.h Sun Feb 23 00:46:12 2014 +0100 @@ -61,10 +61,10 @@ #define INPLACE_OR 79 #define BREAK_LOOP 80 #define WITH_CLEANUP 81 - +#define POP_BLOCK_AND_PUSH_VALUE 82 #define RETURN_VALUE 83 #define IMPORT_STAR 84 - +#define POP_EXCEPT_AND_PUSH_VALUE 85 #define YIELD_VALUE 86 #define POP_BLOCK 87 #define END_FINALLY 88 diff -r 6478c4259ce3 Lib/opcode.py --- a/Lib/opcode.py Thu Jan 10 07:46:29 2013 +0200 +++ b/Lib/opcode.py Sun Feb 23 00:46:12 2014 +0100 @@ -97,10 +97,10 @@ def_op('INPLACE_OR', 79) def_op('BREAK_LOOP', 80) def_op('WITH_CLEANUP', 81) - +def_op('POP_BLOCK_AND_PUSH_VALUE', 82) def_op('RETURN_VALUE', 83) def_op('IMPORT_STAR', 84) - +def_op('POP_EXCEPT_AND_PUSH_VALUE', 85) def_op('YIELD_VALUE', 86) def_op('POP_BLOCK', 87) def_op('END_FINALLY', 88) diff -r 6478c4259ce3 Lib/test/test_grammar.py --- a/Lib/test/test_grammar.py Thu Jan 10 07:46:29 2013 +0200 +++ b/Lib/test/test_grammar.py Sun Feb 23 00:46:12 2014 +0100 @@ -978,6 +978,81 @@ self.assertFalse((False is 2) is 3) self.assertFalse(False is 2 is 3) + def test_except_expr(self): + self.assertEqual(1 except Exception: 2, 1) + # The exception type shouldn't be evaluated unless an exception is + # raised. + self.assertEqual(1 except NotThere: 2, 1) + # And neither should the default value. + self.assertEqual(1 except Exception: NotThere, 1) + # Order of evaluation test: strictly left to right. + it = iter([5, TypeError, 10]) + self.assertEqual(next(it) + "" except next(it): next(it), 10) + # The exception type can be a tuple of exceptions, of course. + self.assertEqual(1 + "" except (RuntimeError, ZeroDivisionError, + TypeError, ValueError): 2, 2) + # ... and actually any kind of expression. + self.assertEqual(1 + "" except RuntimeError and TypeError: 2, 2) + + # Matching exceptions should be caught. + self.assertEqual("".notthere except AttributeError: 2, 2) + # Even when they're baseclasses. + self.assertEqual("".notthere except Exception: 2, 2) + # Nonmatching ones shouldn't. + with self.assertRaises(AttributeError): + "".notthere except ValueError: 2 + + # Prededence tests. Lambda has a lower precedence. + f = lambda x: 1/x except ZeroDivisionError: 7 + self.assertEqual(f(0), 7) + f = lambda x: (1/x except ZeroDivisionError: 7) + self.assertEqual(f(0), 7) + f = (lambda x: 1/x) except ZeroDivisionError: 7 + with self.assertRaises(ZeroDivisionError): + f(0) + + # if-expr has higher precedence than except. + self.assertEqual("" + b"" except TypeError: NotThere if 0 else 9, 9) + self.assertEqual(1 if 0 else 1. & 1 except TypeError: 3, 3) + self.assertEqual(1. & 1 if 0 else 2 except TypeError: 3, 2) + self.assertEqual((1 if 1. & 1 else 2) except TypeError: 3, 3) + with self.assertRaises(TypeError): + 1. & 1 except ValueError: 1 if 0 else 2 + # but sometimes precedence doesn't matter. + self.assertEqual(1. & 1 except TypeError if 1 else RuntimeError: 4, + 4) + self.assertEqual(4 if 1. & 1 except TypeError: 1 else 3, 4) + + # The except expression is allowed in list comprehensions, unlike + # ifexpr. + self.assertEqual([ x + b"" except TypeError: b"" + for x in (b'a', b'b', 'c', 4, b'e') ], + [b'a', b'b', b'', b'', b'e']) + + # Using an except expression in an except or finally block shouldn't + # wipe the previous exception or affect their execution. + tracer = [] + try: + with self.assertRaises(AttributeError): + try: + "".notthere + except AttributeError: + tracer.append(sys.exc_info()[1]) + tracer.append(1 / 0 except ZeroDivisionError: 1) + tracer.append(sys.exc_info()[1]) + raise + finally: + tracer.append(sys.exc_info()[1]) + tracer.append(1.0 & 1 except TypeError: 4) + tracer.append(sys.exc_info()[1]) + self.assertIs(tracer[0], tracer[2]) + self.assertEqual(tracer[1], 1) + self.assertIs(tracer[0], tracer[3]) + self.assertIs(tracer[3], tracer[5]) + self.assertEqual(tracer[4], 4) + finally: + del tracer + def test_main(): run_unittest(TokenTests, GrammarTests) diff -r 6478c4259ce3 Parser/Python.asdl --- a/Parser/Python.asdl Thu Jan 10 07:46:29 2013 +0200 +++ b/Parser/Python.asdl Sun Feb 23 00:46:12 2014 +0100 @@ -58,6 +58,7 @@ | SetComp(expr elt, comprehension* generators) | DictComp(expr key, expr value, comprehension* generators) | GeneratorExp(expr elt, comprehension* generators) + | ExceptExp(expr body, expr etype, expr value) -- the grammar constrains where yield expressions can occur | Yield(expr? value) | YieldFrom(expr value) diff -r 6478c4259ce3 Python/ast.c --- a/Python/ast.c Thu Jan 10 07:46:29 2013 +0200 +++ b/Python/ast.c Sun Feb 23 00:46:12 2014 +0100 @@ -195,6 +195,10 @@ case Lambda_kind: return validate_arguments(exp->v.Lambda.args) && validate_expr(exp->v.Lambda.body, Load); + case ExceptExp_kind: + return validate_expr(exp->v.ExceptExp.body, Load) && + validate_expr(exp->v.ExceptExp.etype, Load) && + validate_expr(exp->v.ExceptExp.value, Load); case IfExp_kind: return validate_expr(exp->v.IfExp.test, Load) && validate_expr(exp->v.IfExp.body, Load) && @@ -1582,7 +1586,7 @@ static expr_ty ast_for_ifexpr(struct compiling *c, const node *n) { - /* test: or_test 'if' or_test 'else' test */ + /* test: except_test 'if' except_test 'else' test */ expr_ty expression, body, orelse; assert(NCH(n) == 5); @@ -1599,6 +1603,27 @@ c->c_arena); } +static expr_ty +ast_for_except_expr(struct compiling *c, const node *n) +{ + /* except_test: or_test 'except' test ':' test */ + expr_ty body, etype, value; + + assert(NCH(n) == 5); + REQ(n, except_test); + body = ast_for_expr(c, CHILD(n, 0)); + if (!body) + return NULL; + etype = ast_for_expr(c, CHILD(n, 2)); + if (!etype) + return NULL; + value = ast_for_expr(c, CHILD(n, 4)); + if (!value) + return NULL; + return ExceptExp(body, etype, value, LINENO(n), n->n_col_offset, + c->c_arena); +} + /* Count the number of 'for' loops in a comprehension. @@ -2250,8 +2275,9 @@ ast_for_expr(struct compiling *c, const node *n) { /* handle the full range of simple expressions - test: or_test ['if' or_test 'else' test] | lambdef - test_nocond: or_test | lambdef_nocond + test: except_test ['if' or_test 'else' test] | lambdef + test_nocond: except_test | lambdef_nocond + except_test: or_test ('except' test ':' expr)* or_test: and_test ('or' and_test)* and_test: not_test ('and' not_test)* not_test: 'not' not_test | comparison @@ -2278,7 +2304,14 @@ return ast_for_lambdef(c, CHILD(n, 0)); else if (NCH(n) > 1) return ast_for_ifexpr(c, n); + assert(NCH(n) == 1); /* Fallthrough */ + case except_test: + if (NCH(n) == 1) { + n = CHILD(n, 0); + goto loop; + } + return ast_for_except_expr(c, n); case or_test: case and_test: if (NCH(n) == 1) { diff -r 6478c4259ce3 Python/ceval.c --- a/Python/ceval.c Thu Jan 10 07:46:29 2013 +0200 +++ b/Python/ceval.c Sun Feb 23 00:46:12 2014 +0100 @@ -1937,12 +1937,35 @@ DISPATCH(); } + TARGET(POP_EXCEPT_AND_PUSH_VALUE) { + /* Like POP_EXCEPT, except pop the top off the stack first, then + push that after unwinding the handler. */ + PyObject *value = POP(); + PyTryBlock *b = PyFrame_BlockPop(f); + if (b->b_type != EXCEPT_HANDLER) { + PyErr_SetString(PyExc_SystemError, + "popped block is not an except handler"); + goto error; + } + UNWIND_EXCEPT_HANDLER(b); + PUSH(value); + DISPATCH(); + } + TARGET(POP_BLOCK) { PyTryBlock *b = PyFrame_BlockPop(f); UNWIND_BLOCK(b); DISPATCH(); } + TARGET(POP_BLOCK_AND_PUSH_VALUE) { + PyObject *value = POP(); + PyTryBlock *b = PyFrame_BlockPop(f); + UNWIND_BLOCK(b); + PUSH(value); + DISPATCH(); + } + PREDICTED(END_FINALLY); TARGET(END_FINALLY) { PyObject *status = POP(); diff -r 6478c4259ce3 Python/compile.c --- a/Python/compile.c Thu Jan 10 07:46:29 2013 +0200 +++ b/Python/compile.c Sun Feb 23 00:46:12 2014 +0100 @@ -871,8 +871,12 @@ return -1; case POP_BLOCK: return 0; + case POP_BLOCK_AND_PUSH_VALUE: + return 0; case POP_EXCEPT: return 0; /* -3 except if bad bytecode */ + case POP_EXCEPT_AND_PUSH_VALUE: + return 0; /* -3 except if bad bytecode */ case END_FINALLY: return -1; /* or -2 or -3 if exception occurred */ @@ -2214,6 +2218,74 @@ return compiler_try_except(c, s); } +/* Compile the except expression, a very limited form of try/except. */ +static int +compiler_exceptexp(struct compiler *c, expr_ty e) +{ + basicblock *body, *except, *value, *cleanup, *end; + + assert(e->kind == ExceptExp_kind); + body = compiler_new_block(c); + if (body == NULL) + return 0; + except = compiler_new_block(c); + if (except == NULL) + return 0; + value = compiler_new_block(c); + if (value== NULL) + return 0; + cleanup = compiler_new_block(c); + if (cleanup == NULL) + return 0; + end = compiler_new_block(c); + if (end == NULL) + return 0; + ADDOP_JREL(c, SETUP_EXCEPT, except); + compiler_use_next_block(c, body); + if (!compiler_push_fblock(c, EXCEPT, body)) + return 0; + VISIT(c, expr, e->v.ExceptExp.body); + ADDOP(c, POP_BLOCK_AND_PUSH_VALUE); + compiler_pop_fblock(c, EXCEPT, body); + ADDOP_JREL(c, JUMP_FORWARD, end); + compiler_use_next_block(c, except); + /* An exception occurred; tb, val and exc are pushed onto the stack (in + that order.) They need to stay on the stack as long as we may need to + re-raise the exception; DUP the exception type so we can COMPARE_OP + it. We don't have any need for tb or val, since the except expression + doesn't allow capturing either. + */ + ADDOP(c, DUP_TOP); + VISIT(c, expr, e->v.ExceptExp.etype); + ADDOP_I(c, COMPARE_OP, PyCmp_EXC_MATCH); + ADDOP_JABS(c, POP_JUMP_IF_FALSE, cleanup); + /* Exception matches; clear the error by poppping tb, val and exc from + the valuestack and pushing the replacement value, then jump to the + end of the expression.*/ + ADDOP(c, POP_TOP); + ADDOP(c, POP_TOP); + ADDOP(c, POP_TOP); + compiler_use_next_block(c, value); + if (!compiler_push_fblock(c, FINALLY_TRY, value)) + return 0; + VISIT(c, expr, e->v.ExceptExp.value); + /* When execution jumps to a handler, the previous exception triplet is + pushed onto the stack first (or Nones if there wasn't one.) Normally + when exiting a handler the previous exception is restored by popping + everything op to and including them off the stack, which is fine + since the except block can't return a value. We need to produce a + value, so we need to pop the value off the stack, then restore the + previous exception, then push the value onto the stack again. + */ + ADDOP(c, POP_EXCEPT_AND_PUSH_VALUE); + compiler_pop_fblock(c, FINALLY_TRY, value); + ADDOP_JREL(c, JUMP_FORWARD, end); + /* End the exception handling, re-raising the exception. */ + compiler_use_next_block(c, cleanup); + ADDOP(c, END_FINALLY); + compiler_use_next_block(c, end); + return 1; +} static int compiler_import_as(struct compiler *c, identifier name, identifier asname) @@ -3326,6 +3398,8 @@ return compiler_lambda(c, e); case IfExp_kind: return compiler_ifexp(c, e); + case ExceptExp_kind: + return compiler_exceptexp(c, e); case Dict_kind: n = asdl_seq_LEN(e->v.Dict.values); ADDOP_I(c, BUILD_MAP, (n>0xFFFF ? 0xFFFF : n)); diff -r 6478c4259ce3 Python/opcode_targets.h --- a/Python/opcode_targets.h Thu Jan 10 07:46:29 2013 +0200 +++ b/Python/opcode_targets.h Sun Feb 23 00:46:12 2014 +0100 @@ -81,10 +81,10 @@ &&TARGET_INPLACE_OR, &&TARGET_BREAK_LOOP, &&TARGET_WITH_CLEANUP, - &&_unknown_opcode, + &&TARGET_POP_BLOCK_AND_PUSH_VALUE, &&TARGET_RETURN_VALUE, &&TARGET_IMPORT_STAR, - &&_unknown_opcode, + &&TARGET_POP_EXCEPT_AND_PUSH_VALUE, &&TARGET_YIELD_VALUE, &&TARGET_POP_BLOCK, &&TARGET_END_FINALLY, diff -r 6478c4259ce3 Python/symtable.c --- a/Python/symtable.c Thu Jan 10 07:46:29 2013 +0200 +++ b/Python/symtable.c Sun Feb 23 00:46:12 2014 +0100 @@ -1383,6 +1383,11 @@ VISIT_QUIT(st, 0); break; } + case ExceptExp_kind: + VISIT(st, expr, e->v.ExceptExp.body); + VISIT(st, expr, e->v.ExceptExp.etype); + VISIT(st, expr, e->v.ExceptExp.value); + break; case IfExp_kind: VISIT(st, expr, e->v.IfExp.test); VISIT(st, expr, e->v.IfExp.body);