From 82206e43235b2cb772410ecc16997a7e2d138806 Mon Sep 17 00:00:00 2001
From: Roman Tsisyk <roman@tsisyk.com>
Date: Mon, 24 Feb 2014 14:11:16 +0400
Subject: [PATCH] tuple:pairs and tuple:next FFI bindings

---
 src/box/lua/tuple.cc    |  52 ----------
 src/box/lua/tuple.lua   |  81 ++++++++++++++-
 src/box/tuple.h         |   6 +-
 src/ffisyms.cc          |   5 +-
 test/box/tuple.result   | 215 ++++++++++++++++++++++++++++++++++++++--
 test/box/tuple.test.lua |  75 +++++++++++++-
 6 files changed, 361 insertions(+), 73 deletions(-)

diff --git a/src/box/lua/tuple.cc b/src/box/lua/tuple.cc
index a725236e26..46968c4b02 100644
--- a/src/box/lua/tuple.cc
+++ b/src/box/lua/tuple.cc
@@ -433,60 +433,8 @@ lbox_pushtuple(struct lua_State *L, struct tuple *tuple)
 	}
 }
 
-/**
- * Sequential access to tuple fields. Since tuple is a list-like
- * structure, iterating over tuple fields is faster
- * than accessing fields using an index.
- */
-static int
-lbox_tuple_next(struct lua_State *L)
-{
-	struct tuple *tuple = lua_checktuple(L, 1);
-	int argc = lua_gettop(L) - 1;
-
-	struct tuple_iterator *it = NULL;
-	if (argc == 0 || (argc == 1 && lua_type(L, 2) == LUA_TNIL)) {
-		it = (struct tuple_iterator *) lua_newuserdata(L, sizeof(*it));
-		assert(it != NULL);
-		luaL_getmetatable(L, tuple_iteratorlib_name);
-		lua_setmetatable(L, -2);
-		tuple_rewind(it, tuple);
-	} else if (argc == 1 && lua_type(L, 2) == LUA_TUSERDATA) {
-		it = (struct tuple_iterator *)
-			luaL_checkudata(L, 2, tuple_iteratorlib_name);
-		assert(it != NULL);
-		lua_pushvalue(L, 2);
-	} else {
-		return luaL_error(L, "tuple.next(): bad arguments");
-	}
-
-	const char *field = tuple_next(it);
-	if (field == NULL) {
-		lua_pop(L, 1);
-		lua_pushnil(L);
-		return 1;
-	}
-
-	luamp_decode(L, &field);
-	return 2;
-}
-
-/** Iterator over tuple fields. Adapt lbox_tuple_next
- * to Lua iteration conventions.
- */
-static int
-lbox_tuple_pairs(struct lua_State *L)
-{
-	lua_pushcfunction(L, lbox_tuple_next);
-	lua_pushvalue(L, -2); /* tuple */
-	lua_pushnil(L);
-	return 3;
-}
-
 static const struct luaL_reg lbox_tuple_meta[] = {
 	{"__gc", lbox_tuple_gc},
-	{"next", lbox_tuple_next},
-	{"pairs", lbox_tuple_pairs},
 	{"slice", lbox_tuple_slice},
 	{"transform", lbox_tuple_transform},
 	{"find", lbox_tuple_find},
diff --git a/src/box/lua/tuple.lua b/src/box/lua/tuple.lua
index 3527a5621e..febd79d066 100644
--- a/src/box/lua/tuple.lua
+++ b/src/box/lua/tuple.lua
@@ -18,14 +18,76 @@ uint32_t
 tuple_arity(const struct tuple *tuple);
 const char *
 tuple_field(const struct tuple *tuple, uint32_t i);
+
+struct tuple_iterator {
+    const struct tuple *tuple;
+    const char *pos;
+    int fieldno;
+};
+
+void
+tuple_rewind(struct tuple_iterator *it, const struct tuple *tuple);
+
+const char *
+tuple_seek(struct tuple_iterator *it, uint32_t field_no);
+
+const char *
+tuple_next(struct tuple_iterator *it);
 ]])
 
 local builtin = ffi.C
 
