vm: Implement "with" statement (SETUP_WITH and WITH_CLEANUP bytecodes).

This commit is contained in:
Paul Sokolovsky 2014-03-29 04:10:11 +02:00
parent 682f9e639d
commit 44307d5ef8
3 changed files with 73 additions and 2 deletions

View File

@ -1798,6 +1798,7 @@ void compile_with_stmt_helper(compiler_t *comp, int n, mp_parse_node_t *nodes, m
EMIT_ARG(setup_with, l_end); EMIT_ARG(setup_with, l_end);
EMIT(pop_top); EMIT(pop_top);
} }
compile_increase_except_level(comp);
// compile additional pre-bits and the body // compile additional pre-bits and the body
compile_with_stmt_helper(comp, n - 1, nodes + 1, body); compile_with_stmt_helper(comp, n - 1, nodes + 1, body);
// finish this with block // finish this with block
@ -1805,6 +1806,7 @@ void compile_with_stmt_helper(compiler_t *comp, int n, mp_parse_node_t *nodes, m
EMIT_ARG(load_const_tok, MP_TOKEN_KW_NONE); EMIT_ARG(load_const_tok, MP_TOKEN_KW_NONE);
EMIT_ARG(label_assign, l_end); EMIT_ARG(label_assign, l_end);
EMIT(with_cleanup); EMIT(with_cleanup);
compile_decrease_except_level(comp);
EMIT(end_finally); EMIT(end_finally);
} }
} }

View File

@ -16,6 +16,8 @@ Q(__qualname__)
Q(__repl_print__) Q(__repl_print__)
Q(__bool__) Q(__bool__)
Q(__enter__)
Q(__exit__)
Q(__len__) Q(__len__)
Q(__iter__) Q(__iter__)
Q(__getitem__) Q(__getitem__)

71
py/vm.c
View File

@ -378,6 +378,73 @@ dispatch_loop:
break; break;
*/ */
case MP_BC_SETUP_WITH: {
obj1 = TOP();
SET_TOP(rt_load_attr(obj1, MP_QSTR___exit__));
mp_obj_t dest[2];
rt_load_method(obj1, MP_QSTR___enter__, dest);
obj2 = rt_call_method_n_kw(0, 0, dest);
SETUP_BLOCK();
PUSH(obj2);
break;
}
case MP_BC_WITH_CLEANUP: {
static const mp_obj_t no_exc[] = {mp_const_none, mp_const_none, mp_const_none};
if (TOP() == mp_const_none) {
sp--;
obj1 = TOP();
SET_TOP(mp_const_none);
obj2 = rt_call_function_n_kw(obj1, 3, 0, no_exc);
} else if (MP_OBJ_IS_SMALL_INT(TOP())) {
mp_obj_t cause = POP();
switch (MP_OBJ_SMALL_INT_VALUE(cause)) {
case UNWIND_RETURN: {
mp_obj_t retval = POP();
obj2 = rt_call_function_n_kw(TOP(), 3, 0, no_exc);
SET_TOP(retval);
PUSH(cause);
break;
}
case UNWIND_JUMP: {
obj2 = rt_call_function_n_kw(sp[-2], 3, 0, no_exc);
// Pop __exit__ boundmethod at sp[-2]
sp[-2] = sp[-1];
sp[-1] = sp[0];
SET_TOP(cause);
break;
}
default:
assert(0);
}
} else if (mp_obj_is_exception_type(TOP())) {
mp_obj_t args[3] = {sp[0], sp[-1], sp[-2]};
obj2 = rt_call_function_n_kw(sp[-3], 3, 0, args);
// Pop __exit__ boundmethod at sp[-3]
// TODO: Once semantics is proven, optimize for case when obj2 == True
sp[-3] = sp[-2];
sp[-2] = sp[-1];
sp[-1] = sp[0];
sp--;
if (rt_is_true(obj2)) {
// This is what CPython does
//PUSH(MP_OBJ_NEW_SMALL_INT(UNWIND_SILENCED));
// But what we need to do is - pop exception from value stack...
sp -= 3;
// ... pop with exception handler, and signal END_FINALLY
// to just execute finally handler normally (signalled by None
// on value stack)
assert(exc_sp >= exc_stack);
assert(exc_sp->opcode == MP_BC_SETUP_WITH);
exc_sp--;
PUSH(mp_const_none);
}
} else {
assert(0);
}
break;
}
case MP_BC_UNWIND_JUMP: case MP_BC_UNWIND_JUMP:
DECODE_SLABEL; DECODE_SLABEL;
PUSH((void*)(ip + unum)); // push destination ip for jump PUSH((void*)(ip + unum)); // push destination ip for jump
@ -387,7 +454,7 @@ unwind_jump:
while (unum > 0) { while (unum > 0) {
unum -= 1; unum -= 1;
assert(exc_sp >= exc_stack); assert(exc_sp >= exc_stack);
if (exc_sp->opcode == MP_BC_SETUP_FINALLY) { if (exc_sp->opcode == MP_BC_SETUP_FINALLY || exc_sp->opcode == MP_BC_SETUP_WITH) {
// We're going to run "finally" code as a coroutine // We're going to run "finally" code as a coroutine
// (not calling it recursively). Set up a sentinel // (not calling it recursively). Set up a sentinel
// on a stack so it can return back to us when it is // on a stack so it can return back to us when it is
@ -601,7 +668,7 @@ unwind_jump:
case MP_BC_RETURN_VALUE: case MP_BC_RETURN_VALUE:
unwind_return: unwind_return:
while (exc_sp >= exc_stack) { while (exc_sp >= exc_stack) {
if (exc_sp->opcode == MP_BC_SETUP_FINALLY) { if (exc_sp->opcode == MP_BC_SETUP_FINALLY || exc_sp->opcode == MP_BC_SETUP_WITH) {
// We're going to run "finally" code as a coroutine // We're going to run "finally" code as a coroutine
// (not calling it recursively). Set up a sentinel // (not calling it recursively). Set up a sentinel
// on a stack so it can return back to us when it is // on a stack so it can return back to us when it is