changeset 4397:a84f21892563 v7.3.947

updated for version 7.3.947 Problem: Python: No iterator for vim.list and vim.bufferlist. Solution: Add the iterators. Also fix name of FunctionType. Add tests for vim.buffers. (ZyX)
author Bram Moolenaar <bram@vim.org>
date Wed, 15 May 2013 14:39:52 +0200
parents 57d8b1be2de5
children d5dbc2cea5b3
files runtime/doc/if_pyth.txt src/eval.c src/if_py_both.h src/if_python.c src/if_python3.c src/proto/eval.pro src/testdir/test86.in src/testdir/test86.ok src/testdir/test87.in src/testdir/test87.ok src/version.c
diffstat 11 files changed, 365 insertions(+), 52 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/if_pyth.txt
+++ b/runtime/doc/if_pyth.txt
@@ -214,6 +214,7 @@ vim.buffers						*python-buffers*
 	    :py b = vim.buffers[i]	# Indexing (read-only)
 	    :py b in vim.buffers	# Membership test
 	    :py n = len(vim.buffers)	# Number of elements
+	    :py for b in vim.buffers:	# Iterating over buffer list
 <
 vim.windows						*python-windows*
 	A sequence object providing access to the list of vim windows.  The
--- a/src/eval.c
+++ b/src/eval.c
@@ -390,8 +390,6 @@ static char_u *get_lval __ARGS((char_u *
 static void clear_lval __ARGS((lval_T *lp));
 static void set_var_lval __ARGS((lval_T *lp, char_u *endp, typval_T *rettv, int copy, char_u *op));
 static int tv_op __ARGS((typval_T *tv1, typval_T *tv2, char_u  *op));
-static void list_add_watch __ARGS((list_T *l, listwatch_T *lw));
-static void list_rem_watch __ARGS((list_T *l, listwatch_T *lwrem));
 static void list_fix_watch __ARGS((list_T *l, listitem_T *item));
 static void ex_unletlock __ARGS((exarg_T *eap, char_u *argstart, int deep));
 static int do_unlet_var __ARGS((lval_T *lp, char_u *name_end, int forceit));
@@ -3106,7 +3104,7 @@ tv_op(tv1, tv2, op)
 /*
  * Add a watcher to a list.
  */
-    static void
+    void
 list_add_watch(l, lw)
     list_T	*l;
     listwatch_T	*lw;
@@ -3119,7 +3117,7 @@ list_add_watch(l, lw)
  * Remove a watcher from a list.
  * No warning when it isn't found...
  */
-    static void
+    void
 list_rem_watch(l, lwrem)
     list_T	*l;
     listwatch_T	*lwrem;
--- a/src/if_py_both.h
+++ b/src/if_py_both.h
@@ -531,66 +531,62 @@ static struct PyMethodDef VimMethods[] =
 };
 
 /*
- * Buffer list object - Implementation
+ * Generic iterator object
  */
 
-static PyTypeObject BufMapType;
+static PyTypeObject IterType;
+
+typedef PyObject *(*nextfun)(void **);
+typedef void (*destructorfun)(void *);
+
+/* Main purpose of this object is removing the need for do python initialization 
+ * (i.e. PyType_Ready and setting type attributes) for a big bunch of objects.
+ */
 
 typedef struct
 {
     PyObject_HEAD
-} BufMapObject;
-
-    static PyInt
-BufMapLength(PyObject *self UNUSED)
+    void *cur;
+    nextfun next;
+    destructorfun destruct;
+} IterObject;
+
+    static PyObject *
+IterNew(void *start, destructorfun destruct, nextfun next)
 {
-    buf_T	*b = firstbuf;
-    PyInt	n = 0;
-
-    while (b)
-    {
-	++n;
-	b = b->b_next;
-    }
-
-    return n;
+    IterObject *self;
+
+    self = PyObject_NEW(IterObject, &IterType);
+    self->cur = start;
+    self->next = next;
+    self->destruct = destruct;
+
+    return (PyObject *)(self);
+}
+
+    static void
+IterDestructor(PyObject *self)
+{
+    IterObject *this = (IterObject *)(self);
+
+    this->destruct(this->cur);
+
+    DESTRUCTOR_FINISH(self);
 }
 
     static PyObject *
