# HG changeset patch # User jbe # Date 1408911859 -7200 # Node ID 332216604f8318e8f2630318c984f3c6f2094b68 # Parent 2a43e8ffbced6ee53f553f1a051f6f497a6b0bb9 Support special return values of __ipairs metamethod (string as first return value) diff -r 2a43e8ffbced -r 332216604f83 README --- a/README Sun Aug 24 22:21:46 2014 +0200 +++ b/README Sun Aug 24 22:24:19 2014 +0200 @@ -144,3 +144,119 @@ ``seqlua_iterloopauto`` is used. +Respected metamethods +--------------------- + +Regarding the behavior of the Lua functions and the C functions and macros +provided by this extension, an existing ``__index`` metamethod will be +respected automatically. An existing ``__ipairs`` metamethod, however, takes +precedence. + +If the ``__ipairs`` field of a value's metatable is set, then it must always +refer to a function. When starting iteration over a value with such a +metamethod being set, then this function is called with ``self`` (i.e. the +value itself) passed as first argument. The return values of the ``__ipairs`` +metamethod may take one of the following 4 forms: + +* ``return function_or_callable, static_argument, startindex`` causes the three + arguments to be returned by ``ipairs`` without further modification. Using + the C macros and functions for iteration, the behavior is according to the + generic loop statement in Lua: + ``for i, v in function_or_callable, static_argument, startindex do ... end`` +* ``return "raw", table`` will result in iteration over the table ``table`` + using ``lua_rawgeti`` +* ``return "index", table_or_userdata`` will result in iteration over the table + or userdata while respecting any ``__index`` metamethod of the table or + userdata value +* ``return "call", function_or_callable`` will use the callable value as + (function) iterator where the function is expected to return a single value + without any index (the index is inserted automatically when using the + ``ipairs`` function for iteration) + +These possiblities are demonstrated by the following example code: + + require "seqlua" + + do + local function ipairsaux(t, i) + i = i + 1 + if i <= 3 then + return i, t[i] + end + end + custom = setmetatable( + {"one", "two", "three", "four", "five"}, + { + __ipairs = function(self) + return ipairsaux, self, 0 + end + } + ) + end + print(string.concat(",", custom)) + -- prints: one, two, three + -- (note that "four" and "five" are not printed) + + tbl = {"alpha", "beta"} + + proxy1 = setmetatable({}, {__index = tbl}) + for i, v in ipairs(proxy1) do print(i, v) end + -- prints: + -- 1 alpha + -- 2 beta + + proxy2 = setmetatable({}, { + __ipairs = function(self) + return "index", proxy1 + end + }) + for i, v in ipairs(proxy2) do print(i, v) end + -- prints: + -- 1 alpha + -- 2 beta + print(proxy2[1]) + -- prints: nil + + cursor = setmetatable({ + "alice", "bob", "charlie", pos=1 + }, { + __call = function(self) + local value = self[self.pos] + if value == nil then + self.pos = 1 + else + self.pos = self.pos + 1 + end + return value + end, + __ipairs = function(self) + return "call", self + end + }) + for i, v in ipairs(cursor) do print(i, v) end + -- prints: + -- 1 alice + -- 2 bob + -- 3 charlie + print(cursor()) + -- prints: alice + for i, v in ipairs(cursor) do print(i, v) end + -- prints: + -- 1 bob + -- 2 charlie + -- (note that "alice" has been returned earlier) + + coefficients = setmetatable({1.25, 3.14, 17.5}, { + __index = function(self) return 1 end, + __ipairs = function(self) return "raw", self end + }) + for i, v in ipairs(coefficients) do print(i, v) end + -- prints: + -- 1 1.25 + -- 2 3.14 + -- 3 17.5 + -- (note that iteration terminates even if coefficients[4] == 1) + print(coefficients[4]) + -- prints: 1 + + diff -r 2a43e8ffbced -r 332216604f83 seqlua.c --- a/seqlua.c Sun Aug 24 22:21:46 2014 +0200 +++ b/seqlua.c Sun Aug 24 22:24:19 2014 +0200 @@ -1,6 +1,7 @@ #include #include #include "seqlualib.h" +#include static int seqlua_ipairsaux_raw(lua_State *L) { lua_Integer i; @@ -37,6 +38,20 @@ if (luaL_getmetafield(L, 1, "__ipairs")) { lua_pushvalue(L, 1); lua_call(L, 1, 3); + if (lua_type(L, -3) == LUA_TSTRING) { + const char *method = lua_tostring(L, -3); + if (!strcmp(method, "raw")) { + lua_pushcfunction(L, seqlua_ipairsaux_raw); + } else if (!strcmp(method, "index")) { + lua_pushcfunction(L, seqlua_ipairsaux_index); + } else if (!strcmp(method, "call")) { + lua_pushcfunction(L, seqlua_ipairsaux_call); + } else { + luaL_error(L, "Unexpected string returned by __ipairs metamethod"); + } + lua_pushvalue(L, -3); + lua_pushinteger(L, 0); + } } else { int t = lua_type(L, 1); if (t == LUA_TFUNCTION) { diff -r 2a43e8ffbced -r 332216604f83 seqlua_c_example_test.lua --- a/seqlua_c_example_test.lua Sun Aug 24 22:21:46 2014 +0200 +++ b/seqlua_c_example_test.lua Sun Aug 24 22:24:19 2014 +0200 @@ -1,4 +1,4 @@ -require "seqlua_c_example" -- defines functions "printcsv" and "printthree" +require "seqlua_c_example" -- defines function "printcsv" function alphabet() local letter = nil @@ -14,7 +14,9 @@ end end -printcsv{"a", "b", "c"} +t = {"a", "b", "c"} + +printcsv(t) -- prints: -- a,b,c @@ -22,3 +24,56 @@ -- prints: -- a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z +call_proxy = setmetatable({}, {__ipairs = function() return "call", alphabet() end}) + +printcsv(call_proxy) +-- prints: +-- a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z + +random_access_alphabet = setmetatable({}, { + __index = function(self, i) + if type(i) == "number" and i >= 1 and i <= 26 then + return string.char(string.byte("a") + i - 1) + end + end +}) + +printcsv(random_access_alphabet) +-- prints: +-- a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z + +index_proxy = setmetatable({}, { + __ipairs = function() return "index", random_access_alphabet end +}) + +printcsv(index_proxy) +-- prints: +-- a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z + +raw_proxy = setmetatable({"dummy"}, { + __ipairs = function() return "raw", t end +}) + +printcsv(raw_proxy) +-- prints: +-- a,b,c + +require "seqlua" + +do + local function ipairsaux(s, i) + i = i + 1 + if i <= s.count then + return i, s.entry + end + end + custom_proxy = setmetatable({entry = "zebra", count=5}, { + __ipairs = function(self) + return ipairsaux, self, 0 + end + }) +end + +printcsv(custom_proxy) +-- prints: zebra,zebra,zebra,zebra,zebra + diff -r 2a43e8ffbced -r 332216604f83 seqlualib.c --- a/seqlualib.c Sun Aug 24 22:21:46 2014 +0200 +++ b/seqlualib.c Sun Aug 24 22:24:19 2014 +0200 @@ -1,6 +1,7 @@ #include #include #include "seqlualib.h" +#include #define SEQLUA_ITERTYPE_IPAIRS 1 #define SEQLUA_ITERTYPE_CALL 2 @@ -11,7 +12,21 @@ if (luaL_getmetafield(L, idx, "__ipairs")) { lua_pushvalue(L, idx); lua_call(L, 1, 3); - iter->itertype = SEQLUA_ITERTYPE_IPAIRS; + if (lua_type(L, -3) == LUA_TSTRING) { + const char *method = lua_tostring(L, -3); + if (!strcmp(method, "raw")) { + iter->itertype = SEQLUA_ITERTYPE_RAW; + } else if (!strcmp(method, "index")) { + iter->itertype = SEQLUA_ITERTYPE_INDEX; + } else if (!strcmp(method, "call")) { + iter->itertype = SEQLUA_ITERTYPE_CALL; + } else { + luaL_error(L, "Unexpected string returned by __ipairs metamethod"); + } + iter->idx = lua_gettop(L) - 1; + } else { + iter->itertype = SEQLUA_ITERTYPE_IPAIRS; + } } else { if (lua_type(L, idx) == LUA_TFUNCTION) { iter->itertype = SEQLUA_ITERTYPE_CALL;