Merge pull request #389 from pfalcon/with-statement
With statement implementation
This commit is contained in:
commit
21a07dc50f
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
71
py/vm.c
@ -381,6 +381,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
|
||||||
@ -390,7 +457,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
|
||||||
@ -604,7 +671,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
|
||||||
|
14
tests/basics/with-break.py
Normal file
14
tests/basics/with-break.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
class CtxMgr:
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
print("__enter__")
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, a, b, c):
|
||||||
|
print("__exit__", repr(a), repr(b))
|
||||||
|
|
||||||
|
for i in range(5):
|
||||||
|
print(i)
|
||||||
|
with CtxMgr():
|
||||||
|
if i == 3:
|
||||||
|
break
|
14
tests/basics/with-continue.py
Normal file
14
tests/basics/with-continue.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
class CtxMgr:
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
print("__enter__")
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, a, b, c):
|
||||||
|
print("__exit__", repr(a), repr(b))
|
||||||
|
|
||||||
|
for i in range(5):
|
||||||
|
print(i)
|
||||||
|
with CtxMgr():
|
||||||
|
if i == 3:
|
||||||
|
continue
|
14
tests/basics/with-return.py
Normal file
14
tests/basics/with-return.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
class CtxMgr:
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
print("__enter__")
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, a, b, c):
|
||||||
|
print("__exit__", repr(a), repr(b))
|
||||||
|
|
||||||
|
def foo():
|
||||||
|
with CtxMgr():
|
||||||
|
return 4
|
||||||
|
|
||||||
|
print(foo())
|
71
tests/basics/with1.py
Normal file
71
tests/basics/with1.py
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
class CtxMgr:
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
print("__enter__")
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, a, b, c):
|
||||||
|
print("__exit__", repr(a), repr(b))
|
||||||
|
|
||||||
|
|
||||||
|
with CtxMgr() as a:
|
||||||
|
print(isinstance(a, CtxMgr))
|
||||||
|
|
||||||
|
try:
|
||||||
|
with CtxMgr() as a:
|
||||||
|
raise ValueError
|
||||||
|
except ValueError:
|
||||||
|
print("ValueError")
|
||||||
|
|
||||||
|
|
||||||
|
class CtxMgr2:
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
print("__enter__")
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, a, b, c):
|
||||||
|
print("__exit__", repr(a), repr(b))
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
with CtxMgr2() as a:
|
||||||
|
raise ValueError
|
||||||
|
print("No ValueError2")
|
||||||
|
except ValueError:
|
||||||
|
print("ValueError2")
|
||||||
|
|
||||||
|
|
||||||
|
# These recursive try-finally tests are attempt to get some interpretation
|
||||||
|
# of last phrase in http://docs.python.org/3.4/library/dis.html#opcode-WITH_CLEANUP
|
||||||
|
# "If the stack represents an exception, and the function call returns a ‘true’
|
||||||
|
# value, this information is “zapped” and replaced with a single WHY_SILENCED
|
||||||
|
# to prevent END_FINALLY from re-raising the exception. (But non-local gotos
|
||||||
|
# will still be resumed.)"
|
||||||
|
print("===")
|
||||||
|
with CtxMgr2() as a:
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
raise ValueError
|
||||||
|
print("No ValueError3")
|
||||||
|
finally:
|
||||||
|
print("finally1")
|
||||||
|
finally:
|
||||||
|
print("finally2")
|
||||||
|
|
||||||
|
print("===")
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
with CtxMgr2() as a:
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
raise ValueError
|
||||||
|
print("No ValueError3")
|
||||||
|
finally:
|
||||||
|
print("finally1")
|
||||||
|
finally:
|
||||||
|
print("finally2")
|
||||||
|
finally:
|
||||||
|
print("finally3")
|
||||||
|
finally:
|
||||||
|
print("finally4")
|
Loading…
Reference in New Issue
Block a user