objgenerator: Implement return with value and .close() method.
Return with value gets converted to StopIteration(value). Implementation
keeps optimizing against creating of possibly unneeded exception objects,
so there're considerable refactoring to implement these features.
diff --git a/py/obj.h b/py/obj.h
index f80bd43..9599d13 100644
--- a/py/obj.h
+++ b/py/obj.h
@@ -273,6 +273,7 @@
mp_obj_t mp_obj_new_complex(mp_float_t real, mp_float_t imag);
#endif
mp_obj_t mp_obj_new_exception(const mp_obj_type_t *exc_type);
+mp_obj_t mp_obj_new_exception_args(const mp_obj_type_t *exc_type, uint n_args, const mp_obj_t *args);
mp_obj_t mp_obj_new_exception_msg(const mp_obj_type_t *exc_type, const char *msg);
mp_obj_t mp_obj_new_exception_msg_varg(const mp_obj_type_t *exc_type, const char *fmt, ...); // counts args by number of % symbols in fmt, excluding %%; can only handle void* sizes (ie no float/double!)
mp_obj_t mp_obj_new_range(int start, int stop, int step);
@@ -342,6 +343,7 @@
// exception
bool mp_obj_is_exception_type(mp_obj_t self_in);
bool mp_obj_is_exception_instance(mp_obj_t self_in);
+bool mp_obj_exception_match(mp_obj_t exc, const mp_obj_type_t *exc_type);
void mp_obj_exception_clear_traceback(mp_obj_t self_in);
void mp_obj_exception_add_traceback(mp_obj_t self_in, qstr file, machine_uint_t line, qstr block);
void mp_obj_exception_get_traceback(mp_obj_t self_in, machine_uint_t *n, machine_uint_t **values);
diff --git a/py/objexcept.c b/py/objexcept.c
index de9bf16..f96523e 100644
--- a/py/objexcept.c
+++ b/py/objexcept.c
@@ -8,6 +8,8 @@
#include "qstr.h"
#include "obj.h"
#include "objtuple.h"
+#include "runtime.h"
+#include "runtime0.h"
// This is unified class for C-level and Python-level exceptions
// Python-level exceptions have empty ->msg and all arguments are in
@@ -156,6 +158,11 @@
return mp_obj_new_exception_msg_varg(exc_type, NULL);
}
+mp_obj_t mp_obj_new_exception_args(const mp_obj_type_t *exc_type, uint n_args, const mp_obj_t *args) {
+ assert(exc_type->make_new == mp_obj_exception_make_new);
+ return exc_type->make_new((mp_obj_t)exc_type, n_args, 0, args);
+}
+
mp_obj_t mp_obj_new_exception_msg(const mp_obj_type_t *exc_type, const char *msg) {
return mp_obj_new_exception_msg_varg(exc_type, msg);
}
@@ -202,6 +209,13 @@
return mp_obj_get_type(self_in)->make_new == mp_obj_exception_make_new;
}
+// return true if exception (type or instance) is a subclass of given
+// exception type.
+bool mp_obj_exception_match(mp_obj_t exc, const mp_obj_type_t *exc_type) {
+ // TODO: move implementation from RT_BINARY_OP_EXCEPTION_MATCH here.
+ return rt_binary_op(RT_BINARY_OP_EXCEPTION_MATCH, exc, (mp_obj_t)exc_type) == mp_const_true;
+}
+
void mp_obj_exception_clear_traceback(mp_obj_t self_in) {
// make sure self_in is an exception instance
assert(mp_obj_get_type(self_in)->make_new == mp_obj_exception_make_new);
diff --git a/py/objgenerator.c b/py/objgenerator.c
index 39e362e..3cfa02a 100644
--- a/py/objgenerator.c
+++ b/py/objgenerator.c
@@ -8,6 +8,7 @@
#include "obj.h"
#include "runtime.h"
#include "bc.h"
+#include "objgenerator.h"
/******************************************************************************/
/* generator wrapper */
@@ -73,9 +74,10 @@
return self_in;
}
-STATIC mp_obj_t gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_obj_t throw_value) {
+mp_obj_t mp_obj_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_obj_t throw_value, mp_vm_return_kind_t *ret_kind) {
mp_obj_gen_instance_t *self = self_in;
if (self->ip == 0) {
+ *ret_kind = MP_VM_RETURN_NORMAL;
return mp_const_stop_iteration;
}
if (self->sp == self->state - 1) {
@@ -85,10 +87,11 @@
} else {
*self->sp = send_value;
}
- mp_vm_return_kind_t vm_return_kind = mp_execute_byte_code_2(self->code_info, &self->ip,
+ *ret_kind = mp_execute_byte_code_2(self->code_info, &self->ip,
&self->state[self->n_state - 1], &self->sp, (mp_exc_stack*)(self->state + self->n_state),
&self->exc_sp, throw_value);
- switch (vm_return_kind) {
+
+ switch (*ret_kind) {
case MP_VM_RETURN_NORMAL:
// Explicitly mark generator as completed. If we don't do this,
// subsequent next() may re-execute statements after last yield
@@ -96,19 +99,39 @@
// TODO: check how return with value behaves under such conditions
// in CPython.
self->ip = 0;
- if (*self->sp == mp_const_none) {
- return mp_const_stop_iteration;
- } else {
- // TODO return StopIteration with value *self->sp
- return mp_const_stop_iteration;
- }
+ return *self->sp;
case MP_VM_RETURN_YIELD:
return *self->sp;
case MP_VM_RETURN_EXCEPTION:
self->ip = 0;
- nlr_jump(self->state[self->n_state - 1]);
+ return self->state[self->n_state - 1];
+
+ default:
+ assert(0);
+ return mp_const_none;
+ }
+}
+
+STATIC mp_obj_t gen_resume_and_raise(mp_obj_t self_in, mp_obj_t send_value, mp_obj_t throw_value) {
+ mp_vm_return_kind_t ret_kind;
+ mp_obj_t ret = mp_obj_gen_resume(self_in, send_value, throw_value, &ret_kind);
+
+ switch (ret_kind) {
+ case MP_VM_RETURN_NORMAL:
+ // Optimize return w/o value in case generator is used in for loop
+ if (ret == mp_const_none) {
+ return mp_const_stop_iteration;
+ } else {
+ nlr_jump(mp_obj_new_exception_args(&mp_type_StopIteration, 1, &ret));
+ }
+
+ case MP_VM_RETURN_YIELD:
+ return ret;
+
+ case MP_VM_RETURN_EXCEPTION:
+ nlr_jump(ret);
default:
assert(0);
@@ -117,11 +140,11 @@
}
mp_obj_t gen_instance_iternext(mp_obj_t self_in) {
- return gen_resume(self_in, mp_const_none, MP_OBJ_NULL);
+ return gen_resume_and_raise(self_in, mp_const_none, MP_OBJ_NULL);
}
STATIC mp_obj_t gen_instance_send(mp_obj_t self_in, mp_obj_t send_value) {
- mp_obj_t ret = gen_resume(self_in, send_value, MP_OBJ_NULL);
+ mp_obj_t ret = gen_resume_and_raise(self_in, send_value, MP_OBJ_NULL);
if (ret == mp_const_stop_iteration) {
nlr_jump(mp_obj_new_exception(&mp_type_StopIteration));
} else {
@@ -132,7 +155,7 @@
STATIC MP_DEFINE_CONST_FUN_OBJ_2(gen_instance_send_obj, gen_instance_send);
STATIC mp_obj_t gen_instance_throw(uint n_args, const mp_obj_t *args) {
- mp_obj_t ret = gen_resume(args[0], mp_const_none, n_args == 2 ? args[1] : args[2]);
+ mp_obj_t ret = gen_resume_and_raise(args[0], mp_const_none, n_args == 2 ? args[1] : args[2]);
if (ret == mp_const_stop_iteration) {
nlr_jump(mp_obj_new_exception(&mp_type_StopIteration));
} else {
@@ -142,8 +165,30 @@
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(gen_instance_throw_obj, 2, 4, gen_instance_throw);
+STATIC mp_obj_t gen_instance_close(mp_obj_t self_in) {
+ mp_vm_return_kind_t ret_kind;
+ mp_obj_t ret = mp_obj_gen_resume(self_in, mp_const_none, (mp_obj_t)&mp_type_GeneratorExit, &ret_kind);
+
+ if (ret_kind == MP_VM_RETURN_YIELD) {
+ nlr_jump(mp_obj_new_exception_msg(&mp_type_RuntimeError, "generator ignored GeneratorExit"));
+ }
+ // Swallow StopIteration & GeneratorExit (== successful close), and re-raise any other
+ if (ret_kind == MP_VM_RETURN_EXCEPTION) {
+ if (mp_obj_exception_match(ret, &mp_type_GeneratorExit) ||
+ mp_obj_exception_match(ret, &mp_type_StopIteration)) {
+ return mp_const_none;
+ }
+ nlr_jump(ret);
+ }
+
+ // The only choice left is MP_VM_RETURN_NORMAL which is successful close
+ return mp_const_none;
+}
+
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(gen_instance_close_obj, gen_instance_close);
STATIC const mp_method_t gen_type_methods[] = {
+ { "close", &gen_instance_close_obj },
{ "send", &gen_instance_send_obj },
{ "throw", &gen_instance_throw_obj },
{ NULL, NULL }, // end-of-list sentinel
diff --git a/py/objgenerator.h b/py/objgenerator.h
new file mode 100644
index 0000000..3dc69aa
--- /dev/null
+++ b/py/objgenerator.h
@@ -0,0 +1 @@
+mp_obj_t mp_obj_gen_resume(mp_obj_t self_in, mp_obj_t send_val, mp_obj_t throw_val, mp_vm_return_kind_t *ret_kind);