+local tuple_iterator_t = ffi.typeof('struct tuple_iterator')
+
+local function tuple_iterator_next(it, tuple, pos)
+    if pos == nil then
+        pos = 0
+    elseif type(pos) ~= "number" then
+         error("error: invalid key to 'next'")
+    end
+    local field
+    if it.tuple == tuple and it.fieldno == pos then
+        -- Sequential iteration
+        print('optimization')
+        field = builtin.tuple_next(it)
+    else
+        -- Seek
+        builtin.tuple_rewind(it, tuple)
+        field = builtin.tuple_seek(it, pos);
+    end
+    if field == nil then
+        if #tuple == pos then
+            -- No more fields, stop iteration
+            return nil
+        else
+            -- Invalid pos
+            error("error: invalid key to 'next'")
+        end
+    end
+    -- () used to shrink the return stack to one value
+    return it.fieldno, (msgpackffi.decode_unchecked(field))
+end;
+
+-- precreated iterator for tuple_next
+local next_it = ffi.new(tuple_iterator_t)
+
+-- See http://www.lua.org/manual/5.2/manual.html#pdf-next
+local function tuple_next(tuple, pos)
+    return tuple_iterator_next(next_it, tuple, pos);
+end
+
+-- See http://www.lua.org/manual/5.2/manual.html#pdf-ipairs
+local function tuple_ipairs(tuple, pos)
+    local it = ffi.new(tuple_iterator_t)
+    return it, tuple, pos
+end
+
 -- cfuncs table is set by C part