-BufMapItem(PyObject *self UNUSED, PyObject *keyObject)
+IterNext(PyObject *self)
 {
-    buf_T	*b;
-    int		bnr;
-
-#if PY_MAJOR_VERSION < 3
-    if (PyInt_Check(keyObject))
-	bnr = PyInt_AsLong(keyObject);
-    else
-#endif
-    if (PyLong_Check(keyObject))
-	bnr = PyLong_AsLong(keyObject);
-    else
-    {
-	PyErr_SetString(PyExc_ValueError, _("key must be integer"));
-	return NULL;
-    }
-
-    b = buflist_findnr(bnr);
-
-    if (b)
-	return BufferNew(b);
-    else
-    {
-	PyErr_SetString(PyExc_KeyError, _("no such buffer"));
-	return NULL;
-    }
+    IterObject *this = (IterObject *)(self);
+
+    return this->next(&this->cur);
 }
 
-static PyMappingMethods BufMapAsMapping = {
-    (lenfunc)       BufMapLength,
-    (binaryfunc)    BufMapItem,
-    (objobjargproc) 0,
-};
+    static PyObject *
+IterIter(PyObject *self)
+{
+    return self;
+}
 
 typedef struct pylinkedlist_S {
     struct pylinkedlist_S	*pll_next;
@@ -990,6 +986,55 @@ ListSlice(PyObject *self, Py_ssize_t fir
     return list;
 }
 
+typedef struct
+{
+    listwatch_T	lw;
+    list_T	*list;
+} listiterinfo_T;
+
+    static void
+ListIterDestruct(listiterinfo_T *lii)
+{
+    list_rem_watch(lii->list, &lii->lw);
+    PyMem_Free(lii);
+}
+
+    static PyObject *
+ListIterNext(listiterinfo_T **lii)
+{
+    PyObject	*r;
+
+    if (!((*lii)->lw.lw_item))
+	return NULL;
+
+    if (!(r = ConvertToPyObject(&((*lii)->lw.lw_item->li_tv))))
+	return NULL;
+
+    (*lii)->lw.lw_item = (*lii)->lw.lw_item->li_next;
+
+    return r;
+}
+
+    static PyObject *
+ListIter(PyObject *self)
+{
+    listiterinfo_T	*lii;
+    list_T	*l = ((ListObject *) (self))->list;
+
+    if (!(lii = PyMem_New(listiterinfo_T, 1)))
+    {
+	PyErr_NoMemory();
+	return NULL;
+    }
+
+    list_add_watch(l, &lii->lw);
+    lii->lw.lw_item = l->lv_first;
+    lii->list = l;
+
+    return IterNew(lii,
+	    (destructorfun) ListIterDestruct, (nextfun) ListIterNext);
+}
+
     static int
 ListAssItem(PyObject *self, Py_ssize_t index, PyObject *obj)
 {
@@ -2869,6 +2914,116 @@ static struct PyMethodDef BufferMethods[
     { NULL,	    NULL,		0,	    NULL }
 };
 
+/*
+ * Buffer list object - Implementation
+ */
+
+static PyTypeObject BufMapType;
+
+typedef struct
+{
+    PyObject_HEAD
+} BufMapObject;
+
+    static PyInt
+BufMapLength(PyObject *self UNUSED)
+{
+    buf_T	*b = firstbuf;
+    PyInt	n = 0;
+
+    while (b)
+    {
+	++n;
+	b = b->b_next;
+    }
+
+    return n;
+}
+
+    static PyObject *
+BufMapItem(PyObject *self UNUSED, PyObject *keyObject)
+{
+    buf_T	*b;
+    int		bnr;
+
+#if PY_MAJOR_VERSION < 3
+    if (PyInt_Check(keyObject))
+	bnr = PyInt_AsLong(keyObject);
+    else
+#endif
+    if (PyLong_Check(keyObject))
+	bnr = PyLong_AsLong(keyObject);
+    else
+    {
+	PyErr_SetString(PyExc_ValueError, _("key must be integer"));
+	return NULL;
+    }
+
+    b = buflist_findnr(bnr);
+
+    if (b)
+	return BufferNew(b);
+    else
+    {
+	PyErr_SetString(PyExc_KeyError, _("no such buffer"));
+	return NULL;
+    }
+}
+
+    static void
+BufMapIterDestruct(PyObject *buffer)
+{
+    /* Iteration was stopped before all buffers were processed */
+    if (buffer)
+    {
+	Py_DECREF(buffer);
+    }
+}
+
+    static PyObject *
+BufMapIterNext(PyObject **buffer)
+{
+    PyObject	*next;
+    PyObject	*r;
+
+    if (!*buffer)
+	return NULL;
+
+    r = *buffer;
+
+    if (CheckBuffer((BufferObject *)(r)))
+    {
+	*buffer = NULL;
+	return NULL;
+    }
+
+    if (!((BufferObject *)(r))->buf->b_next)
+	next = NULL;
+    else if (!(next = BufferNew(((BufferObject *)(r))->buf->b_next)))
+	return NULL;
+    *buffer = next;
+    /* Do not increment reference: we no longer hold it (decref), but whoever on 
+     * other side will hold (incref). Decref+incref = nothing.
+     */
+    return r;
+}
+
+    static PyObject *
+BufMapIter(PyObject *self UNUSED)
+{
+    PyObject *buffer;
+
+    buffer = BufferNew(firstbuf);
+    return IterNew(buffer,
+	    (destructorfun) BufMapIterDestruct, (nextfun) BufMapIterNext);
+}
+
+static PyMappingMethods BufMapAsMapping = {
+    (lenfunc)       BufMapLength,
+    (binaryfunc)    BufMapItem,
+    (objobjargproc) 0,
+};
+
 /* Current items object
  */
 
@@ -3383,6 +3538,14 @@ init_structs(void)
     OutputType.tp_setattr = OutputSetattr;
 #endif
 
+    vim_memset(&IterType, 0, sizeof(IterType));
+    IterType.tp_name = "vim.iter";
+    IterType.tp_basicsize = sizeof(IterObject);
+    IterType.tp_flags = Py_TPFLAGS_DEFAULT;
+    IterType.tp_doc = "generic iterator object";
+    IterType.tp_iter = IterIter;
+    IterType.tp_iternext = IterNext;
+
     vim_memset(&BufferType, 0, sizeof(BufferType));
     BufferType.tp_name = "vim.buffer";
     BufferType.tp_basicsize = sizeof(BufferType);
@@ -3426,6 +3589,7 @@ init_structs(void)
     BufMapType.tp_basicsize = sizeof(BufMapObject);
     BufMapType.tp_as_mapping = &BufMapAsMapping;
     BufMapType.tp_flags = Py_TPFLAGS_DEFAULT;
+    BufMapType.tp_iter = BufMapIter;
     BufferType.tp_doc = "vim buffer list";
 
     vim_memset(&WinListType, 0, sizeof(WinListType));
@@ -3492,6 +3656,7 @@ init_structs(void)
     ListType.tp_flags = Py_TPFLAGS_DEFAULT;
     ListType.tp_doc = "list pushing modifications to vim structure";
     ListType.tp_methods = ListMethods;
+    ListType.tp_iter = ListIter;
 #if PY_MAJOR_VERSION >= 3
     ListType.tp_getattro = ListGetattro;
     ListType.tp_setattro = ListSetattro;
@@ -3501,7 +3666,7 @@ init_structs(void)
 #endif
 
     vim_memset(&FunctionType, 0, sizeof(FunctionType));
-    FunctionType.tp_name = "vim.list";
+    FunctionType.tp_name = "vim.function";
     FunctionType.tp_basicsize = sizeof(FunctionObject);
     FunctionType.tp_dealloc = FunctionDestructor;
     FunctionType.tp_call = FunctionCall;
--- a/src/if_python.c
+++ b/src/if_python.c
@@ -1219,6 +1219,7 @@ PythonMod_Init(void)
     static char *(argv[2]) = {"/must>not&exist/foo", NULL};
 
     /* Fixups... */
+    PyType_Ready(&IterType);
     PyType_Ready(&BufferType);
     PyType_Ready(&RangeType);
     PyType_Ready(&WindowType);
--- a/src/if_python3.c
+++ b/src/if_python3.c
@@ -1519,6 +1519,7 @@ Py3Init_vim(void)
     /* The special value is removed from sys.path in Python3_Init(). */
     static wchar_t *(argv[2]) = {L"/must>not&exist/foo", NULL};
 
+    PyType_Ready(&IterType);
     PyType_Ready(&BufferType);
     PyType_Ready(&RangeType);
     PyType_Ready(&WindowType);
--- a/src/proto/eval.pro
+++ b/src/proto/eval.pro
@@ -127,4 +127,6 @@ int modify_fname __ARGS((char_u *src, in
 char_u *do_string_sub __ARGS((char_u *str, char_u *pat, char_u *sub, char_u *flags));
 int switch_win __ARGS((win_T **, tabpage_T **, win_T *, tabpage_T *));
 void restore_win __ARGS((win_T *, tabpage_T *));
+void list_add_watch __ARGS((list_T *l, listwatch_T *lw));
+void list_rem_watch __ARGS((list_T *l, listwatch_T *lwrem));
 /* vim: set ft=c : */
--- a/src/testdir/test86.in
+++ b/src/testdir/test86.in
@@ -477,6 +477,9 @@ EOF
 :   call RecVars(oname)
 :endfor
 :only
+:for buf in g:bufs[1:]
+:   execute 'bwipeout!' buf
+:endfor
 :"
 :" Test buffer object
 :vnew
@@ -519,6 +522,62 @@ for expr in ('b[1]','b[:] = ["A", "B"]',
         # Should not happen in any case
         cb.append('No exception for ' + expr)
 EOF
+:"
+:" Test vim.buffers object
+:set hidden
+:edit a
+:buffer #
+:edit b
+:buffer #
+:edit c
+:buffer #
+py << EOF
+# Check GCing iterator that was not fully exhausted
+i = iter(vim.buffers)
+cb.append('i:' + str(next(i)))
+# and also check creating more then one iterator at a time
+i2 = iter(vim.buffers)
+cb.append('i2:' + str(next(i2)))
+cb.append('i:' + str(next(i)))
+# The following should trigger GC and not cause any problems
+del i
+del i2
+i3 = iter(vim.buffers)
+cb.append('i3:' + str(next(i3)))
+del i3
+
+prevnum = 0
+for b in vim.buffers:
+    # Check buffer order
+    if prevnum >= b.number:
+        cb.append('!!! Buffer numbers not in strictly ascending order')
+    # Check indexing: vim.buffers[number].number == number
+    cb.append(str(b.number) + ':' + repr(vim.buffers[b.number]) + '=' + repr(b))
+    prevnum = b.number
+
+cb.append(str(len(vim.buffers)))
+
+bnums = list(map(lambda b: b.number, vim.buffers))[1:]
+
+# Test wiping out buffer with existing iterator
+i4 = iter(vim.buffers)
+cb.append('i4:' + str(next(i4)))
+vim.command('bwipeout! ' + str(bnums.pop(0)))
+try:
+    next(i4)
+except vim.error:
+    pass
+else:
+    cb.append('!!!! No vim.error')
+i4 = iter(vim.buffers)
+vim.command('bwipeout! ' + str(bnums.pop(-1)))
+vim.command('bwipeout! ' + str(bnums.pop(-1)))
+cb.append('i4:' + str(next(i4)))
+try:
+    next(i4)
+except StopIteration:
+    cb.append('StopIteration')
+EOF
 :endfun
 :"
 :call Test()
--- a/src/testdir/test86.ok
+++ b/src/testdir/test86.ok
@@ -319,3 +319,15 @@ bar
 Second line
 Third line
 foo
+i:<buffer test86.in>
+i2:<buffer test86.in>
+i:<buffer a>
+i3:<buffer test86.in>
+1:<buffer test86.in>=<buffer test86.in>
+6:<buffer a>=<buffer a>
+7:<buffer b>=<buffer b>
+8:<buffer c>=<buffer c>
+4
+i4:<buffer test86.in>
+i4:<buffer test86.in>
+StopIteration
--- a/src/testdir/test87.in
+++ b/src/testdir/test87.in
@@ -446,6 +446,9 @@ EOF
 :   call RecVars(oname)
 :endfor
 :only
+:for buf in g:bufs[1:]
+:   execute 'bwipeout!' buf
+:endfor
 :"
 :" Test buffer object
 :vnew
@@ -488,6 +491,62 @@ for expr in ('b[1]','b[:] = ["A", "B"]',
         # Should not happen in any case
         cb.append('No exception for ' + expr)
 EOF
+:"
+:" Test vim.buffers object
+:set hidden
+:edit a
+:buffer #
+:edit b
+:buffer #
+:edit c
+:buffer #
+py3 << EOF
+# Check GCing iterator that was not fully exhausted
+i = iter(vim.buffers)
+cb.append('i:' + str(next(i)))
+# and also check creating more then one iterator at a time
+i2 = iter(vim.buffers)
+cb.append('i2:' + str(next(i2)))
+cb.append('i:' + str(next(i)))
+# The following should trigger GC and not cause any problems
+del i
+del i2
+i3 = iter(vim.buffers)
+cb.append('i3:' + str(next(i3)))
+del i3
+
+prevnum = 0
+for b in vim.buffers:
+    # Check buffer order
+    if prevnum >= b.number:
+        cb.append('!!! Buffer numbers not in strictly ascending order')
+    # Check indexing: vim.buffers[number].number == number
+    cb.append(str(b.number) + ':' + repr(vim.buffers[b.number]) + '=' + repr(b))
+    prevnum = b.number
+
+cb.append(str(len(vim.buffers)))
+
+bnums = list(map(lambda b: b.number, vim.buffers))[1:]
+
+# Test wiping out buffer with existing iterator
+i4 = iter(vim.buffers)
+cb.append('i4:' + str(next(i4)))
+vim.command('bwipeout! ' + str(bnums.pop(0)))
+try:
+    next(i4)
+except vim.error:
+    pass
+else:
+    cb.append('!!!! No vim.error')
+i4 = iter(vim.buffers)
+vim.command('bwipeout! ' + str(bnums.pop(-1)))
+vim.command('bwipeout! ' + str(bnums.pop(-1)))
+cb.append('i4:' + str(next(i4)))
+try:
+    next(i4)
+except StopIteration:
+    cb.append('StopIteration')
+EOF
 :endfun
 :"
 :call Test()
@@ -496,6 +555,7 @@ EOF
 :call garbagecollect(1)
 :"
 :/^start:/,$wq! test.out
+:call getchar()
 ENDTEST
 
 start:
--- a/src/testdir/test87.ok
+++ b/src/testdir/test87.ok
@@ -308,3 +308,15 @@ bar
 Second line
 Third line
 foo
+i:<buffer test87.in>
+i2:<buffer test87.in>
+i:<buffer a>
+i3:<buffer test87.in>
+1:<buffer test87.in>=<buffer test87.in>
+6:<buffer a>=<buffer a>
+7:<buffer b>=<buffer b>
+8:<buffer c>=<buffer c>
+4
+i4:<buffer test87.in>
+i4:<buffer test87.in>
+StopIteration
--- a/src/version.c
+++ b/src/version.c
@@ -729,6 +729,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    947,
+/**/
     946,
 /**/
     945,