/* Generator object implementation */ | |
#include "Python.h" | |
#include "frameobject.h" | |
#include "genobject.h" | |
#include "ceval.h" | |
#include "structmember.h" | |
#include "opcode.h" | |
static int | |
gen_traverse(PyGenObject *gen, visitproc visit, void *arg) | |
{ | |
Py_VISIT((PyObject *)gen->gi_frame); | |
Py_VISIT(gen->gi_code); | |
return 0; | |
} | |
static void | |
gen_dealloc(PyGenObject *gen) | |
{ | |
PyObject *self = (PyObject *) gen; | |
_PyObject_GC_UNTRACK(gen); | |
if (gen->gi_weakreflist != NULL) | |
PyObject_ClearWeakRefs(self); | |
_PyObject_GC_TRACK(self); | |
if (gen->gi_frame != NULL && gen->gi_frame->f_stacktop != NULL) { | |
/* Generator is paused, so we need to close */ | |
Py_TYPE(gen)->tp_del(self); | |
if (self->ob_refcnt > 0) | |
return; /* resurrected. :( */ | |
} | |
_PyObject_GC_UNTRACK(self); | |
Py_CLEAR(gen->gi_frame); | |
Py_CLEAR(gen->gi_code); | |
PyObject_GC_Del(gen); | |
} | |
static PyObject * | |
gen_send_ex(PyGenObject *gen, PyObject *arg, int exc) | |
{ | |
PyThreadState *tstate = PyThreadState_GET(); | |
PyFrameObject *f = gen->gi_frame; | |
PyObject *result; | |
if (gen->gi_running) { | |
PyErr_SetString(PyExc_ValueError, | |
"generator already executing"); | |
return NULL; | |
} | |
if (f==NULL || 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. */ | |
Py_XINCREF(tstate->frame); | |
assert(f->f_back == NULL); | |
f->f_back = tstate->frame; | |
gen->gi_running = 1; | |
result = PyEval_EvalFrameEx(f, exc); | |
gen->gi_running = 0; | |
/* Don't keep the reference to f_back any longer than necessary. It | |
* may keep a chain of frames alive or it could create a reference | |
* cycle. */ | |
assert(f->f_back == tstate->frame); | |
Py_CLEAR(f->f_back); | |
/* If the generator just returned (as opposed to yielding), signal | |
* that the generator is exhausted. */ | |
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 = NULL; | |
} | |
return result; | |
} | |
PyDoc_STRVAR(send_doc, | |
"send(arg) -> send 'arg' into generator,\n\ | |
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 (gen->gi_frame == NULL || 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(gen, NULL); | |
if (res == NULL) | |
PyErr_WriteUnraisable(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. | |
*/ | |
{ | |
Py_ssize_t 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,\n\ | |
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_UnpackTuple(args, "throw", 1, 3, &typ, &val, &tb)) | |
return NULL; | |
/* First, check the traceback argument, replacing None with | |
NULL. */ | |
if (tb == Py_None) | |
tb = NULL; | |
else if (tb != NULL && !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 (PyExceptionClass_Check(typ)) { | |
PyErr_NormalizeException(&typ, &val, &tb); | |
} | |
else if (PyExceptionInstance_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> */ | |
Py_XDECREF(val); | |
val = typ; | |
typ = PyExceptionInstance_Class(typ); | |
Py_INCREF(typ); | |
} | |
} | |
else { | |
/* Not something you can raise. throw() fails. */ | |
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 PyObject * | |
gen_repr(PyGenObject *gen) | |
{ | |
char *code_name; | |
code_name = PyString_AsString(((PyCodeObject *)gen->gi_code)->co_name); | |
if (code_name == NULL) | |
return NULL; | |
return PyString_FromFormat("<generator object %.200s at %p>", | |
code_name, gen); | |
} | |
static PyObject * | |
gen_get_name(PyGenObject *gen) | |
{ | |
PyObject *name = ((PyCodeObject *)gen->gi_code)->co_name; | |
Py_INCREF(name); | |
return name; | |
} | |
PyDoc_STRVAR(gen__name__doc__, | |
"Return the name of the generator's associated code object."); | |
static PyGetSetDef gen_getsetlist[] = { | |
{"__name__", (getter)gen_get_name, NULL, gen__name__doc__}, | |
{NULL} | |
}; | |
static PyMemberDef gen_memberlist[] = { | |
{"gi_frame", T_OBJECT, offsetof(PyGenObject, gi_frame), RO}, | |
{"gi_running", T_INT, offsetof(PyGenObject, gi_running), RO}, | |
{"gi_code", T_OBJECT, offsetof(PyGenObject, gi_code), 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 = { | |
PyVarObject_HEAD_INIT(&PyType_Type, 0) | |
"generator", /* tp_name */ | |
sizeof(PyGenObject), /* tp_basicsize */ | |
0, /* tp_itemsize */ | |
/* methods */ | |
(destructor)gen_dealloc, /* tp_dealloc */ | |
0, /* tp_print */ | |
0, /* tp_getattr */ | |
0, /* tp_setattr */ | |
0, /* tp_compare */ | |
(reprfunc)gen_repr, /* tp_repr */ | |
0, /* tp_as_number */ | |
0, /* tp_as_sequence */ | |
0, /* tp_as_mapping */ | |
0, /* tp_hash */ | |
0, /* tp_call */ | |
0, /* tp_str */ | |
PyObject_GenericGetAttr, /* tp_getattro */ | |
0, /* tp_setattro */ | |
0, /* tp_as_buffer */ | |
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */ | |
0, /* tp_doc */ | |
(traverseproc)gen_traverse, /* tp_traverse */ | |
0, /* tp_clear */ | |
0, /* tp_richcompare */ | |
offsetof(PyGenObject, gi_weakreflist), /* tp_weaklistoffset */ | |
PyObject_SelfIter, /* tp_iter */ | |
(iternextfunc)gen_iternext, /* tp_iternext */ | |
gen_methods, /* tp_methods */ | |
gen_memberlist, /* tp_members */ | |
gen_getsetlist, /* 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 * | |
PyGen_New(PyFrameObject *f) | |
{ | |
PyGenObject *gen = PyObject_GC_New(PyGenObject, &PyGen_Type); | |
if (gen == NULL) { | |
Py_DECREF(f); | |
return NULL; | |
} | |
gen->gi_frame = f; | |
Py_INCREF(f->f_code); | |
gen->gi_code = (PyObject *)(f->f_code); | |
gen->gi_running = 0; | |
gen->gi_weakreflist = NULL; | |
_PyObject_GC_TRACK(gen); | |
return (PyObject *)gen; | |
} | |
int | |
PyGen_NeedsFinalizing(PyGenObject *gen) | |
{ | |
int i; | |
PyFrameObject *f = gen->gi_frame; | |
if (f == NULL || f->f_stacktop == NULL || f->f_iblock <= 0) | |
return 0; /* no frame or empty blockstack == no finalization */ | |
/* Any block type besides a loop requires cleanup. */ | |
i = f->f_iblock; | |
while (--i >= 0) { | |
if (f->f_blockstack[i].b_type != SETUP_LOOP) | |
return 1; | |
} | |
/* No blocks except loops, it's safe to skip finalization. */ | |
return 0; | |
} |