+
 local methods = {
-    ["next"]        = cfuncs.next;
-    ["pairs"]       = cfuncs.pairs;
+    ["next"]        = tuple_next;
+    ["ipairs"]      = tuple_ipairs;
+    ["pairs"]       = tuple_ipairs; -- just alias for ipairs()
     ["slice"]       = cfuncs.slice;
     ["transform"]   = cfuncs.transform;
     ["find"]        = cfuncs.find;
@@ -38,7 +100,7 @@ local methods = {
 }
 
 local tuple_gc = cfuncs.__gc;
-
+local const_struct_tuple_ref_t = ffi.typeof('const struct tuple&')
 local tuple_field = function(tuple, field_n)
     local field = builtin.tuple_field(tuple, field_n)
     if field == nil then
@@ -63,7 +125,18 @@ ffi.metatype('struct tuple', {
             return tuple_field(tuple, key)
         end
         return methods[key]
-    end
+    end;
+    __eq = function(tuple_a, tuple_b)
+        -- Two tuple are considered equal if they have same memory address
+        return ffi.cast('void *', tuple_a) == ffi.cast('void *', tuple_b);
+    end;
+    __pairs = tuple_ipairs;  -- Lua 5.2 compatibility
+    __ipairs = tuple_ipairs; -- Lua 5.2 compatibility
+})
+
+ffi.metatype(tuple_iterator_t, {
+    __call = tuple_iterator_next;
+    __tostring = function(it) return "<tuple iterator>" end;
 })
 
 -- Remove the global variable
diff --git a/src/box/tuple.h b/src/box/tuple.h
index 58a0a64931..0ea1555e5e 100644
--- a/src/box/tuple.h
+++ b/src/box/tuple.h
@@ -324,7 +324,7 @@ struct tuple_iterator {
  * @param[out] it tuple iterator
  * @param[in]  tuple tuple
  */
-static inline void
+extern "C" inline void
 tuple_rewind(struct tuple_iterator *it, const struct tuple *tuple)
 {
 	it->tuple = tuple;
@@ -339,7 +339,7 @@ tuple_rewind(struct tuple_iterator *it, const struct tuple *tuple)
  * @retval field  if the iterator has the requested field
  * @retval NULL   otherwise (iteration is out of range)
  */
-const char *
+extern "C" const char *
 tuple_seek(struct tuple_iterator *it, uint32_t field_no);
 
 /**
@@ -347,7 +347,7 @@ tuple_seek(struct tuple_iterator *it, uint32_t field_no);
  * @param it tuple iterator
  * @return next field or NULL if the iteration is out of range
  */
-const char *
+extern "C" const char *
 tuple_next(struct tuple_iterator *it);
 
 /**
diff --git a/src/ffisyms.cc b/src/ffisyms.cc
index 523b9a9284..887bda605d 100644
--- a/src/ffisyms.cc
+++ b/src/ffisyms.cc
@@ -12,5 +12,8 @@ void *ffi_symbols[] = {
 	(void *) mp_bswap_float,
 	(void *) mp_bswap_double,
 	(void *) tuple_arity,
-	(void *) tuple_field
+	(void *) tuple_field,
+	(void *) tuple_rewind,
+	(void *) tuple_seek,
+	(void *) tuple_next
 };
diff --git a/test/box/tuple.result b/test/box/tuple.result
index c7bfdd862d..50713b7c5e 100644
--- a/test/box/tuple.result
+++ b/test/box/tuple.result
@@ -313,28 +313,108 @@ t[nil]
 ---
 - null
 ...
-# test tuple iterators
+--------------------------------------------------------------------------------
+-- test tuple:next
+--------------------------------------------------------------------------------
+t = box.tuple.new({'a', 'b', 'c'})
 ---
 ...
+state, val = t:next()
+---
+...
+state, val
+---
+- 1
+- a
+...
+state, val = t:next(state)
+---
+...
+state, val
+---
+- 2
+- b
+...
+state, val = t:next(state)
+---
+...
+state, val
+---
+- 3
+- c
+...
+state, val = t:next(state)
+---
+...
+state, val
+---
+- null
+- null
+...
+t:next(nil)
+---
+- 1
+- a
+...
+t:next(0)
+---
+- 1
+- a
+...
+t:next(1)
+---
+- 2
+- b
+...
+t:next(2)
+---
+- 3
+- c
+...
+t:next(3)
+---
+- null
+...
+t:next(4)
+---
+- error: '[string "-- tuple.lua (internal file)..."]:64: error: invalid key to ''next'''
+...
+t:next(-1)
+---
+- error: '[string "-- tuple.lua (internal file)..."]:64: error: invalid key to ''next'''
+...
+t:next("fdsaf")
+---
+- error: '[string "-- tuple.lua (internal file)..."]:46: error: invalid key to ''next'''
+...
+box.tuple.new({'x', 'y', 'z'}):next()
+---
+- 1
+- x
+...
 t=space:insert{1953719668}
 ---
 ...
 t:next(1684234849)
 ---
-- error: 'tuple.next(): bad arguments'
+- error: '[string "-- tuple.lua (internal file)..."]:64: error: invalid key to ''next'''
 ...
 t:next(1)
 ---
-- error: 'tuple.next(): bad arguments'
+- null
 ...
-t:next(t)
+t:next(nil)
 ---
-- error: 'tuple.next(): bad arguments'
+- 1
+- 1953719668
 ...
 t:next(t:next())
 ---
-- error: 'tuple.next(): bad arguments'
+- null
 ...
+--------------------------------------------------------------------------------
+-- test tuple:pairs
+--------------------------------------------------------------------------------
 ta = {} for k, v in t:pairs() do table.insert(ta, v) end
 ---
 ...
@@ -378,12 +458,129 @@ ta
   - c
   - d
 ...
-it, field = t:next()
+t = box.tuple.new({'a', 'b', 'c'})
+---
+...
+gen, init, state = t:pairs()
+---
+...
+gen, init, state
+---
+- <tuple iterator>
+- ['a', 'b', 'c']
+- null
+...
+state, val = gen(init, state)
+---
+...
+state, val
+---
+- 1
+- a
+...
+state, val = gen(init, state)
+---
+...
+state, val
+---
+- 2
+- b
+...
+state, val = gen(init, state)
+---
+...
+state, val
+---
+- 3
+- c
+...
+state, val = gen(init, state)
+---
+...
+state, val
+---
+- null
+- null
+...
+r = {}
+---
+...
+for _state, val in t:pairs() do table.insert(r, val) end
+---
+...
+r
+---
+- - a
+  - b
+  - c
+...
+r = {}
+---
+...
+for _state, val in t:pairs() do table.insert(r, val) end
+---
+...
+r
+---
+- - a
+  - b
+  - c
+...
+r = {}
 ---
 ...
-getmetatable(it)
+for _state, val in t:pairs(1) do table.insert(r, val) end
+---
+...
+r
+---
+- - b
+  - c
+...
+r = {}
+---
+...
+for _state, val in t:pairs(3) do table.insert(r, val) end
+---
+...
+r
+---
+- []
+...
+r = {}
+---
+...
+for _state, val in t:pairs(10) do table.insert(r, val) end
+---
+- error: '[string "-- tuple.lua (internal file)..."]:64: error: invalid key to ''next'''
+...
+r
+---
+- []
+...
+r = {}
+---
+...
+for _state, val in t:pairs(nil) do table.insert(r, val) end
+---
+...
+r
+---
+- - a
+  - b
+  - c
+...
+t:pairs(nil)
+---
+- <tuple iterator>
+- ['a', 'b', 'c']
+- null
+...
+t:pairs("fdsaf")
 ---
-- box.tuple.iterator
+- <tuple iterator>
+- ['a', 'b', 'c']
+- fdsaf
 ...
 space:drop()
 ---
diff --git a/test/box/tuple.test.lua b/test/box/tuple.test.lua
index fd7580b4e5..e2d0912067 100644
--- a/test/box/tuple.test.lua
+++ b/test/box/tuple.test.lua
@@ -94,12 +94,41 @@ space:truncate()
 --  A test case for Bug#1119389 '(lbox_tuple_index) crashes on 'nil' argument'
 t=space:insert{0, 8989}
 t[nil]
-# test tuple iterators
+
+--------------------------------------------------------------------------------
+-- test tuple:next
+--------------------------------------------------------------------------------
+
+t = box.tuple.new({'a', 'b', 'c'})
+state, val = t:next()
+state, val
+state, val = t:next(state)
+state, val
+state, val = t:next(state)
+state, val
+state, val = t:next(state)
+state, val
+t:next(nil)
+t:next(0)
+t:next(1)
+t:next(2)
+t:next(3)
+t:next(4)
+t:next(-1)
+t:next("fdsaf")
+
+box.tuple.new({'x', 'y', 'z'}):next()
+
 t=space:insert{1953719668}
 t:next(1684234849)
 t:next(1)
-t:next(t)
+t:next(nil)
 t:next(t:next())
+
+--------------------------------------------------------------------------------
+-- test tuple:pairs
+--------------------------------------------------------------------------------
+
 ta = {} for k, v in t:pairs() do table.insert(ta, v) end
 ta
 t=space:replace{1953719668, 'another field'}
@@ -111,6 +140,44 @@ ta
 t=box.tuple.new({'a', 'b', 'c', 'd'})
 ta = {} for it,field in t:pairs() do table.insert(ta, field); end
 ta
-it, field = t:next()
-getmetatable(it)
+
+t = box.tuple.new({'a', 'b', 'c'})
+gen, init, state = t:pairs()
+gen, init, state
+state, val = gen(init, state)
+state, val
+state, val = gen(init, state)
+state, val
+state, val = gen(init, state)
+state, val
+state, val = gen(init, state)
+state, val
+
+r = {}
+for _state, val in t:pairs() do table.insert(r, val) end
+r
+
+r = {}
+for _state, val in t:pairs() do table.insert(r, val) end
+r
+
+r = {}
+for _state, val in t:pairs(1) do table.insert(r, val) end
+r
+
+r = {}
+for _state, val in t:pairs(3) do table.insert(r, val) end
+r
+
+r = {}
+for _state, val in t:pairs(10) do table.insert(r, val) end
+r
+
+r = {}
+for _state, val in t:pairs(nil) do table.insert(r, val) end
+r
+
+t:pairs(nil)
+t:pairs("fdsaf")
+
 space:drop()
-- 
GitLab