jbe@0: seqlua: Extended sequences and iterators in Lua jbe@0: =============================================== jbe@0: jbe@0: This is an experimental package to extend Lua in the following manner: jbe@0: jbe@30: * allow ``ipairs(seq)`` to accept tables as well as functions (i.e function jbe@30: iterators), jbe@32: * add a new function ``string.concat(separator, seq)`` to concat either jbe@32: table entries or function return values, jbe@23: * provide auxiliary C functions and macros to simplify iterating over both jbe@0: tables and iterator functions with the same statement. jbe@0: jbe@33: Existing ``__ipairs`` or ``__index`` (but not ``__len``) metamethods are jbe@33: respected by both the Lua functions and the C functions and macros. The jbe@32: ``__ipairs`` metamethod takes precedence over ``__index``, while the jbe@32: ``__len`` metamethod is never used. jbe@32: jbe@0: jbe@0: jbe@0: Lua part of the library jbe@0: ----------------------- jbe@0: jbe@30: The modified ``ipairs(seq)`` and the new ``string.concat(sep, seq)`` functions jbe@30: accept either a table or a function as ``seq``. This is demonstrated in the jbe@30: following examples: jbe@0: jbe@0: require "seqlua" jbe@0: jbe@0: t = {"a", "b", "c"} jbe@0: jbe@0: for i, v in ipairs(t) do jbe@0: print(i, v) jbe@0: end jbe@0: -- prints: jbe@0: -- 1 a jbe@0: -- 2 b jbe@0: -- 3 c jbe@0: jbe@25: print(string.concat(",", t)) jbe@25: -- prints: a,b,c jbe@25: jbe@19: function alphabet() jbe@0: local letter = nil jbe@0: return function() jbe@0: if letter == nil then jbe@19: letter = "a" jbe@19: elseif letter == "z" then jbe@0: return nil jbe@0: else jbe@0: letter = string.char(string.byte(letter) + 1) jbe@0: end jbe@0: return letter jbe@0: end jbe@0: end jbe@0: jbe@23: for i, v in ipairs(alphabet()) do jbe@0: print(i, v) jbe@0: end jbe@0: -- prints: jbe@0: -- 1 a jbe@0: -- 2 b jbe@0: -- 3 c jbe@0: -- ... jbe@0: -- 25 y jbe@0: -- 26 z jbe@0: jbe@25: print(string.concat(",", alphabet())) jbe@25: -- 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 jbe@25: jbe@26: function filter(f) jbe@26: return function(seq) jbe@26: return coroutine.wrap(function() jbe@26: for i, v in ipairs(seq) do f(v) end jbe@26: end) jbe@26: end jbe@0: end jbe@19: jbe@29: alpha_beta_x = filter(function(v) jbe@28: if v == "a" then jbe@28: coroutine.yield("alpha") jbe@28: elseif v == "b" then jbe@28: coroutine.yield("beta") jbe@28: elseif type(v) == "number" then jbe@23: for i = 1, v do jbe@28: coroutine.yield("X") jbe@23: end jbe@0: end jbe@26: end) jbe@0: jbe@29: print((","):concat(alpha_beta_x{"a", 3, "b", "c", "d"})) jbe@28: -- prints: alpha,X,X,X,beta jbe@25: jbe@29: print((","):concat(alpha_beta_x(alphabet()))) jbe@28: -- prints: alpha,beta jbe@27: jbe@0: jbe@0: C part of the library jbe@0: --------------------- jbe@0: jbe@0: In ``seqlualib.h``, the following macro is defined: jbe@0: jbe@0: #define seqlua_iterloop(L, iter, idx) \ jbe@0: for ( \ jbe@0: seqlua_iterinit((L), (iter), (idx)); \ jbe@0: seqlua_iternext(iter); \ jbe@25: ) jbe@25: jbe@25: and jbe@25: jbe@25: #define seqlua_iterloopauto(L, iter, idx) \ jbe@25: for ( \ jbe@25: seqlua_iterinit((L), (iter), (idx)); \ jbe@25: seqlua_iternext(iter); \ jbe@0: lua_pop((L), 1) \ jbe@0: ) jbe@0: jbe@23: This macro allows iteration over either tables or iterator functions as the jbe@23: following example function demonstrates: jbe@0: jbe@0: int printcsv(lua_State *L) { jbe@0: seqlua_Iterator iter; jbe@0: seqlua_iterloop(L, &iter, 1) { jbe@0: if (seqlua_itercount(&iter) > 1) fputs(",", stdout); jbe@0: fputs(luaL_tolstring(L, -1, NULL), stdout); jbe@25: // two values need to be popped (the value pushed by jbe@25: // seqlua_iternext and the value pushed by luaL_tolstring) jbe@25: lua_pop(L, 2); jbe@0: } jbe@0: fputs("\n", stdout); jbe@0: return 0; jbe@0: } jbe@0: jbe@11: printcsv{"a", "b", "c"} jbe@11: -- prints: a,b,c jbe@11: jbe@11: printcsv(assert(io.open("testfile")):lines()) jbe@11: -- prints: line1,line2,... of "testfile" jbe@0: jbe@31: NOTE: During iteration using ``seqlua_iterloop``, ``seqlua_iterloopauto``, or jbe@31: ``seqlua_iterinit``, three extra elements are stored on the stack (additionally jbe@31: to the value). These extra elements are removed automatically when the loop ends jbe@31: (i.e. when ``seqlua_iternext`` returns zero). The value pushed onto the stack jbe@31: for every iteration step has to be removed manually from the stack, unless jbe@31: ``seqlua_iterloopauto`` is used. jbe@0: jbe@31: jbe@35: Respected metamethods jbe@35: --------------------- jbe@35: jbe@35: Regarding the behavior of the Lua functions and the C functions and macros jbe@35: provided by this extension, an existing ``__index`` metamethod will be jbe@35: respected automatically. An existing ``__ipairs`` metamethod, however, takes jbe@35: precedence. jbe@35: jbe@35: If the ``__ipairs`` field of a value's metatable is set, then it must always jbe@35: refer to a function. When starting iteration over a value with such a jbe@35: metamethod being set, then this function is called with ``self`` (i.e. the jbe@35: value itself) passed as first argument. The return values of the ``__ipairs`` jbe@35: metamethod may take one of the following 4 forms: jbe@35: jbe@35: * ``return function_or_callable, static_argument, startindex`` causes the three jbe@35: arguments to be returned by ``ipairs`` without further modification. Using jbe@35: the C macros and functions for iteration, the behavior is according to the jbe@35: generic loop statement in Lua: jbe@35: ``for i, v in function_or_callable, static_argument, startindex do ... end`` jbe@35: * ``return "raw", table`` will result in iteration over the table ``table`` jbe@35: using ``lua_rawgeti`` jbe@35: * ``return "index", table_or_userdata`` will result in iteration over the table jbe@35: or userdata while respecting any ``__index`` metamethod of the table or jbe@35: userdata value jbe@35: * ``return "call", function_or_callable`` will use the callable value as jbe@35: (function) iterator where the function is expected to return a single value jbe@35: without any index (the index is inserted automatically when using the jbe@35: ``ipairs`` function for iteration) jbe@35: jbe@35: These possiblities are demonstrated by the following example code: jbe@35: jbe@35: require "seqlua" jbe@35: jbe@35: do jbe@35: local function ipairsaux(t, i) jbe@35: i = i + 1 jbe@35: if i <= 3 then jbe@35: return i, t[i] jbe@35: end jbe@35: end jbe@35: custom = setmetatable( jbe@35: {"one", "two", "three", "four", "five"}, jbe@35: { jbe@35: __ipairs = function(self) jbe@35: return ipairsaux, self, 0 jbe@35: end jbe@35: } jbe@35: ) jbe@35: end jbe@35: print(string.concat(",", custom)) jbe@35: -- prints: one, two, three jbe@35: -- (note that "four" and "five" are not printed) jbe@35: jbe@35: tbl = {"alpha", "beta"} jbe@35: jbe@35: proxy1 = setmetatable({}, {__index = tbl}) jbe@35: for i, v in ipairs(proxy1) do print(i, v) end jbe@35: -- prints: jbe@35: -- 1 alpha jbe@35: -- 2 beta jbe@35: jbe@35: proxy2 = setmetatable({}, { jbe@35: __ipairs = function(self) jbe@35: return "index", proxy1 jbe@35: end jbe@35: }) jbe@35: for i, v in ipairs(proxy2) do print(i, v) end jbe@35: -- prints: jbe@35: -- 1 alpha jbe@35: -- 2 beta jbe@35: print(proxy2[1]) jbe@35: -- prints: nil jbe@35: jbe@35: cursor = setmetatable({ jbe@35: "alice", "bob", "charlie", pos=1 jbe@35: }, { jbe@35: __call = function(self) jbe@35: local value = self[self.pos] jbe@35: if value == nil then jbe@35: self.pos = 1 jbe@35: else jbe@35: self.pos = self.pos + 1 jbe@35: end jbe@35: return value jbe@35: end, jbe@35: __ipairs = function(self) jbe@35: return "call", self jbe@35: end jbe@35: }) jbe@35: for i, v in ipairs(cursor) do print(i, v) end jbe@35: -- prints: jbe@35: -- 1 alice jbe@35: -- 2 bob jbe@35: -- 3 charlie jbe@35: print(cursor()) jbe@35: -- prints: alice jbe@35: for i, v in ipairs(cursor) do print(i, v) end jbe@35: -- prints: jbe@35: -- 1 bob jbe@35: -- 2 charlie jbe@35: -- (note that "alice" has been returned earlier) jbe@35: jbe@35: coefficients = setmetatable({1.25, 3.14, 17.5}, { jbe@35: __index = function(self) return 1 end, jbe@35: __ipairs = function(self) return "raw", self end jbe@35: }) jbe@35: for i, v in ipairs(coefficients) do print(i, v) end jbe@35: -- prints: jbe@35: -- 1 1.25 jbe@35: -- 2 3.14 jbe@35: -- 3 17.5 jbe@35: -- (note that iteration terminates even if coefficients[4] == 1) jbe@35: print(coefficients[4]) jbe@35: -- prints: 1 jbe@35: jbe@35: