summaryrefslogtreecommitdiffstats
path: root/Objects/genobject.c
diff options
context:
space:
mode:
Diffstat (limited to 'Objects/genobject.c')
-rw-r--r--Objects/genobject.c239
1 files changed, 234 insertions, 5 deletions
diff --git a/Objects/genobject.c b/Objects/genobject.c
index 174c697..d281274 100644
--- a/Objects/genobject.c
+++ b/Objects/genobject.c
@@ -15,15 +15,31 @@ gen_traverse(PyGenObject *gen, visitproc visit, void *arg)
static void
gen_dealloc(PyGenObject *gen)
{
+ PyObject *self = (PyObject *) gen;
+
_PyObject_GC_UNTRACK(gen);
+
if (gen->gi_weakreflist != NULL)
PyObject_ClearWeakRefs((PyObject *) gen);
- Py_DECREF(gen->gi_frame);
+
+
+ _PyObject_GC_TRACK(self);
+
+ if (gen->gi_frame->f_stacktop!=NULL) {
+ /* Generator is paused, so we need to close */
+ gen->ob_type->tp_del(self);
+ if (self->ob_refcnt > 0)
+ return; /* resurrected. :( */
+ }
+
+ _PyObject_GC_UNTRACK(self);
+ Py_XDECREF(gen->gi_frame);
PyObject_GC_Del(gen);
}
+
static PyObject *
-gen_iternext(PyGenObject *gen)
+gen_send_ex(PyGenObject *gen, PyObject *arg, int exc)
{
PyThreadState *tstate = PyThreadState_GET();
PyFrameObject *f = gen->gi_frame;
@@ -34,8 +50,24 @@ gen_iternext(PyGenObject *gen)
"generator already executing");
return NULL;
}
- if (f->f_stacktop == NULL)
+ if ((PyObject *)f == Py_None || f->f_stacktop == NULL) {
+ /* Only set exception if called from send() */
+ if (arg && !exc) PyErr_SetNone(PyExc_StopIteration);
return NULL;
+ }
+
+ if (f->f_lasti == -1) {
+ if (arg && arg != Py_None) {
+ PyErr_SetString(PyExc_TypeError,
+ "can't send non-None value to a just-started generator");
+ return NULL;
+ }
+ } else {
+ /* Push arg onto the frame's value stack */
+ result = arg ? arg : Py_None;
+ Py_INCREF(result);
+ *(f->f_stacktop++) = result;
+ }
/* Generators always return to their most recent caller, not
* necessarily their creator. */
@@ -44,7 +76,7 @@ gen_iternext(PyGenObject *gen)
f->f_back = tstate->frame;
gen->gi_running = 1;
- result = PyEval_EvalFrame(f);
+ result = PyEval_EvalFrameEx(f, exc);
gen->gi_running = 0;
/* Don't keep the reference to f_back any longer than necessary. It
@@ -58,17 +90,199 @@ gen_iternext(PyGenObject *gen)
if (result == Py_None && f->f_stacktop == NULL) {
Py_DECREF(result);
result = NULL;
+ /* Set exception if not called by gen_iternext() */
+ if (arg) PyErr_SetNone(PyExc_StopIteration);
+ }
+
+ if (!result || f->f_stacktop == NULL) {
+ /* generator can't be rerun, so release the frame */
+ Py_DECREF(f);
+ gen->gi_frame = (PyFrameObject *)Py_None;
+ Py_INCREF(Py_None);
}
return result;
}
+PyDoc_STRVAR(send_doc,
+"send(arg) -> send 'arg' into generator, return next yielded value or raise StopIteration.");
+
+static PyObject *
+gen_send(PyGenObject *gen, PyObject *arg)
+{
+ return gen_send_ex(gen, arg, 0);
+}
+
+PyDoc_STRVAR(close_doc,
+"close(arg) -> raise GeneratorExit inside generator.");
+
+static PyObject *
+gen_close(PyGenObject *gen, PyObject *args)
+{
+ PyObject *retval;
+ PyErr_SetNone(PyExc_GeneratorExit);
+ retval = gen_send_ex(gen, Py_None, 1);
+ if (retval) {
+ Py_DECREF(retval);
+ PyErr_SetString(PyExc_RuntimeError,
+ "generator ignored GeneratorExit");
+ return NULL;
+ }
+ if ( PyErr_ExceptionMatches(PyExc_StopIteration)
+ || PyErr_ExceptionMatches(PyExc_GeneratorExit) )
+ {
+ PyErr_Clear(); /* ignore these errors */
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+ return NULL;
+}
+
+static void
+gen_del(PyObject *self)
+{
+ PyObject *res;
+ PyObject *error_type, *error_value, *error_traceback;
+ PyGenObject *gen = (PyGenObject *)self;
+
+ if ((PyObject *)gen->gi_frame == Py_None || gen->gi_frame->f_stacktop==NULL)
+ /* Generator isn't paused, so no need to close */
+ return;
+
+ /* Temporarily resurrect the object. */
+ assert(self->ob_refcnt == 0);
+ self->ob_refcnt = 1;
+
+ /* Save the current exception, if any. */
+ PyErr_Fetch(&error_type, &error_value, &error_traceback);
+
+ res = gen_close((PyGenObject *)self, NULL);
+
+ if (res == NULL)
+ PyErr_WriteUnraisable((PyObject *)self);
+ else
+ Py_DECREF(res);
+
+ /* Restore the saved exception. */
+ PyErr_Restore(error_type, error_value, error_traceback);
+
+ /* Undo the temporary resurrection; can't use DECREF here, it would
+ * cause a recursive call.
+ */
+ assert(self->ob_refcnt > 0);
+ if (--self->ob_refcnt == 0)
+ return; /* this is the normal path out */
+
+ /* close() resurrected it! Make it look like the original Py_DECREF
+ * never happened.
+ */
+ {
+ int refcnt = self->ob_refcnt;
+ _Py_NewReference(self);
+ self->ob_refcnt = refcnt;
+ }
+ assert(!PyType_IS_GC(self->ob_type) ||
+ _Py_AS_GC(self)->gc.gc_refs != _PyGC_REFS_UNTRACKED);
+
+ /* If Py_REF_DEBUG, _Py_NewReference bumped _Py_RefTotal, so
+ * we need to undo that. */
+ _Py_DEC_REFTOTAL;
+ /* If Py_TRACE_REFS, _Py_NewReference re-added self to the object
+ * chain, so no more to do there.
+ * If COUNT_ALLOCS, the original decref bumped tp_frees, and
+ * _Py_NewReference bumped tp_allocs: both of those need to be
+ * undone.
+ */
+#ifdef COUNT_ALLOCS
+ --self->ob_type->tp_frees;
+ --self->ob_type->tp_allocs;
+#endif
+}
+
+
+
+PyDoc_STRVAR(throw_doc,
+"throw(typ[,val[,tb]]) -> raise exception in generator, return next yielded value or raise StopIteration.");
+
+static PyObject *
+gen_throw(PyGenObject *gen, PyObject *args)
+{
+ PyObject *typ;
+ PyObject *tb = NULL;
+ PyObject *val = NULL;
+
+ if (!PyArg_ParseTuple(args, "O|OO:throw", &typ, &val, &tb))
+ return NULL;
+
+ if (tb && !PyTraceBack_Check(tb)) {
+ PyErr_SetString(PyExc_TypeError,
+ "throw() third argument must be a traceback object");
+ return NULL;
+ }
+
+ Py_INCREF(typ);
+ Py_XINCREF(val);
+ Py_XINCREF(tb);
+
+ if (PyClass_Check(typ)) {
+ PyErr_NormalizeException(&typ, &val, &tb);
+ }
+
+ else if (PyInstance_Check(typ)) {
+ /* Raising an instance. The value should be a dummy. */
+ if (val && val != Py_None) {
+ PyErr_SetString(PyExc_TypeError,
+ "instance exception may not have a separate value");
+ goto failed_throw;
+ }
+ else {
+ /* Normalize to raise <class>, <instance> */
+ val = typ;
+ typ = (PyObject*) ((PyInstanceObject*)typ)->in_class;
+ Py_INCREF(typ);
+ }
+ }
+ else {
+ /* Not something you can raise. You get an exception
+ anyway, just not what you specified :-) */
+ PyErr_Format(PyExc_TypeError,
+ "exceptions must be classes, or instances, not %s",
+ typ->ob_type->tp_name);
+ goto failed_throw;
+ }
+
+ PyErr_Restore(typ,val,tb);
+ return gen_send_ex(gen, Py_None, 1);
+
+failed_throw:
+ /* Didn't use our arguments, so restore their original refcounts */
+ Py_DECREF(typ);
+ Py_XDECREF(val);
+ Py_XDECREF(tb);
+ return NULL;
+}
+
+
+static PyObject *
+gen_iternext(PyGenObject *gen)
+{
+ return gen_send_ex(gen, NULL, 0);
+}
+
+
static PyMemberDef gen_memberlist[] = {
{"gi_frame", T_OBJECT, offsetof(PyGenObject, gi_frame), RO},
{"gi_running", T_INT, offsetof(PyGenObject, gi_running), RO},
{NULL} /* Sentinel */
};
+static PyMethodDef gen_methods[] = {
+ {"send",(PyCFunction)gen_send, METH_O, send_doc},
+ {"throw",(PyCFunction)gen_throw, METH_VARARGS, throw_doc},
+ {"close",(PyCFunction)gen_close, METH_NOARGS, close_doc},
+ {NULL, NULL} /* Sentinel */
+};
+
PyTypeObject PyGen_Type = {
PyObject_HEAD_INIT(&PyType_Type)
0, /* ob_size */
@@ -99,11 +313,26 @@ PyTypeObject PyGen_Type = {
offsetof(PyGenObject, gi_weakreflist), /* tp_weaklistoffset */
PyObject_SelfIter, /* tp_iter */
(iternextfunc)gen_iternext, /* tp_iternext */
- 0, /* tp_methods */
+ gen_methods, /* tp_methods */
gen_memberlist, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
+
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ 0, /* tp_new */
+ 0, /* tp_free */
+ 0, /* tp_is_gc */
+ 0, /* tp_bases */
+ 0, /* tp_mro */
+ 0, /* tp_cache */
+ 0, /* tp_subclasses */
+ 0, /* tp_weaklist */
+ gen_del, /* tp_del */
};
PyObject *